参考:https://blog.csdn.net/majinlei121/article/details/49335693
Y(亮度)=(0.299R)+(0.587G)+(0.114*B)
半色调技术是 指用少量的色彩将一幅连续色调图像(如灰度图像和彩色图像)量化为一幅二值图像或是只有少数几种色彩的彩色图像,并且量化后图像在一定距离的视觉效果和原始图像相似的技术 ;
目前半色调技术最普遍的分类法是按照它的处理方式分为:抖动法,误差扩散法,迭代法三大类。
从整体上看,误差扩散的效果是很好的。目前已有的最好的半色调效果仍是基于误差扩散方法。误差扩散也有不足之处。从速度上看,中值阈值法对每个像素只做一次比较运算,速度很快。而误差扩散要进行大量的乘除法运算,速度相对较慢。
下面是我自己
#include <iostream>
#include <opencv.hpp>
#include <opencv\highgui.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
using namespace cv;
using namespace std;
uchar findfit(uchar p_param)
{
//0,16,32,
uchar m = p_param / 16;
uchar n = p_param % 16;
if (n <=8)
{
return m*16;
}
else
{
return (m + 1)*16;
}
}
void fun1(Mat img)
{
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_dst;
int len = sizeof(ushort);
int len1 = sizeof(uchar);
for (int i = 0; i < height; i++)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_dst = dst.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
p_dst[j] = (p_img[j] - minv) / (maxv - minv) * 255;
p_dst[j] = p_dst[j] / 64;
p_dst[j] = p_dst[j] * 64;
//cout << (int)p_dst[j] << " ";
/* if (p_dst[j] > 192)
{
p_dst[j] = 255;
}
else if(p_dst[j] > 128)
{
p_dst[j] = 128;
}
else if (p_dst[j] > 64)
{
p_dst[j] = 64;
}
else
{
p_dst[j] = 0;
}*/
//p_dst[j] = p_img[j*3/2];
//下面是失真较大的转换方法
//int temp = img.at<ushort>(i, j);
//dst.at<uchar>(i, j) = temp;
}
}
// dst = img;
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("2", 0);
cvResizeWindow("2", 192 * 3, 216 * 3);
imshow("2", dst);
imwrite("E:\\4.jpg", dst);
}
void fun2(Mat img)
{
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_dst;
int len = sizeof(ushort);
int len1 = sizeof(uchar);
for (int i = 0; i < height; i++)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_dst = dst.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
p_dst[j] = (p_img[j] - minv) / (maxv - minv) * 255;
if (p_dst[j] > 192)
{
p_dst[j] = 255;
}
else if(p_dst[j] > 128)
{
p_dst[j] = 128;
}
else if (p_dst[j] > 64)
{
p_dst[j] = 64;
}
else
{
p_dst[j] = 0;
}
}
}
// dst = img;
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("3", 0);
cvResizeWindow("3", 192 * 3, 216 * 3);
imshow("3", dst);
imwrite("E:\\4.jpg", dst);
}
void fun3(Mat img)
{
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_dst;
int len = sizeof(ushort);
int len1 = sizeof(uchar);
for (int i = 0; i < height; i++)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_dst = dst.ptr<uchar>(i);
for (int j = 0; j < width; ++j)
{
p_dst[j] = (p_img[j] - minv) / (maxv - minv) * 255;
p_dst[j] = p_dst[j] & 0B11000000;
}
}
// dst = img;
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("4", 0);
cvResizeWindow("4", 192 * 3, 216 * 3);
imshow("4", dst);
imwrite("E:\\4.jpg", dst);
}
//误差扩散法1
void floydsetin(Mat img)
{
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
for (int i = 0; i < height-2;++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i+1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width-2; ++j)
{
if (p_img[j] > 128)
{
tep = 255;
}
else
{
tep = 0;
}
err = p_img[j] - tep;
p_dst[j + 1] += err * a;
p_lw[j - 1] += err * b;
p_lw[j] += err * c;
p_lw[j + 1] += err * d;
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("5", 0);
cvResizeWindow("5", 192 * 3, 216 * 3);
imshow("5", dst);
imwrite("E:\\5.jpg", dst);
}
//误差扩散法2灰阶
void floydsetin_2(Mat img)
{
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
//dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
for (int i = 0; i < height - 2; ++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i + 1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width - 2; ++j)
{
if (p_img[j] > 128)
{
p_dst[j] = 255;
tep = 255;
}
else
{
p_dst[j] = 0;
tep = 0;
}
err = p_img[j] - tep;
p_dst[j + 1] += err * a;
p_lw[j - 1] += err * b;
p_lw[j] += err * c;
p_lw[j + 1] += err * d;
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("2灰阶", 0);
cvResizeWindow("2灰阶", 192 * 3, 216 * 3);
imshow("2灰阶", dst);
imwrite("E:\\2灰阶.jpg", dst);
}
//误差扩散法2灰阶
void my2(Mat img)
{
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
//dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
for (int i = 0; i < height - 2; ++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i + 1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width - 2; ++j)
{
if (p_img[j] > 127)
{
p_dst[j] = 255;
tep = 255;
}
else
{
p_dst[j] = 0;
tep = 0;
}
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("my2灰阶", 0);
cvResizeWindow("my2灰阶", 192 * 3, 216 * 3);
imshow("my2灰阶", dst);
imwrite("E:\\my2灰阶.jpg", dst);
}
//误差扩散法4灰阶
void floydsetin_4(Mat img)
{
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
//dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
for (int i = 0; i < height - 2; ++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i + 1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width - 2; ++j)
{
if (p_img[j] > 192)
{
p_dst[j] = 192;
tep = 192;
}
else if (p_img[j] > 128)
{
p_dst[j] = 128;
tep = 128;
}
else if (p_img[j] > 64)
{
p_dst[j] = 64;
tep = 64;
}
else
{
p_dst[j] = 0;
tep = 0;
}
err = p_img[j] - tep;
p_dst[j + 1] += err * a;
p_lw[j - 1] += err * b;
p_lw[j] += err * c;
p_lw[j + 1] += err * d;
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("4灰阶", 0);
cvResizeWindow("4灰阶", 192 * 3, 216 * 3);
imshow("4灰阶", dst);
imwrite("E:\\4灰阶.jpg", dst);
}
//误差扩散法8灰阶
void floydsetin_8(Mat img)
{
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
//dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
for (int i = 0; i < height - 2; ++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i + 1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width - 2; ++j)
{
if (p_img[j] > 224)
{
p_dst[j] = 224;
tep = 224;
}
else if (p_img[j] > 192)
{
p_dst[j] = 192;
tep = 192;
}
else if (p_img[j] > 160)
{
p_dst[j] = 160;
tep = 160;
}
else if (p_img[j] > 128)
{
p_dst[j] = 128;
tep = 128;
}
else if (p_img[j] > 96)
{
p_dst[j] = 96;
tep = 96;
}
else if (p_img[j] > 64)
{
p_dst[j] = 64;
tep = 64;
}
else if (p_img[j] > 32)
{
p_dst[j] = 32;
tep = 32;
}
else
{
p_dst[j] = 0;
tep = 0;
}
err = p_img[j] - tep;
p_dst[j + 1] += err * a;
p_lw[j - 1] += err * b;
p_lw[j] += err * c;
p_lw[j + 1] += err * d;
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("8灰阶", 0);
cvResizeWindow("8灰阶", 192 * 3, 216 * 3);
imshow("8灰阶", dst);
imwrite("E:\\8灰阶.jpg", dst);
}
//误差扩散法16灰阶
void floydsetin_16(Mat img)
{
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
//dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
for (int i = 0; i < height - 2; ++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i + 1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width - 2; ++j)
{
for (int m = 15; m >= 0; --m)
{
if (p_img[j] > 16*m)
{
p_dst[j] = 16 * m;
tep = 16 * m;
break;
}
}
err = p_img[j] - tep;
p_dst[j + 1] += err * a;
p_lw[j - 1] += err * b;
p_lw[j] += err * c;
p_lw[j + 1] += err * d;
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("16灰阶", 0);
cvResizeWindow("16灰阶", 192 * 3, 216 * 3);
imshow("16灰阶", dst);
imwrite("E:\\16灰阶.jpg", dst);
}
//误差扩散法16灰阶
void floydsetin_16_001(Mat img)
{
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
//dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
for (int i = 0; i < height - 2; ++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i + 1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width - 2; ++j)
{
/*for (int m = 15; m >= 0; --m)
{
if (p_img[j] > 16 * m)
{
p_dst[j] = 16 * m;
tep = 16 * m;
break;
}
}*/
int te = findfit(p_img[j]);
p_dst[j] = te;
tep = te;
err = p_img[j] - tep;
p_dst[j + 1] += err * a;
p_lw[j - 1] += err * b;
p_lw[j] += err * c;
p_lw[j + 1] += err * d;
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("16灰阶_001", 0);
cvResizeWindow("16灰阶_001", 192 * 3, 216 * 3);
imshow("16灰阶_001", dst);
imwrite("E:\\16灰阶_001.jpg", dst);
}
//误差扩散法n灰阶
void floydsetin_16(Mat img , int m)
{
if (m <= 0)
{
return;
}
double a = 0.4375;
double b = 0.1875;
double c = 0.3125;
double d = 0.0625;
DWORD Start_time = GetTickCount(); //计时开始
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
//dst = img;
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(img, minp, maxp); //取得像素值最大值和最小值
int t = CV_8UC1;
//用指针访问像素,速度更快
uchar* p_img;
uchar* p_lw;
uchar* p_dst;
int tep = 0;
int err = 0;
int step = 256 / m;
for (int i = 0; i < height - 2; ++i)
{
p_img = img.ptr<uchar>(i);//获取每行首地址
p_lw = img.ptr<uchar>(i + 1);
p_dst = dst.ptr<uchar>(i);
for (int j = 2; j < width - 2; ++j)
{
for (; m >= 0; --m)
{
if (p_img[j] > step * (m-1))
{
p_dst[j] = step * (m)-1;
tep = step * (m)-1;
if (m == 0)
{
p_dst[j] = 0;
tep = 0;
}
break;
}
}
err = p_img[j] - tep;
p_dst[j + 1] += err * a;
p_lw[j - 1] += err * b;
p_lw[j] += err * c;
p_lw[j + 1] += err * d;
}
}
DWORD End_time = GetTickCount(); //计时结束
cout << "Time used:" << End_time - Start_time << " ms" << '\n';
namedWindow("灰阶", 0);
cvResizeWindow("灰阶", 192 * 3, 216 * 3);
imshow("灰阶", dst);
imwrite("E:\\灰阶.jpg", dst);
}
int main()
{
Mat img = imread("E:\\1.jpg", 0);//加载图像;
if (img.empty())
{
cout << "打开文件失败" << endl;
return -1;
}
int width = img.cols;//图片宽度
int height = img.rows;//图片高度
Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
dst = img;
imwrite("E:\\灰阶原图.jpg", dst);
int m_channel = (&img)->channels();
Size m_size = img.size();
cout << m_size << endl;
cout << img.total() << endl;
cout << img.rows << " " << img.cols << endl;
//cout << img << endl;
cout << img.isContinuous() << endl;
cout << img.type()<< endl;
cout << CV_8UC1 << endl;
cout << CV_8UC2 << endl;
cout << CV_8UC3 << endl;
for (int r = 0; r < img.rows; ++r)
{
const uchar* ptr = img.ptr<uchar>(r);
for (int c = 0; c < img.cols; ++c)
{
//cout << ptr[c] << ",";
}
//cout << endl;
}
namedWindow("1", WINDOW_NORMAL);
cvResizeWindow("1", 192 * 3, 216 * 3);
imshow("1", img);
//方法1
//fun1(img);
//fun2(img);
//fun3(img);
//my2(img);
//floydsetin_2(img);
//floydsetin_2(img);
//floydsetin_4(img);
//floydsetin_8(img);
//floydsetin_16(img);
floydsetin_16_001(img);
findfit(19);
waitKey(0);
system("pause");
return 0;
}