歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> OpenCV 2.3.1 中關於cvCaptureProperty()定位不准的問題

OpenCV 2.3.1 中關於cvCaptureProperty()定位不准的問題

日期:2017/3/1 11:10:04   编辑:Linux編程

問題說明:

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. int pos=0;
  10. int pos1=0;
  11. while(1)
  12. {
  13. cvSetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES,pos);
  14. cout<<pos;
  15. frame = cvQueryFrame(capture);
  16. pos1=cvGetCaptureProperty(capture,CV_CAP_PROP_POS_FRAMES);
  17. cout<<"\t"<<pos1<<endl;
  18. if( !frame ) break;
  19. cvShowImage( "Example2", frame );
  20. char c = cvWaitKey(33);
  21. if( c == 27 ) break;
  22. pos++;
  23. }
  24. cvReleaseCapture( &capture );
  25. cvDestroyWindow( "Example2" );
  26. }
作者發現,在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解壓目錄\modules\highgui\src\cap_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\n",
  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\n",
  5. (double)timestamp / AV_TIME_BASE);
  6. return false;
  7. }
這樣就可以正確定位了,但還不清楚這樣修改對2.3整體代碼有什麼影響,有待更進一步研究。
Copyright © Linux教程網 All Rights Reserved