About BBS

フィルタと色変換

作成者: 怡土順一, 最終変更者: 怡土順一, 最終変更リビジョン: 342, 最終変更日時: 2007-10-14 23:39:34 +0900 (日, 14 10月 2007)
This is a local copy taken at 2010/09/20. CLICK HERE to the original page at opencv.jp.

■ フィルタ

フィルタという単語は,非常に幅広い意味を持つ. OpenCVのリファレンス マニュアルでは, 主に二次元マトリックス(フィルタ,カーネルなどと呼ばれる)と, 画像の処理対象領域の画素値との畳み込み演算を行うことによって実現される, いわゆる空間フィルタ処理についての関数をフィルタ処理としている. 画像勾配を求める処理(微分フィルタ)もフィルタ処理の一部ではあるが,そ れは別途解説されているので,そちらを参照すること.

サンプル


平滑化 cvSmooth

ブラー,ガウシアン,メディアン,バイラテラル,の各フィルタによる平滑化

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i; IplImage *src_img = 0, *dst_img[4]; // (1)画像を読み込む if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); for (i = 0; i < 4; i++) dst_img[i] = cvCloneImage (src_img); // (2)手法を指定して画像を平滑化 cvSmooth (src_img, dst_img[0], CV_BLUR, 5, 0, 0, 0); cvSmooth (src_img, dst_img[1], CV_GAUSSIAN, 11, 0, 0, 0); cvSmooth (src_img, dst_img[2], CV_MEDIAN, 5, 0, 0, 0); cvSmooth (src_img, dst_img[3], CV_BILATERAL, 80, 80, 0, 0); // (3)処理された画像を実際に表示 cvNamedWindow ("Blur", CV_WINDOW_AUTOSIZE); cvShowImage ("Blur", dst_img[0]); cvNamedWindow ("Gaussian", CV_WINDOW_AUTOSIZE); cvShowImage ("Gaussian", dst_img[1]); cvNamedWindow ("Median", CV_WINDOW_AUTOSIZE); cvShowImage ("Median", dst_img[2]); cvNamedWindow ("Bilateral", CV_WINDOW_AUTOSIZE); cvShowImage ("Bilateral", dst_img[3]); cvWaitKey (0); cvDestroyWindow ("Blur"); cvDestroyWindow ("Gaussian"); cvDestroyWindow ("Median"); cvDestroyWindow ("Bilateral"); cvReleaseImage (&src_img); for (i = 0; i < 4; i++) { cvReleaseImage (&dst_img[i]); } return 0; }

// (1)画像を読み込む
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.2番目の引数にCV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLORを指定することで,元画像のデプス,チャンネルを変 更せずに読み込む.

// (2)手法を指定して画像を平滑化
関数cvSmooth()の3番目の引数で, CV_BLUR(ブラーフィルタ),CV_GAUSSIAN(ガウシアンフィルタ),CV_MEDIAN(メディアンフィルタ),CV_BILATERAL(バイラテラルフィルタ) の各手法を指定して平滑化を行う. 4番目以降の引数の意味は,手法毎に異なる.詳しくは,リファレンス マニュアルを参照すること.

// (3)処理された画像を実際に表示
各手法で平滑化された画像を実際に表示し,何かキーが押されるまで待つ.

実行結果例

下段の画像は,処理画像の一部を拡大したものである. ただし,手法毎にパラメータを変えてあるので,単純に比較できる画像ではない.

入力画像 Blur Gaussian Median Bilateral


ユーザ定義フィルタ cvFilter2D

ユーザが定義したカーネルによるフィルタリング

サンプルコード

#include <cv.h> #include <highgui.h> #include <stdio.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img; float data[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; CvMat kernel = cvMat (1, 21, CV_32F, data); // (1)画像の読み込み if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); dst_img = cvCreateImage (cvGetSize (src_img), src_img->depth, src_img->nChannels); // (2)カーネルの正規化と,フィルタ処理 cvNormalize (&kernel, &kernel, 1.0, 0, CV_L1); cvFilter2D (src_img, dst_img, &kernel, cvPoint (0, 0)); // (3)処理画像の表示 cvNamedWindow ("Filter2D", CV_WINDOW_AUTOSIZE); cvShowImage ("Filter2D", dst_img); cvWaitKey (0); cvDestroyWindow ("Filter2D"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); return 0; }

// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.2番目の引数にCV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLORを指定することで,元画像のデプス,チャンネルを変 更せずに読み込む.

// (2)カーネルの正規化と,フィルタ処理
浮動小数点型配列dataの値を各要素とする,1×21;の行列をカーネルとするフィルタ処理を行う. 関数cvNormalize()によって,カーネルを正規化する. つまり,ここでは,絶対値のノルムが"1.0"になるように,配列の値を正規化している. そして,そのカーネルを利用(関数cvFilter2D()の3番目の引数に指定)して,フィルタ処理を行う. 関数cvFilter2D()の4番目の引数は,フィルタ対象ピクセルのカーネル内での相対位置を表しており, デフォルトはcvPoint(-1,-1)でありカーネル中心を指すが, 今回は,cvPoint(0,0)を指定することで,カーネルの左端を指している.

// (3)処理画像の表示
処理された画像を実際に表示し,何かキーが押されるまで待つ.

実行結果例


■ 境界線

画像の境界をどのように扱うかは,多くの画像処理においてしばしば問題になる. 例えば,フィルタ処理において,フィルタが部分的に画像の外にはみだしてし まった場合,どのような画素値を仮定して利用するかによって,処理結果が異なる. OpenCVでは,画像をコピーし、その周りに境界線をつける処理を行う関数が用 意されており,IPL_BORDER_CONSTANT,およびIPL_BORDER_REPLICATをサポートしている. OpenCVで利用される関数は,IPL_BORDER_REPLICAT,つまり複製境界モードを 利用することが多々あるが,ユーザがそのような処理を望んでいない場合には, あらかじめ境界を定数値で埋めてから処理を行い,処理後の画像をクリッピングする事で 境界の扱いをコントロールできる.

サンプル


境界線の作成 cvCopyMakeBorder

画像のコピーと境界の作成

サンプルコード

#include <cxcore.h> #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, offset = 30; IplImage *src_img = 0, *dst_img[2]; // (1)画像の読み込み if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); for (i = 0; i < 2; i++) dst_img[i] = cvCreateImage (cvSize (src_img->width + offset * 2, src_img->height + offset * 2), src_img->depth, src_img->nChannels); // (2)境界線の作成 cvCopyMakeBorder (src_img, dst_img[0], cvPoint (offset, offset), IPL_BORDER_REPLICATE); cvCopyMakeBorder (src_img, dst_img[1], cvPoint (offset, offset), IPL_BORDER_CONSTANT, CV_RGB (255, 0, 0)); // (3)画像の表示 cvNamedWindow ("Border_replicate", CV_WINDOW_AUTOSIZE); cvShowImage ("Border_replicate", dst_img[0]); cvNamedWindow ("Border_constant", CV_WINDOW_AUTOSIZE); cvShowImage ("Border_constant", dst_img[1]); cvWaitKey (0); cvDestroyWindow ("Border_replicate"); cvDestroyWindow ("Border_constant"); cvReleaseImage (&src_img); for (i = 0; i < 2; i++) { cvReleaseImage (&dst_img[i]); } return 0; }

// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.2番目の引数にCV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLORを指定することで,元画像のデプス,チャンネルを変 更せずに読み込む. また,境界を作成するために,入力画像よりも大きな出力領域を確保する.

// (2)境界線の作成
関数cvCopyMakeBorder()によって,入力画像を出力画像にコピーし,さらに境界を作成する. 3番目の引数は,出力画像上にコピーされる入力画像の左上座標を表しており, 画像がコピーされない部分(今回は,上下左右の端から offsetピクセル分)が境界領域となる. 4番目の引数は,境界の種類を表しており,IPL_BORDER_REPLICATEが指定されると, 画像の上/下の端と左/右の端(画像領域の一番外側の値)を用いて境界線を生成する. また,4番目の引数に,IPL_BORDER_CONSTANT が指定されると, 境界はこの関数の最後(5番目)のパラメータとして渡された定数(今回は赤色)で埋められる.

// (3)画像の表示
処理された画像を実際に表示し,何かキーが押されるまで待つ.

実行結果例


■ 閾値処理

画像にある閾値を設けて,画素値がその値よりも大きいか小さいかによって処理を変えたいという事はよくある. 例えば,単純な背景差分では,現画像と背景画像との画素値の差の絶対値がある閾値以下になるか否かで,背景か前景かを判断する. OpenCVでは,このような閾値処理を行うための関数,cvThreshold,および cvAdaptiveThreshold を用意してある.

