問題說明:
OpenCV 2.X 版本中,調用cvCaptureProperty()定位視頻到指定幀,采用下面兩種方法都會出現定位不准的問題。
- cvSetCaptureProperty( capture, CV_CAP_PROP_POS_AVI_RATIO, t)
或
- 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
作者實驗中使用的測試代碼如下:
- #include "highgui.h"
- #include <iostream>
- using namespace std;
- int main( int argc, char** argv )
- {
- cvNamedWindow( "Example2", CV_WINDOW_AUTOSIZE );
- CvCapture* capture = cvCreateFileCapture( "d://11.avi" );
- IplImage* frame;
-
- int pos=0;
- int pos1=0;
- while(1)
- {
- cvSetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES,pos);
- cout<<pos;
- frame = cvQueryFrame(capture);
-
- pos1=cvGetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES);
- cout<<"\t"<<pos1<<endl;
-
- if( !frame ) break;
- cvShowImage( "Example2", frame );
- char c = cvWaitKey(33);
- if( c == 27 ) break;
-
- pos++;
- }
- cvReleaseCapture( &capture );
- cvDestroyWindow( "Example2" );
- }
作者發現,在OpenCV 2.X版本中,隨著pos值遞增,pos1值並不與pos1相等,而是有不規則的跳動,造成無法准確定位視頻幀。
原因與改進方法:
原因在於opencv2.0以後,采用ffmpeg采集視頻,而在opencv1.0采用vfw采集視頻(具體的概念暫時還不清楚,有時間繼續補上)。而opencv在定位時候,調用的ffmpeg的av_seek_frame()函數,此函數原型為:
- 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,
- int ret = av_seek_frame(ic, video_stream, timestamp, 0);
也就是按照默認的讀取關鍵幀。因此,視頻跳躍就出現了。
解決這個問題需要將0改為 AVSEEK_FLAG_ANY ,即:
- 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解壓目錄\modules\highgui\src\cap_ffmpeg_impl.hpp
在函數 bool CvCapture_FFMPEG::setProperty( int property_id, double value ) 中
相關的原始代碼如下:
- int flags = AVSEEK_FLAG_FRAME;
- if (timestamp < ic->streams[video_stream]->cur_dts)
- flags |= AVSEEK_FLAG_BACKWARD;
- int ret = av_seek_frame(ic, video_stream, timestamp, flags);
- if (ret < 0)
- {
- fprintf(stderr, "HIGHGUI ERROR: AVI: could not seek to position %0.3f\n",
- (double)timestamp / AV_TIME_BASE);
- return false;
- }
問題就在於flags的值為 AVSEEK_FLAG_FRAME,而不是AVSEEK_FLAG_ANY
僅修改第一行,還是不能達到效果。
與OpenCV 2.0的代碼進行比較,發現OpenCV 2.0的代碼更少,在2.0版本基礎上進行修改:
- int ret = av_seek_frame(ic, video_stream, timestamp, AVSEEK_FLAG_ANY);
- if (ret < 0)
- {
- fprintf(stderr, "HIGHGUI ERROR: AVI: could not seek to position %0.3f\n",
- (double)timestamp / AV_TIME_BASE);
- return false;
- }
這樣就可以正確定位了,但還不清楚這樣修改對2.3整體代碼有什麼影響,有待更進一步研究。