その他の関数
■ クラスタリング
クラスタリングとは,与えられたデータを,一定の規則に従っていくつかの組(クラスタ)に分類する処理である. 画像処理の分野では,画像そのものの分類はもちろん,各画像領域の分類や減色処理などに利用される. クラスタリングを行うための代表的な手法として, 初期データ数個のクラスタから各クラスタを階層的に結合していく手法, ニューラルネットの一種である自己組織化マップ(Self-organizing maps, SOM)などの手法, ユーザが指定した個数のクラスタにデータを分割するK-Means法(k平均法)などが挙げられる. また,さらに,データが複数のクラスタにまたがる様な手法や,特徴空間距離以外のデー タを利用する手法など,多種の手法が存在する.サンプル
K-means法によるクラスタリング cvKMeans2
K-means法で,サンプルのクラスタリングを行う.これは,OpenCV付属のサンプルコード(とほぼ同一)である.
サンプルコード
#include <cv.h> #include <highgui.h> #include <time.h> #define MAX_CLUSTERS (5) int main (int argc, char **argv) { CvScalar color_tab[MAX_CLUSTERS] = { CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), CV_RGB (100, 100, 255), CV_RGB (255, 0, 255), CV_RGB (255, 255, 0) }; IplImage *img = cvCreateImage (cvSize (500, 500), IPL_DEPTH_8U, 3); CvRNG rng = cvRNG (time (NULL)); CvPoint ipt; while (1) { int c; int k, cluster_count = cvRandInt (&rng) % MAX_CLUSTERS + 1; int i, sample_count = cvRandInt (&rng) % 1000 + MAX_CLUSTERS; CvMat *points = cvCreateMat (sample_count, 1, CV_32FC2); CvMat *clusters = cvCreateMat (sample_count, 1, CV_32SC1); // (1)複数のガウシアンから成るランダムサンプルを生成する for (k = 0; k < cluster_count; k++) { CvPoint center; CvMat point_chunk; center.x = cvRandInt (&rng) % img->width; center.y = cvRandInt (&rng) % img->height; cvGetRows (points, &point_chunk, k * sample_count / cluster_count, k == cluster_count - 1 ? sample_count : (k + 1) * sample_count / cluster_count, 1); cvRandArr (&rng, &point_chunk, CV_RAND_NORMAL, cvScalar (center.x, center.y, 0, 0), cvScalar (img->width * 0.1, img->height * 0.1, 0, 0)); } // (2)ランダムサンプルをシャッフルする for (i = 0; i < sample_count / 2; i++) { CvPoint2D32f *pt1 = (CvPoint2D32f *) points->data.fl + cvRandInt (&rng) % sample_count; CvPoint2D32f *pt2 = (CvPoint2D32f *) points->data.fl + cvRandInt (&rng) % sample_count; CvPoint2D32f temp; CV_SWAP (*pt1, *pt2, temp); } // (3)K-menas法によるクラスタリング cvKMeans2 (points, cluster_count, clusters, cvTermCriteria (CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0)); // (4)クラスタ毎に色を変えてサンプルを描画する cvZero (img); for (i = 0; i < sample_count; i++) { int cluster_idx = clusters->data.i[i]; ipt.x = (int) points->data.fl[i * 2]; ipt.y = (int) points->data.fl[i * 2 + 1]; cvCircle (img, ipt, 2, color_tab[cluster_idx], CV_FILLED, CV_AA, 0); } cvReleaseMat (&points); cvReleaseMat (&clusters); // (5)画像を表示,"Esc"キーが押されたときに終了 cvNamedWindow ("Clusters", CV_WINDOW_AUTOSIZE); cvShowImage ("Clusters", img); c = cvWaitKey (0); if (c == '\x1b') break; cvReleaseMat (&clusters); cvReleaseMat (&points); } cvDestroyWindow ("Clusters"); cvReleaseImage (&img); return 0; }
// (1)複数のガウシアンから成るランダムサンプルを生成する
まず,クラスタリング対象データとなるデータ集合を作成する.
各データは2次元座標を持つ点であり,その全データは,1×sample_countの行列(各要素は,CV_32FC2)pointsで表される.
関数cvGetRowsにより擬似的に(クラスタ数分に)分割された部分行列を,
正規分布するデータで初期化する.このときの平均は,最初にランダムに生成された値,分散は,0.1*画像のサイズ,である.
この行列の初期化には,関数cvRandArr()を用いる.
// (2)ランダムサンプルをシャッフルする
また,生成されたサンプルは,この時点では各クラスタごとに正しく整列しているので,
これを,マクロCV_SWAP()によりシャッフルする.
マクロ CV_SWAPは,cxtypes.h 内で,次の様に定義される.
#define CV_SWAP(a,b,t) ((t) = (a), (a) = (b), (b) = (t))
// (3)K-menas法によるクラスタリング
関数cvKMeans2()により,K-menas法によるクラスタリングを行う.
引数には,それぞれ,サンプルデータ,クラスタ数,出力クラスタ,終了条件,を与える.
出力クラスタには,各サンプルがどのクラスタに属するかを示すクラスタインデックスが入る.
また,終了条件は,繰り返し計算の最大数と,センターの各ステップにおける
移動距離の最小値が与えられ,このどちらかが満足されると計算が終了する.
// (4)クラスタ毎に色を変えてサンプルを描画する
clusters->data.i[i]に,i番めのサンプルが属するクラスタのインデックス
(int型)が保存されているので,
それに従い,全てのサンプルを色分けして円で描画する.
// (5)画像を表示,"Esc"キーが押されたときに終了
実際に画像を表示し,ユーザの入力を待つ.
"Esc"キーが押された場合は終了し,それ以外の入力が合った場合は,再びランダムサンプルを生成する.
実行結果例
各クラスタを代表する最初のセンターは,OpenCV内部でランダムに初期化されるため, 必ずしも適切なクラスタリング結果になるわけではない.クラスタリングによる減色処理 cvKMeans2
k-means法によるクラスタリングを利用して,非常に単純な減色を行う
サンプルコード
#include <cv.h> #include <highgui.h> #define MAX_CLUSTERS (32) /* クラスタ数 */ int main (int argc, char **argv) { int i, size; IplImage *src_img = 0, *dst_img = 0; CvMat *clusters; CvMat *points; CvMat *color = cvCreateMat (MAX_CLUSTERS, 1, CV_32FC3); CvMat *count = cvCreateMat (MAX_CLUSTERS, 1, CV_32SC1); // (1)画像を読み込む if (argc != 2 || (src_img = cvLoadImage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; size = src_img->width * src_img->height; dst_img = cvCloneImage (src_img); clusters = cvCreateMat (size, 1, CV_32SC1); points = cvCreateMat (size, 1, CV_32FC3); // (2)ピクセルの値を行列へ代入 for (i = 0; i < size; i++) { points->data.fl[i * 3 + 0] = (uchar) src_img->imageData[i * 3 + 0]; points->data.fl[i * 3 + 1] = (uchar) src_img->imageData[i * 3 + 1]; points->data.fl[i * 3 + 2] = (uchar) src_img->imageData[i * 3 + 2]; } // (3)クラスタリング cvKMeans2 (points, MAX_CLUSTERS, clusters, cvTermCriteria (CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0)); // (4)各クラスタの平均値を計算 cvSetZero (color); cvSetZero (count); for (i = 0; i < size; i++) { int idx = clusters->data.i[i]; int j = ++count->data.i[idx];; color->data.fl[idx * 3 + 0] = color->data.fl[idx * 3 + 0] * (j - 1) / j + points->data.fl[i * 3 + 0] / j; color->data.fl[idx * 3 + 1] = color->data.fl[idx * 3 + 1] * (j - 1) / j + points->data.fl[i * 3 + 1] / j; color->data.fl[idx * 3 + 2] = color->data.fl[idx * 3 + 2] * (j - 1) / j + points->data.fl[i * 3 + 2] / j; } // (5)クラスタ毎に色を描画 for (i = 0; i < size; i++) { int idx = clusters->data.i[i]; dst_img->imageData[i * 3 + 0] = (char) color->data.fl[idx * 3 + 0]; dst_img->imageData[i * 3 + 1] = (char) color->data.fl[idx * 3 + 1]; dst_img->imageData[i * 3 + 2] = (char) color->data.fl[idx * 3 + 2]; } // (6)画像を表示,キーが押されたときに終了 cvNamedWindow ("src", CV_WINDOW_AUTOSIZE); cvShowImage ("src", src_img); cvNamedWindow ("low-color", CV_WINDOW_AUTOSIZE); cvShowImage ("low-color", dst_img); cvWaitKey (0); cvDestroyWindow ("src"); cvDestroyWindow ("low-color"); cvReleaseImage (&src_img); cvReleaseImage (&dst_img); cvReleaseMat (&clusters); cvReleaseMat (&points); cvReleaseMat (&color); cvReleaseMat (&count); return 0; }
// (1)画像を読み込む
関数 vcLoadImage() により,入力像をカラー画像として読み込む.
// (2)ピクセルの値を行列へ代入
各ピクセルをクラスタリングするために,各ピクセルのRGBチャンネルの値を CvMat 型の変数に代入する.
// (3)クラスタリング
前述のサンプルと同様に,関数 cvKMeans2() により,k-means法を利用したクラスタリングを行う.
クラスタ数はあらかじめ定義されており,これが,減色後の色数となる.
// (4)各クラスタの平均値を計算
クラスタリングされた各ピクセル群を,そのクラスタのセンターで代表できれば処理が簡単であるが,
OpenCVでは,関数 cvKMeans2() によりクラスタリングされた各クラスタのセンターにアクセスできない.
そこで,このサンプルではクラスタリング後の各クラスタの(K個の代表値ではない)完全な平均値を求め,それを各クラスタの代表値とする.
// (5)クラスタ毎に色を描画
各クラスタのピクセルを,求められた平均値で埋める.
// (6)画像を表示,キーが押されたときに終了
実際に,入力画像とクラスタリング(減色)された画像を表示し,何かキーが押されるまで待つ.
このようにK-means法を用いて減色を行う事は,色による画像の領域分割と等しい. それらの処理については, 画像分割,領域結合,輪郭検出 を参照の事.
実行結果例
入力画像 | 4色 | 8色 | 16色 | 32色 |