サンプル


画像の二値化 cvThreshold, cvAdaptiveThreshold

cvThreshold, cvAdaptiveThresholdを利用して,画像の二値化を行う

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img; IplImage *src_img_gray = 0; IplImage *tmp_img1, *tmp_img2, *tmp_img3; // (1)画像を読み込む if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR); if (src_img == 0) return -1; tmp_img1 = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); tmp_img2 = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); tmp_img3 = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); src_img_gray = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); cvCvtColor (src_img, src_img_gray, CV_BGR2GRAY); dst_img = cvCloneImage (src_img); // (2)ガウシアンフィルタで平滑化を行う cvSmooth (src_img_gray, src_img_gray, CV_GAUSSIAN, 5); // (3)二値化:cvThreshold cvThreshold (src_img_gray, tmp_img1, 90, 255, CV_THRESH_BINARY); // (4)二値化:cvAdaptiveThreshold cvAdaptiveThreshold (src_img_gray, tmp_img2, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 11, 10); // (5)二つの二値化画像の論理積 cvAnd (tmp_img1, tmp_img2, tmp_img3); cvCvtColor (tmp_img3, dst_img, CV_GRAY2BGR); // (6)元画像と二値画像の論理積 cvSmooth (src_img, src_img, CV_GAUSSIAN, 11); cvAnd (dst_img, src_img, dst_img); // (7)画像を表示する cvNamedWindow ("Threshold", CV_WINDOW_AUTOSIZE); cvShowImage ("Threshold", tmp_img1); cvNamedWindow ("AdaptiveThreshold", CV_WINDOW_AUTOSIZE); cvShowImage ("AdaptiveThreshold", tmp_img2); cvNamedWindow ("Image", CV_WINDOW_AUTOSIZE); cvShowImage ("Image", dst_img); cvWaitKey (0); cvDestroyWindow ("Threshold"); cvDestroyWindow ("AdaptiveThreshold"); cvDestroyWindow ("Image"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseImage (&src_img_gray); cvReleaseImage (&tmp_img1); cvReleaseImage (&tmp_img2); cvReleaseImage (&tmp_img3); return 0; }

// (1)画像を読み込む
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数cvLoadImage()で読み込む. また,二値化を行うために入力画像をグレースケールに変換する.

// (2)ガウシアンフィルタで平滑化を行う
二値化を行う前に,ガウシアンフィルタで平滑化を行う.平滑化を行うことによって, 画像のノイズを減少させ,安定した(ノイズの少ない)二値化画像を得ることができる.

// (3)二値化:cvThreshold
関数cvThreshold()によって,画像の二値化処理を行う. この関数の最後の引数が,閾値処理の種類を表している. CV_THREASH_BINARYが指定されている場合は,

src(x,y)>thresholdの場合は、dst(x,y) = max_value
                それ以外は、           0
となるように処理を行う.ここで,threshold(閾値)は3番目の引数で, max_value(最大値)は4番目の引数で,それぞれ指定されている. その他の手法を指定した場合の処理については,リファレンス マニュアルを参照のこと.

// (4)二値化:cvAdaptiveThreshold
関数cvAdaptiveThreshold()によって,画像の二値化処理を行う. 関数cvThreshold()では,すべてのピクセルにたいして同じ閾値をもって処理を行ったが, 関数cvAdaptiveThreshold()の場合は,ピクセル毎に使用する閾値が異なる. この関数の5番目の引数が,関数cvThresholdと同様に閾値処理の種類を表している. CV_THRESH_BINARYが指定されている場合は,

src(x,y)>T(x,y)の場合は、dst(x,y) = max_value
             それ以外は、           0
となるように処理を行う.ここで,max_value(最大値)は3番目の引数で指定されている. また,T(x,y)は,各ピクセル毎に計算された閾値であり,4番目の引数で,そ の閾値の算出方法が指定される. CV_ADAPTIVE_THRESH_MEAN_C が指定された場合は,注目ピクセルの block_size×block_size 隣接領域の平均から,param1 を引いた値となり, block_sizeは6番目の引数で,param1は7番目の引数で,それぞれ指定される.

// (5)二つの二値化画像の論理積
二つの関数によって二値化された画像の論理積を計算する. つまり,二種類の二値化画像を合成し,3チャンネル画像へと変換する.

