1、OpenCvSharp视频读取、显示小记

写作原因:前段时间一直在使用这个框架,但是没有细看,现在想了解下这个框架的运行流程,固随手记录下,如有不对的地方,请多多指教;

说明:这一篇博客只记录下视频的读取涉及到函数和类,不做延申。

一、整体流程

打开视频→读取→显示→销毁

VideoCapture类

 此类继承了DisposableObject类,可以调用Dispose()销毁对象释放资源。

二、打开视频文件

  • 方式1:直接通过带参构造函数
#region 211224 komla 带参构造函数打开视频文件
var capture2 = new VideoCapture(@"F:\\Media\\A.avi");//@"F:\\Media\\A.avi"
bool open2 = capture2.IsOpened();
#endregion
//output
//true

 源码中带参构造函数有多种, 对应不同的需求

 笔者测试使用的函数在源码中如下,从注释中可以看到,通过此构造函数可以打开一个视频文件。

/// <summary>
/// Opens a video file or a capturing device or an IP video stream for video capturing with API Preference
/// </summary>
/// <param name="fileName">it can be:
/// - name of video file (eg. `video.avi`)
/// - or image sequence (eg. `img_%02d.jpg`, which will read samples like `img_00.jpg, img_01.jpg, img_02.jpg, ...`)
/// - or URL of video stream (eg. `protocol://host:port/script_name?script_params|auth`).
/// Note that each video stream or IP camera feed has its own URL scheme. Please refer to the
/// documentation of source stream to know the right URL.</param>
/// <param name="apiPreference">apiPreference preferred Capture API backends to use. Can be used to enforce a specific reader
/// implementation if multiple are available: e.g. cv::CAP_FFMPEG or cv::CAP_IMAGES or cv::CAP_DSHOW.</param>
/// <returns></returns>
public VideoCapture(string fileName, VideoCaptureAPIs apiPreference = VideoCaptureAPIs.ANY)
{
    if (string.IsNullOrEmpty(fileName))
        throw new ArgumentNullException(nameof(fileName));

    NativeMethods.HandleException(
        NativeMethods.videoio_VideoCapture_new2(fileName, (int)apiPreference, out ptr));

    if (ptr == IntPtr.Zero)
        throw new OpenCvSharpException("Failed to create VideoCapture");
            
    captureType = CaptureType.File;
}
  • 方式二:通过不带参构造函数VideoCapture()+open()函数读取视频文件
#region 211224 komla 不带参构造函数VideoCapture()+open()函数 打开视频文件
var capture1 = new VideoCapture();//@"F:\\Media\\A.avi"
bool open1 = capture1.IsOpened();
//false
capture1.Open(@"F:\\Media\\A.avi");
open1= capture1.IsOpened();
//true
#endregion

源码中注释说明了使用此函数只是初始化一个空对象,需要配合open函数才能读取视频文件。

/// <summary>
/// Initializes empty capture.
/// To use this, you should call Open. 
/// </summary>
/// <returns></returns>
public VideoCapture()
{
    NativeMethods.HandleException(
        NativeMethods.videoio_VideoCapture_new1(out ptr));
            
        if (ptr == IntPtr.Zero)
            throw new OpenCvSharpException("Failed to create VideoCapture");
            
        captureType = CaptureType.NotSpecified;
}

/// <summary>
/// Opens a video file or a capturing device or an IP video stream for video capturing.
/// </summary>
/// <param name="fileName">it can be:
/// - name of video file (eg. `video.avi`)
/// - or image sequence (eg. `img_%02d.jpg`, which will read samples like `img_00.jpg, img_01.jpg, img_02.jpg, ...`)
/// - or URL of video stream (eg. `protocol://host:port/script_name?script_params|auth`).
/// Note that each video stream or IP camera feed has its own URL scheme. Please refer to the
/// documentation of source stream to know the right URL.</param>
/// <param name="apiPreference">apiPreference preferred Capture API backends to use. Can be used to enforce a specific reader
/// implementation if multiple are available: e.g. cv::CAP_FFMPEG or cv::CAP_IMAGES or cv::CAP_DSHOW.</param>
/// <returns>`true` if the file has been successfully opened</returns>
public bool Open(string fileName, VideoCaptureAPIs apiPreference = VideoCaptureAPIs.ANY)
{
    ThrowIfDisposed();

    NativeMethods.HandleException(
        NativeMethods.videoio_VideoCapture_open1(ptr, fileName, (int)apiPreference, out             var ret));

    GC.KeepAlive(this);
    if (ret == 0) 
        return false;

    captureType = CaptureType.File;
    return true;
}

三、读取帧图像

