k-meansクラスタリングによる画像分割,減色

12 2月 2010 Under: opencv2.x-samples

C

#include <cv.h>
#include <highgui.h>

#define MAX_CLUSTERS (10) /* number of cluster */

int main(int argc, char** argv)
{
  int i, size;
  IplImage *src_img = 0, *dst_img = 0;
  CvMat tmp_header;
  CvMat *clusters, *points, *tmp;
  CvMat *count = cvCreateMat( MAX_CLUSTERS, 1, CV_32SC1);
  CvMat *centers = cvCreateMat( MAX_CLUSTERS, 3, CV_32FC1);
  const char *imagename;

  // (1)load a specified file as a 3-channel color image
  imagename = argc > 1 ? argv[1] : "../image/boat.png";
  src_img = cvLoadImage(imagename, CV_LOAD_IMAGE_COLOR);
  if(src_img == 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)reshape the image to be a 1 column matrix 
#if 0
  tmp = cvCreateMat(size, 1, CV_8UC3);
  tmp = cvReshape(src_img, &tmp_header, 0, size);
  cvConvert(tmp, points);
  cvReleaseMat(&tmp);
#else
  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];
  }
#endif

  // (3)run k-means clustering algorithm to segment pixels in RGB color space
  cvKMeans2( points, MAX_CLUSTERS, clusters,
	     cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0 ),
	     1, 0, 0, centers, 0);

  // (4)make a each centroid represent all pixels in the cluster
  for(i=0; i<size; i++) {
    int idx = clusters->data.i[i];
    dst_img->imageData[i*3+0] = (char)centers->data.fl[idx*3+0];
    dst_img->imageData[i*3+1] = (char)centers->data.fl[idx*3+1];
    dst_img->imageData[i*3+2] = (char)centers->data.fl[idx*3+2];
  }

  // (5)show source and destination image, and quit when any key pressed
  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(¢ers);
  cvReleaseMat(&count);

  return 0;
}

// (1)指定ファイルを3チャンネルカラー画像として読み込みます.

コマンドライン引数で指定されたファイルを,3チャンネルカラー画像として読み込みます.また,必要な構造体などを確保します.

// (2)画像を,1行の行列となるように変形します.

クラスタリングを行うための準備として,入力画像(幅×高さ)を1行の行列となるように変形します. cvReshape で画像を1行の行列に変形し,cvConvert で CV_8UC3 から CV_32FC3 に変換します. cvConvertは,次のようにマクロ定義されています.

#define cvConvert(src, dst )  cvConvertScale((src), (dst), 1, 0 )

また,マクロでコメントアウトされている様に,ポインタを利用した代入とキャストで同様の処理を行うこともできます.

  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)RGB空間内でピクセルを分割するため,k-means クラスタリングを行います.

KMeans2 を利用して,画素値のクラスタリングを行います.引数には,停止条件(CvTermCriteria)の他に,OpenCV2.0より指定可能になったクラスタ中心を格納する行列 centers を指定します.これにより,追加計算を行うことなく,クラスタ中心の値を得ることができるようになりました.また,マクロで定義されている MAX_CLUSTERS が,分割クラスタ数を表します.
なお, “パターン認識と機械学習(下)“(9章)でも述べられているように,近傍画素同士の関連を考慮していないという意味で画像分割として洗練された方法とは言えません.また,減色という観点からも,中間色を表現するためのタイリング処理や,クラスタサイズの考慮ができないという点で問題があります.このサンプルではk-meansアルゴリズムの単なる利用例であることに注意してください.

// (4)各ピクセル値を,属するクラスタの中心値で置き換えます.

処理結果を画像として表現するために,各クラスタに属するピクセルの値を,そのクラスタの中心値で置き換えます.k-meansアルゴリズムはハード割り当てなので,各ピクセルは1つのクラスタに所属します.

// (5)入力画像と,処理結果画像を表示し,何かキーが押されると終了します.

