フィルタと色変換
■ フィルタ
フィルタという単語は,非常に幅広い意味を持つ. 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)に与える値は利用されないので,適当な値を指定して良い.
詳しくは,以下の文献を参考にすること.
- 大津, "判別および最小2乗基準に基づく自動しきい値選定法", 電子通信学会論文誌, Vol.J63-D, No.4, pp.349-356, 1980.
- N. Otsu, "A threshold selection method from gray level histograms",IEEE Trans. Systems, Man and Cybernetics, 1979, Vol.9, pp.62-66