ある画像で使われている色の数を減らしたいので、どういうのがあるか探していたら見つかったk-means法。クラスタリングの手法の一種。OpenCVを使えば簡単にできるっぽい。
k-means法について
非階層型クラスタリング手法の1つ。クラスタの平均を用い、与えられたクラスタ数K個に分類することから、MacQueenによりこう呼ばれた。K-平均法(K-means)、c-平均法(c-means)とも呼ばれる。単純なアルゴリズムで計算することができるため、現在広く用いられている。
K平均法 - Wikipedia
実際にどういう動きなのかわかりやすく説明してあるサイトがあったので見てお勉強。
クラスタリングの定番アルゴリズム「K-means法」をビジュアライズしてみた - てっく煮ブログ
OpenCVのKMeans2では最初のクラスタをどうやって決めているのか、OpenCVのドキュメントサイトではよくわからず。説明してあるサイトを探してみたけど見つからなかった。うーむ、ソースを読むのが一番か。
実装
OpenCVのサンプルを参考に実際に作ってみてみる。
const int MAX_CLUSTERS = 16; IplImage image = Cv.LoadImage(画像ファイル名); int size = image.Width * image.Height; IplImage dstImage = image.Clone(); CvMat clusters = Cv.CreateMat(size, 1, MatrixType.S32C1); CvMat points = Cv.CreateMat(size, 1, MatrixType.F32C3); // ピクセル値を行列へ代入 for(int i= 0; i < size; i++){ points.DataSingle[i * 3 + 0] = image.ImageDataPtr[i * 3 + 0]; points.DataSingle[i * 3 + 1] = image.ImageDataPtr[i * 3 + 1]; points.DataSingle[i * 3 + 2] = image.ImageDataPtr[i * 3 + 2]; } // クラスタリング Cv.KMeans2(points, MAX_CLUSTERS, clusters, Cv.TermCriteria(CriteriaType.Epsilon | CriteriaType.Iteration, 10, 1.0)); // 各クラスタの平均値を計算 CvMat color = Cv.CreateMat(MAX_CLUSTERS, 1, MatrixType.F32C3); color.SetZero(); var count = new int[MAX_CLUSTERS]; for(int i = 0; i < size; i++){ int idx = clusters.DataInt32[i]; int j = ++count[idx]; color.DataSingle[idx * 3 + 0] = color.DataSingle[idx * 3 + 0] * (j - 1) / j + points.DataSingle[i * 3 + 0] / j; color.DataSingle[idx * 3 + 1] = color.DataSingle[idx * 3 + 1] * (j - 1) / j + points.DataSingle[i * 3 + 1] / j; color.DataSingle[idx * 3 + 2] = color.DataSingle[idx * 3 + 2] * (j - 1) / j + points.DataSingle[i * 3 + 2] / j; } // クラスタ毎に色を描画 for(int i = 0; i < size; i++){ int idx = clusters.DataInt32[i]; dstImage.ImageDataPtr[i * 3 + 0] = (byte)color.DataSingle[idx * 3 + 0]; dstImage.ImageDataPtr[i * 3 + 1] = (byte)color.DataSingle[idx * 3 + 1]; dstImage.ImageDataPtr[i * 3 + 2] = (byte)color.DataSingle[idx * 3 + 2]; } Cv.ShowImage("dstImage", dstImage); Cv.WaitKey();
結果
元画像 | 8色 | 16色 |
---|---|---|