OpenCV(Emgu)入门系列(9):在C#中,使用Emgu+PictureBox实现一个简易的视频播放器...

有了之前使用Emgu读取图片并显示在C#的PictureBox中的实践,今天使用相同的思路实现一个视频播放器。

任务

使用C#与Emgu实现一个简单的视频播放器,有以下功能:

  • 可播放avi,rmvb等格式视频
  • 有暂停、继续、停止、上一帧、下一帧等功能
  • 有刻度条显示播放进度,并且可通过拖动刻度条来改来视频进度

要求:

  • VS 2012 RC,代码及库全部采用64位
  • 使用C#的PictureBox,而不是Emgu的ImageBox :我想实际体验一下PictureBox与ImageBox之间的性能差距
  • 采用System.Windows.Forms.Timer取帧:据说当间隔小于100ms时Timer会很不准确。在实际运行时,发现打开一个正常的视频文件(帧频为30/s)时,播放很慢,像在慢放一样,可证明的确有这个问题。不过我这么做是为了熟悉C#的API,下一次将换用更高效的方法。
  • 使用自定义事件来控制按钮与刻度条的状态(如文字)

效果图

image

布局

布局还是比较简单的,主要是要用好以下几点:

  • AutoSize
  • Dock
  • Anchor
  • MinimumSize

就可以设计出一个窗口可随视频大小自动变化,按钮位置不会错位的布局出来。

Emgu相关API

使用Emgu封装好的方法读取视频文件,及获取相关信息:

// 读取视频文件(可以为AVI,rmvb等,只要系统安装了解码器)
IntPtr capture = CvInvoke.cvCreateFileCapture(file);
// 得到总帧数
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_COUNT);
// 视频宽度
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_WIDTH);
// 视频高度
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_HEIGHT);
// 当前帧位置
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_POS_FRAMES);
// 帧频
CvInvoke.cvGetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FPS);

使用以下方法,将Emgu取得的图片变为PictureBox认识的Bitmap:

// 读取下一帧
var frame = CvInvoke.cvQueryFrame(capture);
// 生成一个新的Image容器
Image<Bgr, byte> dest = new Image<Bgr, byte>(movieInfo.width, movieInfo.height);
// 把数据复制过去
CvInvoke.cvCopy(frame, dest, IntPtr.Zero);
// 转换为Bitmap
dest.ToBitmap();

如果想让视频定位到某一帧,使用以下方法:

int newPos = 23;
CvInvoke.cvSetCaptureProperty(capture, Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_POS_FRAMES, newPos);

Emgu的代码就是以上这些,其它的都是C#代码了。

视频信息

通过定义一个叫MovieInfo的struct,来保存与视频相关的信息供使用:

struct MovieInfo {
    public String filename;
    public int frameCount;
    public int width;
    public int height;
    public int currentFrame;
    public int fps;
}

Timer

创建Timer,根据帧频设好Interval,以及Tick对应的操作。如下:

Timer myTimer = new Timer();
myTimer.Interval = 1000 / Convert.ToInt32(movieInfo.fps);
myTimer.Tick += new EventHandler(MyTimer_Tick);
myTimer.Start();

在MyTimer_Tick方法中读取下一帧画面,显示在PictureBox中

事件

各按钮与刻度条的状态,与视频的播放状态,是互相对应的。比如只有打开了一个视频文件,“开始/停止/前一帧/后一帧”按钮才可用,并且刻度条上的指针也随着播放向右移动;同时拖动刻度指针,视频也会随之变化。点击“前一帧/后一帧”时,画面也会跳动。它们之间是通过自定义的事件来协调的。

关于事件,可参考这篇文件:C#事件(event)解析

首先自定义了一个MovieEvent,它有多种不同的状态,用以区分不同的事件:

class MovieEvent : EventArgs {
    public State EventState { get; set; }
    public enum State {
        NewMovie, Started, Stopped, Paused, Playing, Scroll, PrevNext
    }
    public MovieEvent(State state) : base() {
        this.EventState = state;
    }
}

然后在Form1中定义了一个delegate和一个event handler:

delegate void MovieHandler(object sender, MovieEvent e);
event MovieHandler MovieHandlers;

再写一个handler方法,用于捕获事件并处理。这里写的有点乱,不知道有没有更好的做法:

 void handler(object sender, MovieEvent e) {
    switch (e.EventState) {
        case MovieEvent.State.NewMovie:
            btnStart.Enabled = true;
            btnStop.Enabled = true;
            btnPrev.Enabled = true;
            btnNext.Enabled = true;
            btnStart.Text = "暂停";
            break;
        case MovieEvent.State.Paused:
            btnStart.Enabled = true;
            btnStop.Enabled = true;
            btnPrev.Enabled = true;
            btnNext.Enabled = true;
            btnStart.Text = "开始";
            break;
        // 更多
}

在Form1的构造函数中,把handler注册上去:

this.MovieHandlers += new MovieHandler(this.handler);

然后就是在程序的不同地方,执行了不同操作时,创建事件并散播出去:

MovieHandlers(this, new MovieEvent(MovieEvent.State.Stopped));
MovieHandlers(this, new MovieEvent(MovieEvent.State.Playing));

项目源代码

放在了github上:https://github.com/freewind/opencv_emgu_learning/tree/master/AviPlayer_PictureBox

这是一个VS 2012 RC的项目,应该也可以在低版本运行。如果要运行,需要手动安装OpenCV/Emgu等,具体做法可参考我之前的文章。

改进

下一步将对该项目进行如下改进:

  • 使用Emgu的ImageBox,测一下两者的性能差距
  • 使用其它方式代替PictureBox
  • 寻找进一步优化代码的方法

后记

(2012-08-09)

该程序有一个严重的问题:无法按帧频精准的播放视频。比如当帧频为30时,timer的间隔时间为1000/30=33,而准确的应该是33.3333,这样实际上就慢了一点。另外,如果视频比较大,解析一幅画面比较费时的时候,Timer就更加不准确了,播放起来像在慢放。

这应该是一个难以解决的问题,精准的控制时间很难做到,参见How to use c# to write a simple video player which plays the video with accurate fps?

由于我的目的是通过这个例子来学习OpenCV,所以就不往下钻了,做到现在的程度已经达到了目的。

转载于:https://www.cnblogs.com/9keyes/archive/2012/08/09/2629520.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值