天天看點

OpenCV 2.3.1 中關于cvCaptureProperty()定位不準的問題

問題說明:

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後取幀不準的問題》

作者實驗中使用的測試代碼如下:

#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相等,而是有不規則的跳動,造成無法準确定位視訊幀。

原因與改進方法:

具體原因和改進方法,參考下面的讨論

《OpenCV 中文論壇 檢視主題 - 新手問一個設定cvSetCaptureProperty後取幀不準的問題》

及文章:

《opencv中cvSetCaptureProperty定位不準的原因及解決》

作者指出:

原因在于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整體代碼有什麼影響,有待更進一步研究。