web-dev-qa-db-fra.com

Décalage d'OpenCV VideoCapture dû au tampon de capture

Je capture une vidéo via une webcam qui donne un flux mjpeg. J'ai fait la capture vidéo dans un fil de travail. Je commence la capture comme ceci:

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

la caméra alimente le flux à 20 ips. Mais si je fais la lecture en 20fps comme ceci:

if (!cap.isOpened()) return;

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

        m_imageFrame = frame;
        mutex.unlock();

Ensuite, il y a un décalage de 3+ secondes. La raison en est que la vidéo capturée est d'abord stockée dans un tampon. Lorsque je démarre la caméra pour la première fois, le tampon est accumulé mais je n'ai pas lu les images. Donc, si je lis dans le tampon, cela me donne toujours les anciennes images. La seule solution que j'ai maintenant est de lire le tampon à 30 images par seconde afin qu'il nettoie le tampon rapidement et qu'il n'y ait plus de retard sérieux.

Existe-t-il une autre solution possible pour que je puisse nettoyer/rincer le tampon manuellement à chaque démarrage de la caméra?

19
Nyaruko

Solution OpenCV

Selon this source, vous pouvez définir la taille de la mémoire tampon d'un objet cv::VideoCapture.

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

// rest of your code...

Il existe cependant une limitation importante:

CV_CAP_PROP_BUFFERSIZE Nombre de trames stockées dans la mémoire tampon interne (remarque: uniquement pris en charge par le backend DC1394 v 2.x actuellement)

Mise à jour à partir des commentaires. Dans les versions plus récentes d'OpenCV (3.4+), la limitation semble avoir disparu et le code utilise des énumérations de portée:

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

Hackaround 1

Si la solution ne fonctionne pas, jetez un œil à cet article qui explique comment contourner le problème.

En bref: le temps nécessaire pour interroger une trame est mesuré; s'il est trop bas, cela signifie que la trame a été lue dans le tampon et peut être supprimée. Continuez à interroger les trames jusqu'à ce que le temps mesuré dépasse une certaine limite. Lorsque cela se produit, le tampon était vide et la trame renvoyée est à jour.

(La réponse sur le message lié montre: le retour d'un cadre à partir du tampon prend environ 1/8e du temps de retour d'un cadre à jour. Votre kilométrage peut varier, bien sûr!)


Hackaround 2

Une autre solution, inspirée de this post, consiste à créer un troisième thread qui récupère les images en continu à grande vitesse pour garder le tampon vide. Ce thread doit utiliser cv::VideoCapture.grab() pour éviter la surcharge.

Vous pouvez utiliser un simple verrou rotatif pour synchroniser les cadres de lecture entre le véritable thread de travail et le troisième thread.

26
Maarten Bamelis

Les gars, c'est une solution assez stupide et méchante, mais la réponse acceptée ne m'a pas aidé pour certaines raisons. (Code en python mais l'essence est assez claire)

# 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

Vous pouvez vous assurer que la saisie du cadre a pris un peu de temps. Il est assez simple à coder, quoique peu fiable; potentiellement, ce code pourrait conduire à un blocage.

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

Le code utilise C++ 11.

3
emu

Si vous connaissez la fréquence d'images de votre appareil photo, vous pouvez utiliser ces informations (c'est-à-dire 30 images par seconde) pour saisir les images jusqu'à ce que vous obteniez une fréquence d'images inférieure. Cela fonctionne parce que si la fonction de capture est retardée (c'est-à-dire que vous obtenez plus de temps pour saisir une image que la fréquence d'images standard), cela signifie que vous avez obtenu chaque image dans le tampon et que l'ouverture doit attendre que la prochaine image provienne de la caméra.

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)