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,下一次将换用更高效的方法。

使用自定义事件来控制按钮与刻度条的状态(如文字)

效果图

布局

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

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 dest =newImage(movieInfo.width, movieInfo.height);// 把数据复制过去CvInvoke.cvCopy(frame, dest, IntPtr.Zero);// 转换为Bitmapdest.ToBitmap();

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

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

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

视频信息

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

structMovieInfo {publicString filename;publicintframeCount;publicintwidth;publicintheight;publicintcurrentFrame;publicintfps;}

Timer

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

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

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

事件

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

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

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

classMovieEvent : EventArgs {publicState EventState { get; set; }publicenumState {        NewMovie, Started, Stopped, Paused, Playing, Scroll, PrevNext    }publicMovieEvent(State state) :base() {this.EventState = state;    }}

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

delegatevoidMovieHandler(objectsender, MovieEvent e);eventMovieHandler MovieHandlers;

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

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

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

this.MovieHandlers +=newMovieHandler(this.handler);

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

MovieHandlers(this,newMovieEvent(MovieEvent.State.Stopped));MovieHandlers(this,newMovieEvent(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,所以就不往下钻了,做到现在的程度已经达到了目的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容