Home > Tags > opencv

opencv

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

cvFindContoursの結果から座標を得る

OpenCVのcvFindContours()は2値画像から輪郭を取ってきてくれるのだが,
その結果から輪郭線のピクセル座標をどうやって取得するのか?
というのを調べて,実際にやってみた.
(最悪「cvDrawContours()で描いてみてピクセルの色で判断」とかいう手もあるが…)

今回は輪郭線を構成する全ピクセルの座標が必要なのではなくて,輪郭をそれとなく折れ線近似した結果の頂点群の座標くらいが得られればよい状況だったので,

  1. cvFindContour()で輪郭を検出してもらう
  2. 各輪郭を,cvApproxPoly()で折れ線近似する
  3. 折れ線の頂点座標をもらう

という手順をコーディングしてみた.この手順をまとめた関数は以下の通り.(C++)

//cvFindContour -> cvApproxPoly
//の順番で処理.輪郭を多角形近似した際の頂点座標群を得る.
//※入力画像pSrc8U1Cは,cvFindContourの仕様により変更される.
//	嫌ならコピーを渡すこと.
typedef std::vector< std::vector<CvPoint> >	Type_ApproxContourContainer;

bool GetApproxPolyContours(	IplImage *pSrc8U1C,	//constでない入力画像
							Type_ApproxContourContainer *pDst,	//結果受取場所
							double approx_accuracy_param	//cvApproxPolyに渡す近似精度パラメタ
							)
{
	//引数チェック
	if( approx_accuracy_param < 0 )return false;
	if(  pDst==NULL  ||  !CheckImgType( pSrc8U1C, IPL_DEPTH_8U, 1 )  )return false;

	//cvFindContoursでContourを検出
	CvMemStorage* storage = cvCreateMemStorage(0);
	CvSeq* contour = 0;

	int contour_count = cvFindContours( pSrc8U1C, storage, &contour, sizeof(CvContour), CV_RETR_LIST , CV_CHAIN_APPROX_SIMPLE );
	if( contour_count <= 0 )return false;

	//各ContourをcvApproxPolyで近似して,結果格納域にその頂点座標群を保存
	pDst->resize( contour_count );
	Type_ApproxContourContainer::iterator iDst = pDst->begin();

	for( ; contour != 0; contour = contour->h_next )
	{
		CvSeq *approx = cvApproxPoly( contour, sizeof(CvContour), NULL, CV_POLY_APPROX_DP, approx_accuracy_param );

		iDst->resize( approx->total );
		for( int i=0; i<approx->total; i++ )
		{
			iDst->at(i) = *CV_GET_SEQ_ELEM( CvPoint, approx, i );
		}
		iDst++;
	}

	//後始末
	cvReleaseMemStorage( &storage );
	return true;
}

(最初の方にあるCheckImgType(…)は,渡されてきた画像が8bit1chであるかどうかをチェックしているもの.簡素すぎるのでコードは省略.)

cvFindContours()で検出されたcontour群の巡回は,リファレンスのcvDrawContours()のとこにあるサンプル通りに,h_nextで行う(v_nextはいつ使うのであろうか?).
(このサンプルでは後始末処理が省略されている.storageをcvReleaseMemStrorage()で後始末する必要があると思う.)

cvApproxPoly()は,「第3引数がNULLならば入力が使っていた場所に結果を保存する」とのこと(結果としてデータ量が増えた場合とか大丈夫なのだろうか?とりあえず無事に動いているようだが).
精度に関するパラメタは,近似後の折れ線と近似前のデータとの間の許容距離のようなものだと思われる(←アルゴリズム名をググって出てきたページにあった絵だけからの想像).(試しに負の値を入れてみたら即死なされたので注意)

各contourが持っているデータ(cvFindContours()の引数指定で,結果をチェインコードではなく点列にしているので,データは多分点座標でCvPoint型)には,CV_GET_SEQ_ELEMなるマクロでアクセスできる模様で,これで無事に座標を取得できた.

なお,今回はcvApproxPoly()の処理が入っているが,不要な場合は取っ払えばOK.
↑のコードでいえば,近似処理を省く最も簡単な方法は
  CvSeq *approx = cvApproxPoly(…)
のところを,
  CvSeq *approx = contour;
としてしまえばいい.
※ただし,cvFindContours()自体も,引数指定によってデータを近似するのでその点は注意.

Contour
上記関数を使って精度パラメタ=5.0で取得できた点列を赤線でつないでみた.
この関数に渡す入力画像を cvCanny→cvDilate で作ったせいで輪郭が二重に出てますが.

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

Home > Tags > opencv

Search
Feeds
Authorized
奨学金支援制度
Meta

Return to page top