【OpenCV2対応参考書籍】OpenCV 2 Computer Vision Application Programming Cookbook

2011.5月現在、OpenCV2以降に対応した参考書籍としては、まだ、発売されていませんが、これのみ?しかも英語

 

 

OpenCV Ver2.0からはC++インターフェースが追加され、大きく変わったのですが、このC++に対応していると思われる?この書籍の概要は以下の通りです。

 

  • Teaches you how to program computer vision applications in C++ using the different features of the OpenCV library
  • Demonstrates the important structures and functions of OpenCV in detail with complete working examples
  • Describes fundamental concepts in computer vision and image processing
  • Gives you advice and tips to create more effective object-oriented computer vision programs
  • Contains examples with source code and shows results obtained on real images with detailed explanations and the required screenshots

 

なにせ、発売前なので、詳細は不明ですが、気になるところです。

 

OpenCVへ戻る

 

【OpenCV】輪郭処理(cvFindContours)を使ったラベリング処理

OpenCVには標準的にはcvLabelingのようなラベリングの関数は無いので、

 

 

を使いましょう!というのが一般的になってきているように思いますが、最初のラベリングクラスでは、画像の幅の画素数が4の倍数で無い場合、うまく動作してくれなかった気がするし、Blob extraction library は英語なので良く分からないし・・・
※追記)現在、はconnectedComponentsが使えます。

ということで、OpenCVに標準的にある輪郭処理の関数【cvFindContours】を使ってラベリングの処理ができないか?調べてみました。

 

結果、OpenCVの関数だけで、こんな感じ↓まで出来ました。

 

何はともあれ、まずは分かりづらいcvFindContoursの関数です。

 

関数の定義は

int cvFindContours(
          CvArr* image,			                    // 入力画像(8Bitモノクロ)
          CvMemStorage* storage,                // 抽出された輪郭を保存する領域
          CvSeq** first_contour,	              // 一番最初の輪郭(ツリー構造を持つ)へのポインタ
          int header_size = sizeog(CvContour),  // シーケンスのヘッダサイズ
          int mode = CV_RETR_LIST,	            // 抽出モード
          int method = CV_CHAIN_APPROX_SIMPLE,  // 近似手法
          CvPoint offset = cvPoint(0, 0)        // オフセット
          );

となっているのですが、とにかく分かりづらい抽出モード( mode )の理解から。

 

まずは mode = CV_RETR_TREE を例に取って説明したいと思います。

処理前の画像↓

 

この画像の輪郭を外側から順に追いかけると、

 

の内側に 、  の内側に  と  ・・・ というような構造になっています。

 

 

この構造をツリーのように階層構造で表すと、

 

階層
(Level)
輪郭構造

のように、一番外側に白の輪郭(Level = 1)があり、その内側に黒の輪郭(Level = 2)、さらにその内側に白の輪郭(Level = 3)・・・と、一番外側の白の輪郭から始まり、その輪郭の内側に黒の輪郭→白の輪郭→黒の輪郭→白の輪郭・・・とレベルが大きくなるにつれ、さらに内側に輪郭が存在しています。
この構造を保持しているのが CvSeq** first_contour となります。
このfirst_contourには一番最初の輪郭を示すポインタが格納されています。

 

同じ階層(Level)にある別の輪郭を参照したい場合は

CvSeq* contour = first_contour->h_next;

とすれば、同じ階層にある輪郭を参照できます。

 

さらに

contour = contour->h_next;

とすれば、さらに次の輪郭を参照出来ます。

 

もし、contourNULL になったら同じ階層に、同じ親を持つ別の輪郭は無い事を意味しています。

 

同じ様に

contour = contour->v_next;

とすれば、現在の輪郭のさらに内側にある輪郭へとポインタが移動します。

 

このようにh_nextv_nextを使うと、全ての輪郭を構造的に参照することが可能となります。

 

同様に

【mode = CV_RETR_EXTERNAL の場合】

階層
(Level)
輪郭構造

のように、一番外側の白の輪郭のみを取得します。

 

【mode = CV_RETR_LIST の場合】

階層
(Level)
輪郭構造

のように、白の輪郭、黒の輪郭、内側、外側関係なく、同じ階層で輪郭が取得されます。

 

【mode = CV_RETR_CCOMP の場合】

階層
(Level)
輪郭構造

のように、白の輪郭の一つ下のレベルに黒の輪郭を持つ構造となります。
ただし、ここで大事なのは白の輪郭のさらに内側にある白の輪郭(上図の5や6)も同じ階層となるので、ご注意下さい。

 

※modeの設定は共通して最初の階層の輪郭は白色の輪郭になっているようです。
そのため、白色の地に黒色の輪郭のある画像を処理すると、最初の輪郭は画像全体となるのでご注意下さい。

 

ということで、cvFindContours関数を使って輪郭を描画するプログラムはこんな感じ↓になります。

// Labelling.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

//プロジェクトのプロパティ⇒C/C++⇒全般 の追加のインクルードディレクトリに
// 『C:\OpenCV2.2\include』を追加のこと
#include "opencv2\\opencv.hpp"

#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")
#endif

// 関数宣言
void GetContourFeature(CvSeq*);
void DrawChildContour(IplImage*, CvSeq*,int);
void DrawNextContour(IplImage*,	CvSeq*, int);
void cv_Labelling(IplImage*, IplImage*);

