Home > Archives > 2009-09

2009-09

IplImage*を自動でRelease

世間ではポインタのdeleteし忘れを防止するためにauto_ptrとかそういう類のものがあるみたいですが,
それのIplImage*版みたいなやつを何故か自前で書いてみたよ,という低レベルな遊び.

●動機
OpenCVの関数は引数が(処理対象画像,処理結果格納領域)という形が多いので,
画像をいじくる処理を書くと大抵 画像領域を作って→必要な間持っておき→要らなくなったら捨てる ということをすることになる.
画像はcvReleaseImage()関数で捨てるのだが,
処理を途中で中止(returnで関数を抜けるとか)するとなると,
中止場所によって捨てるべき画像がどれとどれなのか違ったりしてこの後始末がたいそう面倒……なのだが,
何故か今まで画像領域を捨てる作業を自動化させてこなかった.何故だろう?不思議.

で,ある日,処理サンプルプログラムみたいな関数コードを隣人に渡したら
「その関数を別スレッドにしてそのまま使う.任意のタイミングで中止できるようにして」とか言われた.(自分でやってくれ)
捨てる処理を関数末尾にまとめて中止時にgotoでそこに飛ぶ,とかやろうとしたら関数の途中で変数宣言してるから怒涛のコンパイルエラーが出て泣いた.

●やること
要するに,
・IplImage*をメンバ変数として持っていて
・デストラクタでそれをcvReleaseImage()する
・IplImage*だと思って使える
ようなクラスを書いてみる.

●個人的に予想される追加の恩恵
OpenCVを使い始めた当初,「cvReleaseImage()したらポインタの値はどうなるの?」という疑問を持った.
結局cvReleaseImage()のソースコードを見るとちゃんと0を代入してくれているのだが,
「実装が変わるかもしれない!(?) せっかくだから俺は自分でNULLを代入するぜ!」という謎の意気込みと
「ポインタのポインタとかってなんとなく嫌(※1)」という半端なこだわりを理由に,
↓の大仰な名前の関数をわざわざ用意して使ったりしていた.

//IplImageの解放.
//	(これを正しく働かせるために,画像を指さないIplImage*変数は常にNULLにしておくこと.初期化とか.)
inline void cvSafeReleaseImage( IplImage* &pImg )
{
	if( pImg ){	cvReleaseImage( &pImg );	pImg=NULL;	}
}

「&」一個をタイプしたくないのと引き換えに,かえって長ったらしい関数名をタイプするというよくわからない事態.
それはそれでそれなりに楽しいのだが,画像破棄を自動化すれば無意味に腱鞘炎になる危険性がいくらか減るかもしれない.

●書いてみました

//NULL初期化と,指す領域のReleaseを自動化したIplImage*
class UCpIplImage
{
public:
	UCpIplImage( IplImage* const pImg=NULL ){	operator=(pImg);	}
	~UCpIplImage(){	Release();	}
	void Release(){	cvSafeReleaseImage(m_pImg);	}

	operator IplImage*&(){	return m_pImg;	}
	operator const IplImage* () const {	return m_pImg;	}
	IplImage *operator =( IplImage* const pImg ){	return (m_pImg = pImg);	}
	IplImage *operator->(){	return m_pImg;	}
	const IplImage *operator->() const {	return m_pImg;	}

private:
	//※このポインタは,有効な画像領域を指さないときはNULLに保っておかねばならない
	IplImage *m_pImg;
};

(しつこくcvSafeReleaseImageを仕込んでみました)

えと,これでいいのか……な?
とりあえずちょっと使ってみた感じ大丈夫そうに見えるが.

とは言え,「勝手に画像を捨ててしまう」ことが問題となる場面ではこれでは逆に使えない.
うっかりコピーができて2重破棄等の問題が出ないようにこのクラスインスタンスの複製を禁止.

	//複製の禁止
private:
	UCpIplImage( const UCpIplImage & );
	UCpIplImage &operator =( const UCpIplImage & );

しかしIplImage*型ポインタを間に介せばコピーできるわけで……
結局,最初に挙げたような限られた場面(作って,使って,すぐ捨てるような一時画像)に限ってしか使えないのかも.
うーん…

—————————————————–

※1
学生のころ,「動的多次元配列」とか言って,4重くらいのポインタをちりばめたコードを書いて自分で収集がつかなくなった.
しかもその悪夢のコードを読まなければならない後輩が出てきて,研究室のみんなの前で
「なんでそんなポインタ地獄になってしまったのか」についてスライド発表した.
二度とやるまいと固く誓った.

  • Comments (Close): 0
  • Trackbacks (Close): 0

cvNamedWindowのウィンドウをうっかり閉じなくする

OpenCVでcvNamedWindow()でウィンドウを作って画像を表示しているとき,うっかり右上の閉じるボタン[x]をポチっと押してしまったりしてウィンドウが閉じてしまうとそれなりに悲しい気持ちになります.
そのような残念な事態を少しでも回避できるように,ウィンドウの閉じるボタンを無効化してみました.

