OpenCV使用k-means算法对图像进行色调分离处理

18

我想使用K均值算法和OpenCV在C++接口(cv命名空间)中对图像进行色调分离,但是结果奇怪。我需要用它来降噪。这是我的代码:

#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;
}

但我得到了奇怪的结果

水果

第一张图片:原始图片

第二张图片:经过k-means算法之后

有任何建议吗?


更新:正确的解决方案。也许有人可以帮助我优化代码?

#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;
}

结果:

色调分离的水果


1
好的,我已经从头开始重新做了,并编辑了我的问题。 - nkint
1
现在看起来结果不错,也许可以进行优化。 - nkint
感谢您发布解决方案。我认为需要在一行中切换0和i来访问Mat bestLabels,即(float)(colors[bestLabels.at<int>(i, 0)]); - Meep
@nkint 上述代码显示了一个运行时错误,错误信息为“Opencv Error: Assertion failed (dims <= 2 && data && (unsigned)i0.....” - AHF
@nkint 当你执行(i/src.cols) / src.rows时,这不会将所有内容都设置为零吗(C++整数除法通过较大的数字)? - hovnatan
显示剩余4条评论
3个回答

9

我不是OpenCV的专家,所以我会给出一个与您的问题相关的一般建议。K-means需要一个向量列表,这本质上是一个矩阵:

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

您提供了一个无法使用的图像。您首先需要将图像转换为k-means矩阵格式。对于源图像的每个像素,结果矩阵中都有一行。还要注意,您应该缩放值,使它们所有的值都相似。如果不这样做,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)
  }
}

如果您更喜欢使用HSV而不是RGB,则只需使用HSV值而不是RGB值。可能OpenCV有执行我上面描述的转换的函数,但我无法通过Google快速找到它们。

抱歉,我在哪里可以找到关于这个kmeans特定输入格式的信息? - nkint
在OpenCV文档(http://opencv.willowgarage.com/documentation/cpp/clustering_and_search_in_multi-dimensional_spaces.html)中:`samples - 输入样本的浮点矩阵,每个样本一行`,其中样本表示一个多维点。对于彩色图像,该点具有5个维度(x,y,r,g,b)。这基本上是执行kmeans的标准方法,OpenCV只是使用自己的数据结构来表达它。关于一般的kmeans介绍,我推荐在http://www.ml-class.org上观看有关kmeans的机器学习视频。 - Karel Petranek
我已经订阅了,下一门课程还没有开始呢!:) - nkint
它完美地工作了,谢谢。请查看我发布的编辑,我已经贴出了正确的代码。也许可以使用一些我不知道的OpenCV API方法来进行优化。 - nkint
很高兴它能够正常工作 :) 但是你的代码存在一个微妙的错误 - 在第一个循环中,你将所有三个通道都分配给了第0个元素。这意味着当它们被覆盖时,你只是按颜色(而不是坐标)对其进行聚类。另外,请确保在计算标准化的x、y坐标时使用浮点除法。 - Karel Petranek

9

如果在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;

好的!很棒的方式,我一直在寻找一个简单的方法来完成它!谢谢! - nkint
@zzzz 特征向量中的坐标应该有助于“空间一致性”,对吧?也就是说,它更喜欢将颜色相似且彼此靠近的像素分组在一起。 - David Doria

0

我相信我让它更易懂了一点,我理解了你的例子,并替换了你只使用灰色阴影来替换颜色的部分,使用图像中心的颜色使其使用图像的“主导”颜色。

cv::Mat MyClass::quantizeImage(cv::Mat _inputImage, int _quantizationColors){

Mat src = _inputImage.clone();  //cloning mat data
Mat data = Mat::zeros(src.cols * src.rows, 3, CV_32F);  //Creating the matrix that holds all pixel data
Mat bestLabels, centers, clustered; //Returns from the K Means
vector<Mat> bgr;    //Holds the BGR channels
cv::split(src, bgr);

//Getting all pixels in the Data row column to be compatible with K Means
for (int i = 0; i < src.cols * src.rows; i++) {
    data.at<float>(i, 0) = bgr[0].data[i] / 255.0;
    data.at<float>(i, 1) = bgr[1].data[i] / 255.0;
    data.at<float>(i, 2) = bgr[2].data[i] / 255.0;
}

int K = _quantizationColors;    //Number of clusters
cv::kmeans(data, K, bestLabels,
    TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 10, 1.0),
    3, KMEANS_PP_CENTERS, centers);

centers = centers.reshape(3, centers.rows);
data = data.reshape(3, data.rows);

clustered = Mat(src.rows, src.cols, CV_32F);


Vec3f* p = data.ptr<Vec3f>();
for (size_t i = 0; i < data.rows; i++) {
    int center_id = bestLabels.at<int>(i);
    p[i] = centers.at<Vec3f>(center_id);
}

clustered = data.reshape(3, src.rows);
return clustered;}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接