//各種輪郭の特徴量の取得
void GetContourFeature(CvSeq *Contour){
	//面積
	double Area = fabs(cvContourArea(Contour, CV_WHOLE_SEQ));
	//周囲長
	double Perimeter = cvArcLength(Contour);
	//円形度
	double CircleLevel = 4.0 * CV_PI * Area / (Perimeter * Perimeter);

	//傾いていない外接四角形領域(フィレ径)
	CvRect rect = cvBoundingRect(Contour);
	//輪郭を構成する頂点座標を取得
	for ( int i = 0; i < Contour->total; i++){
		CvPoint *point = CV_GET_SEQ_ELEM (CvPoint, Contour, i);
	}
}

void DrawChildContour(	//子の輪郭を描画する。
	IplImage *img,	//ラベリング結果を描画するIplImage(8Bit3chカラー)
	CvSeq *Contour, //輪郭へのポインタ
	int Level		//輪郭のレベル(階層)
	){

	// 領域の色
	CvScalar color;
	// 輪郭を描画する色の設定
	CvScalar ContoursColor;

	if ((Level % 2) == 1){
		//白の輪郭の場合
		// 領域の色
		color = CV_RGB( rand()&255, rand()&255, rand()&255 );
		// 輪郭の色
		ContoursColor = CV_RGB( 255, 0, 0 );

	}else{
		//黒の輪郭の場合(内側の場合)
		// 領域の色
		color = CV_RGB(0, 0, 0);
		// 輪郭の色
		ContoursColor = CV_RGB( 0, 0, 255 );
	}

	//輪郭の描画
	cvDrawContours( img, Contour, color, color, 0, CV_FILLED);			// 領域
	cvDrawContours( img, Contour, ContoursColor, ContoursColor, 0, 2);	// 輪郭

	//輪郭を構成する頂点座標を取得
	for ( int i = 0; i < Contour->total; i++){
		CvPoint *point = CV_GET_SEQ_ELEM (CvPoint, Contour, i);
		//cvDrawCircle(img, *point, 3, CV_RGB(0, 255, 0));
	}

	//各種輪郭の特徴量の取得
	GetContourFeature(Contour);

	if (Contour->h_next != NULL)
		//次の輪郭がある場合は次の輪郭を描画
		DrawNextContour(img, Contour->h_next, Level);

	if (Contour->v_next != NULL)
		//子の輪郭がある場合は子の輪郭を描画
		DrawChildContour(img, Contour->v_next, Level + 1);
}

void DrawNextContour(	//次の輪郭を描画する。
	IplImage *img,	//ラベリング結果を描画するIplImage(8Bit3chカラー)
	CvSeq *Contour, //輪郭へのポインタ
	int Level		//輪郭のレベル(階層)
	){

	// 領域の色
	CvScalar color;
	// 輪郭を描画する色の設定
	CvScalar ContoursColor;

	if ((Level % 2) == 1){
		//白の輪郭の場合
		// 領域の色
		color = CV_RGB( rand()&255, rand()&255, rand()&255 );
		// 輪郭の色
		ContoursColor = CV_RGB( 255, 0, 0 );

	}else{
		//黒の輪郭の場合(内側の場合)
		// 領域の色
		color = CV_RGB(0, 0, 0);
		// 輪郭の色
		ContoursColor = CV_RGB( 0, 0, 255 );
	}

	//輪郭の描画
	cvDrawContours( img, Contour, color, color, 0, CV_FILLED);			// 領域
	cvDrawContours( img, Contour, ContoursColor, ContoursColor, 0, 2);	// 輪郭

	//輪郭を構成する頂点座標を取得
	for ( int i = 0; i < Contour->total; i++){
		CvPoint *point = CV_GET_SEQ_ELEM (CvPoint, Contour, i);
		// 頂点座標の描画
		//cvDrawCircle(img, *point, 3, CV_RGB(0, 255, 0));
	}

	//各種輪郭の特徴量の取得
	GetContourFeature(Contour);

	if (Contour->h_next != NULL)
		//次の輪郭がある場合は次の輪郭を描画
		DrawNextContour(img, Contour->h_next, Level);

	if (Contour->v_next != NULL)
		//子の輪郭がある場合は子の輪郭を描画
		DrawChildContour(img, Contour->v_next, Level + 1);
}

void cv_Labelling(	//ラベリング処理
	IplImage *src,	//入力画像(8Bitモノクロ)
	IplImage *dst	//出力画像(8Bit3chカラー)
	) {

	CvMemStorage *storage = cvCreateMemStorage (0);
	CvSeq *contours = NULL;

	if (src == NULL)
		return;

	// 画像の二値化【判別分析法(大津の二値化)】
	cvThreshold (src, src, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

	// 輪郭の検出(戻り値は取得した輪郭の全個数)
	int find_contour_num = cvFindContours (
			src,					// 入力画像
			storage,				// 抽出された輪郭を保存する領域
			&contours,				// 一番外側の輪郭へのポインタへのポインタ
			sizeof (CvContour),		// シーケンスヘッダのサイズ
			CV_RETR_TREE,			// 抽出モード
									// CV_RETR_EXTERNAL - 最も外側の輪郭のみ抽出
									// CV_RETR_LIST - 全ての輪郭を抽出し,リストに追加
									// CV_RETR_CCOMP - 全ての輪郭を抽出し,二つのレベルを持つ階層構造を構成する.1番目のレベルは連結成分の外側の境界線,2番目のレベルは穴(連結成分の内側に存在する)の境界線.
									// CV_RETR_TREE - 全ての輪郭を抽出し,枝分かれした輪郭を完全に表現する階層構造を構成する.
			CV_CHAIN_APPROX_SIMPLE	// CV_CHAIN_APPROX_SIMPLE:輪郭の折れ線の端点を取得
									// CV_CHAIN_APPROX_NONE: 輪郭の全ての点を取得
									// CV_CHAIN_APPROX_TC89_L1		:Teh-Chinチェーンの近似アルゴリズム中の一つを適用する
									// CV_CHAIN_APPROX_TC89_KCOS
			);

	if (contours != NULL){
		//処理後画像を0(黒)で初期化
		cvZero(dst);
		//輪郭の描画
		DrawNextContour(dst, contours, 1);
	}

	//メモリストレージの解放
	cvReleaseMemStorage (&storage);

}

int _tmain(int argc, _TCHAR* argv[])
{

    //画像データの読込(グレースケールで読込)
    IplImage* src = cvLoadImage("sample.bmp", CV_LOAD_IMAGE_GRAYSCALE);
    if (src == NULL){
        return 0;
    }

    //表示ウィンドウの作成
    cvNamedWindow("src");
    cvNamedWindow("dst");

    //処理後画像データの確保
    IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, 3);

	// 入力画像の表示
	cvShowImage ("src", src);

    // ラベリング処理(※入力画像(src)はcvFindContoursにより変更されます。)
    cv_Labelling(src, dst);

    // ラベリング画像の表示
    cvShowImage ("dst", dst);

    // キー入力待ち
    cvWaitKey (0);

    // 全てのウィンドウの削除
    cvDestroyAllWindows();

    // 画像データの解放
    cvReleaseImage(&src);
    cvReleaseImage(&dst);

	return 0;
}