タイトルバー右端にある閉じるボタン[x]を無効化するには,ウィンドウのシステムメニューから「閉じる」を取り払えば良い模様なので,手順は以下のようになります.

[手順]
(1)cvNamedWindow()でウィンドウを作る
(2)ウィンドウのシステムメニューから,「閉じる」(SC_CLOSE)を除去する

cvNamedWindow()で作られるウィンドウは,タイトルバーと枠がある親ウィンドウと,画像を表示する子ウィンドウの2層構造でできており,cvGetWindowHandle()で得られるウィンドウハンドルは,子ウィンドウ側のハンドルになるので注意が必要です.
システムメニューをいじくる対象は,cvGetWindowHandle()で得られるウィンドウの親ウィンドウになります.

//●指定名称のウィンドウが存在するか?
inline bool cvWndIsExist( const char *name )
{
	return ( cvGetWindowHandle( name ) != 0 );
}

//●閉じるボタン[x]が使えないウィンドウを作る
//(ただしウィンドウを閉じるショートカットキーまでは殺せてない)
inline int cvNoCloseWindow( const char* name, int flags=CV_WINDOW_AUTOSIZE )
{
	if( cvWndIsExist( name ) )return 1;

	int ret = cvNamedWindow( name, flags );
	HWND hParent = GetParent( (HWND)cvGetWindowHandle(name) );
	if( hParent )
	{
		RemoveMenu( GetSystemMenu( hParent, FALSE ), SC_CLOSE, MF_BYCOMMAND );
	}
	return ret;
}

指定した名前のウィンドウが既に存在する場合は,なんとなく1を返してみました.
(cvNamedWindowのコードが1を返していたので同じにした)

これで閉じるボタン(と,メニューの「閉じる」)は封じましたが,残念ながら「閉じられない」を達成しているわけではありません.Alt+F4とか押されるとやっぱり閉じてしまいます.
でもまぁ,閉じるボタンを灰色表示することで「閉じちゃいやん」という希望を暗に示すことはできたのではないでしょうか? ということで,個人的にはこれくらいで十分ですが…

一応,Alt+F4もつぶす方法もやってみました.
Alt+F4が押されると,WM_SYSCOMMANDメッセージが飛んできて,wParamがSC_CLOSEになっています.なので,

(3)ウィンドウプロシージャを差し替えて,WM_SYSCOMMAND+SC_CLOSEな時は何もしないようにする

ことで,Alt+F4も撃墜できそうです.コードを以下のように変更します.

LRESULT CALLBACK CV_NoCloseProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if( msg==WM_SYSCOMMAND && wParam==SC_CLOSE )
	{
		return 0;
	}
	else
	{
		WNDPROC DefWP = (WNDPROC)GetProp( hWnd, "CV_DEF_PROC" );
		return CallWindowProc( DefWP, hWnd, msg, wParam, lParam );
	}
}

int cvNoCloseWindow( const char* name, int flags=CV_WINDOW_AUTOSIZE )
{
	if( cvWndIsExist( name ) )return 1;

	int ret = cvNamedWindow( name, flags );
	HWND hParent = GetParent( (HWND)cvGetWindowHandle(name) );
	if( hParent )
	{
		//メニューから「閉じる」を除去(すると,[x]ボタンが灰色になる)
		RemoveMenu( GetSystemMenu( hParent, FALSE ), SC_CLOSE, MF_BYCOMMAND );

		//ウィンドウプロシージャ置き換えでAlt+F4もつぶす
		SetProp( hParent, "CV_DEF_PROC", (HANDLE)GetWindowLong( hParent, GWL_WNDPROC ) );
		SetWindowLong( hParent, GWL_WNDPROC, (LONG)CV_NoCloseProc );

		//再描画.こんなんでいいのか?
		ShowWindow( hParent, SW_HIDE );
		ShowWindow( hParent, SW_SHOW );
	}
	return ret;
}

ウィンドウプロシージャをSetWindowLong()で自前のCV_NoCloseProc()に差し替えています.
つぶしたい処理以外は元のプロシージャに処理してもらいたいので,差し替える前に元のウィンドウプロシージャを取得し,SetProp()で保存しています.
(なんかプロシージャ差し替えコードを追加したところ,閉じるボタンが初期に灰色表示にならないようになったので,ウィンドウ再描画も追加.)

とりあえずかなり閉じれない感じになったと思いますが,ここまでやる必要はなさそうというか,cvShowImage()の前に「ウィンドウの存在を確認して,なかったらウィンドウを作る」類のコードを素直に置いた方がよっぽど良いような予感.

  • Comments (Close): 0
  • Trackbacks (Close): 0

Home > Archives > 2009-09

Search
Feeds
Meta

Return to page top