(1) 了解各种图像艺术化处理方法的原理。
(2) 掌握各种艺术化处理方法的实现过程。
编程实现三种以上艺术化处理方法,三类效果中每类至少实现两种。
计算机, VS2019+openCV
我们通常说的黑白照片并不是数字图像中的二值图,而是灰度图。由彩色图像转化为灰度图像的过程叫做灰度化处理。一般情况下彩色图像每个像素用3个字节表示,每个字节对应着R、G、B分量的亮度(红、绿、蓝),转换后的灰度图像的一个像素用一个字节表示该点的灰度值,它的值在0~255之间,数值越大,该点越白,即越亮,越小则越黑。转换关系为:
其中Gray(i,j)为转换后的灰度图像在(i,j)点处的灰度值。
灰度图只能表现256种颜色,灰度化处理还有其他的方法,如:取3个分量的最大值、最小值、算术平均值等,目的都是使颜色的R、G、B分量值相等。
//黑白
Mat BlackAndWhite(Mat src) {
Mat output(src.size(), CV_8UC1);
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
output.at<uchar>(i, j) = 0.11 * src.at<Vec3b>(i, j)[2] + 0.59 * src.at<Vec3b>(i, j)[1] + 0.30 * src.at<Vec3b>(i, j)[0];
}
}
return output;
}
染色效果在数字图像处理中又叫伪彩色处理,即把整幅图像染成一种给定的色调。方法是指定一种渲染颜色,然后用当前像素的灰度分别乘以指定颜色的R、G、B三分量,并将结果作为当前像素的最终颜色。
//染色
Mat AddColors(Mat src) {
Mat output(src.size(), CV_8UC3);
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
output.at<Vec3b>(i, j)[0] = src.at<uchar>(i, j) * 255 / 255;
output.at<Vec3b>(i, j)[1] = src.at<uchar>(i, j) * 245 / 255;
output.at<Vec3b>(i, j)[2] = src.at<uchar>(i, j) * 0 / 255;
}
}
return output;
}
怀旧效果一般用在一些相片处理上,使整张相片呈棕褐色,仿佛相片已保存很久,已经发黄。方法是对每个像素的RGB分量按照公式(4-2)赋值:
//怀旧
Mat OldStyle(Mat src) {
Mat output(src.size(), CV_8UC3);
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
float R = src.at<Vec3b>(i, j)[2] * 0.393 + src.at<Vec3b>(i, j)[1] * 0.769 + src.at<Vec3b>(i, j)[0] * 0.189;
float G = src.at<Vec3b>(i, j)[2] * 0.349 + src.at<Vec3b>(i, j)[1] * 0.686 + src.at<Vec3b>(i, j)[0] * 0.168;
float B = src.at<Vec3b>(i, j)[2] * 0.272 + src.at<Vec3b>(i, j)[1] * 0.534 + src.at<Vec3b>(i, j)[0] * 0.131;
//防止某个颜色分量的值溢出
R = R > 255 ? 255 : R;
G = G > 255 ? 255 : G;
B = B > 255 ? 255 : B;
output.at<Vec3b>(i, j)[2] = R; //R
output.at<Vec3b>(i, j)[1] = G; //G
output.at<Vec3b>(i, j)[0] = B; //B
}
}
return output;
}
马赛克效果,其原理是将图像从形式上划分为很多小块(5*5),在每块内的各个像素都取到相同的红、绿、蓝颜色值,如块内任意一像素的值。从而对某些细节进行模糊化处理,使图像具有马赛克效果。
//马赛克
//
Mat Mosaic(Mat src, int rectangleLenth) {
Mat output(src.size(), CV_8UC3);
//取中心的像素值
for (int i = 0; i < src.rows; i += rectangleLenth) {
for (int j = 0; j < src.cols; j += rectangleLenth) {
//对矩形区域内的每一个像素值进行遍历
for (int k = i; k < rectangleLenth + i && k < src.rows; k++) {
for (int m = j; m < rectangleLenth + j && m < src.cols; m++) {
//在这里进行颜色的修改
output.at<Vec3b>(k, m)[0] = src.at<Vec3b>(i + rectangleLenth / 2, j + rectangleLenth / 2)[0];
output.at<Vec3b>(k, m)[1] = src.at<Vec3b>(i + rectangleLenth / 2, j + rectangleLenth / 2)[1];
output.at<Vec3b>(k, m)[2] = src.at<Vec3b>(i + rectangleLenth / 2, j + rectangleLenth / 2)[2];
}
}
}
}
return output;
}
浮雕效果针对灰度图像处理,就是只将图像的变化部分突出出来,而相同灰度部分则被淡化,使图像出现纵深感,从而达到浮雕效果。将要处理的像素取值为与处于前一个相邻像素间的差值,这样灰度平淡区因差值几乎为零则变成黑色,可以通过加上一个常量来增加一些亮度。
其中,G(i,j)为处理后图像的像素值,f(i,j)为原图像的像素值,f(i-1,j)为前一个相邻像素的值,常量通常取值为128。
//浮雕
Mat Enchase(Mat src, int con) {
//针对灰度图像处理
Mat output(src.size(), CV_8UC1);
for (int i = 1; i < src.rows; i++) {
for (int j = 1; j < src.cols; j++) {
float temp;
temp = src.at<uchar>(i, j) - src.at<uchar>(i - 1, j) + con;
if (temp < 0 || temp>255)
{
temp = src.at<uchar>(i, j);
}
output.at<uchar>(i, j) = temp;
}
}
return output;
}
霓虹效果用来描绘图像的轮廓,勾画颜色变化的边缘,产生轮廓发光的效果。先计算原图像当前像素f(i,j)的红、绿、蓝分量与其相同行f(i+1,j)及相同列f(i,j+1)相邻像素的梯度,即差的平方和的平方根,然后将梯度值作为处理后像素g(i,j)的红、绿、蓝的3个分量。
上式中r1,g1,b1分别为原图像像素f(i,j)的红、绿、蓝分量值,r2,g2,b2分别为原图像同行相邻像素f(i+1,j)的红、绿、蓝分量值,r3,g3,b3分别为原图像同列相邻像素f(i,j+1)的红、绿、蓝分量值,Red,Green,Blue为图像处理后的像素f(i,j)的红、绿、蓝分量值。
//霓虹
Mat Neon(Mat src, int con) {
//针对灰度图像处理
Mat output(src.size(), CV_8UC3);
for (int i = 1; i < src.rows - 1; i++) {
for (int j = 1; j < src.cols - 1; j++) {
int r1, r2, r3, g1, g2, g3, b1, b2, b3;
r1 = src.at<Vec3b>(i, j)[2];
r2 = src.at<Vec3b>(i + 1, j)[2];
r3 = src.at<Vec3b>(i, j + 1)[2];
g1 = src.at<Vec3b>(i, j)[1];
g2 = src.at<Vec3b>(i + 1, j)[1];
g3 = src.at<Vec3b>(i, j + 1)[1];
b1 = src.at<Vec3b>(i, j)[0];
b2 = src.at<Vec3b>(i, j + 1)[0];
b3 = src.at<Vec3b>(i, j + 1)[0];
double R1, R2, G1, G2, B1, B2;
R1 = pow(r1 - r2, 2);
R2 = pow(r1 - r3, 2);
G1 = pow(g1 - g2, 2);
G2 = pow(g1 - g3, 2);
B1 = pow(b1 - b2, 2);
B2 = pow(b1 - b3, 2);
double Red, Green, Blue;
Red = 2 * sqrt(R1 + R2);
Green = 2 * sqrt(G1 + G2);
Blue = 2 * sqrt(B1 + B2);
Red = Red > 255 ? 255 : Red;
Green = Green > 255 ? 255 : Green;
Blue = Blue > 255 ? 255 : Blue;
Red = Red < 0 ? 0 : Red;
Green = Green < 0 ? 0 : Green;
Blue = Blue < 0 ? 0 : Blue;
output.at<Vec3b>(i, j)[2] = (int)Red;
output.at<Vec3b>(i, j)[1] = (int)Green;
output.at<Vec3b>(i, j)[0] = (int)Blue;
}
}
return output;
}
生成近似素描效果的图像有很多种方法,借助拉普拉斯算子生成素描图的方法较为简单。理想的素描图像要求轮廓清晰,线条柔和,且不能包含过多的杂点干扰。拉普拉斯边缘检测得到的正边缘通常可以较好地描述图形的轮廓线条,但往往包含过多的细节信息或受杂点影响,因此需要对边缘检测结果进行进一步处理。首先使用卷积模板消除大量杂点,然后通过设定阈值消除剩余的少量灰度较低的斑点,最后对得到的素描图进行模糊处理,依然使用卷积模板。
实现步骤:
(1)读取图像文件。
(2)对图像利用拉普拉斯算子进行边缘检测,采用卷积模板的方法。模板如图(a)所示。
(3)去除杂点,同样使用卷积模板的方法。对每个像素分别使用两个模板(如图(b)所示)进行卷积运算,对两个结果取较大值,与阈值(一般设为25)比较,小于阈值的点即为杂点。
(4)去除杂点后,图像仍然含有少量灰度较低的斑点,通过设定灰度阈值Ka(一般设为32)去除。方法为:对每个像素的灰度值乘以2,如果灰度值大于255,则设为255;如果小于阈值Ka,则设为0。
(5)对素描图进行模糊处理,卷积模板如图©所示。
(6)对图像取反操作,显示处理后的图像。
扭曲效果包括挤压效果、球面效果、漩涡效果、波浪效果和摩尔纹效果。这些效果需要的参数都差不多,主要是新的x,y的坐标计算不太一样。
//模板
Mat Extrusion(Mat& src,int degree) {
assert(degree>=1&°ree<=32);
Mat output(src.size(), CV_8UC3);
int width = output.cols;
int heigh = output.rows;
Point center(width / 2, heigh / 2);
for (int y = 0; y < heigh; y++)
{
uchar* img_p = output.ptr<uchar>(y);
for (int x = 0; x < width; x++)
{
Point offset(x - center.x, y - center.y);
double theta = atan2((double)offset.y, (double)offset.x);
double rou = sqrt(pow((double)offset.x, 2) + pow((double)offset.y, 2));
//具体计算方式根据不同的效果进行修改既可
//***计算修改区结束
int newX = center.x + int(degree * sqrt(rou) * cos(theta));
int newY = center.y + int(degree * sqrt(rou) * sin(theta));
//***计算修改区结束
if (newX < 0)
newX = 0;
else if (newX >= width)
newX = width - 1;
if (newY < 0)
newY = 0;
else if (newY >= heigh)
newY = heigh - 1;
img_p[3 * x] = src.at<uchar>(newY, newX * 3);
img_p[3 * x + 1] = src.at<uchar>(newY, newX * 3 + 1);
img_p[3 * x + 2] = src.at<uchar>(newY, newX * 3 + 2);
}
}
return output;
}
挤压效果是将图像向内挤压,产生收缩变形。
挤压效果的实现可以看成是数学极坐标的一种体现,将当前像素点、图像正中心点和过中心点的水平线这三要素画出一个极坐标,然后根据用户指定的挤压度,在当前点与中心点所连的直线上映射出一个像素点,最后将这个像素点作为目标点输出。设原图像上的点坐标用(x’,y’)表示,挤压效果图上的点坐标用(x,y)表示,图像正中心点坐标为(midx,midy),ρ和θ表示点(x,y)所在极坐标系对应的极值和极角,degree为挤压度,取值为[1,32],那么挤压效果满足的关系如下式所示:
//挤压
int newX = center.x + int(degree * sqrt(rou) * cos(theta));
int newY = center.y + int(degree * sqrt(rou) * sin(theta));
球面效果是将图像中部隆起成球形,使对象具有立体凸起效果,就像哈哈镜中的图像一样。其实现类同于挤压效果,也可以看成是极坐标的一种体现。设坐标表示和挤压效果一致,那么球面效果满足的关系如式(4-7)所示:
//球面效果/哈哈镜
int newX = center.x + (int)(pow(rou, 2) / max(center.x, center.y) * cos(theta));
int newY = center.y + (int)(pow(rou, 2) / max(center.x, center.y) * sin(theta));
漩涡效果是将整幅图像由外向内进行选择扭曲,越往中心,旋转扭曲程度越强烈,从而达到一种很真实的漩涡效果。漩涡效果仍然是极坐标的一种体现,坐标表示和挤压效果一致,不同的是用swirldegree表示漩涡幅度,取值范围为[1,100],那么该效果满足的关系如式(4-8)所示:
波浪效果是将图像中的像素产生移位,从而创建出起伏跌荡的图像效果。波浪效果的实现可以理解为某一个像素在它自身一定范围内进行有规律的振动起伏,振动幅度由用户指定,可利用三角函数实现。仍然设原图像上的点坐标用(x’,y’)表示,波浪效果图上的点坐标用(x,y)表示,degree表示振动幅度,取值范围为[1,32],那么该效果满足的关系如式(4-9)所示:
摩尔纹是大屏幕显示器在显示大面积相同色彩的画面时出现的彩色波浪状条纹,它会影响视觉效果,其实现也可以看成是极坐标的一种体现,但是由于通常情况下得到的摩尔纹效果十分抽象,往往将处理后的图像与原图像进行Alpha混合。设坐标表示与挤压效果一致,degree表示强度,取值范围在[1,16],则摩尔纹效果满足的关系如式(4-10)所示:
步骤1、将图片转换为灰色图片;
步骤2、将图片分割成小方块,例如77,1010等;
步骤3、将像素值划分成若干等级,例如划分成4个等级,则0-63为等级一,64-127为等级二,128-191为等级三,192-255为等级四;
步骤4、将步骤2中每个小方块,计算其内各等级的像素点个数;
步骤5、使用出现频率最高等级的像素的平均颜色值替换原像素值。