因为要做一个项目,为了实现他的趣味性,所以想应用图像处理做一些东西,最先想到的就是素描和卡通化,所以通过一番辛苦的查询及改错,最终完成一个简单小功能。
应用opencv将图片进行卡通化处理,基本的思路是将图片的内容部分进行平滑处理,然后让边缘部分更加突出。
1.通过边缘检测滤波器获得图像的黑白素描图,这一步得到素描图,并为卡通化做准备。
2.通过双边滤波器获得平滑后的图像。
3.将素描图覆盖到平滑后的图像上就可以得到类似于卡通图片的效果图。
边缘检测算子
二进制图像处理
中值滤波
双边滤波
我希望实现的是实时将视频图像卡通化,所以需要通过opencv调用摄像头,并对其进行一系列设置。(这部分是opencv基础,在这里不在赘述,后期我会逐步完善opencv自学教程,会在里面有详细的介绍。有不懂的大家可以评论留言,大家一起交流。)源代码如下:
/*--------------------------------------摄像头操作-------------------------------------*/
int capNum = 0;
if (argc > 1)
capNum = atoi(argv[1]);
// 开启摄像头
VideoCapture cap;
cap.open(capNum);
if (!cap.isOpened())
{
cout<< "Error: Could not load the cap.";
exit(1);
}
// 调整摄像头的分辨率
cap.set(CV_CAP_PROP_FRAME_WIDTH, 640);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, 480);
获取到每一帧的图像后,需要对图像做一定的处理,因为用了两种方式做处理分别得到:卡通化及素描。所以在处理之前加一个整形变量,允许用户输入,自由选择处理方式,为了防止用户非法输入,我设置循环做判断。输入合法后才允许执行下面的代码。并通过Switch语句设置两种处理方式。代码如下:
int mode = -1;//动画处理模式
cout << "请输入类型:";
cin >> mode;
while (mode<0 || mode >= 2)
{
cout << "处理模式输入错误,请重新输入:";
cin >> mode;
}
switch (mode)
{
case 0:
while (1)
{
cap >> Frame;
if (!Frame.data)
{
cout << "Couldn't load cap frame.";
exit(1);
}
// 创建一个用于存放输出图像的Mat类
//Mat output(Frame.size(), CV_8UC3);
TransformCartoon(Frame, output);
imshow("【卡通处理】", output);
char keypress = waitKey(30);
if (keypress == 27)
{
break;
}
}
break;
case 1:
while (1)
{
cap >> Frame;
if (!Frame.data)
{
cout << "Couldn't load cap frame.";
exit(1);
}
TransformSketch(Frame, output);
imshow("【素描图】", output);
char keypress = waitKey(30);
if (keypress == 27)
{
break;
}
}
break;
default:
break;
}
大家能够发现,在上面的代码里有两个函数是自己编写的,他们是:TransformSketch(Frame, output) 和TransformCartoon(Frame, output),第一个函数是将图像转化为素描,第二个函数是将图像转化为卡通图。如果后期还有新的模式加入,是可以通过添加case来添加功能的。
上面所有的东西是代码的整个框架,具体实现方法都在这两个写的函数里。接下来,我讲解一下两个函数的内部实现。
首先先讲一下素描,人在画素描用的是黑色铅笔,画出来的是黑白画,所以第一步是需要先将图像转为灰度图像。然后应用中值滤波,中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。通过这个操作可以将图像进行平滑处理。
cvtColor(Frame, Frame, CV_BGR2GRAY);
const int MEDIAN_BLUR_FILTER_SIZE = 7;
medianBlur(Frame, Frame, MEDIAN_BLUR_FILTER_SIZE);
要将一幅图像转换为素描效果图,还需要使用不同的边缘检測算法实现。如经常使用的基于Sobel、Canny、Robert、Prewitt、Laplacian等算子的滤波器均能够实现这一操作,但处理效果各异。
1.Sobel算子:边缘检測中最经常使用的一种方法,在技术上它是以离散型的差分算子,用来运算图像亮度函数的梯度的近似值,缺点是Sobel算子并没有将图像的主题与背景严格地区分开来,换言之就是Sobel算子并没有基于图像灰度进行处理,因为Sobel算子并没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人惬意。
2.Robert算子:依据任一相互垂直方向上的差分都用来预计梯度。Robert算子採用对角方向相邻像素之差。
3.Prewitt算子:该算子与Sobel算子相似。仅仅是权值有所变化,但两者实现起来功能还是有差距的,据经验得知Sobel要比Prewitt更能准确检測图像边缘。
4.Laplacian算子:该算子是一种二阶微分算子,若仅仅考虑边缘点的位置而不考虑周围的灰度差时可用该算子进行检測。
对于阶跃状边缘。其二阶导数在边缘点出现零交叉,并且边缘点两旁的像素的二阶导数异号。
5.Canny算子:该算子的基本性能比前面几种要好。可是相对来说算法复杂。
Canny算子是一个具有滤波。增强,检測的多阶段的优化算子。在进行处理前。Canny算子先利用高斯平滑滤波器来平滑图像以除去噪声,Canny切割算法採用一阶偏导的有限差分来计算梯度幅值和方向,在处理过程中。Canny算子还将经过一个非极大值抑制的过程。最后Canny算子还採用两个阈值来连接边缘。
相比Sobel等其它算子。Canny和Laplacian算子能得到更清晰的素描效果,而Laplacian的噪声抑制要优于Canny边缘检測,而其实素描边缘在不同帧之间经常有剧烈的变化,因此我们选择Laplacian边缘滤波器进行图像处理。
Mat edges_Image;
const int LAPLACIAN_FILTER_SIZE = 5;
Laplacian(Frame, edges_Image, CV_8U, LAPLACIAN_FILTER_SIZE);
经过上述操作之后的图像如下图所示。
变换后的图像边缘亮度不同,为了使图像更像素描,作二进制处理。
const int EDGES_THRESHOLD = 80;
threshold(edges_Image, output, EDGES_THRESHOLD, 255, THRESH_BINARY_INV);
之后的图像如下:
后面的卡通效果是在此基础上接着完成的,你可以调用上面这个函数,也可以将代码重写一遍。我选择的是重写,因为需要调用中间变量。
接下来需要生成彩色图像,并完成卡通效果。由于算法比较复杂,为了提高运行效率,特将图片缩小为原来的1/4。
Size size = Frame.size();
Size reduceSize;
reduceSize.width = size.width / 2;
reduceSize.height = size.height / 2;
Mat reduceImage = Mat(reduceSize, CV_8UC3);
resize(Frame, reduceImage, reduceSize);
接下来我们用到了双边滤波。双边滤波是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部处理的特点。双边滤波的好处在于:对图像进行平滑时,能进行边缘保护。
Mat tmp = Mat(reduceSize, CV_8UC3);
int repetitions = 7;
for (int i = 0; i < repetitions; i++)
{
int kernelSize = 9;
double sigmaColor = 9;
double sigmaSpace = 7;
bilateralFilter(reduceImage, tmp, kernelSize, sigmaColor, sigmaSpace);
bilateralFilter(tmp, reduceImage, kernelSize, sigmaColor, sigmaSpace);
}
弄完上面的处理,接下来就要将图像还原到原来的大小了。
Mat magnifyImage;
resize(reduceImage, magnifyImage, size);
为了得到更好的效果。在以上代码中加入下面函数。将恢复尺寸后的图像与上一部分的素描结果相叠加。得到卡通版的图像~~
Mat dst;
dst.setTo(0);
magnifyImage.copyTo(dst, Binaryzation);
效果如下:
如果大家有什么问题可以给我留言哦。