一.简介
可以通过opencv将图片进行卡通化处理,基本的思路是将图片的内容部分进行平滑处理,然后让边缘部分更加突出。首先,通过边缘检测滤波器获得图像的黑白素描图,然后通过双边滤波器获得平滑后的图像,最后将素描图覆盖到平滑后的图像上就可以得到类似于卡通图片的效果图。
二.具体实现
1.生成素描图
边缘检测的方法有很多,如Sobel,Scharr,Laplacian filters,或者Canny-edge detector。这里使用的是Laplacian算子,因为它产生的边缘图像和素描图像更加接近,而且比Canny-edge边缘检测的连续性要好。Canny-edge边缘检测可以产生很清晰的轮廓,但是受噪声的影响很大。
执行Laplacian运算之前,需要先对图片进行噪声处理,使用中值滤波器滤除噪声可以保持边缘的锐利度。然后需要将彩色图像转换为灰度图,这是因为Laplacian的输入图像要求为灰度图。代码如下
Mat src_Image,gray_Image;
src_Image = imread("1.jpg");
cvtColor(src_Image, gray_Image, CV_BGR2GRAY);
const int MEDIAN_BLUR_FILTER_SIZE = 7;
medianBlur(gray_Image, gray_Image, MEDIAN_BLUR_FILTER_SIZE);
Mat edges_Image;
const int LAPLACIAN_FILTER_SIZE = 5;
Laplacian(gray_Image, edges_Image, CV_8U, LAPLACIAN_FILTER_SIZE);
imshow("均衡化前", edges_Image);
Mat mask_Image;
const int EDGES_THRESHOLD = 80;
//变换后的图像边缘亮度不同,为了使图像更像素描,作二进制处理
threshold(edges_Image, mask_Image, EDGES_THRESHOLD, 255, THRESH_BINARY_INV);
imshow("素描图", mask_Image);
2.生成彩色图和卡通图
双边滤波器作为卡通和油画滤波可以取得不错的效果,它可以使图像更加平滑同时保持边缘的锐利(即它是一种保边去噪的滤波器)但是运行速度很慢,所以在使用之前可以先缩小图像的尺寸,降低分辨率,从而节省处理时间。
Size size = src_Image.size();
Size smallSize;
smallSize.width = size.width / 2;
smallSize.height = size.area / 2;
Mat small_Image = Mat(smallSize, CV_8UC3);
resize(src_Image, small_Image, smallSize, 0, 0, INTER_LINEAR);
我们使用很多小内核双边滤波器而不是大内核滤波器来产生卡通效果,这样可以节省时间。采用截断的滤波器而不是完整的滤波器来对图像的主要部分进行处理(这里不是很懂什么意思),这样可以运行的更加快速。由于bilateralFilter()函数是不支持就地执行的(即输入图像不能作为输出),所以需要新建一个图像变量。
int repetitions = 7;
for (int i = 0; i < repetitions; i++)
{
int ksize = 9;
double sigmaColor = 9;
double sigmaSpace = 7;
bilateralFilter(small_Image, tmp_Image, ksize, sigmaColor, sigmaSpace);
bilateralFilter(tmp_Image, small_Image, ksize, sigmaColor, sigmaSpace);
}
然后把图像恢复到原图尺寸,接下来创建一个黑色背景的图像,并把平滑后的图像和素描图放到里面,就得到了卡通效果图。
Mat big_Image;
resize(small_Image, big_Image, size, 0, 0, INTER_LINEAR);
Mat dst_Image = Mat(size, CV_8UC3);
dst_Image.setTo(0);
big_Image.copyTo(dst_Image, mask_Image);
imshow("卡通图", dst_Image);
整个程序完整代码如下:
#include
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
int main()
{
Mat src_Image,gray_Image;
src_Image = imread("Harry.jpg");
imshow("原图", src_Image);
cvtColor(src_Image, gray_Image, CV_BGR2GRAY);
const int MEDIAN_BLUR_FILTER_SIZE = 7;
medianBlur(gray_Image, gray_Image, MEDIAN_BLUR_FILTER_SIZE);
Mat edges_Image;
const int LAPLACIAN_FILTER_SIZE = 5;
Laplacian(gray_Image, edges_Image, CV_8U, LAPLACIAN_FILTER_SIZE);
//imshow("均衡化前", edges_Image);
Mat mask_Image;
const int EDGES_THRESHOLD = 80;
threshold(edges_Image, mask_Image, EDGES_THRESHOLD, 255, THRESH_BINARY_INV);
//imshow("素描图", mask_Image);
Size size = src_Image.size();
Size smallSize;
smallSize.width = size.width / 2;
smallSize.height = size.height / 2;
Mat small_Image = Mat(smallSize, CV_8UC3);
resize(src_Image, small_Image, smallSize, 0, 0, INTER_LINEAR);
Mat tmp_Image = Mat(smallSize, CV_8UC3);
int repetitions = 7;
for (int i = 0; i < repetitions; i++)
{
int ksize = 9;
double sigmaColor = 9;
double sigmaSpace = 7;
bilateralFilter(small_Image, tmp_Image, ksize, sigmaColor, sigmaSpace);
bilateralFilter(tmp_Image, small_Image, ksize, sigmaColor, sigmaSpace);
}
Mat big_Image;
resize(small_Image, big_Image, size, 0, 0, INTER_LINEAR);
Mat dst_Image = Mat(size, CV_8UC3);
dst_Image.setTo(0);
big_Image.copyTo(dst_Image, mask_Image);
imshow("卡通图", dst_Image);
waitKey();
}