サンプルプログラムのダウンロードはこちらより

opencv-labelling.zip

(OpenCV2.2対応。Visual Studio 2010 C++ Expressにより作成)

 

上記、サンプルプログラムではcvFindContours関数で輪郭情報を取得し、オリジナルのDrawNectContour関数を用いて再帰的に輪郭、および領域を描画しています。

 

また、GetContourFeature関数で、面積、周囲長、円形度、フィレ径、輪郭の頂点座標の計算だけをしています。この部分は必要に応じて改良してみて下さい。

 

ここで注意が必要なのは、輪郭から面積を計算する関数cvContourAreaは、下図の様に輪郭線で囲まれた領域の内側の面積を計算します。(画素数ではありません。

下図の例では面積は 17.5 となります。
輪郭の内側の穴の面積は考慮されないので、穴の部分を除外したい場合はv_nextで一つ下の階層にある黒の輪郭を全て取得し、黒の面積を白の面積から引いて下さい。

 

輪郭座標について

CV_GET_SEQ_ELEMマクロで取得している輪郭を構成する座標はcvFindContours関数の6番目の引数methodの設定できまります。

 

method = CV_CHAIN_APPROX_NONE の場合

上図のように輪郭を構成している座標全てを取得します。

 

method = CV_CHAIN_APPROX_SIMPLE の場合

上図のように輪郭を構成している折れ線の角の座標を取得します。

 

method = CV_CHAIN_APPROX_TC89_L1
もしくは     CV_CHAIN_APPROX_TC89_KCOS の場合

 

上図のように輪郭を構成している座標をTeh-Chinチェーンの近似アルゴリズムに基づいて近似した線の折れ線の角の座標を取得します。
との事ですが、詳細は良く分かりませんでした...(上図も実際の結果と異なるかも?)

 

OpenCVでは他にも、いろいろな特徴量を計算する関数が用意されています。
おそらくcvFindContours関数を使ってラベリング処理をした方が、いろんな使い道が考えられると思うので、上記のサンプルプログラムを目的に応じて改良してみて下さい。

 

OpenCVへ戻る

 

【OpenCV】インプレースモード

OpenCVの関数では入力画像(src)と出力画像(dst)に同じ値(src=dst)を指定しても処理してくれる
関数があり、このことをインプレースモードと言います。

 

例えば

cvErode(src, src);
cvErode(src, src);
cvDilate(src, src);
cvDilate(src, src);

と処理を行っても大丈夫な関数があります。
どの関数がインプレースモードに対応しているのかは、OpenCV.jp、他、リファレンスマニュアルを参照下さい。

 

普通に考えれば入力画像と出力画像に同じデータを用いる事はできない場合が多いかと思いますが、OpenCVの関数のソースコードを見てみると、入力画像(src)と出力画像(dst)の画像データのポインタが等しい場合は、関数内部で処理後の画像データを格納するためのメモリを確保し、画像処理後に処理後の画像データを入力画像にコピーして、メモリを解放する処理が行われています。

 

そのため、インプレースモードを使えば、ちょっとした処理の評価をするにはプログラムのコード量も減って簡単に処理ができるのですが、処理を何回も繰り返す場合には、入力画像と出力画像のデータを分けた方が高速に処理を行うことができる(余計なメモリの確保やコピー処理が行われない)ので、入力画像(src)と出力画像(dst)は別の値を指定した方が良いと思います。

 

また、cvSmoothのように同じ関数でも、引数によって処理を分けている場合、引数によってインプレースモードに対応、非対応な場合があるので、ご注意下さい。

 

例えばcvSmoothの場合、

平滑化の方法
smoothtype
処理内容 インプレースモード
CV_BLUR_NO_SCALE スケーリング無しの単純平滑化 対応
CV_BLUR 単純平滑化 対応
CV_GAUSSIAN ガウシアンフィルタ 対応
CV_MEDIAN メディアンフィルタ 非対応
CV_BILATERAL バイラテラルフィルタ 非対応

 
 

OpenCVへ戻る

 

【OpenCV】アンシャープマスキング(鮮鋭化)

アンシャープマスキング(Unsharp Masking)もOpenCVに無い関数の1つのなのですが、OpenCVには任意カーネルを指定してフィルタ処理を行っているcvFilter2Dという関数があるので、これを使ってアンシャープマスキングを実現する手法を紹介します。

 

アンシャープマスキングのアルゴリズムについては以前、アンシャープマスキング(鮮鋭化フィルタ)のページで紹介しているので、そちらを参照願います。

 

以下、アンシャープマスキングのサンプルプログラムです。OpenCV2.2を用いて、作成しています。

 

// UnsharpMasking.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

//プロジェクトのプロパティ⇒C/C++⇒全般 の追加のインクルードディレクトリに
// 『C:\OpenCV2.2\include』を追加のこと
#include "opencv2\\opencv.hpp"

#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")
#endif

//---------------------------------------------------------------
//【関数名 】:cv_UnsharpMasking
//【処理概要】:アンシャープマスキング
//【引数  】:src        = 入力画像
//      :dst        = 出力画像
//      :k		   = 鮮鋭化の強さ
//【戻り値 】:なし
//【備考  】:
//---------------------------------------------------------------
void cv_UnsharpMasking(IplImage* src, IplImage* dst, float k){
	//カーネルの設定
	float KernelData[] = {
		-k/9.0f, -k/9.0f,			-k/9.0f,
		-k/9.0f, 1 + (8 * k)/9.0f,	-k/9.0f,
		-k/9.0f, -k/9.0f,			-k/9.0f,
	};
	//カーネルの配列をCvMatへ変換
	CvMat kernel = cvMat (3, 3, CV_32F, KernelData);
	//フィルタ処理
	cvFilter2D (src, dst, &kernel);
}

int _tmain(int argc, _TCHAR* argv[])
{

	//画像データの読込
	IplImage* src = cvLoadImage("sample.bmp", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
	if (src == NULL){
		return 0;
	}

	//表示ウィンドウの作成
	cvNamedWindow("src");
	cvNamedWindow("dst");

	//処理後画像データの確保
	IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, src->nChannels);

	//アンシャープマスキング
	cv_UnsharpMasking(src, dst, 2.0f);

	//画像の表示
	cvShowImage ("src", src);
	cvShowImage ("dst", dst);

	//キー入力待ち
	cvWaitKey (0);

	//全てのウィンドウの削除
	cvDestroyAllWindows();

	//画像データの解放
	cvReleaseImage(&src);
	cvReleaseImage(&dst);

	return 0;
}

サンプルプログラムのダウンロードはこちらより。

OpenCV-UnsharpMasking.zip

(OpenCV2.2対応。Visual Studio 2010 C++ Expressにより作成)

 

実行例

 

このサンプルプログラムは、任意カーネルフィルタのサンプルにもなっているので、プログラム中のKernelDataの値をいろいろと変えてみるのも面白いと思います。

 

OpenCVへ戻る

 

【OpenCV】コンピュータにopencv_objdetect220d.dllがないため、

OpenCVのサンプルプログラムなどを動かそうとすると、下図のように

 

コンピュータにopencv_objdetect220d.dllがないため、プログラムを開始できません。
この問題を解決するには、プログラムを再インストールしてみて下さい。

 

 

などと、他にもコンピュータにopencv_core220d.dllがないため、・・・だったりと、ファイル名は違うかもしれませんが、このようなエラーメッセージが表示される場合があります。

 

dllファイルはコンピュータに入っているし、なぜ???と思われる方もいると思いますが、Windowsではダイナミックライブラリファイル(*.dll)は下記の特定のフォルダに入っていないと見つけてくれません。

 

1. アプリケーション(*.exe)と同じフォルダ
2. カレントディレクトリ
3. システムディレクトリ(C:\Windows\System32 など)
4. 16Bitシステムディレクトリ(C:\Windows\System など)
5. Windowsディレクトリ(C:\Windows など)
6. PATH環境変数に列挙されているディレクトリ

 

OpenCV2.2ではインストール時に、OpenCVをPATH環境変数に追加するか?と聞かれるのですが、これを忘れてしまうと、このエラーメッセージが表示されてします。

 

そのため、このエラーが出た時には自分でPATH環境変数にOpenCVのPATH(OpenCVのdllファイルのあるディレクトリ)を設定する必要があります。

 

設定方法は以下の通り。

 

設定方法は、スタートメニューから、コンピュータ右クリックし、プロパティを選択します。

 

次に表示されたウィンドウのシステムの詳細設定をクリック

 

 

詳細設定のタブを選択し、右下の環境変数のボタンをクリック

 

 

すると下図のように××のユーザー環境変数システム環境変数と2種類表示され、両方ともにPathの項目があるのですが、××のユーザー環境変数のPathを設定するとWindowsにログインしたときのユーザーのみでPathの設定が有効となり、システム環境変数のPathを設定すると全ユーザーでPathの設定が有効となります。
私は『ログインしたユーザーを変えるとプログラムが起動しないんだけど?!』とか言われたく無いので、システム環境変数のPathを設定 するようにしています。

 

そして、Pathの項目を選択し、編集ボタンをクリックします。

 

そして、編集値の欄に*.dllファイルがインストールされているパス(フォルダ名のフルパス)をセミコロン(;)に続けて記載します。

 

(例)
;C:\OpenCV2.2\bin

 

この時、元にあった文字は消さないように注意して下さい。
消されたプログラムが起動しなくなります。

 

最後にパソコンを再起動して下さい。

 

これで、 コンピュータに*.dllファイルがないため、 というエラーは無くなると思います。

 

(参考)

ライブラリの使用方法、VisualStudioの設定方法

 

OpenCVへ戻る

 

【OpenCV】ソーベルフィルタ(cvSobel)

OpenCVの関数では、ほとんど入力画像と出力画像のデータはビット数とチャンネル数は等しい場合が多いのですが、ソーベルフィルタの関数(cvSobel)は入力画像が8Bitに対し、出力画像が符号付きの16Bit(IPL_DEPTH_16S)にしないといけません。

 

また、一回のcvSobel関数の処理ではX方向、Y方向のどちらかの方向しか処理できないので、最も一般的?なX方向とY方向を足し合わせた処理を簡単に関数にまとめてみました。

 

// SobelFilter.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

//プロジェクトのプロパティ⇒C/C++⇒全般 の追加のインクルードディレクトリに
// 『C:\OpenCV2.2\include』を追加のこと
#include "opencv2\\opencv.hpp"

#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")
#endif

//---------------------------------------------------------------
//【関数名 】:cv_Sobel
//【処理概要】:ソーベルフィルタ
//【引数  】:src        = 入力画像(8bit)
//      :dst        = 出力画像(8bit)
//【戻り値 】:なし
//【備考  】:
//---------------------------------------------------------------
void cv_Sobel(IplImage* src, IplImage* dst){

    IplImage* xSoble  = cvCreateImage(cvGetSize(src), IPL_DEPTH_16S, src->nChannels);
    IplImage* ySoble  = cvCreateImage(cvGetSize(src), IPL_DEPTH_16S, src->nChannels);
    IplImage* xySoble = cvCreateImage(cvGetSize(src), IPL_DEPTH_16S, src->nChannels);

    //X方向のソーベル
    cvSobel(src, xSoble, 1, 0);
    //Y方向のソーベル
    cvSobel(src, ySoble, 0, 1);

    //X方向 + Y方向
    cvAdd(xSoble, ySoble, xySoble);

    //符号付き16ビット→符号なし8ビットへ
    cvConvertScaleAbs(xySoble, dst);

    //解放
    cvReleaseImage(&xSoble);
    cvReleaseImage(&ySoble);
    cvReleaseImage(&xySoble);

}

int _tmain(int argc, _TCHAR* argv[])
{

	//画像データの読込(グレースケールで読込)
	IplImage* src = cvLoadImage("Lenna.bmp", CV_LOAD_IMAGE_GRAYSCALE);
	if (src == NULL){
		return 0;
	}

	//表示ウィンドウの作成
	cvNamedWindow("src");
	cvNamedWindow("dst");

	//処理後画像データの確保
	IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, src->nChannels);

	//ソーベルフィルタ
	cv_Sobel(src, dst);

	//画像の表示
	cvShowImage ("src", src);
	cvShowImage ("dst", dst);

	//キー入力待ち
	cvWaitKey (0);

	//全てのウィンドウの削除
	cvDestroyAllWindows();

	//画像データの解放
	cvReleaseImage(&src);
	cvReleaseImage(&dst);

	return 0;
}

