OpenCV C++ 動画保存時のフレーム欠損

VC++でのOpenCVのよくあるビデオカメラの読み込みと動画の保存のサンプルは、ほとんど空のプロジェクトを作成して、main() の中に全部入れてしまえ!ってやつですよね。

でも単純ループさせると、録画のフレームが全く時間管理できてない状態になってしまいます。

実際によくあるサンプルで録画したファイルを再生すると大抵早送り状態になってしまうと思います。そこで何かの参考や気づきになるかもと思い、ちょっと実験してみました。

カメラによる差

単純ループで、

cv::VideoWriter writer; //動画保存インスタンス

に対して、MATのインスタンス、大抵 frame といったネーミングになってます。

    while (cap.read(frame)){
        cv::imshow("", frame);//画像を表示.
        writer << frame;
    }

このようなループで出来たファイルを再生すると早送り状態になります。

じゃあ何がどうなんだろうという事で、OpenCVにかかわる時間を計測してみました。

    for (;;) {

        //1フレームキャプチャ
        tts1 = clock();
        cap >> frame;
        tts2 = clock();
       
        //1フレーム表示
        cv::imshow("", frame);
        tts3 = clock();

        //フレーム連結
        writer.write(frame);
        tts4 = clock();

    }

計測点はそれぞれの関数の所。で2つのカメラで計測してみました。以下の結果テーブルは別の値も含んでいるので、まず  #1to2=005, #2to3=01, #3to4=02   #lap  という部分だけご覧ください。

#1to2 :  キャプチャにかかった時間

#2to3 : 1フレームをWindow表示にかかった時間

#3to4 : writerインスタンスに1フレームを書き込んだ時間

すべて mSec 単位

5秒間のキャプチャ

マシンは Corei3  3.5GHz 12Gメモリ で少々ダル、でもそれなりに動作するレベルなPC

カメラA   ELECOMの1.3Mpixelと書いてある一般的なWEBカメラ。

