https://blog.csdn.net/weixin_34406061/article/details/92425842 代码是C#,我把他改成C++
Floyd-Steinberg抖动算法简直量身为价签这种低颜色呈现设备准备的。由于价签的墨水屏能够呈现的色彩非常有限,根据型号不同,有的只能显示黑白,有的只能显示黑白红,有的只能显示黑白黄,所以对于一张普通图片,需要将其转换为两色或者三色才能比较好地在价签上展示出来。
左边图是主函数调用传入参数1 , 右边是传入参数3
#include <iostream>
#include <opencv.hpp>
#include <opencv\highgui.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
using namespace cv;
using namespace std;
#define M_COUNT 255
class RGBTriple
{
public:
byte* channels;
int m_size;
RGBTriple() { channels = new byte[3]; }
RGBTriple(int R, int G, int B)
{
channels = new byte[]{ (byte)R, (byte)G, (byte)B };
}
};
class BMPConverter
{
public:
RGBTriple* getPalette(int deviceType)
{
RGBTriple* palette = NULL;
if (deviceType == 0)
{
//黑白价签
palette = new RGBTriple[]{
RGBTriple(0, 0, 0),
RGBTriple(255, 255, 255)
};
palette->m_size = 2;
}
else if (deviceType == 1) {
//黑白红价签
palette = new RGBTriple[]{
RGBTriple(0, 0, 0),
RGBTriple(255, 255, 255),
RGBTriple(0, 0,255)
};
palette->m_size = 3;
}
else if (deviceType == 2)
{
//黑白黄价签
palette = new RGBTriple[]{
RGBTriple(0, 0, 0),
RGBTriple(255, 255, 255),
RGBTriple(255, 255, 0)
};
palette->m_size = 3;
}
else if (deviceType == 3)
{ //RGB
palette = new RGBTriple[]
{
RGBTriple(0 * M_COUNT, 0 * M_COUNT, 0 * M_COUNT),
RGBTriple(0 * M_COUNT, 0 * M_COUNT, 1 * M_COUNT),
RGBTriple(0 * M_COUNT, 1 * M_COUNT, 0 * M_COUNT),
RGBTriple(0 * M_COUNT, 1 * M_COUNT, 1 * M_COUNT),
RGBTriple(1 * M_COUNT, 0 * M_COUNT, 0 * M_COUNT),
RGBTriple(1 * M_COUNT, 0 * M_COUNT, 1 * M_COUNT),
RGBTriple(1 * M_COUNT, 1 * M_COUNT, 0 * M_COUNT),
RGBTriple(1 * M_COUNT, 1 * M_COUNT, 1 * M_COUNT)
};
palette->m_size = 8;
}
return palette;
};
void floydSteinbergDither(Mat image, RGBTriple palette[])
{
//byte result = new byte[image.length][image[0].length];
Mat result = Mat::zeros(image.rows, image.cols, CV_8UC3);//先生成空的目标图片
//result = image;
uchar* p_img;
Mat matb, matg, matr, matr_dst, matg_dst, matb_dst;
uchar* p_img_b;
uchar* p_img_g;
uchar* p_img_r;
uchar* p_img_b_dst;
uchar* p_img_g_dst;
uchar* p_img_r_dst;
uchar* p_lw_b;
uchar* p_lw_g;
uchar* p_lw_r;
//1、先进行通道分解
Mat planes[3];
split(image, planes);
matb = planes[0];
matg = planes[1];
matr = planes[2];
Mat planes_dst[3];
split(result, planes_dst);
matb_dst = planes_dst[0];
matg_dst = planes_dst[1];
matr_dst = planes_dst[2];
for (int i = 0; i < image.rows - 2; i++)
{
//p_img_b = matb.ptr<uchar>(i);
//p_img_g = matg.ptr<uchar>(i);
//p_img_r = matr.ptr<uchar>(i);
p_img = result.ptr<uchar>(i);
p_img_b = matb.ptr<uchar>(i);
p_img_g = matg.ptr<uchar>(i);
p_img_r = matr.ptr<uchar>(i);
p_img_b_dst = matb_dst.ptr<uchar>(i);
p_img_g_dst = matg_dst.ptr<uchar>(i);
p_img_r_dst = matr_dst.ptr<uchar>(i);
p_lw_b = matb.ptr<uchar>(i + 1);
p_lw_g = matg.ptr<uchar>(i + 1);
p_lw_r = matr.ptr<uchar>(i + 1);
for (int j = 0; j < image.cols - 2; j++)
{
RGBTriple currentPixel;// = (RGBTriple)(matb_dst.ptr<uchar>(x), 1, 1);
currentPixel.channels[0] = p_img_b[j];
currentPixel.channels[1] = p_img_g[j];
currentPixel.channels[2] = p_img_r[j];
byte index = findNearestColor(currentPixel, palette);
//p_img[j] = index;
#if 1
switch (index)
{
case 0:
p_img_b_dst[j] = 0* M_COUNT;
p_img_g_dst[j] = 0*M_COUNT;
p_img_r_dst[j] = 0* M_COUNT;
break;
case 1:
p_img_b_dst[j] = 0* M_COUNT;
p_img_g_dst[j] = 0* M_COUNT;
p_img_r_dst[j] = 1* M_COUNT;
break;
case 2:
p_img_b_dst[j] = 0* M_COUNT;
p_img_g_dst[j] = 1* M_COUNT;
p_img_r_dst[j] = 0* M_COUNT;
break;
case 3:
p_img_b_dst[j] = 0* M_COUNT;
p_img_g_dst[j] = 1* M_COUNT;
p_img_r_dst[j] = 1* M_COUNT;
break;
case 4:
p_img_b_dst[j] = 1* M_COUNT;
p_img_g_dst[j] = 0* M_COUNT;
p_img_r_dst[j] = 0* M_COUNT;
break;
case 5:
p_img_b_dst[j] = 1* M_COUNT;
p_img_g_dst[j] = 0* M_COUNT;
p_img_r_dst[j] = 1* M_COUNT;
break;
case 6:
p_img_b_dst[j] = 1* M_COUNT;
p_img_g_dst[j] = 1* M_COUNT;
p_img_r_dst[j] = 0* M_COUNT;
break;
case 7:
p_img_b_dst[j] = 1* M_COUNT;
p_img_g_dst[j] = 1* M_COUNT;
p_img_r_dst[j] = 1* M_COUNT;
break;
default:
p_img_b_dst[j] = 1* M_COUNT;
p_img_g_dst[j] = 1* M_COUNT;
p_img_r_dst[j] = 1* M_COUNT;
break;
}
#endif
// for (int m = 0; m < 3; m++)
{
int error_b = (currentPixel.channels[0] & 0xff) - (palette[index].channels[0] & 0xff);
int error_g = (currentPixel.channels[1] & 0xff) - (palette[index].channels[1] & 0xff);
int error_r = (currentPixel.channels[2] & 0xff) - (palette[index].channels[2] & 0xff);
//planes_dst[i + 0][j + 1] = plus_truncate_uchar(image[y + 0][x + 1].channels[i], (error * 7) >> 4);
#if 0
p_img_b[j + 1] = plus_truncate_uchar(p_img_b[j + 1], (error_b * 7) >> 4);
p_img_g[j + 1] = plus_truncate_uchar(p_img_g[j + 1], (error_g * 7) >> 4);
p_img_r[j + 1] = plus_truncate_uchar(p_img_r[j + 1], (error_r * 7) >> 4);
p_lw_b[j - 1] = plus_truncate_uchar(p_lw_b[j - 1], (error_b * 3) >> 4);
p_lw_g[j - 1] = plus_truncate_uchar(p_lw_g[j - 1], (error_g * 3) >> 4);
p_lw_r[j - 1] = plus_truncate_uchar(p_lw_r[j - 1], (error_r * 3) >> 4);
p_lw_b[j] = plus_truncate_uchar(p_lw_b[j], (error_b * 5) >> 4);
p_lw_g[j] = plus_truncate_uchar(p_lw_g[j], (error_g * 5) >> 4);
p_lw_r[j] = plus_truncate_uchar(p_lw_r[j], (error_r * 5) >> 4);
p_lw_b[j + 1] = plus_truncate_uchar(p_lw_b[j], (error_b * 1) >> 4);
p_lw_g[j + 1] = plus_truncate_uchar(p_lw_g[j], (error_g * 1) >> 4);
p_lw_r[j + 1] = plus_truncate_uchar(p_lw_r[j], (error_r * 1) >> 4);
#endif
#if 1
p_img_b[j + 1] = plus_truncate_uchar(p_img_b[j + 1], (error_b * 7) / 16);
p_img_g[j + 1] = plus_truncate_uchar(p_img_g[j + 1], (error_g * 7) / 16);
p_img_r[j + 1] = plus_truncate_uchar(p_img_r[j + 1], (error_r * 7) / 16);
p_lw_b[j - 1] = plus_truncate_uchar(p_lw_b[j - 1], (error_b * 3) / 16);
p_lw_g[j - 1] = plus_truncate_uchar(p_lw_g[j - 1], (error_g * 3) / 16);
p_lw_r[j - 1] = plus_truncate_uchar(p_lw_r[j - 1], (error_r * 3) / 16);
p_lw_b[j] = plus_truncate_uchar(p_lw_b[j], (error_b * 5) / 16);
p_lw_g[j] = plus_truncate_uchar(p_lw_g[j], (error_g * 5) / 16);
p_lw_r[j] = plus_truncate_uchar(p_lw_r[j], (error_r * 5) / 16);
p_lw_b[j + 1] = plus_truncate_uchar(p_lw_b[j + 1], (error_b * 1) / 16);
p_lw_g[j + 1] = plus_truncate_uchar(p_lw_g[j + 1], (error_g * 1) / 16);
p_lw_r[j + 1] = plus_truncate_uchar(p_lw_r[j + 1], (error_r * 1) / 16);
#endif
}
}
}
merge(planes_dst, 3, result);
imshow("输出1", result);
imwrite("E:\\00test001.png", result);
// cout << result << endl;
waitKey();
}
void floydSteinbergDither_test(Mat image, RGBTriple palette[])
{
Mat result = Mat::zeros(image.rows, image.cols, CV_8UC3);//先生成空的目标图片
//result = image;
Mat matr_dst, matg_dst, matb_dst;
uchar* p_img_b_dst;
uchar* p_img_g_dst;
uchar* p_img_r_dst;
vector<Mat> planes_dst;
split(result, planes_dst);
matb_dst = planes_dst[0];
matg_dst = planes_dst[1];
matr_dst = planes_dst[2];
for (int i = 0; i < image.rows - 2; i++)
{
p_img_b_dst = matb_dst.ptr<uchar>(i);
p_img_g_dst = matg_dst.ptr<uchar>(i);
p_img_r_dst = matr_dst.ptr<uchar>(i);
for (int j = 0; j < image.cols - 2; j++)
{
p_img_b_dst[j] = 255;
p_img_g_dst[j] = 255;
p_img_r_dst[j] = 255;
}
}
merge(planes_dst, result);
imshow("输出1", result);
//cout << result << endl;
waitKey();
}
byte plus_truncate_uchar(byte a, int b)
{
if ((a & 0x80) != 0)
{
//cout << "hhh" << endl;
}
if ((a & 0xff) + b < 0)
{
return 0;
}
else if ((a & 0xff) + b > 255)
{
return (byte)255;
}
else
{
return (byte)(a + b);
}
}
byte findNearestColor(RGBTriple color, RGBTriple palette[])
{
int minDistanceSquared = 255 * 255 + 255 * 255 + 255 * 255 + 1;
byte bestIndex = 0;
for (byte i = 0; i < palette->m_size; i++)
{
int Rdiff = (color.channels[0] & 0xff) - (palette[i].channels[0] & 0xff);
int Gdiff = (color.channels[1] & 0xff) - (palette[i].channels[1] & 0xff);
int Bdiff = (color.channels[2] & 0xff) - (palette[i].channels[2] & 0xff);
int distanceSquared = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff;
if (distanceSquared < minDistanceSquared)
{
minDistanceSquared = distanceSquared;
bestIndex = i;
}
}
return bestIndex;
}
};
int main()
{
cout << "hello" << endl;
//Mat img = imread("E:\\photo\\003.png", 1);
Mat img = imread("E:\\test.png");
if (img.empty())
{
cout << "读取原图错误!" << endl;
return false;
}
if (img.empty())
{
cout << "读取logo错误!" << endl;
}
namedWindow("原图");
//cvResizeWindow("原图", 2245/3 , 3142/3 );
imshow("原图", img);
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
//Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
BMPConverter m;
RGBTriple* palette = m.getPalette(3);
m.floydSteinbergDither(img, palette);
//m.floydSteinbergDither_test(img, palette);
return 0;
}