Welcome 微信登录

首页 / 操作系统 / Linux / OpenCV 2.3.1 中关于cvCaptureProperty()定位不准的问题

问题说明:OpenCV 2.X 版本中,调用cvCaptureProperty()定位视频到指定帧,采用下面两种方法都会出现定位不准的问题。
  1. cvSetCaptureProperty( capture, CV_CAP_PROP_POS_AVI_RATIO, t)  
  1. cvSetCaptureProperty(capture, CV_CAP_PROP_POS_FRAMES, t);  
都会显示诸如此类的错误警告信息: HIGHGUI ERROR: AVI: could not seek to position 2.701
其中黄色数字就是OpenCV函数中对应的帧数,不知道因为什么原因,变成非整数,与之前程序中指定的帧数不一致,导致无法定位到准确的位置。之前用OpenCV 2.2版本,一样出现相同的问题。而使用OpenCV 1.1版本,就可以正常定位。
更详细的问题说明:很多人都遇到这个问题,更详细的实验可以参见下面文章:《设定cvSetCaptureProperty后取帧不准的问题》 见 http://www.linuxidc.com/Linux/2011-10/46045.htm作者实验中使用的测试代码如下:
  1. #include "highgui.h"   
  2. #include <iostream>   
  3. using namespace std;  
  4. int main( int argc, char** argv )  
  5. {   
  6.    cvNamedWindow( "Example2", CV_WINDOW_AUTOSIZE );  
  7.    CvCapture* capture = cvCreateFileCapture( "d://11.avi" );  
  8.    IplImage* frame;  
  9.   
  10.    int pos=0;  
  11.    int pos1=0;  
  12.    while(1)  
  13.    {  
  14.       cvSetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES,pos);  
  15.       cout<<pos;  
  16.       frame = cvQueryFrame(capture);  
  17.   
  18.       pos1=cvGetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES);  
  19.       cout<<" "<<pos1<<endl;  
  20.   
  21.       if( !frame ) break;  
  22.       cvShowImage( "Example2", frame );  
  23.       char c = cvWaitKey(33);  
  24.       if( c == 27 ) break;  
  25.   
  26.       pos++;  
  27.    }  
  28.    cvReleaseCapture( &capture );  
  29.    cvDestroyWindow( "Example2" );  
  30. }  
作者发现,在OpenCV 2.X版本中,随着pos值递增,pos1值并不与pos1相等,而是有不规则的跳动,造成无法准确定位视频帧。

原因与改进方法:原因在于opencv2.0以后,采用ffmpeg采集视频,而在opencv1.0采用vfw采集视频(具体的概念暂时还不清楚,有时间继续补上)。而opencv在定位时候,调用的ffmpeg的av_seek_frame()函数,此函数原型为:
  1. int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags);  
其中,最后一个参数有

AVSEEK_FLAG_BACKWARD = 1; ///< seek backward
AVSEEK_FLAG_BYTE = 2; ///< seeking based on position in bytes
AVSEEK_FLAG_ANY = 4; ///< seek to any frame, even non key-frames

ffmpeg默认的是选取关键帧(这个概念需要具体定义)。opencv里面这个函数的参数flag是0,
  1. int ret = av_seek_frame(ic, video_stream, timestamp, 0);  
也就是按照默认的读取关键帧。因此,视频跳跃就出现了。

解决这个问题需要将0改为 AVSEEK_FLAG_ANY ,即:
  1. int ret = av_seek_frame(ic, video_stream, timestamp, AVSEEK_FLAG_ANY );  
之后重新编译opencv库,就可以了。
我在OpenCV 2.3.1中的处理方法:
OpenCV 2.3.1中的与cvCaptureProperty()和FFMPEG相关的文件是:opencv2.3.1解压目录moduleshighguisrccap_ffmpeg_impl.hpp在函数 bool CvCapture_FFMPEG::setProperty( int property_id, double value ) 中相关的原始代码如下:
  1. int flags = AVSEEK_FLAG_FRAME;  
  2. if (timestamp < ic->streams[video_stream]->cur_dts)  
  3. flags |= AVSEEK_FLAG_BACKWARD;  
  4. int ret = av_seek_frame(ic, video_stream, timestamp, flags);  
  5. if (ret < 0)  
  6. {  
  7.    fprintf(stderr, "HIGHGUI ERROR: AVI: could not seek to position %0.3f ",  
  8.           (double)timestamp / AV_TIME_BASE);  
  9.    return false;  
  10. }  
问题就在于flags的值为 AVSEEK_FLAG_FRAME,而不是AVSEEK_FLAG_ANY仅修改第一行,还是不能达到效果。
与OpenCV 2.0的代码进行比较,发现OpenCV 2.0的代码更少,在2.0版本基础上进行修改:
  1. int ret = av_seek_frame(ic, video_stream, timestamp, AVSEEK_FLAG_ANY);  
  2. if (ret < 0)  
  3. {  
  4.     fprintf(stderr, "HIGHGUI ERROR: AVI: could not seek to position %0.3f ",  
  5.     (double)timestamp / AV_TIME_BASE);  
  6.     return false;  
  7. }  
这样就可以正确定位了,但还不清楚这样修改对2.3整体代码有什么影响,有待更进一步研究。