20个基础到进阶版的OpenCV4.9.0趣味项目(C++版)(七)——图片素描化和卡通化
文章目录
一、引言
在图像处理领域,将图片转化为素描或卡通风格是一项既有趣又实用的技术。这种转换不仅增强了图像的视觉效果,还为艺术创作、教育演示、游戏开发以及电影特效制作等领域带来了无限可能。本文将详细介绍如何使用OpenCV库来实现图片的素描化和卡通化处理。
二、核心知识
1.中值滤波
中值滤波的基本原理是选取图像中某一个像素点的邻域窗口,将窗口内的所有像素点的灰度值(或颜色值)进行排序,然后取排序后的中间值作为该像素点滤波后的新值。这个过程可以消除孤立的噪声点,使像素值更加接近真实值,从而达到平滑去噪的目的。
具体步骤:
1)确定邻域窗口:
首先,需要确定一个邻域窗口的大小和形状,常用的窗口大小有3x3、5x5等,形状通常为正方形或矩形。窗口的大小和形状会影响滤波的效果,需要根据具体的应用场景和图像特性进行选择。
2)排序窗口内像素值:
将邻域窗口内的所有像素点的灰度值(或颜色值)进行排序。排序的方法可以是从小到大,也可以是从大到小,但结果是一致的,因为都是取中间值。
3)取中间值:
排序完成后,取排序后的中间值作为该像素点滤波后的新值。如果窗口内的像素点数为奇数,则中间值就是排序后位于中间的那个值;如果窗口内的像素点数为偶数,则中间值可以是排序后中间两个值的平均值,也可以是其他某种方式确定的值(但这种情况较少见,因为通常选择奇数大小的窗口以避免这种模糊性)。
未排序的点:
中值排序后的点:
特点与优势:
1)去除噪声:
中值滤波能够有效地去除图像中的椒盐噪声等脉冲噪声,因为这些噪声点通常是孤立的,排序后不会成为中间值。
2)保持边缘:
与均值滤波等线性滤波方法相比,中值滤波在去除噪声的同时能够更好地保持图像的边缘信息,因为边缘信息通常是由相邻像素间较大的灰度值差异形成的,这种差异在中值滤波过程中不会被平滑掉。
3)适应性强:
中值滤波的滤波效果与窗口大小和形状的选择密切相关,因此可以根据不同的应用场景和图像特性进行灵活调整,以达到最佳的滤波效果。
未滤波的图像:
中值滤波后的图像:
2.拉普拉斯边缘提取
在图像中,像素值的变化通常是不均匀的,而在边缘处,像素值的变化通常是最大的。拉普拉斯算子通过计算图像的二阶导数来检测这些变化点。在连续函数中,二阶导数的零点通常对应于一阶导数的极值点,这些极值点就是图像中的边缘。
3.阈值分割–反向二值化
在OpenCV中,阈值分割是一种用于图像二值化的技术,它将图像转换为仅包含两种像素值的图像(通常是0和255)。反向二值化(Inverse Binary Thresholding)是其中的一种方法,它通过设置阈值将高于该阈值的像素值设置为0,低于该阈值的像素值设置为255。
4.双边滤波
双边滤波结合了图像的空间邻近度和像素值相似度,同时考虑空域信息和灰度相似性,以达到保留边缘并去除噪声的目的。具体来说,双边滤波采用了两个高斯滤波的结合:
1)空间邻近度高斯滤波:负责计算像素之间的空间邻近度权值,即常用的高斯滤波器原理。
2)像素值相似度高斯滤波:负责计算像素值之间的相似度权值。
这两个高斯滤波的同时作用,构成了双边滤波。因此,双边滤波能够在滤除噪声、平滑图像的同时,较好地保留边缘信息。
三、代码演示
1.图片素描化
cv::Mat img = *(cv::Mat*)userdata;
if (img.empty())
{
printf("载入图像为空!\r\n");
return;
}
cv::Mat grayImg;
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
cv::Mat filtImg;
cv::medianBlur(grayImg, filtImg, 7);
cv::Mat eImg;
cv::Laplacian(filtImg, eImg, CV_8UC1, 5);
cv::Mat resImg;
cv::threshold(eImg, resImg, val, 255, cv::THRESH_BINARY_INV);
cv::imshow("Sketch", resImg);
2.图片卡通化
cv::Mat img = *(cv::Mat*)userdata;
if (img.empty())
{
printf("载入图像为空!\r\n");
return;
}
cv::Mat grayImg;
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
cv::Mat filtImg;
cv::medianBlur(grayImg, filtImg, 7);
cv::Mat eImg;
cv::Laplacian(filtImg, eImg, CV_8UC1, 5);
cv::Mat resImg;
cv::threshold(eImg, resImg, val, 255, cv::THRESH_BINARY_INV);
cv::Mat cal1 = img.clone();
cv::Mat cal2 = img.clone();
int iter = 3;
for (int i = 0; i < iter; ++i)
{
int kernelSize = 9;
double sigmaColor = 9;
double sigmaSpace = 7;
cv::bilateralFilter(cal1, cal2, kernelSize, sigmaColor, sigmaSpace);
cv::bilateralFilter(cal2, cal1, kernelSize, sigmaColor, sigmaSpace);
}
cv::Mat dstImg;
dstImg.setTo(0);
cal1.copyTo(dstImg, resImg);
cv::imshow("Cartoon", dstImg);