http://ocean2oo6.spaces.live.com/default.aspx?sa=396897813
问题由来
最近开始研究动态图像中的信息提取和处理,比如对象提取,跟踪等,本来以为OpenCV中已经有了视频封装,像视频的存取都不应该遇到问题的。然而 今天在Yahoo Group中看到一哥们儿在问这方面的问题,他发觉自己的OpenCV要么是不能正常存取视频。反正也就几行代码,我也来试一试。结果,的确如那位哥们儿 所言,的确不能正常存取视频,比如常用的cvCaptureFromAVI或者cvCaptureFromFile都不能返回有效的CvCapture, 但是似乎写视频又是可以的。此版本是从sourceforge上下载的1.1 alpha,没有修改过任何源代码,如果你也遇到和我一样的问题,那么这里将告诉你如何解决这样的问题。
探索
使用OpenCV源代码编译一份debug shared的dll以便跟踪到源代码中。
我们可以看到,大致的程序流程是这样的:代码进入CvCapture * cvCreateFileCapture (const char * filename) ,这个里面看起来是这样:
CvCapture * result = 0;
if (! result)
result = cvCreateFileCapture_Images (filename);
#ifdef WIN32
if (! result)
result = cvCreateFileCapture_Win32 (filename);
…….
可以看到OpenCV是在不断尝试各种Capture,包括各种系统及图形系统上的代理看是否有合适的capture被创建,此处我使用的是winxp,因此逻辑正常应该进入cvCreateFileCapture_Win32 ,这儿方法看起来如下:
CvCapture_FFMPEG_proxy* result = new CvCapture_FFMPEG_proxy;
if( result->open( filename ))
return result;
delete result;
return cvCreateFileCapture_VFW(filename);
那么很显然,他是要创建一个ffmpeg代理,如果不成功则使用VFW,什么是VFW请自行查阅相关资料。而ffmpeg则是在视频处理中 鼎鼎有名的开源库,他能支持众多的视频编解码格式,尤其是h26x与mpeg系列以及xvid,divx等等。这时候我们会想,我们的机器上有这样的东西 吗?一般说来机器上有相关解码器(ffmpeg decodc)才能正常解码,如果你使用过DirectShow或者其他多媒体处理框架,对此应该不会抱有疑问的。OK,回过头来,继续。创建完 ffmpeg代理之后马上Open,正如你所想像的,Open的代码正是OpenCV对ffmpeg交互的关键起始点,他开始在内部创建ffmpeg的封 装对象(其实只是函数对象),操作也非常简单,只是简单的使用LoadLibrary加载dll.,然后GetProcAddress获得相关的导出函数 地址。代码如下:
icvFFOpenCV = LoadLibrary( ffopencv_name );
if( icvFFOpenCV )
{
icvCreateFileCapture_FFMPEG_p =
(CvCreateFileCapture_Plugin)GetProcAddress(icvFFOpenCV, "cvCreateFileCapture_FFMPEG");
icvCreateVideoWriter_FFMPEG_p =
(CvCreateVideoWriter_Plugin)GetProcAddress(icvFFOpenCV, "cvCreateVideoWriter_FFMPEG");
icvReleaseCapture_FFMPEG_p =
(CvReleaseCapture_Plugin)GetProcAddress(icvFFOpenCV, "cvReleaseCapture_FFMPEG");
icvReleaseVideoWriter_FFMPEG_p =
(CvReleaseVideoWriter_Plugin)GetProcAddress(icvFFOpenCV, "cvReleaseVideoWriter_FFMPEG");
}
就是他们!包括创建,释放,存取capture都被封装于这四个函数中。我在想为什么我的视频无法读取呢?是不是这里出了什么问题?果然! 调试之下icvCreateFileCapture_FFMPEG_p为空值,而只有 icvCreateVideoWriter_FFMPEG_p有正常值,怪不得只能写视频,不能读取啦!
解决之道
既然已经知道症结所在,我们就是要想办法让他回到正常轨道上,没错,我们需要那四个函数对象不为空值。跟踪之下我们看到ffopencv_name 这个dll的名字是ffopencv110.dll 这里要说说,应该是OpenCV一个小的不足,他应该对debug版本的代码加载ffopencv110d.dll(注意有d),这样才能达到版本一致。 查看程序运行目录下,ffopencv110.dll已经在那个地方了,没有问题。没有正常获得导出函数地址,会不会是dll本身有问题?上网找一个 dll导出函数查看器,一看,果然只有cvCreateVideoWriter_FFMPEG被导出了。ok,接下来我们该检索一下ffopencv这个 项目了,他就是万恶之源 哈哈。他位于otherlibs\ffopencv,我们看看ffopencv.h,这里有几个函数的导出
CVAPI(CvCapture*) cvCaptureFromFile_FFMPEG( const char* filename );
CVAPI(CvVideoWriter*) cvCreateVideoWriter_FFMPEG( const char * filename, int fourcc,
double fps, CvSize frameSize, int is_color );
CVAPI(int) cvWriteFrame_FFMPEG( CvVideoWriter * writer, const IplImage * image );
CVAPI(void) cvReleaseVideoWriter_FFMPEG( CvVideoWriter ** writer );
仔细看看这几个函数,和前面GetProcAddress想获得的函数声明都不一样嘛,这时候我们不得不抱怨OpenCV的开发者了,同一个代码版本维护这么有问题,代码都不一致。我们得想办法让这一切归于正常才行。对比了这几个函数,这里缺失了几个函数:
第一:修正导出函数所在头文件,请张大你的眼睛,这个头文件缺少的是cvCreateFileCapture_FFMPEG与cvReleaseCapture_FFMPEG,那么我们添上他们两个:
CVAPI(CvCapture*) cvCreateFileCapture_FFMPEG( const char* filename );
CVAPI(void) cvReleaseCapture_FFMPEG(CvCapture** capture);
第二:修正cvcap_ffmpeg.cpp里面的函数定义,请注意,这里如果你不补充定义的话,编译不会有任何错误,但是最后你用dll导出函数 查看器却看不到头文件已经定义的几个导出函数,这说明我们必须对这些导出函数进行定义!我们看到这个版本缺少的是 cvReleaseCapture_FFMPEG与cvReleaseVideoWriter_FFMPEG定义,在代码文件中补上他们:
void cvReleaseVideoWriter_FFMPEG(CvVideoWriter** writer) {
if (writer!=NULL) {
if (*writer!=NULL)
delete *writer;
*writer=NULL;
}
}
void cvReleaseCapture_FFMPEG(CvCapture** capture) {
if (capture!=NULL) {
if (*capture!=NULL)
delete *capture;
*capture=NULL;
}
}
OK! 这样就完美解决了,重新编译ffopencv(请记住是release版本,刚才我也说过OpenCV加载dll时不管当前是debug还是release都加载ffopencv的release版本,唉!)
这回我们在跟踪进去就能看到这四个函数对象被正常创建了,多种视频格式都能够正常的存取。
非常感谢,同样的问题困扰了我一个下午,却一直没有想过到是opencv代码的不一致问题,看来时刻保持怀疑态度还是很重要的啊~再次感谢!
回复删除