OpenCV - Как нанести Kmeans на изображение в градациях серого? - PullRequest
0 голосов
/ 29 января 2020

Я пытаюсь сгруппировать изображение в градациях серого с помощью Kmeans.

Во-первых, у меня вопрос:

Является ли Kmeans лучшим способом кластеризации мата или есть более новые и более эффективные подходы?

Во-вторых, когда я пытаюсь это сделать:

Mat degrees = imread("an image" , IMREAD_GRAYSCALE);
const unsigned int singleLineSize = degrees.rows * degrees.cols;
Mat data = degrees.reshape(1, singleLineSize);
data.convertTo(data, CV_32F);
std::vector<int> labels;
cv::Mat1f colors;
cv::kmeans(data, 3, labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 10, 1.), 2, cv::KMEANS_PP_CENTERS, colors);
for (unsigned int i = 0; i < singleLineSize; i++) {
    data.at<float>(i) = colors(labels[i]);
}

Mat outputImage = data.reshape(1, degrees.rows);
outputImage.convertTo(outputImage, CV_8U);
imshow("outputImage", outputImage);

Результат (outputImage) пуст.

Когда я пытаюсь умножить colors в for l oop как data.at<float>(i) = 255 * colors(labels[i]); Я получаю эту ошибку:

Необработанное исключение: целочисленное деление на ноль.

Как правильно кластеризовать изображение в градациях серого?

1 Ответ

1 голос
/ 29 января 2020

Мне кажется, что вы неправильно анализируете метки и цвета в своей выходной матрице.

K-means возвращает эту информацию:

  • Labels - Это матрица int со всеми метками кластера. Это «столбцовая» матрица размером TotalImagePixels x 1.

  • Центры - это то, что вы называете «Цвета». Это матрица с плавающей точкой, содержащая центры кластеров. Матрица имеет размер NumberOfClusters x FeatureMean.

В этом случае, когда вы используете BGR пикселей , поскольку в качестве «объектов» учитывается, что Центры имеют 3 столбца: Одно среднее для канала B - одно среднее для канала G и, наконец, среднее для канала R.

Таким образом, в основном вы проходите через матрицу (простой) метки, извлекаете метку и используете это значение. в качестве индекса в матрице Центров для получения 3 цветов.

Один из способов сделать это состоит в следующем, используя автоматический спецификатор данных и зацикливая вместо этого входное изображение (таким образом, мы можем индексировать каждую метку ввода проще):

    //prepare an empty output matrix
    cv::Mat outputImage( inputImage.size(), inputImage.type() );

    //loop thru the input image rows...
    for( int row = 0; row != inputImage.rows; ++row ){

        //obtain a pointer to the beginning of the row
        //alt: uchar* outputImageBegin = outputImage.ptr<uchar>(row);            
        auto outputImageBegin = outputImage.ptr<uchar>(row);

        //obtain a pointer to the end of the row
        auto outputImageEnd = outputImageBegin + outputImage.cols * 3;

        //obtain a pointer to the label:
        auto labels_ptr = labels.ptr<int>(row * inputImage.cols);

        //while the end of the image hasn't been reached...
        while( outputImageBegin != outputImageEnd ){

            //current label index:
            int const cluster_idx = *labels_ptr;

            //get the center of that index:
            auto centers_ptr = centers.ptr<float>(cluster_idx);

            //we got an implicit VEC3B vector, we must map the BGR items to the
            //output mat:
            clusteredImageBegin[0] = centers_ptr[0];
            clusteredImageBegin[1] = centers_ptr[1];
            clusteredImageBegin[2] = centers_ptr[2];

            //increase the row "iterator" of our matrices:
            clusteredImageBegin += 3; ++labels_ptr;
        }
    }
...