读取方式也有两种方式,一种是直接通过Read()读取,另一种需要通过两个函数搭配进行读取,

  • 方式1:Read()
        /// <summary>
        /// Grabs, decodes and returns the next video frame.
        ///
        /// The method/function combines VideoCapture::grab() and VideoCapture::retrieve() in one call. This is the
        /// most convenient method for reading video files or capturing data from decode and returns the just
        /// grabbed frame. If no frames has been grabbed (camera has been disconnected, or there are no more
        /// frames in video file), the method returns false and the function returns empty image (with %cv::Mat, test it with Mat::empty()).
        /// </summary>
        /// <returns>`false` if no frames has been grabbed</returns>
        public bool Read(Mat image)
        {
            ThrowIfDisposed();
            if(image == null)
                throw new ArgumentNullException(nameof(image));
            image.ThrowIfDisposed();

            NativeMethods.HandleException(
                NativeMethods.videoio_VideoCapture_read_Mat(ptr, image.CvPtr, out var ret));

            GC.KeepAlive(this);
            GC.KeepAlive(image);
            return ret != 0;
        }

 可以看到mat中已经有了数据,当然Read()函数有两种重载类型,笔者使用的是传参为Mat的那一个

  • 方式2:Grab()+Retrieve()

 如果只使用Retrieve()是抓不到帧图像的,可以从源码中的定义看,Retrieve()只是解码和返回抓取到的帧,因此在使用Retrieve()之前需要先使用Grab()抓取帧图像。

//
        // 摘要:
        //     Decodes and returns the grabbed video frame. The method decodes and returns the
        //     just grabbed frame. If no frames has been grabbed (camera has been disconnected,
        //     or there are no more frames in video file), the method returns false and the
        //     function returns an empty image (with %cv::Mat, test it with Mat::empty()).
        //
        // 参数:
        //   image:
        //     the video frame is returned here. If no frames has been grabbed the image will
        //     be empty.
        //
        //   flag:
        //     it could be a frame index or a driver specific flag
        public bool Retrieve(Mat image, int flag = 0);
        /// <summary>
        /// Grabs the next frame from video file or capturing device.
        ///
        /// The method/function grabs the next frame from video file or camera and returns true (non-zero) in the case of success.
        ///
        /// The primary use of the function is in multi-camera environments, especially when the cameras do not
        /// have hardware synchronization. That is, you call VideoCapture::grab() for each camera and after that
        /// call the slower method VideoCapture::retrieve() to decode and get frame from each camera. This way
        /// the overhead on demosaicing or motion jpeg decompression etc. is eliminated and the retrieved frames
        /// from different cameras will be closer in time.
        ///
        /// Also, when a connected camera is multi-head (for example, a stereo camera or a Kinect device), the
        /// correct way of retrieving data from it is to call VideoCapture::grab() first and then call
        /// VideoCapture::retrieve() one or more times with different values of the channel parameter.
        /// </summary>
        /// <returns>`true` (non-zero) in the case of success.</returns>
        public bool Grab()
        {
            ThrowIfDisposed();
            NativeMethods.HandleException(
                NativeMethods.videoio_VideoCapture_grab(ptr, out var ret));
            GC.KeepAlive(this);
            return ret != 0;
        }

 当然,Retrieve()根据不同的需求定义了多种重载函数。

 最后一个重载函数在获取到图象时是会返回一个Mat,其他的都是在内部方法中对传参mat进行赋值。

四 显示

  • 说明:用到的类是Window,在OpenCvSharp命名空间下。使用的函数是ShowImage(),两个重载。

  •  源码
        /// <summary>
        /// Shows the image in this window
        /// </summary>
        /// <param name="img">Image to be shown. </param>
        public void ShowImage(Mat? img)
        {
            if (img != null)
            {
                image = img;
                NativeMethods.HandleException(
                    NativeMethods.highgui_imshow(name, img.CvPtr));
                GC.KeepAlive(img);
            }
        }
  • 使用
            #region 211225 komla 显示
            var capture4 = new VideoCapture(@"F:\\Media\\A.avi");//@"F:\\Media\\A.avi" 
            Mat image4 = new Mat(); 
            //初始化显示窗体并命名
            var win4 = new Window("yflbi");
            //循环读取
            while(true)
            {
                capture4.Grab();
                capture4.Retrieve(image4);
                win4.ShowImage(image4);
                //等待时间,单位ms
                Cv2.WaitKey(1000);
            }

五 销毁 

主要是销毁VideoCapture对象

继承关系VideoCapture:DisposableCvObject:DisposableObject:IDisposable,因此VideoCapture可以调用Dispose()销毁对象释放资源。

            #region 211225 komla Dispose() 销毁VideoCapture对象
            var capture5 = new VideoCapture(@"F:\\Media\\A.avi");//@"F:\\Media\\A.avi" 
            bool dis = capture5.IsDisposed;
            //false
            capture5.Dispose();
            bool dis1 = capture5.IsDisposed;
            //true
            #endregion

 笔者还注意到,VideoCapture类中还有一个Release()函数,源码如下:

        /// <summary>
        /// Closes video file or capturing device.
        /// </summary>
        /// <returns></returns>
        public void Release()
        {
            ThrowIfDisposed();
            NativeMethods.HandleException(
                NativeMethods.videoio_VideoCapture_release(ptr));
        }

 注释中解释说是关闭视频文件或抓取视频设备,笔者测试了下,这个会把VideoCapture对象关闭掉,但是并不会销毁VideoCapture对象。

            #region 211225 komla Release() 关闭VideoCapture对象
            var capture6 = new VideoCapture(@"F:\\Media\\A.avi");//@"F:\\Media\\A.avi" 
            bool open6 = capture6.IsOpened();
            //true 
            bool dis6 = capture6.IsDisposed;
            //false 
            capture6.Release();//close VideoCapture对象
            bool open61 = capture6.IsOpened();
            //false 
            bool dis62 = capture6.IsDisposed;//并没有销毁,只是close
            //false 
            #endregion

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值