// (6)元画像と二値画像の論理積
(平滑化した)入力画像と二値化画像の要素毎の論理積を計算する. これにより,入力画像上に二値化画像が合成された画像を得る.

// (7)画像を表示する
それぞれの二値化画像,および入力画像と重ね合わせた画像を実際に表示し, 何かキーが押されるまで待つ.

実行結果例

入力画像 Threshold AdaptiveThreshold 合成画像


画像の二値化(大津の手法) cvThreshold

大津の手法を利用して閾値を決定し,画像の二値化を行う

サンプルコード

#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img; if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); if (src_img == 0) return -1; dst_img = cvCreateImage (cvGetSize (src_img), IPL_DEPTH_8U, 1); cvSmooth (src_img, src_img, CV_GAUSSIAN, 5); // (1)二値化(大津の手法を利用) cvThreshold (src_img, dst_img, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); cvNamedWindow ("Threshold", CV_WINDOW_AUTOSIZE); cvShowImage ("Threshold", dst_img); cvWaitKey (0); cvDestroyWindow ("Threshold"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); return 0; }

// (1)二値化(大津の手法を利用)
前述の二値化と異なるのは,最後の引数(threshold type)に,CV_THRESHOLD_OTSU が加わっている点である. OpencvCV-1.0.0時点でのリファレンス マニュアルには記述がない(CVS版にはある)が, 関数cvThreshold()は,大津の手法と呼ばれる閾値決定手法を実装している. ここで利用される大津の手法とは,ある値の集合を二つクラスに分類する場合の,適切な閾値を決定する手法である. 二つのクラス内の分散とクラス間の分散を考え,これらの比が最小に(つまり,クラス内分散はできるだけ小さく,クラス間分散はできるだけ大きく) なるような閾値を求める. よって,3番目の引数(threshold)に与える値は利用されないので,適当な値を指定して良い.
詳しくは,以下の文献を参考にすること.

実行結果例

  

OpenCV-1.0 リファレンス マニュアル
OpenCV-1.1pre リファレンス マニュアル
OpenCVサンプルコード


画素値の直接操作
部分画像のシャッフル
画像の連結
画像のコピー
画像形状の変形
タイリング
画像の反転
逆行列(擬似逆行列)の計算
色空間の写像
離散フーリエ変換
階層構造を持つ輪郭の座標取得
図形の描画
ポリゴンの描画
凸ポリゴンの描画
テキストの描画
IplImage構造体情報の保存
マップのシーケンスを保存
IplImage構造体情報の読み込み
マップのシーケンスを読み込む
K-means法によるクラスタリング
クラスタリングによる減色処理
エッジの検出
コーナーの検出
並進移動のためのピクセルサンプリング
回転移動のためのピクセルサンプリング
画像のサイズ変更
画像のアフィン変換(1)
画像のアフィン変換(2)
画像の透視投影変換
全方位画像の透視投影変換
モルフォロジー変換
平滑化
ユーザ定義フィルタ
境界線の作成
画像の二値化
画像の二値化(大津の手法)
画像ピラミッドの作成
画像ピラミッドを用いた画像の領域分割
平均値シフト法による画像のセグメント化
Watershedアルゴリズムによる画像の領域分割
輪郭の検出と描画
画像のモーメントを計算
ハフ変換による直線検出
ハフ変換による円検出
距離変換とその可視化
不要オブジェクトの除去
ヒストグラムの描画
ヒストグラム間の距離
二次元のヒストグラム
バックプロジェクションパッチ
ヒストグラムの均一化
テンプレートマッチング
形状のマッチング
点列を包含する矩形
輪郭領域の面積と輪郭の長さ
二つの矩形を包含する矩形
楕円のフィッティング
点列を包含する図形
動的背景更新による物体検出
snakeによる輪郭追跡(静止画)
オプティカルフロー1
オプティカルフロー2
オプティカルフロー3
Condensation
顔の検出
カメラキャリブレーション
歪み補正
マップを利用した歪み補正
サポートベクターマシン
画像の各ピクセル値を特徴ベクトルとしたSVMの学習
画像の各ピクセル値を特徴ベクトルとしたSVMによる物体検出
マウスイベントの取得
トラックバーの利用
カメラからの画像キャプチャ
動画としてファイルへ書き出す
ラベリング