学习OpenCV已有一段时间,除了研究各种算法的内容,在空闲之余,根据书本及资料的引导,尝试结合图像处理算法和日常生活联系起来,首先在台式机上(带摄像头)完成一系列视频流处理功能,开发平台为Qt5.3.2+OpenCV2.4.9。
本次试验实现的功能主要有:
- 调用摄像头捕获视频流;
- 将帧图像转换为素描效果图片;
- 将帧图像卡通化处理;
- 简单地生成“怪物”形象;
- 人脸肤色变换。
本节所有的算法均由类cartoon中的函数cartoonTransform()来实现:
// Frame:输入每一帧图像 output:输出图像
cartoonTransform(cv::Mat &Frame, cv::Mat &output)
后续将使用更多的OpenCV技巧实现更多功能,并将该应用移植到Android系统上。
一、使用OpenCV访问摄像头
OpenCV提供了一个简便易用的框架以提取视频文件和USB摄像头中的图像帧,如果你只是想读取某个视频,你只需要创建一个cv::VideoCapture实例,然后在循环中提取每一帧。这里需要访问摄像头,因此需要创建一个cv::VideoCapture对象,简单调用对象的open()方法。这里访问摄像头的函数如下,首先在Qt中创建控制台项目,在main函数中添加:
int cameraNumber = 0; // 设定摄像头编号为0
if(argc > 1)
cameraNumber = atoi(argv[1]);
// 开启摄像头
cv::VideoCapture camera;
camera.open(cameraNumber);
if(!camera.isOpened())
{
qDebug() << "Error: Could not open the camera.";
exit(1);
}
// 调整摄像头的输出分辨率
camera.set(CV_CAP_PROP_FRAME_WIDTH, 640);
camera.set(CV_CAP_PROP_FRAME_HEIGHT, 480);
在摄像头被初始化后,可以使用C++流运算符将cv::VideoCapture对象转换成cv::Mat对象,这样可以获取视频的每一帧图像。关于视频流读取可参考: http://blog.csdn.net/liyuefeilong/article/details/44066097
二、将帧图像转换为素描效果图片
要将一幅图像转换为素描效果图,可以使用不同的边缘检测算法实现,如常用的基于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边缘滤波器进行图像处理。
一般在进行Laplacian检测之前,需要对图像进行的预操作有:
- Laplacian算法只能作用于灰度图像,因此需要将彩色帧图像进行转换;
- 平滑处理,这是因为图像的平滑处理减少了噪声的影响并且一定成都市抵消了由Laplacian算子的二阶导数引起的噪声影响。因此可使用中值滤波器来去噪。
void cartoon::cartoonTransform(cv::Mat &Frame, cv::Mat &output)
{
cv::Mat grayImage;
cv::cvtColor(Frame, grayImage, CV_BGR2GRAY);
// 设置中值滤波器参数
cv::medianBlur(grayImage, grayImage, 7);
// Laplacian边缘检测
cv::Mat edge; // 用于存放边缘检测输出结果
cv::Laplacian(grayImage, edge, CV_8U, 5);
// 对边缘检测结果进行二值化
cv::Mat Binaryzation; // 用于存放二值化输出结果
cv::threshold(edge, Binaryzation, 80, 255, cv::THRESH_BINARY_INV);
}
生成的素描效果:
三、将图像卡通化
在项目中调用一些运算量大的算法时,通常需要考虑到效率问题,比如这里将要用到的双边滤波器。这里我们利用双边滤波器的平滑区域及保持边缘锐化的特性,将其运用到卡通图片效果生成中。而考虑到双边滤波器运行效率较低,因此考虑在更低的分辨率中使用,这对效果影响不大,但是运行速度大大加快。
这里使用的策略是将要处理的图像的宽度和高度缩小为原来的1/2,经过双边滤波器处理后,再将其恢复为原来的尺寸。在函数cartoonTransform()中添加以下代码:
// 采用双边滤波器
// 由于算法复杂,因此需减少图像尺寸
cv::Size size = Frame.size();
cv::Size reduceSize;
reduceSize.width = size.width / 2;
reduceSize.height = size.height / 2;
cv::Mat reduceImage = cv::Mat(reduceSize, CV_8UC3);
cv::resize(Frame, reduceImage, reduceSize);
// 双边滤波器实现过程
cv::Mat tmp = cv::Mat(reduceSize, CV_8UC3);
int repetitions = 7;
for (int i=0 ; i < repetitions; i++)
{
int kernelSize = 9;
double sigmaColor = 9;
double sigmaSpace = 7;
cv::bilateralFilter(reduceImage, tmp, kernelSize, sigmaColor, sigmaSpace);
cv::bilateralFilter(tmp, reduceImage, kernelSize, sigmaColor, sigmaSpace);
}
// 由于图像是缩小后的图像,需要恢复
cv::Mat magnifyImage;
cv::resize(reduceImage, magnifyImage, size);
为了得到更好的效果,在以上代码中添加以下函数,将恢复尺寸后的图像与上一部分的素描结果相叠加,得到卡通版的图像~~
cv::Mat dst;
dst.setTo(0);
magnifyImage.copyTo(dst, Binaryzation);
//output = dst; //输出
卡通效果,阈值各方面有待优化:
四、简单地生成“怪物”形象
这里是结合了边缘滤波器和中值滤波器的另一个小应用,即通过小的边缘滤波器找到图像中的各处边缘,之后使用中值滤波器来合并这些边缘。具体实现步骤如下:
- 这里同样需要原图像的灰度图,因此格式转换依然是必要的;
- 分别沿着x和y方向采用3*3的Scharr梯度滤波器(效果图);
- 使用截断值很低的阈值进行二值化处理;
- 最后使用3*3的中值平滑滤波得到“怪物”掩码。
详细代码如下,同样在函数cartoonTransform()中添加: