cvReleaseImageの挙動について

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

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

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

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

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

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

[cpp]
// Deallocates array’s data
CV_IMPL void
cvReleaseData( CvArr* arr )
{
CV_FUNCNAME( “cvReleaseData” );

__BEGIN__;

if( CV_IS_MAT_HDR( arr ) || CV_IS_MATND_HDR( arr ))
{
CvMat* mat = (CvMat*)arr;
cvDecRefData( mat );
}
else if( CV_IS_IMAGE_HDR( arr ))
{
IplImage* img = (IplImage*)arr;

if( !CvIPL.deallocate )
{
char* ptr = img->imageDataOrigin;
img->imageData = img->imageDataOrigin = 0;
cvFree( &ptr );
}
else
{
CvIPL.deallocate( img, IPL_IMAGE_DATA );
}
}
else
{
CV_ERROR( CV_StsBadArg, “unrecognized or unsupported array type” );
}

__END__;
}
[/cpp]

その中で,
[cpp]
if( !CvIPL.deallocate )
{
char* ptr = img->imageDataOrigin;
img->imageData = img->imageDataOrigin = 0;
cvFree( &ptr );
}
[/cpp]
という部分からわかるように実際に解放するメモリ領域の先頭アドレスとして指定されるのは
img->imageDataOriginであることがわかると思います.

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

[cpp]
typedef struct _IplImage
{
int nSize; /* sizeof(IplImage) */
int ID; /* version (=0)*/
int nChannels; /* Most of OpenCV functions support 1,2,3 or 4 channels */
int alphaChannel; /* ignored by OpenCV */
int depth; /* pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16S,
IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported */
char colorModel[4]; /* ignored by OpenCV */
char channelSeq[4]; /* ditto */
int dataOrder; /* 0 – interleaved color channels, 1 – separate color channels.
cvCreateImage can only create interleaved images */
int origin; /* 0 – top-left origin,
1 – bottom-left origin (Windows bitmaps style) */
int align; /* Alignment of image rows (4 or 8).
OpenCV ignores it and uses widthStep instead */
int width; /* image width in pixels */
int height; /* image height in pixels */
struct _IplROI *roi;/* image ROI. if NULL, the whole image is selected */
struct _IplImage *maskROI; /* must be NULL */
void *imageId; /* ditto */
struct _IplTileInfo *tileInfo; /* ditto */
int imageSize; /* image data size in bytes
(==image->height*image->widthStep
in case of interleaved data)*/
char *imageData; /* pointer to aligned image data */
int widthStep; /* size of aligned image row in bytes */
int BorderMode[4]; /* ignored by OpenCV */
int BorderConst[4]; /* ditto */
char *imageDataOrigin; /* pointer to very origin of image data
(not necessarily aligned) –
needed for correct deallocation */
}
IplImage;
[/cpp]

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

[cpp]
if( !CvIPL.allocateData )
{
CV_CALL( img->imageData = img->imageDataOrigin =
(char*)cvAlloc( (size_t)img->imageSize ));
}
[/cpp]

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

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

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

OpenCVあれこれ

久しくOpenCVで遊んでないけど,これまで書き損ねてた豆知識的な事を書いてみます.参考までに.

■opencv_introduction
既出かもしれませんが,opencv-docにある
・opencv_introduction_2007June9.pdf
・opencv_objectdetection_2007june10.pdf
が結構良い感じにまとまっているよなーと.
この発表の資料かな?

(英語だけど)OpenCVの概要を掴むには良い教材だし,それなりに情報も新しい.
他所の情報を引用しただけの微妙なサイトよりも遙かに有用.

■CodingStyleGuide
ソースを直に読むなら,既存のファイルや関数の命名規則などが分かっていた方が遙かに効率が良いです.
CodingStyleGuide – OpenCV Library Wikiに軽く目を通すだけでも効率アップするはず!!

■CVS版
人柱的な意味合いが強いのでCVS版は避けられがちですが, 機能追加やbug fixがあって,
面白いので時々チェックしてます.

