サンプリング,補間,幾何変換
■ 領域のサンプリング
例えば,画像の並進移動は,移動後の画像の各画素値を元画像のどの位置からサンプリングして決めるかという問題に帰着する.OpenCVには,画像の並進・回転移動と同様の意味で,ピクセルサンプリングをするための関数が用意されている.サンプル
並進移動のためのピクセルサンプリング cvGetRectSubPix
指定した座標が画像中心になるように元画像を並進させる.非整数値座標のピクセル値は、バイリニア補間により得られる,また画像境界の外側に存在するピクセルの値を得るために、複製境界モード(画像の最も外側のピクセル値が外側無限遠まで伸びていると仮定) が使用される.
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img = 0; CvPoint2D32f center; // (1)画像の読み込み,出力用画像領域の確保を行なう if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvCloneImage (src_img); // (2)dst_imgの画像中心になるsrc_img中の位置centerを指定する center.x = src_img->width - 1; center.y = src_img->height - 1; // (3)centerが画像中心になるように,GetRectSubPixを用いて画像全体をシフトさせる cvGetRectSubPix (src_img, dst_img, center); // (4)結果を表示する cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst", CV_WINDOW_AUTOSIZE); cvShowImage ("src", src_img); cvShowImage ("dst", dst_img); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("dst"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); return 1; }
// (1)画像の読み込み,出力用画像領域の確保を行なう
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.また,出力用画像の領域を,入力画像をコピーすることで確保する.
// (2)dst_imgの画像中心になるsrc_img中の位置centerを指定する
このサンプルでは,入力画像の右下隅を出力画像の中心にしている.
// (3)centerが画像中心になるように,GetRectSubPixを用いて画像全体をシフトさせる
画像をシフトさせるということは,出力画像(dst_img)中の各ピクセル値を以下の式を用いて入力画像中のピクセルからサンプリングする事で決定するとうことでもある.
dst(x, y) = src(x + center.x - (width(dst)-1)*0.5, y + center.y - (height(dst)-1)*0.5)
// (4)結果を表示する
ウィンドウを生成し,入力画像,結果画像を表示し,何かキーが押されるまで待つ.
実行結果例
入力画像と出力画像
[左から] 入力画像,出力画像
出力画像の中心が入力画像の右下になるように画面が左上方向にシフトされている.
残りの部分は複製境界モードが適用されて,元画像の境界部のピクセル値が横・縦そして右下領域にのびている.
回転移動のためのピクセルサンプリング cvGetQuadrangleSubPix
指定した回転行列を用いて元画像を回転させる.非整数値座標のピクセル値は、バイリニア補間により得られる,また画像境界の外側に存在するピクセルの値を得るために、複製境界モード(画像の最も外側のピクセル値が外側無限遠まで伸びていると仮定)が使われる.
サンプルコード
#include <cv.h> #include <highgui.h> #include <math.h> int main (int argc, char **argv) { int angle = 45; float m[6]; IplImage *src_img = 0, *dst_img = 0; CvMat M; // (1)画像の読み込み,出力用画像領域の確保を行なう if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvCloneImage (src_img); // (2)回転のための行列(アフィン行列)要素を設定し,CvMat行列Mを初期化する m[0] = (float) (cos (angle * CV_PI / 180.)); m[1] = (float) (-sin (angle * CV_PI / 180.)); m[2] = src_img->width * 0.5; m[3] = -m[1]; m[4] = m[0]; m[5] = src_img->height * 0.5; cvInitMatHeader (&M, 2, 3, CV_32FC1, m, CV_AUTOSTEP); // (3)指定された回転行列により,GetQuadrangleSubPixを用いて画像全体を回転させる cvGetQuadrangleSubPix (src_img, dst_img, &M); // (4)結果を表示する cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst", CV_WINDOW_AUTOSIZE); cvShowImage ("src", src_img); cvShowImage ("dst", dst_img); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("dst"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); return 1; }
// (1)画像の読み込み,出力用画像領域の確保を行なう
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.また,出力用画像の領域を,入力画像をコピーすることで確保する.
// (2)回転のための行列(アフィン行列)要素を設定し,CvMat行列Mを初期化する
ここで指定するアフィン行列は次のように設定する.
| A11 A12 b1 | map_matrix = | | | A21 A22 b2 | m[0] = A11 = cos(angle*π/180) m[1] = A12 = -sin(angle*π/180) m[2] = b1 = 回転中心のx座標 m[3] = A21 = sin(angle*π/180) m[4] = A22 = cos(angle*π/180) m[5] = b2 = 回転中心のy座標 angleの単位はdegree.
// (3)指定された回転行列により,GetQuadrangleSubPixを用いて画像全体を回転させる
画像を回転させるということは,出力画像(dst_img)中の各ピクセル値を入力画像中のピクセルから以下の式に従ってサンプリングする事で,決定するということでもある.
dst(x, y) = src( A11x' + A12y' + b1, A21x' + A22y' + b2 ) x' = x - ( dst_img->width - 1 ) * 0.5 y' = y - ( dst_img->height - 1 ) * 0.5
// (4)結果を表示する
ウィンドウを生成し,入力画像,結果画像を表示し,何かキーが押されるまで待つ.
実行結果例
入力画像と出力画像
[左から] 入力画像,出力画像
入力画像の中心座標を回転中心として45[deg]回転した結果が出力になっている.
出力画像のピクセルで対応点がない場合(入力画像の領域外)は,複製境界モードが適用されて元画像の境界部のピクセル値が各隅方向にのびている事が確認できる.
■ 補間
様々な補間方法を用いて画像のサイズ変更を行う.サンプル
画像のサイズ変更 cvResize
指定した出力画像サイズに合うように、入力画像のサイズを変更し出力する.
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img1 = 0, *dst_img2 = 0; // (1)画像を読み込む if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; // (2)出力用画像領域の確保を行なう dst_img1 = cvCreateImage (cvSize (src_img->width / 2, src_img->height / 2), src_img->depth, src_img->nChannels); dst_img2 = cvCreateImage (cvSize (src_img->width * 2, src_img->height * 2), src_img->depth, src_img->nChannels); // (3)画像のサイズ変更を行う cvResize (src_img, dst_img1, CV_INTER_NN); cvResize (src_img, dst_img2, CV_INTER_NN); // (4)結果を表示する cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst1", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst2", CV_WINDOW_AUTOSIZE); cvShowImage ("src", src_img); cvShowImage ("dst1", dst_img1); cvShowImage ("dst2", dst_img2); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("dst1"); cvDestroyWindow ("dst2"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img1); cvReleaseImage (&dst_img2); return 1; }
// (1)画像を読み込む
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.
// (2)出力用画像領域の確保を行なう
入力画像サイズのそれぞれ1/2倍,2倍のサイズを指定した出力画像領域を関数cvCreateImage()で確保する.
// (3)画像のサイズ変更を行う
補間方法を指定して,サイズ変更を行う.
// (4)結果を表示する
ウィンドウを生成し,入力画像,結果画像を表示し,何かキーが押されるまで待つ.
実行結果例
最近隣接補間とバイキュービック補間では画像縮小のときにモアレが発生していることが確認できる.補間方法 | サイズ1/2倍 | 入力画像 | サイズ2倍 |
最近隣接 | |||
バイリニア | |||
エリア | |||
バイキュービック |
■ 幾何変換
アフィン変換・透視投影変換等の画像の幾何学的変換を実装しているサンプル
画像のアフィン変換(1) cvGetAffineTransform + cvWarpAffine
画像上の3点対応よりアフィン変換行列を計算し,その行列を用いて画像全体のアフィン変換を行う.
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img = 0; CvMat *map_matrix; CvPoint2D32f src_pnt[3], dst_pnt[3]; // (1)画像の読み込み,出力用画像領域の確保を行なう if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvCloneImage (src_img); // (2)三角形の回転前と回転後の対応する頂点をそれぞれセットし // cvGetAffineTransformを用いてアフィン行列を求める src_pnt[0] = cvPoint2D32f (200.0, 200.0); src_pnt[1] = cvPoint2D32f (250.0, 200.0); src_pnt[2] = cvPoint2D32f (200.0, 100.0); dst_pnt[0] = cvPoint2D32f (300.0, 100.0); dst_pnt[1] = cvPoint2D32f (300.0, 50.0); dst_pnt[2] = cvPoint2D32f (200.0, 100.0); map_matrix = cvCreateMat (2, 3, CV_32FC1); cvGetAffineTransform (src_pnt, dst_pnt, map_matrix); // (3)指定されたアフィン行列により,cvWarpAffineを用いて画像を回転させる cvWarpAffine (src_img, dst_img, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll (0)); // (4)結果を表示する cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst", CV_WINDOW_AUTOSIZE); cvShowImage ("src", src_img); cvShowImage ("dst", dst_img); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("dst"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseMat (&map_matrix); return 1; }
// (1)画像の読み込みと,出力用画像の領域確保
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.また,出力用画像の領域を,入力画像をコピーすることで確保する.
// (2)三角形の回転前と回転後の対応する頂点をそれぞれセットしcvGetAffineTransform()を用いてアフィン行列を求める
上図の水色の三角形がピンクの三角形になるように回転したとする.このサンプルプログラムでは,三角形のそれぞれの対応点が既知としてコードを記述しているが,実際のプログラムでは三角形似対応する特徴点のトラッキングなどによりこれらの点対応を求める事が出来る.src_pnt[]に回転前の頂点座標を,dst_pnt[]に回転後の対応点をセットする.cvGetAffineTransform()により,2×3のmap_matrixに求められたアフィン行列が格納される.
// (3)指定されたアフィン行列により,cvWarpAffineを用いて画像を回転させる
変換後対応のとれない画素に対しては fillvalとしてcvScalarAll(0) を指定し,黒で埋める.アフィン変換を用いれば,画像の拡大・縮小,並進・回転を行なう事ができる.
// (4)結果を表示する
ウィンドウを生成し,入力画像,結果画像を表示し,何かキーが押されるまで待つ.
実行結果例
画像のアフィン変換(2) cv2DRotationMatrix + cvWarpAffine
回転角度,スケール係数,回転中心を与えて変換行列を計算し,その行列を用いて画像のROI部分のアフィン変換を行う.cvWarpAffine()は 上で示したcvGetQuadrangleSubPix() に類似した機能を持っているが,入出力の画像形式が同じという制約を持ちオーバーヘッドが大きい.しかし,ROIを用いて画像の一部を変換する事ができる.
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { double angle = -45.0, scale = 1.0; IplImage *src_img = 0, *dst_img = 0; CvMat *map_matrix; CvPoint2D32f center; CvPoint pt1, pt2; CvRect rect; // (1)画像の読み込み,出力用画像領域の確保を行なう if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; rect.x = (int) (src_img->width * 0.25); rect.y = (int) (src_img->height * 0.25); rect.width = (int) (src_img->width * 0.5); rect.height = (int) (src_img->height * 0.5); cvSetImageROI (src_img, rect); dst_img = cvCloneImage (src_img); // (2)角度,スケール係数,回転中心を指定して // cv2DRotationMatrixを用いてアフィン行列を求める map_matrix = cvCreateMat (2, 3, CV_32FC1); center = cvPoint2D32f (src_img->width * 0.25, src_img->height * 0.25); cv2DRotationMatrix (center, angle, scale, map_matrix); // (3)指定されたアフィン行列により,cvWarpaffineを用いて画像のROI部分を回転させる cvWarpAffine (src_img, dst_img, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll (255)); // (4)結果を表示する cvResetImageROI (src_img); cvResetImageROI (dst_img); pt1 = cvPoint (rect.x, rect.y); pt2 = cvPoint (rect.x + rect.width, rect.y + rect.height); cvRectangle (src_img, pt1, pt2, CV_RGB (255, 0, 255), 2, 8, 0); cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst", CV_WINDOW_AUTOSIZE); cvShowImage ("src", src_img); cvShowImage ("dst", dst_img); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("dst"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseMat (&map_matrix); return 1; }
// (1)画像の読み込み,出力用画像領域の確保を行なう
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込み,画像の中央で,幅・高さとも元画像の半分の領域を指定しROIをセットする.その後,出力用画像の領域を入力画像をコピーすることで確保する.
// (2)角度,スケール係数,回転中心を指定してcv2DRotationMatrixを用いてアフィン行列を求める
サンプルプログラムでは,時計回りに45度,倍率1.0倍を指定し,cv2DRotationMatrix()でアフィン変換行列を求める.
// (3)指定されたアフィン行列により,cvWarpaffineを用いて画像のROI部分を回転させる
変換後対応のとれない画素に対しては fillvalとしてcvScalarAll(255) を指定し,白で埋める.ROIが指定されている場合,指定領域だけを変換している.
// (4)結果を表示する
結果表示の前にROIを解除することで,画像全体を表示出来るようにする.その後,ウィンドウを生成し,入力画像,結果画像を表示し,何かキーが押されるまで待つ.
実行結果例
入力画像と出力画像
[左から] 入力画像,出力画像
ROIで指定した領域のみ,変換されその他の部分に変更がない事が確認できる.
画像の透視投影変換 cvGetPerspectiveTransform + cvWarpPerspective
画像上の4点対応より透視投影変換行列を計算し,その行列を用いて画像全体の透視投影変換を行う.
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img = 0; CvMat *map_matrix; CvPoint2D32f src_pnt[4], dst_pnt[4]; // (1)画像の読み込み,出力用画像領域の確保を行なう if (argc >= 2) src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvCloneImage (src_img); // (2)四角形の変換前と変換後の対応する頂点をそれぞれセットし // cvWarpPerspectiveを用いて透視投影変換行列を求める src_pnt[0] = cvPoint2D32f (150.0, 150.0); src_pnt[1] = cvPoint2D32f (150.0, 300.0); src_pnt[2] = cvPoint2D32f (350.0, 300.0); src_pnt[3] = cvPoint2D32f (350.0, 150.0); dst_pnt[0] = cvPoint2D32f (200.0, 200.0); dst_pnt[1] = cvPoint2D32f (150.0, 300.0); dst_pnt[2] = cvPoint2D32f (350.0, 300.0); dst_pnt[3] = cvPoint2D32f (300.0, 200.0); map_matrix = cvCreateMat (3, 3, CV_32FC1); cvGetPerspectiveTransform (src_pnt, dst_pnt, map_matrix); // (3)指定された透視投影変換行列により,cvWarpPerspectiveを用いて画像を変換させる cvWarpPerspective (src_img, dst_img, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll (100)); // (4)結果を表示する cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst", CV_WINDOW_AUTOSIZE); cvShowImage ("src", src_img); cvShowImage ("dst", dst_img); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("dst"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseMat (&map_matrix); return 1; }
// (1)画像の読み込み,出力用画像領域の確保を行なう
コマンド引数で指定されたファイル名の画像(入力画像)をオープンし,関数 cvLoadImage()で読み込む.また,出力用画像の領域を,入力画像をコピーすることで確保する.
// (2)四角形の変換前と変換後の対応する頂点をそれぞれセットしcvGetPerspectiveTransform()を用いて透視投影変換行列を求める
上図の水色の四角形がピンクの四角形になるように(サンプルコードでは,それぞれの四角形の底辺は同じであるとしている)画像を変換させる.このサンプルプログラムでは,四角形のそれぞれの対応点が既知としてコードを記述しているが,実際のプログラムでは特徴点抽出等により対応点を求めて行列を作成したり,カメラキャリブレーションマトリクスなどから直接透視投影変換行列を求める.
src_pnt[]に変換前の座標を,dst_pnt[]に対応する返還後の座標をセットし,cvGetPerspectiveTransform() を呼ぶ事によって,3×3のmap_matrixに透視投影変換行列が格納され返される.
// (3)指定された透視投影変換行列により,cvWarpPerspectiveを用いて画像を変換させる
変換後対応のとれない画素に対しては fillvalとしてcvScalarAll(100) を指定し,グレーで埋めている.
// (4)結果を表示する
ウィンドウを生成し,入力画像,結果画像を表示し,何かキーが押されるまで待つ.
実行結果例
全方位画像の透視投影変換 cvGetPerspectiveTransform + cvWarpPerspective
全方位画像の一部に対して透視投影変換を行う.
サンプルコード
#include <cv.h> #include <highgui.h> #include <stdio.h> #include <ctype.h> int main (int argc, char **argv) { IplImage *src = NULL, *dst = NULL; CvPoint2D32f src_pnt[4], dst_pnt[4]; CvMat *map_matrix; int i; double t; double a = 29.0, b = 40.0, c = sqrt (a * a + b * b); /* ミラーパラメータ */ double f = 100; int xc = 343, yc = 258; /* 画像中心 */ int x[4] = { -30, 30, 30, -30 }; int y[4] = { -50, -50, -50, -50 }; int z[4] = { 180, 180, 100, 100 }; int xx[4], yy[4]; // (1) ファイルから画像を読み込む src = cvLoadImage ("src.jpg", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR); if (src == NULL) exit (-1); dst = cvCloneImage (src); // (2) ウインドウを作成する cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvNamedWindow ("dst", CV_WINDOW_AUTOSIZE); // (3) 対応点を計算する for (i = 0; i < 4; i++) { t = -a * a * f / ((b * b + c * c) * z[i] - 2 * b * c * sqrt (x[i] * x[i] + y[i] * y[i] + z[i] * z[i])); xx[i] = (int) (x[i] * t + xc + 0.5); yy[i] = (int) (y[i] * t + yc + 0.5); src_pnt[i] = cvPoint2D32f (xx[i], yy[i]); } // (4) 出力画像の対応点を指定 dst_pnt[0] = cvPoint2D32f (0.0, 0.0); dst_pnt[1] = cvPoint2D32f (dst->width, 0.0); dst_pnt[2] = cvPoint2D32f (dst->width, dst->height); dst_pnt[3] = cvPoint2D32f (0.0, dst->height); // (5) 透視投影変換を実行する map_matrix = cvCreateMat (3, 3, CV_32FC1); cvGetPerspectiveTransform (src_pnt, dst_pnt, map_matrix); cvWarpPerspective (src, dst, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll (0)); // (6) 展開領域を描画する for (i = 0; i < 4; i++) { cvLine (src, cvPoint (xx[i], yy[i]), cvPoint (xx[(i + 1) % 4], yy[(i + 1) % 4]), CV_RGB (255, 255, 0), 1, 0, 0); } // (7) 入出力画像を表示する cvShowImage ("src", src); cvShowImage ("dst", dst); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("dst"); cvReleaseImage (&src); return 0; }
// (1) ファイルから画像を読み込む
入力画像(全方位画像)を関数 cvLoadImage()で読み込む.
また出力画像の領域を関数 cvCloneImage ()で確保する.
// (2) ウインドウを作成する
入力画像と出力画像を表示するためのウインドウを作成する.
// (3) 対応点を計算する
三次元座標の4頂点から画像座標の4頂点を以下の計算式により求め,配列src_pntに格納する.
// (4) 出力画像の対応点を指定
src_pntに対応する出力画像の座標を配列dst_pntに格納する.
// (5) 透視投影変換を実行する
透視投影変換を実行する.
// (6) 展開領域を描画する
入力画像に展開領域を描画する.