サンプルプログラムのダウンロードはこちらより。

OpenCV-SobelFilter.zip

(OpenCV2.2対応。Visual Studio 2010 C++ Expressにより作成)

 

実行例

 

今回の例ではソーベルフィルタの結果の絶対値を取りましたが、必ずしもソーベルフィルタは絶対値を取る! とは思わない方が良いかと思います。

 

上図のような処理例を見ても分かるように、ソーベルの絶対値を取ると、狭い間隔の輪郭は白く潰れてしまいます。

 

しかし、絶対値を取らないと、狭い間隔のエッジでも、正の値と負の値とで区別ができるので、エッジ間隔を取得し易くなります。

 

その点、cvSobel関数では、処理結果をあえて符号付きの16ビットで出力するようにしたOpenCVには共感が持てる。

 

OpenCVへ戻る

 

【C++/CLI】配列の配列(ジャグ配列)

配列の配列をジャグ配列と言います。

サンプルコードは以下の通り。

//////////////////////////////////////////////
// 配列の配列(ジャグ配列) その1
//////////////////////////////////////////////
array<array^>^ jag1 = gcnew array<array^>(2);

int index1 = 0;

for (int j = 0; j < jag1->Length; j++)
{
	jag1[j] = gcnew array(3);
	for (int i = 0; i < jag1[j]->Length; i++)
	{
		jag1[j][i] = index1++;
	}
}
// jag1[0][0] = 0  jag1[0][1] = 1  jag1[0][2] = 2
// jag1[1][0] = 3  jag1[1][1] = 4  jag1[1][2] = 5

