L'implémentation de l'algorithm de tramage floyd steinberg dans opencv ne fonctionne pas correctement

J'utilise un morceau de code que j'ai obtenu de stackoverflow pour l'algorithm de dithering de floyd steinberg.

C'est comme suit. Mais il n'est pas proprement dithering l'image comme prévu. Quelqu'un a-t-il une implémentation correcte de ceci ou quelqu'un peut-il corriger le code suivant. L'exigence réelle est de convertir une image couleur de 24 bit une image tramée de 1 bit grise.

Je pense que la partie de la méthode floyd steinberg ci-dessous est correcte, mais avant que cette méthode est appelée certaines fonctions sont appelées que je ne sais pas.Je suis très nouveau à opencv.it est pour un projet ios.

  -(UIImage*)processImage:(UIImage*)chosenImage//ios { int nrColors = 8; cv::Mat img; UIImageToMat(chosenImage, img); // i am not sure of this part---> cv::Mat colVec = img.reshape(1, img.rows*img.cols); // change to a Nx3 column vector cv::Mat colVecD; colVec.convertTo(colVecD, CV_32FC3, 1.0); // convert to floating point cv::Mat labels, centers; cv::kmeans(colVecD, nrColors, labels, cv::TermCriteria(CV_TERMCRIT_ITER, 100, 0.1), 3, cv::KMEANS_PP_CENTERS, centers); // compute k mean centers // replace pixels by there corresponding image centers cv::Mat imgPosterized = img.clone(); for(int i = 0; i < img.rows; i++ ) { for(int j = 0; j < img.cols; j++ ) { for(int k = 0; k < 3; k++) { imgPosterized.at<cv::Vec3b>(i,j)[k] = centers.at<float>(labels.at<int>(j+img.cols*i),k); } } } //<---- i am not sure of this part // convert palette back to uchar cv::Mat palette; centers.convertTo(palette,CV_8UC3,1.0); img= floydSteinberg(img,palette); cv::Mat imgGray; //cvtColor(img, imgGray,cv::COLOR_RGBA2GRAY); chosenImage= MatToUIImage(img); return chosenImage; } //floyd steinberg algorithm cv::Mat floydSteinberg(cv::Mat imgOrig, cv::Mat palette) { cv::Mat img = imgOrig.clone(); cv::Mat resImg = img.clone(); for(int i = 0; i < img.rows; i++ ) { for(int j = 0; j < img.cols; j++ ) { cv::Vec3b newpixel = findClosestPaletteColor(img.at<cv::Vec3b>(i,j), palette); resImg.at<cv::Vec3b>(i,j) = newpixel; for(int k=0;k<3;k++) { int quant_error = (int)img.at<cv::Vec3b>(i,j)[k] - newpixel[k]; if(i+1<img.rows) img.at<cv::Vec3b>(i+1,j)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i+1,j)[k] + (7 * quant_error) / 16)); if(i-1 > 0 && j+1 < img.cols) img.at<cv::Vec3b>(i-1,j+1)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i-1,j+1)[k] + (3 * quant_error) / 16)); if(j+1 < img.cols) img.at<cv::Vec3b>(i,j+1)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i,j+1)[k] + (5 * quant_error) / 16)); if(i+1 < img.rows && j+1 < img.cols) img.at<cv::Vec3b>(i+1,j+1)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i+1,j+1)[k] + (1 * quant_error) / 16)); } } } return resImg; } float vec3bDist(cv::Vec3b a, cv::Vec3b b) { return sqrt( pow((float)a[0]-b[0],2) + pow((float)a[1]-b[1],2) + pow((float)a[2]-b[2],2) ); } cv::Vec3b findClosestPaletteColor(cv::Vec3b color, cv::Mat palette) { int i=0; int minI = 0; cv::Vec3b diff = color - palette.at<cv::Vec3b>(0); float minDistance = vec3bDist(color, palette.at<cv::Vec3b>(0)); for (int i=0;i<palette.rows;i++) { float distance = vec3bDist(color, palette.at<cv::Vec3b>(i)); if (distance < minDistance) { minDistance = distance; minI = i; } } return palette.at<cv::Vec3b>(minI); } 

entrez la description de l'image ici

Les décalages que vous utilisez lors du tramage sont incorrects. Par exemple, vous modifiez le pixel à i-1, qui est la ligne précédente que vous avez déjà traitée. Fondamentalement, vous avez échangé le x et le y.

Changez le code pour ceci:

 for(int k=0;k<3;k++) { int quant_error = (int)img.at<cv::Vec3b>(i,j)[k] - newpixel[k]; if(j+1<img.cols) img.at<cv::Vec3b>(i,j+1)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i,j+1)[k] + (7 * quant_error) / 16)); if(i+1 < img.rows && j-1 >= 0) img.at<cv::Vec3b>(i+1,j-1)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i+1,j-1)[k] + (3 * quant_error) / 16)); if(i+1 < img.rows) img.at<cv::Vec3b>(i+1,j)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i+1,j)[k] + (5 * quant_error) / 16)); if(i+1 < img.rows && j+1 < img.cols) img.at<cv::Vec3b>(i+1,j+1)[k] = fmin(255,fmax(0,(int)img.at<cv::Vec3b>(i+1,j+1)[k] + (1 * quant_error) / 16)); } 

La raison pour laquelle seuls les 3/4 de l'image sont tramés est parce que l'image transmise a 4 canaux et que vous la img.at<cv::Vec4b> comme si elle en avait 3. Vous pouvez corriger ceci en utilisant img.at<cv::Vec4b> au lieu d' img.at<cv::Vec3b> .

Si vous voulez modifier le dithering, vous pouvez utiliser un kernel de diffusion d'erreur différent. Floyd Steinberg utilise le motif 7 3 5 1, mais vous pouvez utiliser des motifs et des tailles de kernel différents pour diffuser des quantités différentes et des caractéristiques différentes. Par exemple, vous pouvez diffuser less que la quantité totale d'erreur. Floyd Steinberg diffuse toute l'erreur parce que 7/16 + 3/16 + 5/16 + 1/16 = 1, mais vous pouvez choisir des termes qui totalisent less d'un. Le tramage Atkinson (le type utilisé sur Apple Macintoshes) par exemple ne diffuse que 6 / 8ème de l'erreur, ce qui lui donne un aspect légèrement plus contrasté. Différents kernelx auront des motifs de mouchetures légèrement différents et des "regards". Si vous voulez juste avoir un seul contrôle pour "Dithering", il suffit d'avoir une valeur entre 0 et 1 et de multiplier chacun des termes de votre kernel par celui-ci. Une façon simple d'implémenter ceci dans votre code serait de multiplier le 7 3 5 1 par une valeur entre 0 et 256 représentant votre montant de dither, et split par 4096 au lieu de 16.