特殊な画像変換
■ ハフ変換
OpenCVでは,標準的ハフ変換,確率的ハフ変換,マルチスケール型の古典的ハフ変換の3種類の手法を実装している.標準的ハフ変換と古典的ハフ変換では,検出された線は点(0, 0)から線までの距離ρと,線の法線がx軸と成す角θの2つの値で表される.一方確率的ハフ変換では,端点をもつ線分として線を検出する.そのため,検出された線分は,始点と終点で表される.また,円は中心と半径を表す3つのパラメータで表現できるため,直線検出で投票に用いるハフ空間を,三次元に拡張することで円を検出する事が可能になる.
サンプル
ハフ変換による直線検出 cvHoughLines2
標準的ハフ変換と確率的ハフ変換を指定して線(線分)の検出を行なう.サンプルコード内の各パラメータ値は処理例の画像に対してチューニングされている.
サンプルコード
#include <cv.h> #include <highgui.h> #include <math.h> int main (int argc, char **argv) { int i; float *line, rho, theta; double a, b, x0, y0; IplImage *src_img_std = 0, *src_img_prob = 0, *src_img_gray = 0; CvMemStorage *storage; CvSeq *lines = 0; CvPoint *point, pt1, pt2; // (1)画像の読み込み if (argc >= 2) src_img_gray = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); if (src_img_gray == 0) return -1; src_img_std = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR); src_img_prob = cvCloneImage (src_img_std); // (2)ハフ変換のための前処理 cvCanny (src_img_gray, src_img_gray, 50, 200, 3); storage = cvCreateMemStorage (0); // (3)標準的ハフ変換による線の検出と検出した線の描画 lines = cvHoughLines2 (src_img_gray, storage, CV_HOUGH_STANDARD, 1, CV_PI / 180, 50, 0, 0); for (i = 0; i < MIN (lines->total, 100); i++) { line = (float *) cvGetSeqElem (lines, i); rho = line[0]; theta = line[1]; a = cos (theta); b = sin (theta); x0 = a * rho; y0 = b * rho; pt1.x = cvRound (x0 + 1000 * (-b)); pt1.y = cvRound (y0 + 1000 * (a)); pt2.x = cvRound (x0 - 1000 * (-b)); pt2.y = cvRound (y0 - 1000 * (a)); cvLine (src_img_std, pt1, pt2, CV_RGB (255, 0, 0), 3, 8, 0); } // (4)確率的ハフ変換による線分の検出と検出した線分の描画 lines = 0; lines = cvHoughLines2 (src_img_gray, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 50, 50, 10); for (i = 0; i < lines->total; i++) { point = (CvPoint *) cvGetSeqElem (lines, i); cvLine (src_img_prob, point[0], point[1], CV_RGB (255, 0, 0), 3, 8, 0); } // (5)検出結果表示用のウィンドウを確保し表示する cvNamedWindow ("Hough_line_standard", CV_WINDOW_AUTOSIZE); cvNamedWindow ("Hough_line_probalistic", CV_WINDOW_AUTOSIZE); cvShowImage ("Hough_line_standard", src_img_std); cvShowImage ("Hough_line_probalistic", src_img_prob); cvWaitKey (0); cvDestroyWindow ("Hough_line_standard"); cvDestroyWindow ("Hough_line_standard"); cvReleaseImage (&src_img_std); cvReleaseImage (&src_img_prob); cvReleaseImage (&src_img_gray); cvReleaseMemStorage (&storage); return 0; }
// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数
cvLoadImage()で読み込む.2番目の引数にCV_LOAD_IMAGE_GRAYSCALE
を指定することで,処理用として1チャンネル,グレースケールに変換する.
また,表示用(検出した線を赤で示す)のために,2番目の引数にCV_LOAD_IMAGE_COLOR
を指定して,カラー画像としても読み込んでおく.
// (2)ハフ変換のための前処理
cvHoughLines2()は,入力として,8ビット,シングルチャンネル,二値化画像を取る.
そのため前処理としてcvCanny()を用いて,入力処理用のグレースケール画像をエッジ画像に変換する.cvCanny()は,サンプルのようにインプレース処理が可能である.
また,cvHoughLines2()で使用する検出結果の線を保存するメモリストレージを生成しておく.
// (3)標準的ハフ変換による線の検出と検出した線の描画
標準的ハフ変換CV_HOUGH_STANDARDを指定して,エッジ画像から線を検出する.
検出結果はシーケンスlinesの先頭ポインタとして返される.
一つ一つの線は,シーケンスlinesから,cvGetSeqElem()を使用して,インデックスの順に取り出す.
シーケンスlinesの一つの要素lineは,32ビット浮動小数点型2チャンネル配列(配列要素が2個)
CV_32FC2(注1)であるため,line[0]にρが,line[1]にθが入る.
最初に,ρとθを用い線上の1点(x0, y0)を,x0=ρ*cos(θ),
y0=ρ*sin(θ)として計算する.
次に(x0, y0)を中心として,それぞれ1000pixel離れた線上の2点pt1,pt2を計算する.
ここでθが検出された線の法線方向を表しているため,検出された線とx軸が成す角は
θ+90[deg] となる.sin(θ+90) = cos(θ),cos(θ+90) = -sin(θ)
であるため,pt1.x = x0 + 1000 * (-sin(θ)), pt1.y = y0 + 1000 * cos(θ) で得られる.
線上の2点を用いて,結果表示用の画像データsrc_img_std上に赤色,線幅3で直線を表示する.
画像領域外の線はクリッピングされる.
検出直線数が多くなり結果が見にくくなるので,表示を100 lineまでに押さえている.
// (4)確率的ハフ変換による線分の検出と検出した線分の描画
確率的ハフ変換CV_HOUGH_PROBABILISTICを指定して,エッジ画像から線分を検出する.
検出結果はシーケンスlinesの先頭ポインタとして返される.
一つ一つの線分は,シーケンスlinesから,cvGetSeqElem()を使用して,インデックスの順に取り出す.
シーケンスlinesの一つの要素pointは,32ビット整数型4チャンネル配列(配列要素が4個)
CV_32SC4(注1)であるため,point[0]に始点座標が,point[1]に終点座標が入る.
始点,終点を用いて,結果表示用の画像データsrc_img_prob上に赤色,線幅3で線分を表示する.
// (5)検出結果表示用のウィンドウを確保し表示する
ウィンドウを生成し,直線検出結果を表示し,何かキーが押されるまで待つ.
(注1) CV_<ビット数(デプス)>(S|U|F)C<チャンネル数>
実行結果例
入力画像と直線検出結果
[左] 入力画像
[中] 標準的ハフ変換により,検出された直線(100[line]のみ表示)
[右] 確率的ハフ変換により,検出された線分
ハフ変換による円検出 cvHoughCircles
グレースケール画像中の円をハフ変換を用いて検出する.サンプルコード内の各パラメータ値は処理例の画像に対してチューニングされている.
サンプルコード
#include <stdio.h> #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i; float *p; IplImage *src_img = 0, *src_img_gray = 0; CvMemStorage *storage; CvSeq *circles = 0; // (1)画像の読み込み if (argc >= 2) src_img_gray = cvLoadImage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); if (src_img_gray == 0) exit (-1); src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR); // (2)ハフ変換のための前処理(画像の平滑化を行なわないと誤検出が発生しやすい) cvSmooth (src_img_gray, src_img_gray, CV_GAUSSIAN, 11, 11, 0, 0); storage = cvCreateMemStorage (0); // (3)ハフ変換による円の検出と検出した円の描画 circles = cvHoughCircles (src_img_gray, storage, CV_HOUGH_GRADIENT, 1, 100, 20, 50, 10, MAX (src_img_gray->width, src_img_gray->height)); for (i = 0; i < circles->total; i++) { p = (float *) cvGetSeqElem (circles, i); cvCircle (src_img, cvPoint (cvRound (p[0]), cvRound (p[1])), 3, CV_RGB (0, 255, 0), -1, 8, 0); cvCircle (src_img, cvPoint (cvRound (p[0]), cvRound (p[1])), cvRound (p[2]), CV_RGB (255, 0, 0), 3, 8, 0); } // (4)検出結果表示用のウィンドウを確保し表示する cvNamedWindow ("circles", 1); cvShowImage ("circles", src_img); cvWaitKey (0); cvDestroyWindow ("circles"); cvReleaseImage (&src_img); cvReleaseImage (&src_img_gray); cvReleaseMemStorage (&storage); return 0; }
// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数
cvLoadImage()でsrc_img_grayに読み込む.2番目の引数にCV_LOAD_IMAGE_GRAYSCALE
を指定することで,処理用として1チャンネル,グレースケールに変換する.
また,表示用(検出した線を赤で示す)のために,2番目の引数にCV_LOAD_IMAGE_COLOR
を指定して,カラー画像src_imgとしても読み込んでおく.
// (2)ハフ変換のための前処理
cvHoughCircles()は,入力として,8ビット,シングルチャンネル,グレースケール画像を用いる.
誤検出を少なくするために,前処理としてcvSmooth()を用いた画像の平滑化を施す.
cvSmooth()は,サンプルのようにインプレース処理が可能である.
また,cvHoughCircles()で使用する検出結果の円を保存するメモリストレージを生成しておく.
// (3)ハフ変換による円の検出と検出した円の描画
基本的な2段階ハフ変換CV_HOUGH_GRADIENTを指定して(現在はこれしか実装されていない),
グレースケール画像から円を検出する.
検出結果はシーケンスcirclesの先頭ポインタとして返される.
一つ一つの円は,シーケンスcirclesから,cvGetSeqElem()を使用して,インデックスの順に取り出す.
シーケンスcirclesの一つの要素pは,32ビット浮動小数点型3チャンネル配列(配列要素が3個)
CV_32FC3(注1)であるため,p[0]に円中心のx座標値が,p[1]に円中心のy座標が,
p[2]に円の半径がそれぞれ入る.
検出された円を結果表示用の画像データsrc_img上に赤色,線幅3で表示し,円の中心を緑で表示する,
// (4)検出結果表示用のウィンドウを確保し表示する
ウィンドウを生成し,円検出結果を表示し,何かキーが押されるまで待つ.
(注1) CV_<ビット数(デプス)>(S|U|F)C<チャンネル数>
実行結果例
入力画像と円検出結果
[左] 入力画像
[右] ハフ変換により検出された円
■ 距離変換
2値画像の各画素に対して,そこから値が0である画素への最短距離を与える変換を距離変換という.OpenCVでは,マスクを指定せずに近似なしの正確な距離計算を行なう事も,処理の高速化のために,指定したマスクのシフトを用いた近似計算を行なう事も可能である.サンプル
距離変換とその可視化 cvDistTransform
入力画像に対して距離変換を行ない,結果を0-255に正規化し可視化する
サンプルコード
#include <stdio.h> #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img, *dst_img_norm; // (1)画像を読み込み if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); if (src_img->nChannels != 1) exit (-1); if (src_img->depth != IPL_DEPTH_8U) exit (-1); // (2)処理結果の距離画像出力用の画像領域と表示ウィンドウを確保 dst_img = cvCreateImage (cvSize (src_img->width, src_img->height), IPL_DEPTH_32F, 1); dst_img_norm = cvCreateImage (cvSize (src_img->width, src_img->height), IPL_DEPTH_8U, 1); // (3)距離画像を計算し,表示用に結果を0-255に正規化する cvDistTransform (src_img, dst_img, CV_DIST_L2, 3, NULL, NULL); cvNormalize (dst_img, dst_img_norm, 0.0, 255.0, CV_MINMAX, NULL); // (4)距離画像を表示,キーが押されたときに終了 cvNamedWindow ("Source", CV_WINDOW_AUTOSIZE); cvNamedWindow ("Distance Image", CV_WINDOW_AUTOSIZE); cvShowImage ("Source", src_img); cvShowImage ("Distance Image", dst_img_norm); cvWaitKey (0); cvDestroyWindow ("Source"); cvDestroyWindow ("Distance Image"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseImage (&dst_img_norm); return 0; }
// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数
cvLoadImage()で読み込む.2番目の引数にCV_LOAD_IMAGE_ANYDEPTH |
CV_LOAD_IMAGE_ANYCOLORを指定することで,元画像のデプス,チャンネルを変
更せずに読み込む.
cvDistTransform()は,入力として8ビットシングルチャンネルの2値画像しか取れない
ので,ここで入力画像のチャンネル数とデプスのチェックを行なう.
// (2)処理結果の距離画像出力用の画像領域と表示ウィンドウを確保
cvDistTransform()の出力は,32ビット浮動小数点型シングルチャンネルであるため,
チャンネル,デプスを指定して出力用画像dst_imgを用意する.
また,結果を見やすく表示するための結果表示用の8ビット符号なしシングルチャンネルの
画像領域dst_img_normを用意し,この画像を表示するためのウィンドウを用意する.
// (3)距離画像を計算し,表示用に結果を0-255に正規化する
各ピクセルから値0までの最近距離距離を計算する.cvDistTransform()の第1,第2引数の型は
CvArr* であるが,サンプルで示すように,直接IplImage* 型の変数を指定できる.
またこのサンプルでは,高速に近似距離を求めるために CV_DIST_L2(ユークリッド距離),
3×3 マスクを指定している.
計算後の画像の画素値の範囲を,cvNormalized()を用いて0-255に正規化する
(8ビット符号なしシングルチャンネル画像にする).
正規化のタイプとして,CV_MINMAX(配列の値が指定の範囲に収まるようにスケーリングとシフトを行う)を
指定し,第3,第4引数でその範囲を指定しておくと簡単に正規化が出来る.
// (4)距離画像を表示,キーが押されたときに終了
入力画像を表示するためのウィンドウをcvNamedWindow()で確保し,cvShowImage()に入力画像のポインタを渡すことで,実際の表示を行なう.
距離画像も同様.
正規化された距離画像dst_img_normをウィンドウを実際に表示し,何かキーが押されるまで待つ.
最後に,終了するときには確保した構造体のメモリを解放する.
実行結果例
入力画像(8ビット,1チャンネル,2値画像)と,0-255に正規化した距離画像
[左] 256×256の画像の周囲に値0の幅20のボーダーを持つ入力画像.
[右] 入力画像に対して距離変換した結果,画像の中心が最も距離が遠く,輝度が高くなっている.
■ 画像修復
OpenCV1.0で新しく追加された機能で,指定した領域境界近傍の画素を用いて指定した領域内 の画素値を再構成する. PhotoShop(Ver.CS2以降)のスポット修復ブラシ機能のように,画像ノイズを除去したり, 画像から不必要なオブジェクトを除去するために用いる事が可能である.サンプル
不要オブジェクトの除去 cvInpaint
画像の不要な文字列部分に対するマスク画像を指定して文字列を除去する
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *mask_img = 0, *dst_img; // (1)画像の読み込み if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); if (src_img->depth != IPL_DEPTH_8U) exit (-1); // (2)マスク画像の読み込み if (argc >= 3) mask_img = cvLoadImage (argv[2], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (mask_img == 0) exit (-1); if (mask_img->nChannels != 1) exit (-1); if (mask_img->depth != IPL_DEPTH_8U) exit (-1); // (3)修復画像出力用の画像領域を確保し,画像修復を実行 dst_img = cvCloneImage (src_img); cvInpaint (src_img, mask_img, dst_img, 10.0, CV_INPAINT_NS); // (4)入力,マスク,修復結果画像の表示 cvNamedWindow ("Source", CV_WINDOW_AUTOSIZE); cvNamedWindow ("Mask", CV_WINDOW_AUTOSIZE); cvNamedWindow ("Inpaint", CV_WINDOW_AUTOSIZE); cvShowImage ("Source", src_img); cvShowImage ("Mask", mask_img); cvShowImage ("Inpaint", dst_img); cvWaitKey (0); cvDestroyWindow ("Source"); cvDestroyWindow ("Mask"); cvDestroyWindow ("Inpaint"); cvReleaseImage (&src_img); cvReleaseImage (&mask_img); cvReleaseImage (&dst_img); return 1; }
// (1)画像の読み込み
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数
cvLoadImage()で読み込む.2番目の引数にCV_LOAD_IMAGE_ANYDEPTH |
CV_LOAD_IMAGE_ANYCOLORを指定することで,元画像のデプス,チャンネルを変
更せずに読み込む.
cvInpaint()は,入力として8ビットの画像しか取れないので,
ここで入力画像のデプスのチェックを行なう.
// (2)マスク画像の読み込み
コマンド引数で指定されたファイル名の画像(マスク画像)を読み込む.
マスクとしては8ビット,シングルチャンネルの画像しか取れない
ので,ここでマスク画像のチャンネル数とデプスのチェックを行なう.
マスク画像の非0部分が,修復対象領域となる.
// (3)修復画像出力用の画像領域を確保し,画像修復を実行
入力画像と同じサイズ,チャンネル数,デプスの出力用画像dst_imgを,cvCloneImage()で確保し,
ナビエ・ストークスベースの手法 CV_INPAINT_NS を指定して,画像修復を実行する.
// (4)入力,マスク,修復結果画像の表示
ウィンドウを生成し,入力,マスク,結果を表示し,何かキーが押されるまで待つ.
実行結果例
[左] 「Inpaint Sample」という不要な文字が挿入された画像
[中] 文字領域を画素値255の白ピクセルで示したマスク画像
[右] 画像修復結果,きれいに修復するにはマスク形状を細かく指定する方が良い.