//////////////////////////////////////////////
// 配列の配列(ジャグ配列) その2
//////////////////////////////////////////////
array<array^>^ jag2 = gcnew array<array^>(3);

int index2 = 0;

for (int j = 0; j < jag2->Length; j++)
{
	jag2[j] = gcnew array(j + 1);
	for (int i = 0; i < jag2[j]->Length; i++)
	{
		jag2[j][i] = index2++;
	}
}

// jag2[0][0] = 0
// jag2[1][0] = 1  jag2[1][1] = 2
// jag2[2][0] = 3  jag2[2][1] = 4  jag2[2][2] = 5

上記の例は、配列の配列までの例ですが、配列の配列の配列も可能です。

array<array<array<int>^>^>^ jag;

 

このジャグ配列は、個人的にはあまり用いていませんが、上記の ジャグ配列 その2 の例でも示しているように、必ずしも1行あたりの配列の要素数は同じである必要は無いので、例えば、CSVファイルなどのように、1行あたりの要素数が不定になる可能性のある配列などに用いると便利です。

 

【OpenCV】ガンマ補正

OpenCVにはcvGammaのようなガンマ補正の関数は無いのですが、ルックアップテーブルを使った輝度値変換の関数(cvLUT)はあるので、これを使ってガンマ補正を行いたいと思います。

 