TTS #1to2=459, #2to3=17, #3to4=41, lap=517, nShots=14, LOOP=014
TTS #1to2=004, #2to3=01, #3to4=08, lap=54, nShots=1, LOOP=015
TTS #1to2=004, #2to3=02, #3to4=07, lap=22, nShots=1, LOOP=016
TTS #1to2=041, #2to3=02, #3to4=07, lap=57, nShots=1, LOOP=017
TTS #1to2=008, #2to3=02, #3to4=05, lap=22, nShots=1, LOOP=018
TTS #1to2=053, #2to3=03, #3to4=42, lap=103, nShots=1, LOOP=019
TTS #1to2=004, #2to3=02, #3to4=11, lap=59, nShots=2, LOOP=021
TTS #1to2=004, #2to3=03, #3to4=02, lap=20, nShots=0, LOOP=021
TTS #1to2=074, #2to3=03, #3to4=15, lap=94, nShots=3, LOOP=024
TTS #1to2=004, #2to3=02, #3to4=02, lap=23, nShots=0, LOOP=024
TTS #1to2=052, #2to3=02, #3to4=08, lap=64, nShots=2, LOOP=026
TTS #1to2=005, #2to3=02, #3to4=06, lap=21, nShots=1, LOOP=027
TTS #1to2=057, #2to3=02, #3to4=08, lap=73, nShots=2, LOOP=029
TTS #1to2=003, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=029
TTS #1to2=080, #2to3=02, #3to4=07, lap=91, nShots=2, LOOP=031
TTS #1to2=004, #2to3=01, #3to4=05, lap=17, nShots=1, LOOP=032
TTS #1to2=061, #2to3=02, #3to4=07, lap=75, nShots=2, LOOP=034
TTS #1to2=003, #2to3=02, #3to4=02, lap=14, nShots=0, LOOP=034
TTS #1to2=062, #2to3=02, #3to4=10, lap=76, nShots=2, LOOP=036
TTS #1to2=004, #2to3=02, #3to4=05, lap=21, nShots=1, LOOP=037
TTS #1to2=057, #2to3=03, #3to4=10, lap=75, nShots=2, LOOP=039
TTS #1to2=004, #2to3=03, #3to4=02, lap=19, nShots=0, LOOP=039
TTS #1to2=075, #2to3=02, #3to4=09, lap=89, nShots=3, LOOP=042
TTS #1to2=004, #2to3=01, #3to4=02, lap=17, nShots=0, LOOP=042
TTS #1to2=059, #2to3=02, #3to4=07, lap=70, nShots=2, LOOP=044
TTS #1to2=004, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=044
TTS #1to2=064, #2to3=01, #3to4=08, lap=75, nShots=2, LOOP=046
TTS #1to2=004, #2to3=01, #3to4=06, lap=20, nShots=1, LOOP=047
TTS #1to2=058, #2to3=03, #3to4=09, lap=76, nShots=2, LOOP=049
TTS #1to2=005, #2to3=02, #3to4=02, lap=18, nShots=0, LOOP=049
TTS #1to2=060, #2to3=01, #3to4=09, lap=72, nShots=2, LOOP=051
TTS #1to2=003, #2to3=02, #3to4=02, lap=17, nShots=0, LOOP=051
TTS #1to2=078, #2to3=01, #3to4=12, lap=93, nShots=3, LOOP=054
TTS #1to2=003, #2to3=02, #3to4=02, lap=19, nShots=0, LOOP=054
TTS #1to2=061, #2to3=01, #3to4=10, lap=74, nShots=2, LOOP=056
TTS #1to2=004, #2to3=01, #3to4=06, lap=21, nShots=1, LOOP=057
TTS #1to2=056, #2to3=02, #3to4=07, lap=72, nShots=2, LOOP=059
TTS #1to2=004, #2to3=01, #3to4=02, lap=14, nShots=0, LOOP=059
TTS #1to2=064, #2to3=01, #3to4=08, lap=76, nShots=2, LOOP=061
TTS #1to2=004, #2to3=02, #3to4=05, lap=20, nShots=1, LOOP=062
TTS #1to2=075, #2to3=01, #3to4=08, lap=89, nShots=2, LOOP=064
TTS #1to2=004, #2to3=02, #3to4=02, lap=16, nShots=0, LOOP=064
TTS #1to2=063, #2to3=02, #3to4=08, lap=75, nShots=2, LOOP=066
TTS #1to2=004, #2to3=02, #3to4=05, lap=19, nShots=1, LOOP=067
TTS #1to2=058, #2to3=02, #3to4=14, lap=79, nShots=2, LOOP=069
TTS #1to2=065, #2to3=01, #3to4=07, lap=87, nShots=2, LOOP=071
TTS #1to2=003, #2to3=02, #3to4=04, lap=17, nShots=1, LOOP=072
TTS #1to2=078, #2to3=01, #3to4=08, lap=91, nShots=2, LOOP=074
TTS #1to2=003, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=074
TTS #1to2=064, #2to3=02, #3to4=09, lap=77, nShots=3, LOOP=077
TTS #1to2=003, #2to3=02, #3to4=02, lap=16, nShots=0, LOOP=077
TTS #1to2=061, #2to3=01, #3to4=10, lap=74, nShots=2, LOOP=079
TTS #1to2=005, #2to3=02, #3to4=02, lap=19, nShots=0, LOOP=079
TTS #1to2=060, #2to3=03, #3to4=07, lap=72, nShots=2, LOOP=081
TTS #1to2=004, #2to3=01, #3to4=04, lap=16, nShots=1, LOOP=082
TTS #1to2=061, #2to3=02, #3to4=07, lap=75, nShots=2, LOOP=084
TTS #1to2=004, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=084
TTS #1to2=077, #2to3=02, #3to4=10, lap=91, nShots=3, LOOP=087
TTS #1to2=005, #2to3=02, #3to4=02, lap=19, nShots=0, LOOP=087
TTS #1to2=060, #2to3=02, #3to4=07, lap=72, nShots=2, LOOP=089
TTS #1to2=004, #2to3=01, #3to4=02, lap=15, nShots=0, LOOP=089
TTS #1to2=061, #2to3=01, #3to4=07, lap=72, nShots=2, LOOP=091
TTS #1to2=004, #2to3=02, #3to4=04, lap=17, nShots=1, LOOP=092
TTS #1to2=062, #2to3=01, #3to4=07, lap=74, nShots=2, LOOP=094
TTS #1to2=003, #2to3=02, #3to4=02, lap=14, nShots=0, LOOP=094
TTS #1to2=081, #2to3=02, #3to4=12, lap=97, nShots=3, LOOP=097
TTS #1to2=004, #2to3=01, #3to4=02, lap=19, nShots=0, LOOP=097
TTS #1to2=058, #2to3=03, #3to4=14, lap=78, nShots=2, LOOP=099
TTS #1to2=005, #2to3=04, #3to4=07, lap=30, nShots=1, LOOP=100
TTS #1to2=048, #2to3=01, #3to4=05, lap=61, nShots=1, LOOP=101
TTS #1to2=004, #2to3=02, #3to4=06, lap=18, nShots=1, LOOP=102
TTS #1to2=059, #2to3=02, #3to4=09, lap=76, nShots=2, LOOP=104
TTS #1to2=004, #2to3=02, #3to4=02, lap=17, nShots=0, LOOP=104
TTS #1to2=079, #2to3=01, #3to4=09, lap=91, nShots=3, LOOP=107
TTS #1to2=004, #2to3=02, #3to4=02, lap=17, nShots=0, LOOP=107
TTS #1to2=062, #2to3=02, #3to4=08, lap=74, nShots=2, LOOP=109
TTS #1to2=004, #2to3=02, #3to4=04, lap=18, nShots=1, LOOP=110
TTS #1to2=059, #2to3=02, #3to4=08, lap=74, nShots=2, LOOP=112
TTS #1to2=004, #2to3=02, #3to4=02, lap=16, nShots=0, LOOP=112
TTS #1to2=062, #2to3=02, #3to4=08, lap=74, nShots=2, LOOP=114
TTS #1to2=003, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=114
TTS #1to2=063, #2to3=02, #3to4=07, lap=74, nShots=2, LOOP=116
TTS #1to2=004, #2to3=01, #3to4=04, lap=16, nShots=1, LOOP=117
TTS #1to2=076, #2to3=02, #3to4=08, lap=90, nShots=2, LOOP=119
TTS #1to2=003, #2to3=02, #3to4=04, lap=17, nShots=1, LOOP=120
TTS #1to2=060, #2to3=02, #3to4=08, lap=75, nShots=2, LOOP=122
TTS #1to2=005, #2to3=02, #3to4=02, lap=17, nShots=0, LOOP=122
TTS #1to2=063, #2to3=02, #3to4=07, lap=74, nShots=2, LOOP=124
TTS #1to2=004, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=124
TTS #1to2=061, #2to3=02, #3to4=06, lap=71, nShots=2, LOOP=126
TTS #1to2=090, #2to3=05, #3to4=12, lap=113, nShots=3, LOOP=129
TTS #1to2=004, #2to3=03, #3to4=04, lap=23, nShots=1, LOOP=130
TTS #1to2=051, #2to3=01, #3to4=09, lap=65, nShots=2, LOOP=132
TTS #1to2=003, #2to3=02, #3to4=02, lap=16, nShots=0, LOOP=132
TTS #1to2=062, #2to3=02, #3to4=07, lap=73, nShots=2, LOOP=134
TTS #1to2=004, #2to3=01, #3to4=02, lap=15, nShots=0, LOOP=134
TTS #1to2=062, #2to3=02, #3to4=07, lap=74, nShots=2, LOOP=136
TTS #1to2=004, #2to3=02, #3to4=04, lap=17, nShots=1, LOOP=137
TTS #1to2=061, #2to3=03, #3to4=09, lap=77, nShots=2, LOOP=139
TTS #1to2=004, #2to3=02, #3to4=03, lap=18, nShots=0, LOOP=139
TTS #1to2=076, #2to3=02, #3to4=09, lap=90, nShots=3, LOOP=142
TTS #1to2=003, #2to3=02, #3to4=03, lap=17, nShots=0, LOOP=142
TTS #1to2=062, #2to3=02, #3to4=08, lap=75, nShots=2, LOOP=144
TTS #1to2=004, #2to3=01, #3to4=05, lap=19, nShots=1, LOOP=145
TTS #1to2=058, #2to3=01, #3to4=08, lap=73, nShots=2, LOOP=147
TTS #1to2=004, #2to3=02, #3to4=03, lap=17, nShots=0, LOOP=147
TTS #1to2=062, #2to3=01, #3to4=07, lap=73, nShots=2, LOOP=149
TTS #1to2=004, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=149

