web-dev-qa-db-ja.com

k平均法を使用して画像をポスタリゼーションするOpenCV

C++インターフェース(cv名前空間)でk-meansとOpenCVを使用して画像をポスタライズしたいのですが、奇妙な結果が得られます。ノイズを減らすために必要です。これは私のコードです:

#include "cv.h"
#include "highgui.h"

using namespace cv;

int main() {
    Mat imageBGR, imageHSV, planeH, planeS, planeV;

    imageBGR = imread("fruits.jpg");
    imshow("original", imageBGR);

    cv::Mat labels, data;
    cv::Mat centers(8, 1, CV_32FC1);
    imageBGR.convertTo(data, CV_32F);

    cv::kmeans(data, 8, labels,
            cv::TermCriteria(CV_TERMCRIT_ITER, 10, 1.0),
            3, cv::KMEANS_PP_CENTERS, &centers);
    imshow("posterized hue", data);
    data.convertTo(data, CV_32FC3);

    waitKey();
    return 0;
}

しかし、私は奇妙な結果を得ます

Fruit

最初の画像:オリジナル

2番目の画像:k平均法の後。

何かアドバイス?


更新:適切なソリューション。多分誰かがコードを最適化するのを手伝ってくれる?

#include "cv.h"
#include "highgui.h"

#include <iostream>

using namespace cv;
using namespace std;

int main() {
    Mat src;

    src = imread("fruits.jpg");
    imshow("original", src);

    blur(src, src, Size(15,15));
    imshow("blurred", src);

    Mat p = Mat::zeros(src.cols*src.rows, 5, CV_32F);
    Mat bestLabels, centers, clustered;
    vector<Mat> bgr;
    cv::split(src, bgr);
    // i think there is a better way to split pixel bgr color
    for(int i=0; i<src.cols*src.rows; i++) {
        p.at<float>(i,0) = (i/src.cols) / src.rows;
        p.at<float>(i,1) = (i%src.cols) / src.cols;
        p.at<float>(i,2) = bgr[0].data[i] / 255.0;
        p.at<float>(i,3) = bgr[1].data[i] / 255.0;
        p.at<float>(i,4) = bgr[2].data[i] / 255.0;
    }

    int K = 8;
    cv::kmeans(p, K, bestLabels,
            TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 1.0),
            3, KMEANS_PP_CENTERS, centers);

    int colors[K];
    for(int i=0; i<K; i++) {
        colors[i] = 255/(i+1);
    }
    // i think there is a better way to do this mayebe some Mat::reshape?
    clustered = Mat(src.rows, src.cols, CV_32F);
    for(int i=0; i<src.cols*src.rows; i++) {
        clustered.at<float>(i/src.cols, i%src.cols) = (float)(colors[bestLabels.at<int>(0,i)]);
//      cout << bestLabels.at<int>(0,i) << " " << 
//              colors[bestLabels.at<int>(0,i)] << " " << 
//              clustered.at<float>(i/src.cols, i%src.cols) << " " <<
//              endl;
    }

    clustered.convertTo(clustered, CV_8U);
    imshow("clustered", clustered);

    waitKey();
    return 0;
}

結果:

Posterized Fruit

17
nkint

私はOpenCVの専門家ではないので、あなたの質問に関連する一般的なアドバイスをします。K-meansは基本的に行列であるベクトルのリストをとります:

[x0, y0, r0, g0, b0]
[x1, y1, r1, g1, b1]
[x2, y2, r2, g2, b2]
.
.
.

機能しないイメージを与えています。まず、画像をこのk平均行列形式に変換する必要があります。ソース画像のピクセルごとに、結果の行列に1行あります。また、値がすべて同じような値になるように値をスケーリングする必要があることにも注意してください。そうしないと、x座標とy座標は通常、色よりもはるかに「重力」が高くなり、満足のいく結果が得られません。 C++疑似コード:

int pixel_index = 0;
for (int y = 0; y < image height; y++)  {
  for (int x = 0; x < image width; x++)  {
     matrix[pixel_index][0] = (float)x / image width;
     matrix[pixel_index][1] = (float)y / image height;
     matrix[pixel_index][2] = (float)pixel(x, y).r / 255.0f;
     matrix[pixel_index][3] = (float)pixel(x, y).g / 255.0f;
     matrix[pixel_index][4] = (float)pixel(x, y).b / 255.0f;
  }
}
// Pass the matrix to kmeans...

その結果、割り当てられているクラスターに対応する個々のピクセルのラベルが表示されます。次に、クラスターの色を決定する必要があります。これは、中心のピクセルの色の値の取得から、クラスターの平均/中央値の色の計算までさまざまです。色を決定したら、画像をウォークして、ピクセルをクラスターの色に設定します。

for (int y = 0; y < image height; y++)  {
  for (int x = 0; x < image width; x++)  {
     int index = y * image width + x;  // This corresponds to pixel_index above
     int cluster_index = labels[index]; // 0 to 7 in your case
     Color color = colors[cluster_index];  // Colors is an array of 8 colors of the clusters
     image.setpixel(x, y, color)
  }
}

RGBの代わりにHSVを使用したい場合は、RGB値の代わりにHSV値を使用してください。

OpenCVには、上記で説明した変換を正確に実行する機能がある可能性がありますが、Googleを使用してそれらをすばやく見つけることができませんでした。

8
Karel Petranek

K-meansでx、y座標を使用する必要がない場合は、reshapeコマンドを使用して、次のようにデータをより迅速に配置できます。

  int origRows = img.rows;
  notes << "original image is: " << img.rows << "x" << img.cols << endl;
  Mat colVec = img.reshape(1, img.rows*img.cols); // change to a Nx3 column vector
  cout << "colVec is of size: " << colVec.rows << "x" << colVec.cols << endl;
  Mat colVecD, bestLabels, centers, clustered;
  int attempts = 5;
  int clusts = 8;
  double eps = 0.001;
  colVec.convertTo(colVecD, CV_32FC3, 1.0/255.0); // convert to floating point
  double compactness = kmeans(colVecD, clusts, bestLabels, 
        TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, attempts, eps), 
        attempts, KMEANS_PP_CENTERS, centers);
  Mat labelsImg = bestLabels.reshape(1, origRows); // single channel image of labels
  cout << "Compactness = " << compactness << endl;
8
zzzz