由于捕获缓冲区,OpenCV VideoCapture滞后

时间:2015-05-04 13:59:35

标签: c++ opencv video

我正在通过网络摄像头捕获视频,该网络摄像头提供了一个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的速度读取缓冲区,这样就可以快速清理缓冲区,并且没有更严重的延迟。

是否有其他可能的解决方案,以便每次启动相机时都可以手动清洁/冲洗缓冲区?

6 个答案:

答案 0 :(得分:21)

根据this来源,您可以设置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后端

如果解决方案不起作用,请查看解释如何解决此问题的this post

简而言之:测量查询框架所需的时间;如果它太低,则表示从缓冲区读取帧并可以丢弃。继续查询帧,直到测量的时间超过一定限度。发生这种情况时,缓冲区为空,返回的帧是最新的。

(链接帖子的答案显示:从缓冲区返回一个帧大约需要返回最新帧的时间的1/8。当然,你的里程可能会有所不同!)

this帖子启发的另一个解决方案是创建第三个线程,以高速连续抓取帧以保持缓冲区为空。该线程应该使用cv::VideoCapture.grab()来避免开销。

您可以使用简单的自旋锁来同步实际工作线程和第三个线程之间的读取帧。

答案 1 :(得分:4)

伙计,这是非常愚蠢和讨厌的解决方案,但是由于某些原因,被接受的答案并没有帮助我。 (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()

答案 2 :(得分:2)

您可以确保抓取框架需要一些时间。编码非常简单,虽然有点不可靠;可能,此代码可能导致死锁。

#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 :(得分:1)

如果使用GStreamer管道,则可以选择删除旧缓冲区。 appsink drop=true option “当缓冲区队列已满时,丢弃旧缓冲区” 。在我的特殊情况下,实时流处理过程中会时不时地有延迟,因此需要获取每个VideoCapture.read调用的最新帧。

#include <chrono>
#include <thread>

#include <opencv4/opencv2/highgui.hpp>

static constexpr const char * const WINDOW = "1";

void video_test() {
    // It doesn't work properly without `drop=true` option
    cv::VideoCapture video("v4l2src device=/dev/video0 ! videoconvert ! videoscale ! videorate ! video/x-raw,width=640 ! appsink drop=true", cv::CAP_GSTREAMER);

    if(!video.isOpened()) {
        return;
    }

    cv::namedWindow(
        WINDOW,
        cv::WINDOW_GUI_NORMAL | cv::WINDOW_NORMAL | cv::WINDOW_KEEPRATIO
    );
    cv::resizeWindow(WINDOW, 700, 700);

    cv::Mat frame;
    const std::chrono::seconds sec(1);
    while(true) {
        if(!video.read(frame)) {
            break;
        }
        std::this_thread::sleep_for(sec);
        cv::imshow(WINDOW, frame);
        cv::waitKey(1);
    }
}

答案 4 :(得分:0)

如果您知道相机的帧速率,则可以使用此信息(即每秒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)

答案 5 :(得分:0)

Maarten的答案中使用Python实现了Hackaround 2。它启动一个线程,并将camera.read()中的最新帧保留为类属性。可以在c ++中完成类似的策略

import threading
import cv2

# Define the thread that will continuously pull frames from the camera
class CameraBufferCleanerThread(threading.Thread):
    def __init__(self, camera, name='camera-buffer-cleaner-thread'):
        self.camera = camera
        self.last_frame = None
        super(CameraBufferCleanerThread, self).__init__(name=name)
        self.start()

    def run(self):
        while True:
            ret, self.last_frame = self.camera.read()

# Start the camera
camera = cv2.VideoCapture(0)

# Start the cleaning thread
cam_cleaner = CameraBufferCleanerThread(camera)

# Use the frame whenever you want
while True:
    if cam_cleaner.last_frame is not None:
        cv2.imshow('The last frame', cam_cleaner.last_frame)
    cv2.waitKey(10)