opencv 中的 floyd steinberg 抖动算法实现无法正常工作
Posted
技术标签:
【中文标题】opencv 中的 floyd steinberg 抖动算法实现无法正常工作【英文标题】:floyd steinberg dithering algorithm implementation in opencv does not work properly 【发布时间】:2015-08-12 06:09:36 【问题描述】:我正在使用从 ***
获得的一段代码用于 floyd steinberg 抖动算法。
如下。但它没有像预期的那样正确抖动图像。有没有人对此有正确的实现,或者任何人都可以更正下面的代码。实际需求是将24 bit
彩色图像转换为1 bit
灰度抖动图像。
我认为下面的 floyd steinberg 方法部分是正确的,但是在调用该方法之前调用了一些我不知道的函数。我对 opencv 很陌生。它是针对 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);
【问题讨论】:
问题是palette
,目前是8位。所以你的算法只是将它从 24 位转换为 8 位
此代码不是为了抖动到黑白,而是为了优化调色板。而且效率极低。
@YvesDaoust 我添加了 cvtColor 以将优化的调色板转换为灰度。您有使用 floyd steinberg 算法将 24 位彩色图像转换为灰色的实现吗?
@bro 不仅如您在图像中看到的那样,它不是正确抖动的图像。它应该包含点而不是线。并且它的部分转换仅如您在图像中看到的那样,右侧未转换
使用 (R+G+B)/3 作为亮度,而不是 3 个单独的通道。将此平均值与 128 进行比较,以决定黑色 (0) 或白色 (255)。使用该值计算量化误差。根据像素格式分配目标图像 (0, 0, 0)/(255, 255, 255) 或 0/255 或 0/1。
【参考方案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>
而不是 img.at<cv::Vec3b>
来解决这个问题。
如果您想修改抖动,您可以使用不同的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 的错误,这使它看起来具有更高的对比度。不同的内核会有略微不同的斑点图案和“外观”。如果您只想对“抖动量”进行单一控制,只需将值设置在 0 到 1 之间,然后将内核的每个项乘以它。在您的代码中实现此功能的一种简单方法是将 7 3 5 1 乘以 0 到 256 之间的值(代表您的抖动量),然后除以 4096 而不是 16。
【讨论】:
与之前的代码相比,它给出了相同的输出。你能检查一下为什么这张图片只转换了部分 和以前完全一样还是差不多? 好吧,我觉得它现在好多了,线条现在不太明显了......但是正确的部分仍然没有转换......你能检查一下 它正在转换图像的 3/4 的外观。也许传入的图像有 4 个通道,但您将其视为 3 个通道?尝试使用 img.at<:vec4b> 好的,解决了这个问题。现在图像看起来很好..你能把它作为答案吗?你知道如何增加或减少图像的抖动量吗?以上是关于opencv 中的 floyd steinberg 抖动算法实现无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章
Android Floyd-Steinberg-DitheringStucki-dither 抖动处理
使用特定调色板在 C# 中实现 Floyd-Steinberg 抖动