入力画像,および処理結果画像を表示し,何かキーが押されるとプログラムを終了します.

C++

#include <cv.h>
#include <highgui.h>

using namespace cv;

int
main (int argc, char **argv)
{
  const int cluster_count = 10; /* number of cluster */

  // (1)load a specified file as a 3-channel color image
  const char *imagename = argc > 1 ? argv[1] : "../image/boat.png";
  Mat src_img = imread(imagename);
  if(!src_img.data)
    return -1;

  // (2)reshape the image to be a 1 column matrix 
  Mat points;
  src_img.convertTo(points, CV_32FC3);
  points = points.reshape(3, src_img.rows*src_img.cols);

  // (3)run k-means clustering algorithm to segment pixels in RGB color space
  Mat_<int> clusters(points.size(), CV_32SC1);
  Mat centers;
  kmeans(points, cluster_count, clusters, 
	 cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0), 1, KMEANS_PP_CENTERS, ¢ers);

  // (4)make a each centroid represent all pixels in the cluster
  Mat dst_img(src_img.size(), src_img.type());
  MatIterator_<Vec3f> itf = centers.begin<Vec3f>();
  MatIterator_<Vec3b> itd = dst_img.begin<Vec3b>(), itd_end = dst_img.end<Vec3b>();
  for(int i=0; itd != itd_end; ++itd, ++i) {
    Vec3f color = itf[clusters(1,i)];
    (*itd)[0] = saturate_cast<uchar>(color[0]);
    (*itd)[1] = saturate_cast<uchar>(color[1]);
    (*itd)[2] = saturate_cast<uchar>(color[2]);
  }

  // (5)show source and destination image, and quit when any key pressed
  namedWindow("src_img", CV_WINDOW_AUTOSIZE);
  imshow("src_img", src_img);
  namedWindow("dst_img", CV_WINDOW_AUTOSIZE);
  imshow("dst_img", dst_img);
  waitKey(0);

  return 0;
}

// (1)指定ファイルを3チャンネルカラー画像として読み込みます.

コマンドライン引数で指定されたファイルを,3チャンネルカラー画像として読み込みます.

// (2)画像を,1行の行列となるように変形します.

クラスタリングを行うための準備として,入力画像(幅×高さ)を1行の行列となるように変形します. Mat.convertTo で CV_8UC3 から CV_32FC3 に変換し, Mat.reshape 画像を1行の行列に変形します.

// (3)RGB空間内でピクセルを分割するため,k-means クラスタリングを行います.

C++インタフェースでは cv::kmeans を利用することができます(Cのインタフェース KMeans2 でも,内部では kmeans を呼び出しています).ここでは,初期値選択に関するフラグに KMEANS_PP_CENTERS を渡すことで,初期クラスタ中心選択アルゴリズムとして k-means++ を利用できます. k-means++ では,最初の1点以降の初期値を,その時点でのクラスタに属している確率の低いものから選択することで,初期値の偏りをなくし収束を早めることができます.その他のパラメータは,Cの場合と同様です.
なお, “パターン認識と機械学習(下)“(9章)でも述べられているように,近傍画素同士の関連を考慮していないという意味で画像分割として洗練された方法とは言えません.また,減色という観点からも,中間色を表現するためのタイリング処理や,クラスタサイズの考慮ができないという点で問題があります.このサンプルではk-meansアルゴリズムの単なる利用例であることに注意してください.

// (4)各ピクセル値を,属するクラスタの中心値で置き換えます.

処理結果を画像として表現するために,各クラスタに属するピクセルの値を,そのクラスタの中心値で置き換えます.k-meansアルゴリズムはハード割り当てなので,各ピクセルは1つのクラスタに所属します.

// (5)入力画像と,処理結果画像を表示し,何かキーが押されると終了します.

入力画像,および処理結果画像を表示し,何かキーが押されるとプログラムを終了します.

実行結果例


(左上から)元画像,K=2,K=3,K=5,K=10,K=20