web-dev-qa-db-ja.com

キャプチャバッファによるOpenCV VideoCaptureの遅延

私はmjpegストリームを提供するウェブカメラでビデオをキャプチャしています。ワーカースレッドでビデオキャプチャを行いました。次のようにキャプチャを開始します。

const std::string videoStreamAddress = "http://192.168.1.173:80/live/0/mjpeg.jpg?x.mjpeg";
qDebug() << "start";
cap.open(videoStreamAddress);
qDebug() << "really started";
cap.set(CV_CAP_PROP_FRAME_WIDTH, 720);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, 576);

カメラは20fpsでストリームを供給しています。しかし、次のように20fpsで読み取りを行った場合:

if (!cap.isOpened()) return;

        Mat frame;
        cap >> frame; // get a new frame from camera
        mutex.lock();

        m_imageFrame = frame;
        mutex.unlock();

その後、3秒以上の遅れがあります。その理由は、キャプチャされたビデオが最初にバッファに保存されるためです。最初にカメラを起動したとき、バッファは蓄積されますが、フレームを読み取れませんでした。したがって、バッファから読み取ると、常に古いフレームが表示されます。私が現在持っている唯一の解決策は、30fpsでバッファを読み取ることです。これにより、バッファが迅速にクリーニングされ、深刻な遅延がなくなります。

カメラを起動するたびに手動でバッファをクリーニング/フラッシュできる他の解決策はありますか?

19
Nyaruko

OpenCVソリューション

this sourceによると、_cv::VideoCapture_オブジェクトのバッファサイズを設定できます。

_cv::VideoCapture cap;
cap.set(CV_CAP_PROP_BUFFERSIZE, 3); // internal buffer will now store only 3 frames

// rest of your code...
_

ただし、重要な制限があります。

CV_CAP_PROP_BUFFERSIZE内部バッファメモリに格納されているフレームの量(注:DC1394 v 2.xバックエンドのみで現在サポートされています)

コメントからの更新。OpenCVの新しいバージョン(3.4+)では、制限がなくなったようで、コードはスコープ付き列挙を使用します。

_cv::VideoCapture cap;
cap.set(cv::CAP_PROP_BUFFERSIZE, 3);
_

ハックアラウンド1

解決策が機能しない場合は、 この投稿 をご覧ください。問題を回避する方法を説明しています。

簡単に言うと、フレームのクエリに必要な時間が測定されます。低すぎる場合、フレームがバッファから読み取られ、破棄できることを意味します。測定された時間が特定の制限を超えるまで、フレームのクエリを続けます。この場合、バッファは空で、返されたフレームは最新です。

(リンクされた投稿の答えは、バッファからフレームを返すには、最新のフレームを返す時間の約1/8がかかることを示しています。もちろん、走行距離は異なる場合があります!)


ハックアラウンド2

this postに触発された別の解決策は、バッファーを空に保つために高速でフレームを継続的に取得する3番目のスレッドを作成することです。このスレッドは、オーバーヘッドを回避するために cv::VideoCapture.grab() を使用する必要があります。

単純なスピンロックを使用して、実際のワーカースレッドと3番目のスレッドの間で読み取りフレームを同期できます。

26
Maarten Bamelis

みんなこれはかなり愚かで厄介な解決策ですが、受け入れられた答えはいくつかの理由で私を助けませんでした。 (pythonのコードですが、本質はかなり明確です)

# vcap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
data = np.zeros((1140, 2560))
image = plt.imshow(data)

while True:
    vcap = cv2.VideoCapture("rtsp://admin:@192.168.3.231")
    ret, frame = vcap.read()
    image.set_data(frame)
    plt.pause(0.5) # any other consuming operation
    vcap.release()
6
Ivan Talalaev

フレームをつかむのに少し時間がかかったことを確認できます。コーディングは簡単ですが、少し信頼できません。潜在的に、このコードはデッドロックを引き起こす可能性があります。

#include <chrono>
using clock = std::chrono::high_resolution_clock;
using duration_float = std::chrono::duration_cast<std::chrono::duration<float>>;
// ...
while (1) {
    TimePoint time_start = clock::now();
    camera.grab();
    if (duration_float(clock::now() - time_start).count() * camera.get(cv::CAP_PROP_FPS) > 0.5) {
        break;
    }
}
camera.retrieve(dst_image);

コードはC++ 11を使用します。

3
emu

カメラのフレームレートがわかっている場合は、この情報(1秒あたり30フレーム)を使用して、フレームレートが低くなるまでフレームを取得できます。グラブ機能が遅れると(つまり、標準フレームレートよりもフレームをグラブする時間が長くなると)、バッファ内のすべてのフレームを取得し、opencvが次のフレームがカメラから来るのを待つ必要があるためです。

while(True):
    prev_time=time.time()
    ref=vid.grab()
    if (time.time()-prev_time)>0.030:#something around 33 FPS
        break
ret,frame = vid.retrieve(ref)