次に

カメラB  メーカー不明のMegaPixelと書いてある物。

TTS #1to2=1466, #2to3=21, #3to4=110, lap=1597, nShots=44, LOOP=044
TTS #1to2=008, #2to3=02, #3to4=21, lap=142, nShots=4, LOOP=048
TTS #1to2=005, #2to3=02, #3to4=07, lap=36, nShots=1, LOOP=049
TTS #1to2=061, #2to3=03, #3to4=36, lap=107, nShots=2, LOOP=051
TTS #1to2=005, #2to3=01, #3to4=06, lap=48, nShots=1, LOOP=052
TTS #1to2=043, #2to3=02, #3to4=07, lap=58, nShots=2, LOOP=054
TTS #1to2=004, #2to3=02, #3to4=02, lap=15, nShots=0, LOOP=054
TTS #1to2=061, #2to3=02, #3to4=07, lap=72, nShots=2, LOOP=056
TTS #1to2=004, #2to3=01, #3to4=03, lap=15, nShots=0, LOOP=056
TTS #1to2=097, #2to3=02, #3to4=13, lap=115, nShots=3, LOOP=059
TTS #1to2=005, #2to3=04, #3to4=05, lap=27, nShots=1, LOOP=060
TTS #1to2=081, #2to3=02, #3to4=08, lap=96, nShots=3, LOOP=063
TTS #1to2=004, #2to3=01, #3to4=02, lap=16, nShots=0, LOOP=063
TTS #1to2=095, #2to3=02, #3to4=08, lap=107, nShots=3, LOOP=066
TTS #1to2=004, #2to3=01, #3to4=02, lap=15, nShots=0, LOOP=066
TTS #1to2=095, #2to3=01, #3to4=09, lap=108, nShots=3, LOOP=069
TTS #1to2=004, #2to3=02, #3to4=04, lap=19, nShots=1, LOOP=070
TTS #1to2=106, #2to3=01, #3to4=10, lap=121, nShots=3, LOOP=073
TTS #1to2=004, #2to3=01, #3to4=05, lap=20, nShots=1, LOOP=074
TTS #1to2=091, #2to3=02, #3to4=12, lap=110, nShots=3, LOOP=077
TTS #1to2=004, #2to3=03, #3to4=02, lap=22, nShots=0, LOOP=077
TTS #1to2=104, #2to3=02, #3to4=09, lap=118, nShots=3, LOOP=080
TTS #1to2=003, #2to3=02, #3to4=04, lap=18, nShots=1, LOOP=081
TTS #1to2=092, #2to3=01, #3to4=11, lap=108, nShots=3, LOOP=084
TTS #1to2=004, #2to3=02, #3to4=02, lap=19, nShots=0, LOOP=084
TTS #1to2=108, #2to3=02, #3to4=09, lap=121, nShots=4, LOOP=088
TTS #1to2=004, #2to3=01, #3to4=02, lap=17, nShots=0, LOOP=088
TTS #1to2=092, #2to3=02, #3to4=09, lap=106, nShots=3, LOOP=091
TTS #1to2=003, #2to3=02, #3to4=02, lap=16, nShots=0, LOOP=091
TTS #1to2=110, #2to3=01, #3to4=13, lap=127, nShots=4, LOOP=095
TTS #1to2=004, #2to3=02, #3to4=02, lap=21, nShots=0, LOOP=095
TTS #1to2=105, #2to3=02, #3to4=12, lap=121, nShots=4, LOOP=099
TTS #1to2=004, #2to3=01, #3to4=03, lap=21, nShots=0, LOOP=099
TTS #1to2=090, #2to3=01, #3to4=10, lap=104, nShots=3, LOOP=102
TTS #1to2=003, #2to3=02, #3to4=02, lap=17, nShots=0, LOOP=102
TTS #1to2=107, #2to3=02, #3to4=11, lap=122, nShots=4, LOOP=106
TTS #1to2=004, #2to3=01, #3to4=02, lap=18, nShots=0, LOOP=106
TTS #1to2=093, #2to3=02, #3to4=09, lap=106, nShots=3, LOOP=109
TTS #1to2=005, #2to3=02, #3to4=04, lap=20, nShots=1, LOOP=110
TTS #1to2=107, #2to3=02, #3to4=13, lap=126, nShots=3, LOOP=113
TTS #1to2=004, #2to3=02, #3to4=03, lap=22, nShots=1, LOOP=114
TTS #1to2=087, #2to3=02, #3to4=07, lap=100, nShots=2, LOOP=116
TTS #1to2=004, #2to3=02, #3to4=06, lap=20, nShots=1, LOOP=117
TTS #1to2=106, #2to3=02, #3to4=09, lap=123, nShots=3, LOOP=120
TTS #1to2=004, #2to3=01, #3to4=05, lap=19, nShots=1, LOOP=121
TTS #1to2=091, #2to3=02, #3to4=08, lap=106, nShots=3, LOOP=124
TTS #1to2=004, #2to3=02, #3to4=02, lap=16, nShots=0, LOOP=124
TTS #1to2=110, #2to3=02, #3to4=09, lap=123, nShots=3, LOOP=127
TTS #1to2=004, #2to3=02, #3to4=04, lap=19, nShots=1, LOOP=128
TTS #1to2=091, #2to3=02, #3to4=09, lap=106, nShots=3, LOOP=131
TTS #1to2=004, #2to3=02, #3to4=02, lap=17, nShots=0, LOOP=131
TTS #1to2=109, #2to3=02, #3to4=10, lap=123, nShots=4, LOOP=135
TTS #1to2=004, #2to3=02, #3to4=02, lap=18, nShots=0, LOOP=135
TTS #1to2=092, #2to3=02, #3to4=08, lap=104, nShots=3, LOOP=138
TTS #1to2=004, #2to3=02, #3to4=02, lap=16, nShots=0, LOOP=138
TTS #1to2=110, #2to3=02, #3to4=10, lap=124, nShots=4, LOOP=142
TTS #1to2=004, #2to3=02, #3to4=02, lap=18, nShots=0, LOOP=142
TTS #1to2=092, #2to3=02, #3to4=09, lap=105, nShots=3, LOOP=145
TTS #1to2=004, #2to3=01, #3to4=05, lap=19, nShots=1, LOOP=146
TTS #1to2=106, #2to3=03, #3to4=08, lap=122, nShots=3, LOOP=149
TTS #1to2=004, #2to3=03, #3to4=03, lap=18, nShots=0, LOOP=149