以下、ガンマ補正のサンプルプログラムです。OpenCV2.2を用いて、作成しています。

#include "stdafx.h"

//プロジェクトのプロパティ⇒C/C++⇒全般 の追加のインクルードディレクトリに
// 『C:\OpenCV2.2\include』を追加のこと
#include "opencv2\\opencv.hpp"
 
#ifdef _DEBUG
    //Debugモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")
#else
    //Releaseモードの場合
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")
    #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")
#endif
//---------------------------------------------------------------  
//【関数名 】:cv_Gamma  
//【処理概要】:ガンマ補正  
//【引数  】:src        = 入力画像  
//      :dst        = 出力画像  
//      :gamma   = ガンマ補正値  
//【戻り値 】:なし  
//【備考  】:モノクロ/カラー対応可  
//      :カラーの場合はRGB全て同じガンマ補正値  
//---------------------------------------------------------------   
void cv_Gamma(IplImage* src, IplImage* dst, double gamma){  

	int i;  

	uchar LUT[256];  
              
	//ガンマ補正テーブルの作成  
	for (i = 0; i < 256; i++)
	{  
		LUT[i] = (int)(pow((double)i / 255.0, 1.0 / gamma) * 255.0);  
	}  

	//CvMatへ変換  
	CvMat lut_mat = cvMat(1, 256, CV_8UC1, LUT);  

	//ルックアップテーブル変換  
	cvLUT(src, dst, &lut_mat);  
}

int _tmain(int argc, _TCHAR* argv[])
{
	//画像データの読込
	IplImage* src = cvLoadImage("Girl.bmp", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
	if (src == NULL){
		return 0;
	}
 
	//表示ウィンドウの作成
	cvNamedWindow("src");
	cvNamedWindow("dst");
 
	//処理後画像データの確保
	IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, src->nChannels);
 
	//ガンマ補正
	cv_Gamma(src, dst, 2.0);
 
	//画像の表示
	cvShowImage ("src", src);
	cvShowImage ("dst", dst);
 
	//キー入力待ち
	cvWaitKey (0);
 
	//全てのウィンドウの削除
	cvDestroyAllWindows();
 
	//画像データの解放
	cvReleaseImage(&src);
	cvReleaseImage(&dst);

	return 0;
}

サンプルプログラムのダウンロードはこちらより。

gamma-correction-opencv-sample.zip

(OpenCV2.2対応。Visual Studio 2010 C++ Expressにより作成)

 

実行例

 

OpenCV2.2の使用方法および、ガンマ補正については下記のページを参照下さい。

 

OpenCVへ戻る

 

【C++/CLI】配列の確保(Arrayクラス)

配列の確保は

 

array<型>^変数名 = gcnew array<型>(要素数);

 

のように行います。

 

多次元の場合は

 

array<型, 次数>^変数名 = gcnew array<型, 次数>(0次の要素数, 1次の要素数, 2次の要素数・・・);

 

コード例は以下の通り

 

///////////////////////////////////////////////
// 配列の初期化
///////////////////////////////////////////////
array^a = gcnew array {0, 1, 2, 3, 4};
// a[0] = 0
// a[1] = 1
// a[2] = 2
// a[3] = 3
// a[4] = 4

///////////////////////////////////////////////
// 配列の確保
///////////////////////////////////////////////
array^b = gcnew array(6);
int Len = b->Length;	// 配列の全個数

for (int i = 0; i < Len; i++)
{
	b[i] = i * 2;
}
// b[0] = 0
// b[1] = 2
// b[2] = 4
// b[3] = 6
// b[4] = 8
// b[5] = 10

///////////////////////////////////////////////
// 二次元配列の確保
///////////////////////////////////////////////
array<int, 2>^c = gcnew array<int, 2>(2, 3);

int iLen = c->GetLength(1);	// 1次の配列の個数  (= 3)
int jLen = c->GetLength(0);	// 0次の配列の個数 (= 2)

for (int j = 0; j < jLen; j++)
{
	for (int i = 0; i < iLen; i++)
	{
		c[j, i] = j * 10 + i;
	}
}
//c[0, 0] = 0	c[0, 1] = 1		c[0, 2] = 2
//c[1, 0] = 10	c[1, 1] = 11	c[1, 2] = 12

///////////////////////////////////////////////
// ポインタで二次元配列を参照し一次元配列へ代入
///////////////////////////////////////////////
pin_ptrp = &c[0, 0];
array^d = gcnew array(6);

for (int i = 0; i < iLen * jLen; i++){
	d[i] = p[i];
}
// d[0] = 0
// d[1] = 1
// d[2] = 2
// d[3] = 10
// d[4] = 11
// d[5] = 12

ここで、多次元配列の場合、メモリに格納されている順番は右側の添え字から順番に格納されているので、ご注意下さい。
ポインタで参照している部分を参考にして下さい。

 

良く使うメソッド、プロパティは以下の通り

メソッド

  • GetLength(dimension)
    指定した次元(dimension)の要素数を取得します。
  • Resize(array, newSize)
    配列(array)のサイズをnewSizeに変更し、各要素は新しい配列へコピーされます。

 

プロパティ

  • Length
    全ての次元内の要素数を取得します。

 

【C++/CLI】文字列の右寄せ、左寄せ、中央寄せ描画

文字列を描画する時にはSystem::Drawing::Graphics::DrawStringメソッドを用いますが、文字列を描画するのに、右寄せ、左寄せ、中央寄せを指定するには、6つあるオーバーロードのうち、System::Drawing::StringFormatのあるメソッドを用います。

 

