2008 9月 13 のアーカイブ

OpenCV/videoInput Libraryによるビデオキャプチャのサンプルプログラムについて,
HandyARのデモが動かない問題の原因を発見した - サイScripterの旅立ちにて以下の指摘がありました.

どこのサイトを見ても、どうも怪しいコードしか載っていないように見える。

cvCreateImage後はimageDataにアロケートされているんだから、imageDataに代入しちゃったら解放するアドレスがわからないと思うんだけどなぁ。

でもメモリーリークの警告でないしなぁ・・・


結論から言うとメモリ解放について問題は無いです.ということで,
こちらとしても,「怪しいコードと断定」されるのも癪なので,
なぜ問題無いかについて少し書いてみます.

まず,cvReleaseImageの内部で何をやっているかというと,
確保したイメージ領域の解放のためにcvReleaseDataをコールしています.
cxarray.cppのcvReleaseData関数の中身を以下に示します.

C++:
  1. // Deallocates array's data
  2. CV_IMPL void
  3. cvReleaseData( CvArr* arr )
  4. {
  5.     CV_FUNCNAME( "cvReleaseData" );
  6.    
  7.     __BEGIN__;
  8.  
  9.     if( CV_IS_MAT_HDR( arr ) || CV_IS_MATND_HDR( arr ))
  10.     {
  11.         CvMat* mat = (CvMat*)arr;
  12.         cvDecRefData( mat );
  13.     }
  14.     else if( CV_IS_IMAGE_HDR( arr ))
  15.     {
  16.         IplImage* img = (IplImage*)arr;
  17.  
  18.         if( !CvIPL.deallocate )
  19.         {
  20.             char* ptr = img->imageDataOrigin;
  21.             img->imageData = img->imageDataOrigin = 0;
  22.             cvFree( &ptr );
  23.         }
  24.         else
  25.         {
  26.             CvIPL.deallocate( img, IPL_IMAGE_DATA );
  27.         }
  28.     }
  29.     else
  30.     {
  31.         CV_ERROR( CV_StsBadArg, "unrecognized or unsupported array type" );
  32.     }
  33.  
  34.     __END__;
  35. }


その中で,
C++:
  1. if( !CvIPL.deallocate )
  2. {
  3.     char* ptr = img->imageDataOrigin;
  4.     img->imageData = img->imageDataOrigin = 0;
  5.     cvFree( &ptr );
  6. }

という部分からわかるように実際に解放するメモリ領域の先頭アドレスとして指定されるのは
img->imageDataOriginであることがわかると思います.

では,img->imageDataOriginって何?という話になるので,
IplImage構造体について一度確認してみましょう.
cxtypes.hのIplImage構造体の定義を以下に示します.

C++:
  1. typedef struct _IplImage
  2. {
  3.     int  nSize;         /* sizeof(IplImage) */
  4.     int  ID;            /* version (=0)*/
  5.     int  nChannels;     /* Most of OpenCV functions support 1,2,3 or 4 channels */
  6.     int  alphaChannel;  /* ignored by OpenCV */
  7.     int  depth;         /* pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16S,
  8.                            IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported */
  9.     char colorModel[4]; /* ignored by OpenCV */
  10.     char channelSeq[4]; /* ditto */
  11.     int  dataOrder;     /* 0 - interleaved color channels, 1 - separate color channels.
  12.                            cvCreateImage can only create interleaved images */
  13.     int  origin;        /* 0 - top-left origin,
  14.                            1 - bottom-left origin (Windows bitmaps style) */
  15.     int  align;         /* Alignment of image rows (4 or 8).
  16.                            OpenCV ignores it and uses widthStep instead */
  17.     int  width;         /* image width in pixels */
  18.     int  height;        /* image height in pixels */
  19.     struct _IplROI *roi;/* image ROI. if NULL, the whole image is selected */
  20.     struct _IplImage *maskROI; /* must be NULL */
  21.     void  *imageId;     /* ditto */
  22.     struct _IplTileInfo *tileInfo; /* ditto */
  23.     int  imageSize;     /* image data size in bytes
  24.                            (==image->height*image->widthStep
  25.                            in case of interleaved data)*/
  26.     char *imageData;  /* pointer to aligned image data */
  27.     int  widthStep;   /* size of aligned image row in bytes */
  28.     int  BorderMode[4]; /* ignored by OpenCV */
  29.     int  BorderConst[4]; /* ditto */
  30.     char *imageDataOrigin; /* pointer to very origin of image data
  31.                               (not necessarily aligned) -
  32.                               needed for correct deallocation */
  33. }
  34. IplImage;


コメントにもありますが,imageDataOriginは,cvCreateImageで確保したイメージ領域を解放する
ことを保証するために用意されていて,実際にcvCreateImage関数の内部で
呼ばれるcvCreateData関数の中で,イメージ領域の割り当て時に
イメージ領域の先頭アドレスはimageDataとimageDataOriginの両方に格納されます.
cvarray.cppのcvCreateData関数の一部を以下に示します.

C++:
  1. if( !CvIPL.allocateData )
  2. {
  3.     CV_CALL( img->imageData = img->imageDataOrigin =
  4.                         (char*)cvAlloc( (size_t)img->imageSize ));
  5. }


ということで,imageDataが変更されても,imageDataOriginにイメージ領域の先頭アドレスが
格納されているので,cvReleaseImageできちんと解放されることがおわかりいただけると思います.

そんなわけで,OpenCVの内部でどうなっているかを把握した上で書いている点ご理解ください.
※誤解を生まないようVI.getPixelsで取得したイメージデータをmemcpy使ってコピーすれば
 いいんでしょうけど,ループ中にメモリコピーを行うと速度が落ちそうなので,ポインタの変更で済ませちゃってます.

#まぁ,指摘通り,C,C++がある程度わかる人には一見危なそうに見えるのは事実なんですが,
#このようにきちんと内部動作まで解説するとそれなりに大変で,初心者がそれを読むと挫折しそうなので
#今のところ説明を省いています.その辺のバランスって難しいですよね。。。
#何か良い案があれば教えてください.

Comments 4 コメント »