Задержка 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);

камера подает поток со скоростью 20 кадров в секунду. Но если бы я сделал чтение в 20fps, как это:

if (!cap.isOpened()) return;

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

        m_imageFrame = frame;
        mutex.unlock();

Тогда есть 3 + секундное отставание. Причина в том, что захваченное видео сначала сохраняется в буфере. Когда я впервые запускаю камеру, буфер накапливается, но я не читал фреймы. Поэтому, если я читаю из буфера, он всегда дает мне старые фреймы. Единственные решения, которые у меня есть сейчас, - это прочитать буфер со скоростью 30 кадров в секунду, чтобы он быстро очистил буфер и не было более серьезного запаздывания.

Есть ли другое возможное решение, чтобы я мог вручную очищать/очищать буфер вручную при каждом запуске камеры?

Ответы

Ответ 1

Решение OpenCV

Согласно этому источнику, вы можете установить размер cv::VideoCapture объекта 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

Другое решение, вдохновленное этой статьей, заключается в создании третьего потока, который непрерывно захватывает кадры с высокой скоростью, чтобы сохранить буфер пустым. Этот поток должен использовать cv::VideoCapture.grab() чтобы избежать накладных расходов.

Вы можете использовать простую спин-блокировку для синхронизации рамок чтения между реальным рабочим потоком и третьим потоком.

Ответ 2

Ребята, это довольно глупое и неприятное решение, но принятый ответ не помог мне по некоторым причинам. (Код на 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()

Ответ 3

Вы можете убедиться, что захват кадра занимает немного времени. Это довольно просто кодировать, хотя немного ненадежным; возможно, этот код может привести к тупиковой ситуации.

#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);

В коде используется С++ 11.

Ответ 4

Если вам известна частота кадров вашей камеры, вы можете использовать эту информацию (например, 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)