OpenCV 動画保存 - eiichiromomma/CVMLAB GitHub Wiki

(OpenCV) 動画保存

カメラのスペックに近いフレームレートで動画を保存する方法

CvVideoWriterの問題

遅い。まぁディスクにガリガリ書いているようなので、リアルタイムでの高速処理は望めない。

やり方

メモリ上にIplImageを確保できるだけ確保して、処理中はcvCopyImageだけにする。 ESCキーで処理を終了し、その後cvWriteFrameで書き込む。書き込み時間は長い。

Codecは-1にしてその都度選ぶ。が、DivXとかだと上手く動かないのはウチの環境だけだろうか。 動画への保存はあまり使わないので特に調査してない。

サンプルソース

MAX_N_IMAGEが最大フレーム数。想定するFPSで割ったのが撮影可能な秒数になる。 WAIT_TIMEはFPSの逆数に1000を乗算した値。 スペックギリギリの値にすると処理が間に合わなくなる。 ウェイト制御は適当に作ったが、そこそこ正確。

フレーム画像はpngで保存。ファイル名はWindowsAPIを使ってms精度のキャプチャ時間とした。

あと、なぜかLogicoolのUSBカメラだと大きさが320x240固定になる。解像度の変更は未実装らしい?

    #include <cv.h>
    #include <highgui.h>
    #include <stdio.h>
    #include <windows.h>
    
    enum{
      MAX_N_IMAGE=1000,
      SAVE_PNG=1,
      WAIT_TIME=67  //1/fps * 1000
    };
    
    IplImage **imagep = new IplImage *[MAX_N_IMAGE];
    char **filenamep = new char *[MAX_N_IMAGE];
    long grabCount=0;
    
    void effect(IplImage *image){
      IplImage *image1;
      SYSTEMTIME stTime;
      image1 = cvCloneImage(image);
      //image1をいじる
      cvFlip(image1,image1,0);
      cvShowImage("feed window",image1);
      //タイミングはWAIT_TIMEまかせ
      grabCount++;
      GetLocalTime(&stTime);
      sprintf(*(filenamep+grabCount),"%d%02d%02d%02d%02d%02d%03d.png)",
        stTime.wYear,stTime.wMonth,stTime.wDay,stTime.wHour,stTime.wMinute,stTime.wSecond,stTime.wMilliseconds);
      //ここでは処理後のimage1だけキャプチャ
      //原画像も保存するならファイル名を工夫してimageをcvCopyImageすれば良い
      cvCopyImage(image1,*(imagep+grabCount));
      cvReleaseImage(&image1);
    }
      
    int main()
    {
      IplImage *image;
      CvCapture *capture = cvCaptureFromCAM(CV_CAP_ANY);
      int keyInput;
    //  int width = (int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH); //Logicoolのカメラには効かない
    //  int height = (int)cvGetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT);
      int width=320;
      int height=240;
    //  cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_WIDTH,(double)width);//Logicoolのカメラには効かない
    //  cvSetCaptureProperty(capture,CV_CAP_PROP_FRAME_HEIGHT,(double)height);
      image = cvCreateImage(cvSize(width,height),IPL_DEPTH_8U,3);
      if(!capture){
        return 1;
      }
      for (int i=0; i< MAX_N_IMAGE; i++){
        if(NULL == (*(imagep+i) = cvCreateImage(cvSize(width,height),IPL_DEPTH_8U,3))){
          return 1;
        }
        *(filenamep+i) = new char[256];
      }
      
    //  cvNamedWindow("main window",CV_WINDOW_AUTOSIZE);
      cvNamedWindow("feed window",CV_WINDOW_AUTOSIZE);
      
      int64 preTick;
      double pastTime;
      int myWait=0;
      double tickFreq=cvGetTickFrequency();
      preTick=cvGetTickCount();
      while(grabCount < MAX_N_IMAGE){
        //フレーム画像を取得
        if (NULL==(image=cvQueryFrame(capture))){
          return 1;
        }
    //    cvShowImage("main window",image);
        //画像処理はここで行なう
        effect(image);
        //WAIT_TIMEで制御
        pastTime=(cvGetTickCount()-preTick)/tickFreq/1000.0;
        keyInput=cvWaitKey(pastTime<WAIT_TIME ? WAIT_TIME-(int)pastTime : 1);
        preTick=cvGetTickCount();
        //ESCキーでループを終了
        if((keyInput&255)==27){
          break;
        }
      }
      //後片付け
    //  cvDestroyWindow("main window");
      cvDestroyWindow("feed window");
      CvVideoWriter *vwrite = cvCreateVideoWriter("test.avi",-1,1000.0/WAIT_TIME,cvSize(width,height),1);
      if (grabCount){
        for (int i=1; i<grabCount; i++){
          if(SAVE_PNG){
            cvSaveImage(*(filenamep+i),*(imagep+i));
          }
          cvWriteFrame(vwrite,*(imagep+i));
        }
      }
      cvReleaseVideoWriter(&vwrite);
      cvReleaseCapture(&capture);
      return 0;
    }

動画保存あれこれ

諸々のコーデック

当たり前だがmpeg等の圧縮をかけるにはマシンパワーを要するので、リアルタイムの記録には向かない。 Video 1が一番軽いが、画質が酷い。

非圧縮aviの作成

cvCreateVideoWriterの第2引数のfourccにCV_FOURCC('D','I','B',' ')を指定する。 但しディスク容量を食うので要注意。

適正なFPSの算出

cvCreateVideoWriterで一番問題となる所は処理が重くて実質2fpsになろうと宣言したfpsで記録される事。 つまりfps=30と宣言した場合、処理が重くて2fpsとなり15秒かかって30フレームが作成されても1秒の動画として保存される。 従って予めシステムの処理の時間からおおまかにfpsを決定しておく必要がある。

cvGetTickCountを使うとtickをカウントとして使え、cvGetTickFrequencyでusあたりのticksを得られるので時間換算に利用する。

[処理前のticks(初期値)] = cvGetTickCount();
[ticksの周波数] = cvGetTickFrequency();
フレームキャプチャの無限ループ{
  (フレームキャプチャ)
  (諸々の処理)
  (フレーム画像の記録や表示)
  [処理にかかったticks] = cvGetTickCount() - [処理前のticks];
  [処理にかかった時間(ms)] = [処理にかかったticks] / ([ticksの周波数] * 1000);
  [実際のfps] = 1000/[処理にかかった時間(ms)];
  (fpsの記録、表示)
  [処理前のticks] = cvGetTickCount();
  [キー等の入力] = cvWaitKey(1);
  (switchでの[キー等の入力]の処理)
}

といった感じで実際のfpsを算出できるので、fprintfでstderrにでも出力すれば常時監視できる。 ちなみに処理の内容やマシンを変えた場合は計測し直す必要がある。cvGetTickFrequencyの値でfpsの比例定数を求めても良いが、処理時間が線形である保証は無い。

キャプチャしたフレームによって処理があったり無かったりする場合はfpsにゆらぎが生じる。 処理が速過ぎる場合は目的とするfpsから適切なウェイトをかける必要がある。 また、カメラのfpsより処理が速過ぎる場合も適切なウェイトをかけた方が良い。

⚠️ **GitHub.com Fallback** ⚠️