3
votes

Synchronisation de la fréquence d'images C ++ OpenCV VideoWriter

Je suis sur le point de capturer des images à partir d'une carte d'acquisition vidéo. Ces images sont traitées et écrites sur le disque dur. L'ensemble du paramètre est dans un environnement multithreading, de sorte que le grabber écrit les images dans une file d'attente, et dans un autre thread, les images sont traitées et un autre écrit sur hdd. Si l'image est bonne par la définition du processeur, l'image est écrite sur hdd. Si 10 images consécutives sont «mauvaises», le fichier est terminé. S'il y a 9 images ou moins «mauvaises», toutes les images sont écrites avec la bonne image suivante, de sorte que le rédacteur du fichier est informé.

Voici le problème, si je ne le fais pas de cette façon, au lieu d'écrire chaque fichier directement après son traitement, le fichier vidéo va bien, mais 9 "mauvaises" images sont écrites. Si je le fais comme dans ma description ci-dessus, la vitesse / fréquence d'images de la vidéo ne convient pas. Cette description est un peu bizarre, voici donc juste un exemple simplifié, pour que vous puissiez voir le problème:

void FrameWriter::writeFrameLoop() {

    string path = getPath();
    cv::Size2i size(1350, 1080);
    cv::VideoWriter videoWriter(path, fourcc, 30, size);

    while (this->isRunning) {

        while (!this->frames.empty()) {
        
            usleep(100000); // this effects the speed/frame
            videoWriter.write(this->pop());
        }
        std::this_thread::sleep_for(10ms);
    }

    videoWriter.release();
}

L'exemple est assez simple, ici je "bloque" le processus d'écriture avec un sommeil, rappelez-vous que c'est un fil différent. Cela signifie qu'une fois la capture arrêtée, l'écriture du fichier prend un peu plus de temps. Mais je m'attendrais à ce que cela n'affecte pas la vidéo elle-même, car il y a un framerate de 30 et les images sont toujours dans le même ordre. Mais pour moi, il semble affecter le fichier vidéo, lorsque j'appelle "videoWriter.write" pas à temps. Dans ce cas, la vidéo est beaucoup plus rapide que prévu. Je pensais que seules les images configurées de 30 et le nombre d'images écrites affecteraient la vitesse de la vidéo, mais ce n'est pas le cas. Quelqu'un peut-il m'aider à comprendre ce qui se passe ici?

J'utilise openCV 4.4.0 avec Ubuntu 18.04. Merci de votre aide. BR Michael


5 commentaires

Je vous dis que si vous appelez videoWriter.write() par exemple 1000 fois par seconde, alors la vidéo résultante sera videoWriter.write() très rapidement? Donc, le rédacteur vidéo ne respecte pas le FPS de 30 fourni dans le constructeur?


Au contraire, j'appelle videoWriter.write () seulement 10 fois par seconde, puis la vidéo est plus rapide. Mais tu as raison, ça ne respecte pas les fps de 30. Ou du moins je n'ai aucune idée de ce qui se passe


Il existe une méthode getBackendNam () qui indique quel backend est utilisé (comme FFMPEG ), cela pourrait être le problème avec ce backend uniquement. Il devrait également y avoir des méthodes pour le changer en d'autres populaires.


Je pense savoir quel est le problème. Vous dites que vous appelez write() exactement 10 fois par seconde, soit 10 images. Vous obtenez également ces 10 images comme je l'attends des données en temps réel d'une carte à saisir. Et vous write() que ces 10 images via write() . Mais pour la bibliothèque de CV, vous avez dit que la vidéo est de 30 images par seconde. Cela signifie que vos 10 écritures remplissent en fait des données pendant 1/3 de seconde. Par conséquent, la vidéo doit être 3 fois plus rapide, chaque seconde de vidéo lit 3 secondes de vidéo capturée. Définissez simplement 10 pour le taux de FPS dans le constructeur du vidéographe au lieu de 30, ou dupliquez 3 fois chaque image


J'ai créé ma réponse avec plus de détails sur ce que j'ai dit ci-dessus.


3 Réponses :


1
votes

Je pense connaître la raison des vidéos de résultats à lecture rapide.

Dans le constructeur cv::VideoWriter videoWriter(path, fourcc, 30, size); vous définissez la fréquence d'images (FPS) de la vidéo résultante à 30. Cela signifie que la bibliothèque de CV attend exactement 30 images à write() par la fonction write() pour chaque seconde de flux vidéo résultant.

Aussi pour la bibliothèque de CV, il n'y a aucune différence à quelle vitesse vous appelez write() avec un nouveau cadre, vous pouvez l'appeler 5 fois par seconde ou 10 ou même 1000 fois. La seule chose qui compte, c'est que vous devez fournir exactement 30 images pour chaque seconde de vidéo, et la vitesse à laquelle vous fournissez ces 30 images n'a pas d'importance.