例えば,CVS版には,otherlibsの下にffopencvというディレクトリが新規で追加されてたりします.
CVSのコメントには,
>ffmpeg wrapper for opencv
と書かれているので,うまく使えばいろんな動画フォーマットに対応できるのかな?(多分)
あと,CVS版には目検出用の学習データ
・haarcascade_eye_tree_eyeglasses.xml
・haarcascade_eye.xml
が追加されていたりします.新しもの好きな方はCVS版で遊んでみる価値アリかも.

■公式サイト消失!?
個人的に結構前から凄く気になっていたのですが,IntelにあるOpenCV 公式サイトが無くなってますよね。。。
もしかして,Intelが手を引こうとしている??最近,というかかなりMLのチェックを怠ってたので,状況が掴めてません.
OpenCV本がそろそろ出るだけに気になる所です.

[tmkm-amazon]0596516134[/tmkm-amazon]

今まで作ってきた(しょうもない)ものリスト

アクセスが少し増えてきたので,今まで自分が作ってきたしょうもないものを挙げてみるテスト.

■学部
GA(遺伝的アルゴリズム)の研究室配属だったのに,OpenCVの存在を知り楽しそうだったので
学部4年の夏から研究テーマを画像処理に変更(一応,GAも前処理に入れてたりするけど).
#高専のときに画像処理をやってたので,ゼロからのスタートでないのが唯一の救い.

当時,OpenCVの日本語サイトはほとんど存在しなかったので,英語による情報収集がメインの暗黒時代でした.

研究としてはwebカメラで普通のディスプレイをタッチパネルにしよう!!という試み.
実際にやっていることはそんなに難しくなくて,2つのカメラで指先をトラッキングして,3次元位置情報を算出し,
スクリーンに触れているかを判定して,マウスの動作に置き換えているだけです.

あと,今思うと,マルチタッチとかやってみれば良かったなーと思いますが,
自分には先見の明がなかったのでそこまではやってない。。。


ペイント操作のデモ.320×240のキャプチャ画像を解析して,
ディスプレイ座標を算出しているので精度はお世辞にも良いとは言えない。。。


ブラウザ操作のデモ.それなりに動くのでやってて楽しかったデモ.
ただ,指先検出の前処理に肌色抽出をやっているため,webページに人の顔や
肌色に似た色が出てくると誤認識してしまう(苦笑)

■修士
可動式+透過型のマルチタッチスクリーン.マルチタッチはFTIR方式で実現.
情報の獲得と作業を並列に行う際にいかに視線移動を少なくできるかという考えのもとに実装.

透明なスクリーンにプロジェクターの映像をそれなりに投影しつつ,
FTIRも実現するということに地味に苦労した記憶が。。。
#透明なスクリーンに映像を投影するってことはスクリーンの上方向に光を拡散させないといけないんだけど,
#反面,FTIRをやる場合には,スクリーンの下方向に光を拡散させる必要があるのでかなり鬼門だった.

あと,下のムービーでは音をカットしていますが,音声合成による操作ナビゲートもやってたりします.

■修士(おまけ)
うちの大学院では,学部生が研究室をプチ体験できるイベントがあったりするわけですが,
そこで,参加者の学部生が用意していた課題を予想以上にこなしてしまったので,
急遽何か作ってくれと頼まれて作ったもの.
#実験室に3時間くらい籠もって学部生に見つからないように作ってた。。。

デモは,マーカーに書かれた場所にGoogle Earthで移動するというもので,
AISTだと産総研,JAISTだと北陸先端,NAISTだと奈良先端に移動します.
#体験セミナーのテーマが画像処理だったのでマーカー検出部も一から作りました.
#ただ,画像処理よりもGoogle Earth APIをC++側で操作する方がいろいろ苦労した気がします(苦笑)

アルゴリズムはこんな感じ.

1. 2値化
2. 輪郭線抽出
3. 輪郭線の多角形近似
4. 辺の数が4となる領域をマーカー領域として残す
5. マーカー領域のコーナー位置よりマーカーの傾きを計算
6. 傾き情報を用いてアフィン変換(傾き補正)
7. テンプレートマッチングにより文字認識
8. 認識結果をGoogle Earthに渡す

学部生対象の画像処理の課題に良いレベルだと思うので,オススメです.

と,ここまで書いてて思ったのは,学生時代しょうもないものばかり作っていたんだなぁと。。。(遠い目)
ということで,今日はこの辺にしときます.