この結果を見ると、まず、

#lap  これはfor一周にかかった時間で、30fpsなら33.33mSec以内でないとフレーム欠損している状況

つまり多量にフレーム落ちしています。カメラBの安物はまったく遅いですね。 でその処理の部分はと言えば、#1to2で表した、キャプチャの部分です。ここの数値が非常に大きくなっています。

特に1回目のループでは何をしているのかカメラBでは、1466mSec秒もかかっています。WindowsなのでOSがどのように時間配分したのか分りませんが、少なくともOpenCVは明らかに別スレッドで走っているので、Windowsアプリに組み込んでも邪魔はしません。でもかかり過ぎですね。カメラAでも459mSec。マシですが、10枚以上のフレームが落ちてます。

これをそのままビデオ保存して再生すると、5秒の録画時間が3秒程度に縮まります。

そこで、考えられるのは、タイマ割り込みで処理する方法。

これやってみました。例えば WM_TIMER で OnTimer() 云々の定番パターン。SetTIimeを1mSecとして33mSecをカウントしても、Timerそのものを33mSecにしても、結局キャプチャ時間が遅いシステムではコードが状態遷移したり複雑になるだけで、あまり意味が無いことに気付き始めました。なのでここでは全部割愛!(汗)

おそらくいろんなシステムで動作するであろうのは、実時間と録画フレーム数の関係を保ち続けることだと考え、以下の方針でコードを作成してみました。

