【问题标题】:What is the difference between kmeans and cvKMeans2 algorithms in OpenCV?OpenCV中的kmeans和cvKMeans2算法有什么区别?
【发布时间】:2018-05-31 21:22:14
【问题描述】:

我想在图片上找到主要的 N 种颜色。为此,我决定使用 KMeans 算法。我的项目是用 C 语言编写的,这就是我使用 cvKMeans2 算法的方式。但它给了我非常奇怪的结果。然后我决定在 OpenCV C++ 上尝试 kmeans 算法。它给了我更准确的结果。那么,我的错在哪里?有人可以向我解释吗?

1.我用这张图片做测试。

2。在 C 上实现。

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

#define CLUSTERS 3


int main(int argc, char **argv) {

    const char *filename = "test_12.jpg";
    IplImage *tmp = cvLoadImage(filename);
    if (!tmp) {
        return -1;
    }

    IplImage *src = cvCloneImage(tmp);
    cvCvtColor(tmp, src, CV_BGR2RGB);

    CvMat *samples = cvCreateMat(src->height * src->width, 3, CV_32F);
    for (int i = 0; i < samples->height; i++) {
        samples->data.fl[i * 3 + 0] = (uchar) src->imageData[i * 3 + 0];
        samples->data.fl[i * 3 + 1] = (uchar) src->imageData[i * 3 + 1];
        samples->data.fl[i * 3 + 2] = (uchar) src->imageData[i * 3 + 2];
    }

    CvMat *labels = cvCreateMat(samples->height, 1, CV_32SC1);
    CvMat *centers = cvCreateMat(CLUSTERS, 3, CV_32FC1);

    int flags = 0;
    int attempts = 5;
    cvKMeans2(samples, CLUSTERS, labels,
              cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 10000, 0.005),
              attempts, 0, flags, centers);

    int rows = 40;
    int cols = 300;
    IplImage *des = cvCreateImage(cvSize(cols, rows), 8, 3);

    int part = 4000;
    int r = 0;
    int u = 0;
    for (int y = 0; y < 300; ++y) {
        for (int x = 0; x < 40; ++x) {
            if (u >= part) {
                r++;
                part = (r + 1) * part;
            }
            des->imageData[(300 * x + y) * 3 + 0] = static_cast<char>(centers->data.fl[r * 3 + 0]);
            des->imageData[(300 * x + y) * 3 + 1] = static_cast<char>(centers->data.fl[r * 3 + 1]);
            des->imageData[(300 * x + y) * 3 + 2] = static_cast<char>(centers->data.fl[r * 3 + 2]);
            u++;
        }
    }

    IplImage *dominant_colors = cvCloneImage(des);
    cvCvtColor(des, dominant_colors, CV_BGR2RGB);

    cvNamedWindow("dominant_colors", CV_WINDOW_AUTOSIZE);
    cvShowImage("dominant_colors", dominant_colors);
    cvWaitKey(0);
    cvDestroyWindow("dominant_colors");

    cvReleaseImage(&src);
    cvReleaseImage(&des);
    cvReleaseMat(&labels);
    cvReleaseMat(&samples);
    return 0;
}

3.在 C++ 上实现。

#include <cv.h>
#include <opencv/cv.hpp>

#define CLUSTERS 3

int main(int argc, char **argv) {
    const cv::Mat &tmp = cv::imread("test_12.jpg");
    cv::Mat src;
    cv::cvtColor(tmp, src, CV_BGR2RGB);

    cv::Mat samples(src.rows * src.cols, 3, CV_32F);

    for (int y = 0; y < src.rows; y++)
        for (int x = 0; x < src.cols; x++)
            for (int z = 0; z < 3; z++)
                samples.at<float>(y + x * src.rows, z) = src.at<cv::Vec3b>(y, x)[z];

    int attempts = 5;
    cv::Mat labels;
    cv::Mat centers;

    kmeans(samples, CLUSTERS, labels, cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 1000, 0.005),
           attempts, cv::KMEANS_PP_CENTERS, centers);

    cv::Mat colors(cv::Size(CLUSTERS * 100, 30), tmp.type());
    int p = 100;
    int cluster_id = 0;
    for (int x = 0; x < CLUSTERS * 100; x++) {
        for (int y = 0; y < 30; y++) {
            if (x >= p) {
                cluster_id++;
                p = (cluster_id + 1) * 100;
            }
            colors.at<cv::Vec3b>(y, x)[0] = static_cast<uchar>(centers.at<float>(cluster_id, 0));
            colors.at<cv::Vec3b>(y, x)[1] = static_cast<uchar>(centers.at<float>(cluster_id, 1));
            colors.at<cv::Vec3b>(y, x)[2] = static_cast<uchar>(centers.at<float>(cluster_id, 2));
        }
    }

    cv::Mat dominant_colors;
    cv::cvtColor(colors, dominant_colors, CV_RGB2BGR);
    cv::imshow("dominant_colors", dominant_colors);
    cv::waitKey(0);

    return 0;
}

4. C 上的代码结果。

5. C++ 上的代码结果。

【问题讨论】:

  • 没有区别,它是相同的底层代码。不同的是您调用它们时使用的参数——TermCriteria 和标志。最重要的是,您的 C 和 C++ 实现之间还有其他一些细微差别。
  • 我认为标准之间没有显着差异。所以我意识到我在哪里犯了错误。 IplImage 有一个字段 widthStep。它被填充到 4 的倍数。

标签: c++ c opencv


【解决方案1】:

我发现了我的错误。它与IplImage 的 widthStep 字段有关。当我阅读here widthStep 时,出于性能原因,它被填充为 4 的倍数。如果 widthStep 等于 30,它将填充到 32。

int h = src->height;
int w = src->width;
int c = 3;
int delta = 0;
for (int i = 0, y = 0; i < h; ++i) {
    for (int j = 0; j < w; ++j) {
        for (int k = 0; k < c; ++k, y++) {
            samples->data.fl[i * w * c + c * j + k] = (uchar) src->imageData[delta + i * w * c + c * j + k];
        }
    }
    delta += src->widthStep - src->width * src->nChannels;
}

带指针

for (int x = 0, i = 0; x < src->height; ++x) {
    auto *ptr = (uchar *) (src->imageData + x * src->widthStep);
    for (int y = 0; y < src->width; ++y, i++) {
        for (int j = 0; j < 3; ++j) {
            samples->data.fl[i * 3 + j] = ptr[3 * y + j];
        }
    }
}

【讨论】:

    猜你喜欢
    • 2014-01-06
    • 1970-01-01
    • 2017-08-31
    • 1970-01-01
    • 2013-09-02
    • 2014-08-09
    • 1970-01-01
    • 2015-06-17
    • 1970-01-01
    相关资源
    最近更新 更多