皆さん、コンソールつかってますか?
大体デバッグで使っている人がいると思いますが、今回は動画をコンソールに出してみよう!という企画です。
今回すること
例として稲葉曇さんの『ラグトレイン』Vo. 歌愛ユキを出してみようと思います。
BadAppleとかでもよかったのですが、すでに多く作成されていたのでこれにしてみました。
作り方
準備物
今回は↓のものを使います。
- VScode
- CMake
- OpenCV
- 変換したいmp4ファイル
VScodeは、https://code.visualstudio.com/ からダウンロードしてインストールしてください。
CMakeはhttps://cmake.org/download/ からインストールしてください。
インストーラー(Windows x64 Installer)でインストールすればいいと思います。
MP4はいい感じにゲットしてください。
OpenCVのインストール

Releases
OpenCV Releases Are Brought To You By Intel Intel is a multinational corporation known for its semiconductor products, i...
↑の公式サイトからダウンロードします。

exeファイルを実行したらフォルダが出てきます。それをわかりやすい場所(”C:\Users\ユーザー名”)等に置きます。そして、次のパスを環境変数のpathに追加します。
置いた場所\opencv\build\x64\vc16\bin
置いた場所\opencv\build\x64\vc16\lib
プログラムを書く
新しいフォルダーを作り、以下のプログラムを書いてください。名前はcode.cpp等にしてください。
#include <iostream>
#include <windows.h>
#include <cstdio>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <fstream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/videoio.hpp>
// 画面の変更を表す構造体
struct Change
{
short index; // 文字の位置
char ch; // 変更された文字
};
// フレームを読み込み、指定サイズにリサイズして文字列に変換する関数
void processFrame(cv::Mat &frame, std::string &result, const cv::Size &size)
{
// 文字リストの読み込み(1行目の文字列を取得)
static const std::string CHAR_LIST = []() {
std::ifstream file("chars.txt"); // 文字リストファイルを開く
std::string chars;
std::getline(file, chars); // 1行目を読み込み
return chars;
}();
const static int CHAR_COUNT = CHAR_LIST.size() - 1; // 文字リストのサイズ
cv::resize(frame, frame, size); // フレームを指定サイズにリサイズ
auto pix = frame.begin<cv::Vec3b>(); // ピクセルデータへのポインタを取得
for (int row = 0; row < size.height; ++row) // 行をループ
{
for (int col = 0; col < size.width; ++col, ++pix) // 列をループ
// ピクセルの平均値に基づいて対応する文字を選択し、結果文字列に追加
result.push_back(CHAR_LIST[(int)cv::mean(*pix)[0] * CHAR_COUNT / 255]);
}
}
int main(int argc, char **argv)
{
if (argc <= 1)
{
std::cout << "Usage: BadApple.exe videoFile consoleWidthInChars";
return 0;
}
using namespace std::chrono;
const long long FRAME_DURATION_US = 1e6 / 15; // 15fps のフレーム持続時間(マイクロ秒)
// OpenCV のログレベルを SILENT に設定(デバッグ情報を抑制)
cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_SILENT);
cv::VideoCapture video(argv[1]); // 動画ファイルを開く
if (!video.isOpened())
{
std::cout << "ファイルを開けません: " << argv[1];
return -1;
}
const int CONSOLE_WIDTH = atoi(argv[2]); // コンソールの幅(文字数)
const int VIDEO_WIDTH = (int)video.get(cv::CAP_PROP_FRAME_WIDTH); // 動画の幅
const int VIDEO_HEIGHT = (int)video.get(cv::CAP_PROP_FRAME_HEIGHT); // 動画の高さ
const int CONSOLE_HEIGHT = VIDEO_HEIGHT * CONSOLE_WIDTH / (VIDEO_WIDTH * 2); // コンソールの高さ
const int FRAME_SKIP = (int)video.get(cv::CAP_PROP_FPS) / 15; // スキップするフレーム数
const int TOTAL_FRAMES = (int)video.get(cv::CAP_PROP_FRAME_COUNT) / FRAME_SKIP; // 総フレーム数
const cv::Size FRAME_SIZE(CONSOLE_WIDTH, CONSOLE_HEIGHT); // フレームサイズ
std::vector<std::vector<Change>> changes; // 各フレームの変更情報を保存するベクター
cv::Mat frame; // 現在のフレーム
std::string initialFrame; // 初期フレームの文字列
video >> frame; // 初期フレームを読み込む
processFrame(frame, initialFrame, FRAME_SIZE); // フレームを処理して文字列に変換
// 指定フレーム数分スキップ
for (int i = 0; i < FRAME_SKIP; ++i)
video >> frame;
// フレームの変更情報を収集
std::string previousFrame = initialFrame; // 前回のフレーム
int frameCount = 0, lastProgress = 0; // フレーム数と進捗状況の追跡
while (!frame.empty())
{
changes.push_back(std::vector<Change>()); // 新しいフレームの変更情報を追加
std::string currentFrame; // 現在のフレームの文字列
processFrame(frame, currentFrame, FRAME_SIZE); // フレームを処理して文字列に変換
auto ¤tChanges = changes.back(); // 最新フレームの変更情報
for (int i = 0; i < currentFrame.length(); ++i)
// 現在のフレームと前回のフレームで異なる部分を変更情報に追加
if (currentFrame[i] != previousFrame[i])
currentChanges.push_back({ (short)i, currentFrame[i] });
previousFrame = currentFrame; // 前回のフレームを更新
// 次のフレームを読み込む
for (int i = 0; i < FRAME_SKIP; ++i)
video >> frame;
// 進捗状況を表示
const int progress = ++frameCount * 100 / TOTAL_FRAMES;
if (progress % 10 == 0 && progress / 10 != lastProgress)
{
lastProgress = progress / 10;
std::cout << progress << "%\n";
}
}
std::cout << "処理完了!";
std::this_thread::sleep_for(std::chrono::milliseconds(1500)); // 1.5秒待機
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 標準出力ハンドルの取得
// コンソール画面をクリア
DWORD charsWritten;
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
GetConsoleScreenBufferInfo(hOut, &consoleInfo); // 現在のコンソール情報を取得
SetConsoleCursorPosition(hOut, { 0, 0 }); // カーソルをコンソールの先頭に移動
FillConsoleOutputCharacter(hOut, (TCHAR)' ', consoleInfo.dwSize.X * consoleInfo.dwSize.Y, { 0, 0 }, &charsWritten); // コンソールを空白で埋める
WriteConsoleOutputCharacter(hOut, initialFrame.c_str(), initialFrame.size(), { 0, 0 }, &charsWritten); // 初期フレームを表示
int frameIdx = 0; // 現在のフレームインデックス
auto startTime = steady_clock::now(); // 開始時刻
previousFrame = initialFrame; // 前回のフレームを初期化
while (frameIdx < TOTAL_FRAMES - 1)
{
auto now = steady_clock::now(); // 現在時刻
auto elapsed = duration_cast<microseconds>(now - startTime).count(); // 経過時間
bool shouldDrawNewFrame = (elapsed >= FRAME_DURATION_US); // 新しいフレームを描画するかどうか
if (shouldDrawNewFrame)
startTime = now; // 開始時刻を更新
int framesToProcess = 0; // 処理するフレーム数
while (elapsed >= FRAME_DURATION_US)
{
elapsed -= FRAME_DURATION_US; // 経過時間を調整
framesToProcess++; // 処理するフレーム数を増加
frameIdx++; // フレームインデックスを更新
if (frameIdx >= TOTAL_FRAMES) break; // 最後のフレームに達したら終了
}
if (shouldDrawNewFrame)
{
startTime -= microseconds(elapsed); // 経過時間を調整
int startFrame = frameIdx - framesToProcess; // 処理するフレームの開始位置
std::string buffer = previousFrame; // 現在のフレームバッファをコピー
for (int i = startFrame; i < frameIdx; ++i)
// 変更情報に基づいてフレームバッファを更新
for (const auto &change : changes[i])
buffer[change.index] = change.ch;
previousFrame = buffer; // 前回のフレームを更新
// コンソールサイズ変更時に画面をクリア
CONSOLE_SCREEN_BUFFER_INFO newSize;
GetConsoleScreenBufferInfo(hOut, &newSize);
if (newSize.dwSize.X != consoleInfo.dwSize.X || newSize.dwSize.Y != consoleInfo.dwSize.Y)
{
consoleInfo = newSize; // 新しいコンソールサイズを保存
FillConsoleOutputCharacter(hOut, (TCHAR)' ', consoleInfo.dwSize.X * consoleInfo.dwSize.Y, { 0, 0 }, &charsWritten); // コンソールを空白で埋める
}
// コンソールの幅に合わせてバッファに空白を挿入
int whitespace = consoleInfo.dwSize.X - CONSOLE_WIDTH;
if (whitespace > 0)
{
for (int i = 0; i < CONSOLE_HEIGHT; ++i)
buffer.insert(CONSOLE_WIDTH * (i + 1) + whitespace * i, whitespace, ' ');
}
WriteConsoleOutputCharacter(hOut, buffer.c_str(), buffer.size(), { 0,0 }, &charsWritten); // バッファの内容をコンソールに出力
}
}
// 画面をクリアして終了メッセージを表示
FillConsoleOutputCharacter(hOut, (TCHAR)' ', consoleInfo.dwSize.X * consoleInfo.dwSize.Y, { 0, 0 }, &charsWritten);
std::cout << "終了";
std::cin.get(); // ユーザーの入力を待つ
return 0;
}
書けたら「CMakeLists.txt」というファイルを作って、次の内容を書いてください。
cmake_minimum_required(VERSION 3.0)
project(YourProjectName)
find_package(OpenCV REQUIRED)
add_executable(YourExecutableName code.cpp)
target_link_libraries(YourExecutableName PRIVATE ${OpenCV_LIBS})
VScodeでopencvをコンパイルする
cmake . -B build
cmake --build build
ターミナルで↑を実行してください。

そしたら、\build\Debug\うんちゃら.exe が作られているはずなので
.\build\Debug\うんちゃら.exe .\hoge.mp4 150
と実行したら実行されます!!!
お疲れさまでした!

とある高専生。
AIとネットが好き。
将来はAIの妹と火星に住みたい。
discord : r_nightcore
このサイトの管理者。
コメント