时间:2022-06-30 09:27:58 | 栏目:C代码 | 点击:次
最近对图像处理十分感兴趣,也学着用opencv 实现各种简单的图像处理,因此,有了下面的实验,就是将照片处理成漫画的风格。
对照片进行动漫话一般需要四个步骤
1、边缘检测
2、将边缘检测得到的边缘 以黑色的形式贴在原来的画上。
3、对贴了边缘的图进行双边滤波,双边滤波可以较好的滤波的同时保留边缘。
4、修改图像的颜色的饱和度,本文采用的是将RGB转化为HSI空间,然后调整S分量。
对于边缘检测,本文采用的是canny算法
此文中将低阈值设定在70,高阈值则为70*3。
执行后的结果为:
将边缘图以黑色贴到原图上,原图上非边缘区域仍然为原来的颜色,动漫就是边缘很明显,且边缘不是很多,不注重细节,因此这里将边缘贴上面当作边缘,后续利用双倍滤波将图中的其他相对小的细节边缘去掉。针对纹理贴图主要用到下面这个函数:
// 将边缘检测后的图 cannyImage 边以黑色的形式贴在原图 image上。 void pasteEdge(Mat &image, Mat &outImg, IplImage cannyImage) { Mat cannyMat; //将IplImage转化为Mat cannyMat = cvarrToMat(&cannyImage); //颜色反转 cannyMat = cannyMat < 100; image.copyTo(outImg, cannyMat); }
执行后的效果如下:
双边滤波(Bilateral filter)在图像美化,美颜上有广泛的运用,是一种可以保边去噪的滤波器,由两个函数构成。为了节约时间,这里就借用一张图来充当介绍了
opencv也对此有函数调用:
void bilateralFilter( InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT );
前面2个参数为输入图像,输出图像,d为双倍滤波的算子大小,sigmacolor ,sigmaSpace是2个滤波函数的nameda值(这里节约时间不打符号了)
本文相关代码:
// 双边滤波 Mat binateMat; bilateralFilter(pasteEdgeMat, binateMat, 10, 50, 50, BORDER_DEFAULT);
执行后的结果如下:
关于HSI颜色空间这里就不详细介绍了,大家可以百度下,很多文章介绍,后续我也可能总结一下各个颜色空间,并且与rgb转换方法。主要思路:将贴有边缘 且 双边滤波后的图像 转化为 HSI 空间,而将S分量增大到原来的SRadio倍,然后将HSI空间图像转化回Rgb,并显示。
将颜色空间转化HSI,并增加S分量为原来的sRadio倍,主要是使用了下面这个函数:
// 将image 像素转化到 HSI 空间,并调整S 即颜色的饱和度, void changeSImage(Mat &image, IplImage &outImg, float sRadio) { int rows = image.rows; int cols = image.cols; // 三个HSI空间数据矩阵 CvMat* HSI_H = cvCreateMat(rows, cols, CV_32FC1); CvMat* HSI_S = cvCreateMat(rows, cols, CV_32FC1); CvMat* HSI_I = cvCreateMat(rows, cols, CV_32FC1); // 原始图像数据指针, HSI矩阵数据指针 uchar* data; // rgb分量 int img_r, img_g, img_b; int min_rgb; // rgb分量中的最小值 // HSI分量 float fHue, fSaturation, fIntensity; int channels = image.channels(); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { data = image.ptr<uchar>(i); data = data + j*channels; img_b = *data; data++; img_g = *data; data++; img_r = *data; // Intensity分量[0, 1] fIntensity = (float)((img_b + img_g + img_r) / 3) / 255; // 得到RGB分量中的最小值 float fTemp = img_r < img_g ? img_r : img_g; min_rgb = fTemp < img_b ? fTemp : img_b; // Saturation分量[0, 1] fSaturation = 1 - (float)(3 * min_rgb) / (img_r + img_g + img_b); // 计算theta角 float numerator = (img_r - img_g + img_r - img_b) / 2; float denominator = sqrt( pow((img_r - img_g), 2) + (img_r - img_b)*(img_g - img_b)); // 计算Hue分量 if (denominator != 0) { float theta = acos(numerator / denominator) * 180 / 3.14; if (img_b <= img_g) { fHue = theta; } else { fHue = 360 - theta; } } else { fHue = 0; } // 赋值 cvmSet(HSI_H, i, j, fHue); cvmSet(HSI_S, i, j, fSaturation * sRadio); cvmSet(HSI_I, i, j, fIntensity); } } outImg = *HSI2RGBImage(HSI_H, HSI_S, HSI_I); }
HSI2RGBImage(HSI_H, HSI_S, HSI_I)是将三个分类的Mat 合并并转化为BGR的图,函数如下:
IplImage* HSI2RGBImage(CvMat* HSI_H, CvMat* HSI_S, CvMat* HSI_I) { IplImage * RGB_Image = cvCreateImage(cvGetSize(HSI_H), IPL_DEPTH_8U, 3); int iB, iG, iR; for (int i = 0; i < RGB_Image->height; i++) { for (int j = 0; j < RGB_Image->width; j++) { // 该点的色度H double dH = cvmGet(HSI_H, i, j); // 该点的色饱和度S double dS = cvmGet(HSI_S, i, j); // 该点的亮度 double dI = cvmGet(HSI_I, i, j); double dTempB, dTempG, dTempR; // RG扇区 if (dH < 120 && dH >= 0) { // 将H转为弧度表示 dH = dH * 3.1415926 / 180; dTempB = dI * (1 - dS); dTempR = dI * (1 + (dS * cos(dH)) / cos(3.1415926 / 3 - dH)); dTempG = (3 * dI - (dTempR + dTempB)); } // GB扇区 else if (dH < 240 && dH >= 120) { dH -= 120; // 将H转为弧度表示 dH = dH * 3.1415926 / 180; dTempR = dI * (1 - dS); dTempG = dI * (1 + dS * cos(dH) / cos(3.1415926 / 3 - dH)); dTempB = (3 * dI - (dTempR + dTempG)); } // BR扇区 else { dH -= 240; // 将H转为弧度表示 dH = dH * 3.1415926 / 180; dTempG = dI * (1 - dS); dTempB = dI * (1 + (dS * cos(dH)) / cos(3.1415926 / 3 - dH)); dTempR = (3 * dI - (dTempG + dTempB)); } iB = dTempB * 255; iG = dTempG * 255; iR = dTempR * 255; cvSet2D(RGB_Image, i, j, cvScalar(iB, iG, iR)); } } return RGB_Image; }
执行后就大功告成了,效果如下:
上述执行基本完成了照片的漫画风格,但看到天空的云的一些边缘泰国刺眼,本着好玩的性子,去掉了第一步和第二步,直接图原图进行了双边滤波和增加颜色饱和度,感觉图清晰,自然了些,但漫画风格也少了些,具体如何见下图:
github地址:https://github.com/hurtnotbad/cartoon