Je veux dire que toutes vos fonctionnalités de sleep(...) n'ont pas d'importance pour la classe CV VideoWriter. Et c'est toujours vrai pour toutes les bibliothèques de rendu / conversion vidéo. Donc, suspendre le thread ne change rien du tout.

Mais dans votre cas, vous dites que vous récupérez 10 images par seconde à partir des données vidéo en temps réel de la carte vidéo de votre grabber. Cela signifie que votre FPS est en réalité de 10 images par seconde. Donc, pour résoudre correctement votre tâche, les étapes suivantes doivent être effectuées:

  1. Supprimez toutes les fonctionnalités de mise en pause, comme appeler sleep() . Ce n'est pas du tout nécessaire. Et ne change pas le comportement de VideoWriter.
  2. Ensuite, la première façon de résoudre la tâche est de modifier votre constructeur cv::VideoWriter videoWriter(path, fourcc, 30, size); valeur 30 à 10. Cela résoudra déjà votre tâche, mais vous devez être sûr de saisir 10 images par seconde, pas plus ni moins. Ensuite, votre vidéo sera une vidéo correctement lue (vitesse correcte) avec une fréquence d'images de 10 images par seconde. C'est la solution la plus simple. La vidéo n'a pas besoin d'être à 30 FPS pour être lue correctement plus tard, la vidéo 10 FPS sera correctement lue plus tard par n'importe quel lecteur.
  3. Une autre solution, si vous voulez vraiment que votre vidéo résultante joue 30 images par seconde, pas moins, pas plus, alors dupliquez chaque image de votre vidéo capturée trois fois, ainsi vous obtiendrez 30 images sur 10 images de votre vidéo capturée. Par duplication, je veux simplement dire que vous devez appeler videWriter.write(...) trois fois (dans une petite boucle) avec la même image unique, appelez cette write sans aucune pause (comme sleep ). Ensuite, votre vidéo résultante aura exactement 30 images par seconde.

Je pense que vous n'avez pas compris comment fonctionne CV::VideoWriter . Vous pensiez que write() rendait la vidéo résultante en temps réel, ce qui signifie que si vous la nourrissez de 10 images mais exactement en une seconde, la vitesse de la vidéo devrait être correcte. Mais cet auteur ne rend pas la vidéo en temps réel, ce qui signifie qu'il suppose simplement que 10 images passées ne constituent que 1/3 de seconde des vidéos résultantes, par conséquent, il s'attend à ce que 30 images soient écrites pendant 1 seconde résultante.


3 commentaires

Non, j'ai bien compris le videoWriter, je m'attendais à ce que lorsque j'écris 30 images par seconde, le résultat devrait être le même que l'écriture de ces 30 images en 2 minutes. Ainsi, seuls les fps configurés devraient avoir de l'importance. Et c'est exactement comme ça, voir mon post pour comprendre ce que j'ai fait de mal


@ user2267367 Très étrange, j'ai toujours pensé que toutes les routines de conversion / rendu vidéo devraient être hors ligne, ce qui signifie qu'elles rendront la même sortie vidéo quelle que soit la vitesse d'alimentation des images. Alors désolé, bien que vous ayez trouvé la bonne solution .


Il le fait, il agit comme prévu, mais à cause de l'environnement multithread et de ma copie profonde foirée, cela n'a pas fonctionné. Donc c'était juste ma faute.



0
votes

J'ai compris mon problème et c'était moi, pas OpenCV. En raison de l'environnement multithreading, j'écrivais les images (cv :: Mat) dans une file d'attente. Puisqu'il y a beaucoup de transformations YUV -> RGB -> BGR -> Crop etc. Je m'attendais à ce que l'objet cv :: Mat que j'ai mis dans la file d'attente soit une copie profonde. Au lieu de cela, le cv :: Mat était toujours le même objet. Ce qui signifie que même s'il y avait déjà 20 entrées cv :: Mat dans ma file d'attente, elles étaient toutes identiques et toutes "changées" chaque fois qu'il y avait une nouvelle image.


0 commentaires

0
votes

Si vous avez une caméra qui n'est pas en mesure de fournir une fréquence d'images constante de disons 30 images, vous pouvez également envisager de limiter la fréquence d'images vous-même à 25 par exemple et mesurer le temps écoulé depuis l'écriture de la dernière image. Vous pouvez également modifier votre fréquence d'images par des valeurs arbitraires, tant que la caméra est en mesure de la fournir. Un exemple d'implémentation:

m_fps = 25;
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point now;

while (1) {

    now = std::chrono::steady_clock::now();
    long duration = std::chrono::duration_cast<std::chrono::nanoseconds>(now - start).count();
    
    if ((double)duration > (1e9 / m_fps)) {

        start = std::chrono::steady_clock::now();
        
        m_duration += duration;

        // Capture frame-by-frame
        if(m_cap->grab()) { m_cap->retrieve(m_frame); }

        // write frame
        m_writer->write(m_frame);
        cv::waitKey(1);

    }
}


0 commentaires