Loop をキャプチャ回数とすれば、経過時間-(Loop*(1秒/fps)>0 なら
その値を1000/fpsで割った回数分、同じ画像を書き込んで辻褄を合わせ、必要回数Loopしたことにする。

何のことだ?と思われる方にはコードを見てもらった方が早いので、実験コードを掲示しておきます。

このコードでは、時間をスーパーインポーズして再生時にわかりやすくしています。

#include <opencv2\opencv.hpp>
#include <opencv2\highgui\highgui_c.h>
#include <stdio.h>
#include <Windows.h>



int main(void)
{
    //ビデオ関連初期化
    cv::VideoCapture cap(0);    //動画キャプチャインスタンス CAM番号 0,1,....
    if (cap.isOpened() == false) { return -1; }

    int width = (int)cap.get(cv::CAP_PROP_FRAME_WIDTH);	    //フレーム幅取得
    int height = (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT);	//フレーム縦取得
    double fps = cap.get(cv::CAP_PROP_FPS);					//フレームレートFPS取得
    int fourcc = cv::VideoWriter::fourcc('M', 'P', '4', 'V');	//エンコードMPEG4指定

    cv::VideoWriter writer;     //動画保存インスタンス
    writer.open("d:\\_temp\\recordtest.mp4", fourcc, fps, cv::Size(width, height));
    cv::Mat frame;  //画像用行列インスタンス

    //時間計測用ワーク
    DWORD tts0 = clock();
    DWORD tts1;
    DWORD tts2;
    DWORD tts3;
    DWORD tts4;

    //フレーム調整用
    DWORD msecStart = clock();
    DWORD nLoop = 0;
    DWORD msecLap = 0;

    //プロセス
    for (;;) {

        //1フレームキャプチャ
        tts1 = clock();
        cap >> frame;

        //経過時間をインポーズ
        char StrLapTime[100];
        sprintf_s(StrLapTime, "%06u", msecLap);
        cv::putText(frame,StrLapTime,cv::Point(0,50),cv::FONT_HERSHEY_COMPLEX,2,cv::Scalar(255, 255, 0),5);
        tts2 = clock();
       
        //1フレーム表示
        cv::imshow("", frame);
        tts3 = clock();

        msecLap = clock() - msecStart;	//経過時間
        //フレーム連結
        //nLoop がここを通過した回数なので、経過時間-(nLoop*(1秒/fps)>0 なら
        //その値を1000/fpsで割った回数分書き込んでその回数をnLoopに加算して補正しておく
        int diff = msecLap - (nLoop * 1000 / fps);
        int nShoot = diff < 0 ? 0 : diff / (1000 / fps);
        nLoop += nShoot;

        for (int i = 0; i < nShoot; i++)    //同じフレームで穴埋め
                writer.write(frame);
        
        //終了トリガ
        if (cv::waitKey(1) == 'q' || msecLap > 5000 )  //5000mSec
            break;

        tts4 = clock();
        printf("TTS #1to2=%03d, #2to3=%02d, #3to4=%02d, lap=%02d, nShots=%01d, LOOP=%03d\n", tts2-tts1, tts3-tts2, tts4-tts3, tts4-tts0, nShoot, nLoop);
        tts0 = tts3;
    }

    cv::destroyAllWindows();

    return 0;
}

上の方の時間計測は、このコードの実行結果です。

#nShot は一周にかかった時間から何フレーム分の画像データを録画するか計算して、同じ画像ではありますが、必要枚数詰め込んで辻褄を合わせています。

5秒分のビデオ画像ですが、最初の一秒は止まってしまってます。ただ時間軸は正しく補正されているので、長時間録画しても実時間とのずれは無いようです。

実際のアプリケーションで使う場合は、動画のトリガとなるのが、キーボードであることは少なくて、Desktopアプリならマウスボタンのクリック、組み込みならBluetoothやらWi-Fi通信での端末からの指令というパターンが多いはず。あとはタイマー自動起動とかでしょうか。いずれの場合でもUIなど他のスレッドで動作しているプログラムが停止してしまうことは許されないはずなので、いろんな手段での工夫が必要なようです。

ずっとOpenCVを動画保存でも使っておられる方はもっとスマートな方法を使ってるのかもしれませんが、ひとまず人間が見て違和感のない範囲で許されるならこの程度の補正で大丈夫なようです。コストを掛けて良いなら、キャプチャデータをもっと高速で吐き出せるカメラを用意する方が先かもしれません。データキャプチャは高速で安定してできるなら、逆に時間待ちだけですべて処理できますからね。

ここまで実験した結果でした。