基本構造体
作成者: 怡土順一, 最終変更者: 怡土順一, 最終変更リビジョン: 497, 最終変更日時: 2009-09-10 22:37:23 +0900 (木, 10 9月 2009)
■ IplImage
OpenCVでは,IPL(Intel Image Processing Library)で使われていた構造体 IplImage フォーマットの一部をサポートしている. OpenCVの多くの関数が,構造体 IplImage を含む CvArr を,その引数に取る. 構造体 IplImageのメンバである imageData に実際の画素値が格納されており, 画像の幅と高さはそれぞれ,width,heightで示される. また,メンバ変数 widthStep は,画像の水平方向1ライン分をバイト単位で表す値であり, これは,imageData のデータに直接アクセスする際に利用される. widthStep はメモリのアライメント(alignment)により,width と同じか, それよりも大きい値となる.これらの知識は,画素値を直接操作する際に必要になるが, 簡単なプログラムを書く場合は,このようなメモリ上の画素値の配置を考慮することはないかもしれない. しかし,OpenCVには実装されていない画像処理手法を用いたい場合や, 任意の描画を行いたい(画像上に二次曲線を描きたいなど)場合, IplImageの画像データを別の構造体(他のライブラリの構造体など)で扱いたい場合などは, どうしても画素値を直接操作する(各画素へのポインタを取得する)方法が必要になる. しかし,ここでは単純なサンプルを使っていくつかの方法を紹介する.
サンプル
画素値の直接操作 IplImage
8ビット3チャンネルカラー画像を読み込み,画素値を変更する
サンプルコード
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int x, y; uchar p[3]; IplImage *img; if (argc != 2 || (img = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; // (1)画素値(R,G,B)を順次取得し,変更する for (y = 0; y < img->height; y++) { for (x = 0; x < img->width; x++) { /* 画素値を直接操作する一例 */ p[0] = img->imageData[img->widthStep * y + x * 3]; // B p[1] = img->imageData[img->widthStep * y + x * 3 + 1]; // G p[2] = img->imageData[img->widthStep * y + x * 3 + 2]; // R img->imageData[img->widthStep * y + x * 3] = cvRound (p[0] * 0.6 + 10); img->imageData[img->widthStep * y + x * 3 + 1] = cvRound (p[1] * 1.0); img->imageData[img->widthStep * y + x * 3 + 2] = cvRound (p[2] * 0.0); } } cvNamedWindow ("Image", CV_WINDOW_AUTOSIZE); cvShowImage ("Image", img); cvWaitKey (0); cvDestroyWindow ("Image"); cvReleaseImage (&img); return 0; }
// (1)画素値(R,G,B)を順次取得し,変更する
画像の各画素値(カラー画像ならば,各ピクセルのRGB値であることが多い)を取得する,
あるいは値を格納するという処理は,画像処理の基本操作である.
OpenCVでは,構造体 IplImage のメンバである imageData 配列に画素値が格納されている.
この配列の値を取り出して,適当な演算により値を変更している.
今回は,R(赤)チャンネルの値を 0 に,B(青)チャンネル値を約0.6倍にしている.
また,画素値を取得するためのコード書き方には,以下のようなものがある.
(1)画素値へのポインタを最初に計算する
char *pt1, *pt2; for(pt1 = img->imageData; pt1 < img->imageData + img->widthStep*img->height; pt1 += img->widthStep) { for(pt2 = pt1; pt2 < pt1 + img->width*3; pt2 += 3) { p[0] = pt2[0]; p[1] = pt2[1]; p[2] = pt2[2]; } }
(2)CV_IMAGE_ELEM マクロを利用する
for(y=0; yheight; y++) { for(x=0; x width; x++) { p[0] = CV_IMAGE_ELEM(img, uchar, y, x*3+0); p[1] = CV_IMAGE_ELEM(img, uchar, y, x*3+1); p[2] = CV_IMAGE_ELEM(img, uchar, y, x*3+2); } }
(3)OpenCVの配列アクセス関数を利用する
CvScalar s; for(y = 0; y < img->height; y++) { for(x = 0; x < img->width; x++) { s = cvGet2D(img, y, x); } }
ここで,(2)のCV_IMAGE_ELEMマクロは,cxtypes.h で以下のように定義されている(これに限らず,多くのマクロがこのヘッダファイルで定義されている).
#define CV_IMAGE_ELEM( image, elemtype, row, col ) \ (((elemtype*)((image)->imageData + (image)->widthStep*(row)))[(col)])このマクロは,引数に(IplImage構造体へのポインタ,要素の型,行番号,列番号)をとる. ここで注意することは,ここでいう(行番号,列番号)は画像上のxy座標その ままではなく,メモリ上の配置順とも言うべき値なので, 列番号にはチャンネル数をかけなければならない,という点である. つまり,BGRの3チャンネル画像で列番号が dx だけ進むと,メモリ上は,(BGR3チャンネル分 * dx)進むということを意味する. また,(行,列)の順番で指定するので,xy座標系で言うと(y, x)となる事にも注意する. 前述のサンプルプログラムで説明すると,col が x *3 + 0 など指定されてい るが,これは,x(座標) * 3(チャンネル数) + 0(BGRの0番目の要素=B),を意味する.
また,最後に紹介した(3)の関数cvGet*Dを用いたアクセスは,関数呼び出しのオーバヘッドもあり非常に低速である. ある特定ピクセルの値に対してのみアクセスしたい場合を除いて,関数cvGet*Dを利用した方法は推奨されない.