具体的には、

  • Graphics.DrawString (String, Font, Brush, PointF, StringFormat)
  • Graphics.DrawString (String, Font, Brush, Single, Single, StringFormat)
  • Graphics.DrawString (String, Font, Brush, RectangleF, StringFormat)

のいづれかを用います。

 

この3つのメソッドのうち、上の2つが点を基準として文字列を描画し、最後の1つは領域を基準として文字列を描画します。

 

また、基準位置に対して、StringFormatクラスを用いて上下方向左右方向に位置を調整します。

 

StringFormat.Alignmentプロパティ水平方向を調整し、
StringFormat.LineAlignmentプロパティ垂直方向を調整します。

 

何はともあれ、サンプルコードと実行結果を見て頂くと分かり易いと思います。

 

【実行結果】

 

【サンプルコード】

private: System::Void pictureBox1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {

	Graphics^ g = e->Graphics;

	System::Drawing::Font^ drawFont = gcnew System::Drawing::Font( "Arial",10 );

	// テキスト レイアウト情報
	StringFormat^ drawFormat = gcnew StringFormat;

	//////////////////////////////////////////
	//
	// 点基準
	//
	//////////////////////////////////////////

	// 基準点の描画 (350, 60)
	g->FillEllipse(Brushes::Red, Rectangle(345, 55, 10, 10));
	// 右左寄せ
	drawFormat->Alignment = StringAlignment::Far;
	g->DrawString("右寄せ(StringAlignment::Far)", drawFont, Brushes::Black, 350, 60, drawFormat);

	// 基準点の描画 (50, 100)
	g->FillEllipse(Brushes::Red, Rectangle(45, 95, 10, 10));
	// 左寄せ
	drawFormat->Alignment = StringAlignment::Near;
	g->DrawString("左寄せ(StringAlignment::Near)", drawFont, Brushes::Black, 50, 100, drawFormat);

	// 基準点の描画 (200, 140)
	g->FillEllipse(Brushes::Red, Rectangle(195, 135, 10, 10));
	// 中央寄せ
	drawFormat->Alignment = StringAlignment::Center;
	g->DrawString("中央寄せ(StringAlignment::Center)", drawFont, Brushes::Black, 200, 140, drawFormat);

	//////////////////////////////////////////
	//
	// 領域基準
	//
	//////////////////////////////////////////

	// 基準領域の描画 (50, 200)-(350, 300)
	Rectangle rect = Rectangle(50, 200, 300, 100);
	g->DrawRectangle(Pens::Red, rect);
	g->DrawLine(Pens::Cyan, 50, 250, 350, 250);
	g->DrawLine(Pens::Cyan, 200, 200, 200, 300);

	// 左上寄せ
	drawFormat->Alignment = StringAlignment::Near;
	drawFormat->LineAlignment = StringAlignment::Near;
	g->DrawString("(Near, Near)", drawFont, Brushes::Black, rect, drawFormat);
	// 中央上寄せ
	drawFormat->Alignment = StringAlignment::Center;
	drawFormat->LineAlignment = StringAlignment::Near;
	g->DrawString("(Center, Near)", drawFont, Brushes::Black, rect, drawFormat);
	// 右上寄せ
	drawFormat->Alignment = StringAlignment::Far;
	drawFormat->LineAlignment = StringAlignment::Near;
	g->DrawString("(Far, Near)", drawFont, Brushes::Black, rect, drawFormat);

	// 左中央寄せ
	drawFormat->Alignment = StringAlignment::Near;
	drawFormat->LineAlignment = StringAlignment::Center;
	g->DrawString("(Near, Center)", drawFont, Brushes::Black, rect, drawFormat);
	// 中央中央寄せ
	drawFormat->Alignment = StringAlignment::Center;
	drawFormat->LineAlignment = StringAlignment::Center;
	g->DrawString("(Center, Center)", drawFont, Brushes::Black, rect, drawFormat);
	// 右中央寄せ
	drawFormat->Alignment = StringAlignment::Far;
	drawFormat->LineAlignment = StringAlignment::Center;
	g->DrawString("(Far, Center)", drawFont, Brushes::Black, rect, drawFormat);

	// 左下寄せ
	drawFormat->Alignment = StringAlignment::Near;
	drawFormat->LineAlignment = StringAlignment::Far;
	g->DrawString("(Near, Far)", drawFont, Brushes::Black, rect, drawFormat);
	// 中央下寄せ
	drawFormat->Alignment = StringAlignment::Center;
	drawFormat->LineAlignment = StringAlignment::Far;
	g->DrawString("(Center, Far)", drawFont, Brushes::Black, rect, drawFormat);
	// 右下寄せ
	drawFormat->Alignment = StringAlignment::Far;
	drawFormat->LineAlignment = StringAlignment::Far;
	g->DrawString("(Far, Far)", drawFont, Brushes::Black, rect, drawFormat);

}

【Visual Studio】作成中のユーザーコントロールをツールボックスに表示する方法

作成中のユーザーコントロールのアイコンがツールボックスに表示されない場合あります。(Visual Studioのバージョンにより異なる?)

 

ツールボックスへ表示させるには、Visual Studioのメニューからツールオプションを選択し、ウィンドウを表示します。

 

 

次にオプション設定ウィンドウのWindowsフォームデザイナ全般を選択し、ツールボックスAutoToolboxPopulateFlaseからTrueへ変更します。

 

これでツールボックスに作成中のユーザーコントロールが表示されます。

 

Visual Studioへ戻る

 

【Visual Studio】Express Editionでユーザーコントロールを作成する方法

Visual StudioのExpress Editionでは、プロジェクトの作成でユーザーコントロールの項目が表示されないので、ユーザーコントロールは作成できないのか?と思いがちですが、VB.NETとC#ではExpress Editionでもユーザーコントロールを作成する事が可能です。ただし、C++/CLIは非対応。

 

