我正在使用stackoverflow
获得的一段代码,用于floyd steinberg抖动算法。
如下所示。但它并没有像预期的那样正确地抖动图像。任何人都有正确的实现,或任何人都可以更正以下代码。实际要求是将24 bit
彩色图像转换为1 bit
灰色抖动图像。
我认为下面的floyd steinberg方法部分是正确的,但在调用该方法之前,我调用了一些我不知道的函数。我是一个非常新的opencv.it用于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);
}
答案 0 :(得分:1)
抖动错误时使用的偏移量。例如,您正在更改i-1处的像素,这是您已处理的上一行。基本上,你交换了x和y。
将代码更改为:
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));
}
只有3/4的图像被抖动的原因是因为传入的图像有4个通道而你正在处理它,好像它有3个。你可以使用img.at<cv::Vec4b>
而不是{{}来解决这个问题。 1}}。
如果您想修改抖动,可以使用其他error-diffusion kernel。 Floyd Steinberg使用7 3 5 1模式,但您可以使用不同的模式和内核大小来抖动不同的数量和不同的特征。例如,您可以扩散小于总错误量。 Floyd Steinberg扩散了所有错误,因为7/16 + 3/16 + 5/16 + 1/16 = 1,但您可以选择加起来小于1的项。例如,Atkinson dithering(Apple Macintoshes上使用的那种)只会扩散错误的6/8,从而使其具有更高的对比度外观。不同的内核将具有略微不同的斑点模式和&#34;外观&#34;。如果您只想对&#34; dither amount&#34;进行单一控制。只需要一个介于0和1之间的值,然后将内核的每个项乘以它。在代码中实现此功能的一种简单方法是将7 3 5 1乘以0到256之间的值,表示您的抖动量,并除以4096而不是16。