如何从USB摄像头获取的每个帧中提取时间戳?

时间:2017-06-12 02:47:01

标签: c# video

此场景在实时视频处理中很常见。我需要时间戳与其他设备同步。

我尝试了cv::VideoCapture,但它无法从视频流中提取时间戳。

所以我在这里有两个问题:

  1. USB摄像头提供的视频流确实包含时间戳信息吗?
  2. 如果有的话。我应该怎么做才能提取它? C#解决方案是最好的,而C ++没问题。
  3. 增加:

    使用这两个属性不起作用:

    secCounter = (long) cap.get(CAP_PROP_POS_MSEC);
    frameNumber = (long) cap.get(CAP_PROP_POS_FRAMES);
    

    它总是给出以下结果:

      

    VIDEOIO ERROR:V4L2:不支持获取属性#1

         

    msecCounter = 0

         

    frameNumber = -1

1 个答案:

答案 0 :(得分:1)

OpenCV的VideoCapture类是一个非常高级的界面,用于从相机中检索帧,因此它“隐藏”了连接相机,从相机检索帧和解码所需的大量细节。那些框架进入像BGR这样有用的颜色空间。这很好,因为您不必担心抓取帧的细节,但缺点是您无法直接访问您可能需要的其他数据,例如帧编号或帧时间戳。但这并不意味着无法获得您想要的数据!

这是一个示例帧抓取循环,可以根据example code from here松散地获得您想要的内容。这是用C ++编写的。

#include "opencv2/opencv.hpp"
using namespace cv;
int main(int, char**)
{
    VideoCapture cap(0); // open the default camera
    if(!cap.isOpened())  // check if we succeeded
        return -1;

    // TODO: change the width, height, and capture FPS to your desired
    // settings.
    cap.set(CAP_PROP_FRAME_WIDTH, 1920);
    cap.set(CAP_PROP_FRAME_HEIGHT, 1080);
    cap.set(CAP_PROP_FPS, 30);

    Mat frame;
    long msecCounter = 0;
    long frameNumber = 0;

    for(;;)
    {            
        // Instead of cap >> frame; we'll do something different.
        //
        // VideoCapture::grab() tells OpenCV to grab a frame from
        // the camera, but to not worry about all the color conversion
        // and processing to convert that frame into BGR.
        //
        // This means there's less processing overhead, so the time
        // stamp will be more accurate because we are fetching it
        // immediately after.
        //
        // grab() should also wait for the next frame to be available
        // based on the capture FPS that is set, so it's okay to loop
        // continuously over it.

        if(cap.grab())
        {
            msecCounter = (long) cap.get(CAP_PROP_POS_MSEC);
            frameNumber = (long) cap.get(CAP_PROP_POS_FRAMES);

            // VideoCapture::retrieve color converts the image and places
            // it in the Mat that you provide.
            if(cap.retrieve(&frame))
            {
                // Pass the frame and parameters to your processing
                // method.
                ProcessFrame(&frame, msecCounter, frameNumber);
            }
        }

        // TODO: Handle your loop termination condition here
    }
    // the camera will be deinitialized automatically in VideoCapture destructor
    return 0;
}

void ProcessFrame(Mat& frame, long msecCounter, long frameNumber)
{
    // TODO: Make a copy of frame if you are going to process it
    // asynchronously or put it in a buffer or queue and then return
    // control from this function. This is because the reference Mat
    // being passed in is "owned" by the processing loop, and on each
    // iteration it will be destructed, so any references to it will be
    // invalid. Hence, if you do any work async, you need to copy frame.
    //
    // If all your processing happens synchronously in this function,
    // you don't need to make a copy first because the loop is waiting
    // for this function to return.

    // TODO: Your processing logic goes here.
}

如果你正在使用C#和Emgu CV,它会看起来有点不同。我没有测试过这段代码,但它应该可以工作或者非常接近解决方案。

using System;
using Emgu.CV;
using Emgu.CV.CvEnum;
static class Program
{
    [STAThread]
    static void Main()
    {
        VideoCapture cap = new VideoCapture(0);
        if(!cap.IsOpened)
        {
            return;
        }

        cap.SetCaptureProperty(CapProp.FrameWidth, 1920);
        cap.SetCaptureProperty(CapProp.FrameHeight, 1080);
        cap.SetCaptureProperty(CapProp.Fps, 30);

        Mat frame = new Mat();            
        long msecCounter = 0;
        long frameNumber = 0;

        for(;;)
        {
            if(cap.Grab())
            {
                msecCounter = (long) cap.GetCaptureProperty(CapProp.PosMsec);
                frameNumber = (long) cap.GetCaptureProperty(CapProp.PosFrames);

                if(cap.Retrieve(frame))
                {
                    ProcessFrame(frame, msecCounter, frameNumber);
                }
            }

            // TODO: Determine when to quit the processing loop
        }
    }

    private static void ProcessFrame(Mat frame, long msecCounter, long frameNumber)
    {
        // Again, copy frame here if you're going to queue the frame or
        // do any async processing on it.

        // TODO: Your processing code goes here.
    }
}

Emgu的VideoCapture实现还允许为您完成异步Grab操作,并在抓取的帧准备好与Retrieve一起使用时发出通知。看起来像这样:

using System;
using Emgu.CV;
using Emgu.CV.CvEnum;
static class Program
{
    private static Mat s_frame;
    private static VideoCapture s_cap;
    private static object s_retrieveLock = new object();

    [STAThread]
    static void Main()
    {
        s_cap = new VideoCapture(0);
        if(!s_cap.IsOpened)
        {
            return;
        }

        s_frame = new Mat();

        s_cap.SetCaptureProperty(CapProp.FrameWidth, 1920);
        s_cap.SetCaptureProperty(CapProp.FrameHeight, 1080);
        s_cap.SetCaptureProperty(CapProp.Fps, 30);

        s_cap.ImageGrabbed += FrameIsReady;

        s_cap.Start();

        // TODO: Wait here until you're done with the capture process,
        // the same way you'd determine when to exit the for loop in the
        // above example.

        s_cap.Stop();
        s_cap.ImageGrabbed -= FrameIsReady;
    }

    private static void FrameIsReady(object sender, EventArgs e)
    {
        // This function is being called from VideoCapture's thread,
        // so if you rework this code to run with a UI, be very careful
        // about updating Controls here because that needs to be Invoke'd
        // back to the UI thread.

        // I used a lock here to be extra careful and protect against
        // re-entrancy, but this may not be necessary if Emgu's
        // VideoCapture thread blocks for completion of this event
        // handler.
        lock(s_retrieveLock)
        {
            msecCounter = (long) s_cap.GetCaptureProperty(CapProp.PosMsec);
            frameNumber = (long) s_cap.GetCaptureProperty(CapProp.PosFrames);

            if(s_cap.Retrieve(s_frame))
            {
                ProcessFrame(s_frame, msecCounter, frameNumber);
            }
        }
    }

    private static void ProcessFrame(Mat frame, long msecCounter, long frameNumber)
    {
        // Again, copy frame here if you're going to queue the frame or
        // do any async processing on it.

        // TODO: Your processing code goes here.
    }
}