作成方法は、まず、新しいプロジェクトクラスライブラリを選択し、プロジェクトを作成します。

 

 

次にソリューションエクスプローラプロジェクト名右クリックし、追加ユーザーコントロールユーザーコントロールを選択します。

 

 

新しい項目の追加でユーザーコントロールを選択し、ユーザーコントロールを追加します。

 

 

これで、最初に作成したクラスライブラリのプロジェクトにユーザーコントロールが追加されます。

また、最初に作成されたクラスライブラリのファイル(下図の例ではClass1.cs)は削除して構いません。

 

 

Visual Studioへ戻る

 

【Visual Studio】既存ユーザーコントロールの使用方法

既存のユーザーコントロールを使用する方法は、Visual Studioのツールボックス上でマウスの右ボタンをクリックし、表示されたメニューのアイテムの選択をクリックします。
(クリック後、時間がかかる場合があります。)

 

表示されたメニューの.NET Frameworkコンポーネントに使用したいコントロールが表示されたいたら、そのコントロールにチェックを入れます。

 

使用したいコントロールが表示されていなかったら、右下の参照ボタンをクリックし、使用するユーザーコントロールのファイル(*.dll)を選択します。

 

 

すると、ツールボックス上に追加したユーザーコントロールが表示され、使用する事が可能となります。

 

 

Visual Studioへ戻る

 

【.NET】GraphicsBoxコントロール

.NET(VB.NET、C#)に対応した画像や線などの描画向けにPictureBoxコントロールを継承した
GraphicsBoxなるコントロールを作成しました。

 

このGraphicsBoxはPictureBoxを使っていて足りないな~と思う、

 

  • 再描画してくれるGraphicsオブジェクト
  • MouseWheelイベント
  • PensやColorsのようなFontのクラス

 

をそれぞれ、

 

  • Graphicsプロパティ
    コントロールがリサイズされるたびに、Graphicオブジェクトを再確保し、プロパティで取得できます。
  • MouseWheeledイベント
    GraphicsBoxにフォーカスがある時(Focus()をした後)、マウスホイールを上下に回転させたときにMouseWheeledイベントが発生します。
  • Fontsプロパティ
    フォームのデザイナ上のプロパティのFontsでGraphicsBoxへFontを指定します。
    このFontを使用する場合は、 graphicsbox1.Fonts などとすると、Fontsプロパティで取得できます。

 

としてPictureBoxに追加しました。

 

対象のフレームワークを.NET Framework2.0としました。

 

で、このGraphicsBoxコントロールは

 

GraphicsBoxSample_Ver111.zip

 

 

よりダウンロードできます。

 

このコントロールの実態は上記ファイルを解凍したフォルダ内の

 

ImagingSolution.Control.GraphicsBox.dll

 

となります。

 

使い方はVisual Studioを起動し、ツールボックス上で右ボタンアイテムの選択参照ボタンをクリック→ImagingSolution.Control.GraphicsBox.dllを選択

 

すると、ツールボックスにGraphicsBoxのアイコンが表示されるので、あとはPictureBoxと同様に使用できます。

 

GraphicsBoxSample_Ver111.zipをダウンロードするとVisual Studio 2010 Express EditionのVB.NET、C#のサンプルもつけているので、お試し下さい。

 

サンプルプログラムを実行し、ウィンドウ内をクリックした後、マウスホイールを上下させると下図のように線と文字がホイールの回転に合わせて上下しながら表示されます。

 

 

バージョン 更新日 ファイル 更新内容
Ver.1.1.1 2017.12.19 GraphicsBoxSample_Ver111.zip SplitContainerへDockしたときに画像がちらつくのを修正
Ver.1.1.0 2013.9.16 GraphicsBoxSample_Ver110.zip ●名前空間をImagingSolution.GraphicsBoxからImagingSolution.Control.GraphicsBoxへ変更

●GraphicsオブジェクトをBufferedGraphicsへ変更し描画の高速化

Ver.1.0.2 2011.5.7 GraphicsBoxSample_Ver102.zip MouseWheeledイベントを実装していないときのエラーを修正
Ver.1.0.1 2011.4.20 GraphicsBoxSample_Ver101.zip Focus()をコントロール内のイベントへ移動
名前空間名の修正
ユーザーコントロールのアイコンを指定
Ver.1.0.0 2011.4.20 GraphicsBoxSample.zip 初版

【VisualStudio】ウォッチでポインタの中身(値)を参照する

デバッグ実行時に下図のようなプログラムの場合、

 

 

ポインタの変数(pBuf)をウォッチウィンドウで見てみると、ポインタのアドレスと先頭の
値しか見る事ができません。↓

 

 

そこで、ウォッチの名前の部分に、ポインタ変数名に ,(カンマ)と表示する個数を追加します。
上記の例では

 

pBuf, 256

 

とします。
すると、ポインタの中身が配列と同じように参照する事が可能となります。↓

 

 

Visual Studioへ戻る

 

【VisualStudio】重なったコントロールの選択

分かりづらいかもしれませんが、下図のように、フォームにsplitContainerを配置し、親(フォーム)にドッキングした後、PanelPictureBoxをドッキングした場合など、マウス操作でsplitContainerを選択するのは意外と難しかったりもします。

 

 

そんな時には、選択したいコントロール上で、マウスの右ボタンをクリックします。

 

 

すると、右クリックしたポインタの位置に重なっているコントロールの一覧がメニューで表示されるので、メニューからコントロールを選択すると、簡単に重なったコントロールを選択する事が出来ます。

 

Visual Studioへ戻る