17


11

OpenCV utilisant k-means pour postériser une image

Je veux poster une image avec k-means et OpenCV dans l’interface C ++ (espace de noms cv) et j’obtiens des résultats étranges. J’en ai besoin pour réduire le bruit. Voici mon code:

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

Mais j’obtiens un résultat bizarre

image: https: //i.stack.imgur.com/dGy2i.png [Fruit]

Première image: originale

Deuxième image: après k-moyennes.

Aucun conseil?

'' '' '

Mise à jour: la bonne solution. peut-être que quelqu’un peut m’aider à optimiser le code?

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

#include

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 bgr;
    cv::split(src, bgr);
    // i think there is a better way to split pixel bgr color
    for(int i=0; i(i,0) = (i/src.cols) / src.rows;
        p.at(i,1) = (i%src.cols) / src.cols;
        p.at(i,2) = bgr[0].data[i] / 255.0;
        p.at(i,3) = bgr[1].data[i] / 255.0;
        p.at(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(i/src.cols, i%src.cols) = (float)(colors[bestLabels.at(0,i)]);
//      cout << bestLabels.at(0,i) << " " <<
//              colors[bestLabels.at(0,i)] << " " <<
//              clustered.at(i/src.cols, i%src.cols) << " " <<
//              endl;
    }

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

    waitKey();
    return 0;
}

Résultat:

image: https: //i.stack.imgur.com/QBVcV.png [Fruit postérisé]

2 Answer


8


Je ne suis pas un expert en OpenCV donc je vais donner un conseil général qui se rapporte à votre question K-means prend une liste de vecteurs qui est essentiellement une matrice:

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

Vous lui donnez une image qui ne fonctionnera pas. Vous devez d’abord convertir l’image dans ce format de matrice k-means. Pour chaque pixel de l’image source, vous avez une ligne dans la matrice résultante. Notez également que vous devez mettre à l’échelle les valeurs afin qu’elles aient toutes des valeurs similaires. Si vous ne le faites pas, les coordonnées x et y auront généralement une "gravité" beaucoup plus élevée que la couleur, ce qui conduit à des résultats insatisfaisants. Pseudocode 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...

Par conséquent, vous obtenez des étiquettes pour chaque pixel individuel qui correspond au cluster auquel il a été affecté. Vous devez ensuite déterminer la couleur des clusters - cela peut varier de la prise de la valeur de couleur du pixel central au calcul d’une couleur moyenne / médiane du cluster. Après avoir déterminé la couleur, parcourez simplement l’image et définissez les pixels sur leurs couleurs de cluster:

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)
  }
}

Si vous préférez utiliser HSV au lieu de RVB, utilisez simplement des valeurs HSV au lieu de RVB.

Il est possible qu’OpenCV ait des fonctions qui effectuent exactement la conversion que j’ai décrite ci-dessus mais je n’ai pas pu les trouver rapidement en utilisant Google.


8


Si vous n’avez pas besoin de coordonnées x, y dans vos k-moyennes, vous pouvez organiser les données beaucoup plus rapidement comme suit en utilisant la commande de remodelage:

  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;