本次要整理的内容是关于图像的锐化。
图像锐化,本质上是对图像高通滤波后的结果和原图进行像素叠加后的输出。图像的高通滤波,就是一幅图像通过滤波器后,使其细节、边缘部分都保留下来,而其他的主体内容则过滤掉。所以图像进行高通滤波后的输出结果就是图像的细节部分,再将这些细节部分以一定的权重叠加到原图像中去,就使得原图像中的细节部分更为突出,视觉感受上更加的立体。
下面是图像锐化的代码实现:
Mat sharpen_image, blur_image, sub_image;
//GaussianBlur(image, blur_image, Size(), 5, 5);
fastNlMeansDenoisingColored(image, blur_image, 10, 10, 7, 21);
subtract(image, blur_image, sub_image, Mat(), CV_16S);
add(sub_image, image, sharpen_image, Mat(), CV_16S);
convertScaleAbs(sharpen_image, sharpen_image);
imshow("锐化", sharpen_image);
首先通过滤波算法对输入图像去噪,同时模糊掉其边缘和细节部分,可以使用非局部均值滤波算法来进行去噪,因为非局部均值滤波算法对高斯噪声的抑制效果比较好。然后通过用原图像减去模糊后的图像,得到本来被模糊掉的边缘和细节部分,注意两个CV_8UC类型相减,可能出现负值,所以相减后的输出图像用CV_16S或者更大范围的类型定义。最后将相减得到的边缘和细节部分,合成到原图中,再通过convertScaleAbs(sharpen_image, sharpen_image)
这个API将其他类型的图像转换为CV_8UC类型,并输出图像。下面展示一下利用这种方式进行图像锐化的效果图:
上面的锐化实现方式,是将提取到的细节边缘信息完全地合成到原图像中,这样 形成的锐化图像虽然也能达到效果,但是可能是因为将细节过于突出的原因,导致看起来会给人很粗糙、不自然的感觉。尤其是在上面的效果图中,经过这样锐化的图像观感上很生硬,甚至显得有些毛刺,和原图对比起来显得有些不舒服。我们可以通过使用USM(UnSharpen Mask)锐化算法来解决这个问题。USM锐化算法的思路是,先对原图像进行高斯模糊达到降噪、抹除细节边缘的效果,并获得处理后的图像,再将经过高斯模糊后的图像与原图像按照权重进行加权求和。
可以认为:输出图像 = (1 + weight)x 原图像 + (-weight)x 高斯模糊图像
这样处理后,通过一个权重来平衡了叠加的高频信息在输出图像中的比例,可以让图像进行锐化后的显示过渡仍然比较流畅、平滑,避免过多的高频信息影响图像的显示效果。下面是USM算法是代码实现:
//USM(UnSharpen Mask)锐化算法
Mat gaussian_image, USM_image;
GaussianBlur(image, gaussian_image, Size(), 10, 10);
addWeighted(image, 1.5, gaussian_image, -0.5, 0, USM_image, CV_16S);
convertScaleAbs(USM_image, USM_image);
imshow("USM_image", USM_image);
在这里使用了addWeighted(image, 1.5, gaussian_image, -0.5, 0, USM_image, CV_16S)
这个API,它的作用是将输入的两幅图像按照各自的权重进行相加,并注意要以比[0,255]范围更大的类型(如CV_16S)返回。
下面是USM算法的效果图:
从我的主观感受上来看,USM算法是明显优于普通锐化算法的,无论是在细节、边缘、纹理、图像内容之间的过渡等等方面,都有比较好的表现。
但是无论是普通的锐化算法,或者是USM算法,都有一个问题就是:没有考虑到轮廓边缘与物体内部纹理的区别。在上述的两个图像锐化算法中,图像中的所有高频信息都得到了一样的加强,以效果图中的猫和其他物体为例:猫作为主体和其他物体之间的边缘是需要被加强的,这样可以显得图像更立体化,增强视觉效果,但是其他物体表面的那些纹理是不需要被同等强度地加强的,否则会削弱图像主体的显示效果,甚至是被其他的纹理所充斥。所以在图像锐化处理中,最好是能够对主体边缘进行比较大的增强,而对其他纹理则进行比较小的增强,这样才能达到比较好的视觉效果。
动态USM锐化算法就实现了这样的功能,下面给出我自定义的一个USM动态算法:
/************************************动态USM锐化算法***********************************/
Mat gaussian_image, USM_image;
//cvtColor(image, USM_image, COLOR_BGR2GRAY);
//cvtColor(image, image, COLOR_BGR2GRAY);
GaussianBlur(image, gaussian_image, Size(), 20, 20);
int height = image.rows;
int width = image.cols;
int channels = image.channels();
//定义两个阈值标识,将[0,255]分为三个区间
int threshold_L = 60;
int threshold_R = 120;
//分别定义三个像素值区间的权重
float weight_L = 0.4;
float weight_M = 0.6;
float weight_R = 0.8;
//初始化输出图像
USM_image = image.clone();
if (channels != 1) //非单通道图像
{
//将原图像、高斯模糊图像、USM输出图像进行三通道分离
vector<Mat> image_bgr, gaus_image_bgr, USM_bgr;
split(image, image_bgr);
split(gaussian_image, gaus_image_bgr);
split(USM_image, USM_bgr);
//分别对每个通道进行像素点遍历
for (int ch = 0; ch < channels; ch++)
{
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
//计算原图像和高斯模糊图像在该点的像素差异值
uchar image_value = image_bgr[ch].at<uchar>(row, col);
uchar gaus_image_value = gaus_image_bgr[ch].at<uchar>(row, col);
int dif_value = image_value - gaus_image_value;
if (dif_value < threshold_L) //差异值位于第一区间[0, 60]
{
//将原图像的像素值和高斯模糊图像的像素值以权重相加
uchar new_value = saturate_cast<uchar>(((1 + weight_L) * image_value - weight_L * gaus_image_value));
//将新的像素值赋给当前通道的当前位置的像素点
USM_bgr[ch].at<uchar>(row, col) = new_value;
}
else if(dif_value >= threshold_L && dif_value <= threshold_R) //差异值位于第二区间[60, 120]
{
uchar new_value = saturate_cast<uchar>(((1+weight_M) * image_value - weight_M * gaus_image_value));
USM_bgr[ch].at<uchar>(row, col) = new_value;
}
else //差异值位于第三区间[60, 255]
{
char new_value = saturate_cast<uchar>(((1 + weight_R) * image_value - weight_R * gaus_image_value));
USM_bgr[ch].at<uchar>(row, col) = new_value;
}
}
}
}
merge(USM_bgr, USM_image); //将分别锐化处理后的三通道图像合并
}
else //单通道图像
{
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
uchar image_value = image.at<uchar>(row, col);
uchar gaus_image_value = gaussian_image.at<uchar>(row, col);
int dif_value = image_value - gaus_image_value;
if (dif_value < threshold_L)
{
uchar new_value = saturate_cast<uchar>(((1 + weight_L) * image_value - weight_L * gaus_image_value));
USM_image.at<uchar>(row, col) = new_value;
}
else if (dif_value >= threshold_L && dif_value <= threshold_R)
{
uchar new_value = saturate_cast<uchar>(((1 + weight_M) * image_value - weight_M * gaus_image_value));
USM_image.at<uchar>(row, col) = new_value;
}
else
{
uchar new_value = saturate_cast<uchar>(((1 + weight_R) * image_value - weight_R * gaus_image_value));
USM_image.at<uchar>(row, col) = new_value;
}
}
}
}
imshow("USM_image", USM_image);
//imwrite("D:\\opencv_c++\\opencv_tutorial\\data\\images\\USM_flower2.jpg", USM_image);
在上述算法中,对输入图像进行遍历,并将原图像和经过高斯模糊后的图像进行对比,对像素值差异大于阈值的就进行锐化处理。并且将差异值分为三个区间进行处理, 对差异大的区间使用较大的权重,更突出其边缘细节,对差异小的区间则用较小的权重,避免过度增强。如果输入的是三通道图像,则先进行通道分离后对不同通道进行锐化处理,最后再将三通道合并起来。
下面是动态USM算法的效果图:
处理上面的算法实现图像锐化外,还可以通过自定义卷积来实现图像锐化
//通过自定义卷积核来进行图像锐化,以下为两个常见锐化算子
Mat sharpen = (Mat_<int>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
//Mat sharpen = (Mat_(3, 3) << -1, -1, -1, -1, 9, -1, -1, -1, -1);
filter2D(image, sharpen_image, CV_32F, sharpen);
convertScaleAbs(sharpen_image, sharpen_image);
imshow("锐化", sharpen_image);
上面给出了两个常见的锐化算子,是通过卷积计算梯度的方式来实现图像的锐化,但是效果其实并不是很理想,这里就不做展示了。
其中filter2D(image, sharpen_image, CV_32F, sharpen)
是用来实现一幅图像和一个卷积核进行卷积操作的API,卷积核需要提前定义好,并且输出的类型是浮点型的(如CV_32F),卷积完成后再将输出图像归一化到 [ 0 , 255 ] 区间中,OpenCV只有像素值是在 [ 0 , 255 ] 区间的图像才能够正常的显示。
好了,这次关于图像锐化的内容就记录到这里,而且也满足了上一篇博客中想换下实验图片的心愿。。。下次再继续把~~~
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!