在OpenCV中利用卷积进行图像滤波
简 介: 本文首先讨论了卷积核的概念,以及如何用于对图像进行滤波。然后通过他们对图像进行数学运算来实现特定的效果,比如平和和锐化。展示了如何在OpenCV中实现2D滤波。 在等同卷积卷积核之后,我们创建了更多定制的核,用在OpenCV中的 filter2D()函数中。 介绍了OpenCV中的重要内置函数MediaBlur(),GaussianBlur()。 最后展示了 OpenCV中的 bilateralFilter()函数,是如何在保留图片中清晰边缘的同时又平滑了图像。
关键词
: 中值滤波,平滑滤波,图像滤波
本文是根据 Image Filtering Using Convolution in OpenCV 中的内容进行整理,以备今后的学习和应用。
§00 前 言
你是否在Photoshop中,或者在手机应用的帮助下对图片进行模糊化或者清晰化处理过?如果是,实际上你已经是用过卷积核。这里,我们解释如何通过卷积在OpenCV中对于图像进行滤波。
你将会使用2D卷积以及OpenCV中计算机视觉库对图片进行不同的模糊化和锐化。我们将会通过Python,C++程序展示如何实现这些技术。
0.1 实现代码
首先看一下下面的图像滤波代码。后面会对代码的每一行进行讨论,你可以对他们更加了解。
- Python
import cv2
import numpy as np
image = cv2.imread('test.jpg')
# Print error message if image is null
if image is None:
print('Could not read image')
# Apply identity kernel
kernel1 = np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]])
identity = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)
cv2.imshow('Original', image)
cv2.imshow('Identity', identity)
cv2.waitKey()
cv2.imwrite('identity.jpg', identity)
cv2.destroyAllWindows()
# Apply blurring kernel
kernel2 = np.ones((5, 5), np.float32) / 25
img = cv2.filter2D(src=image, ddepth=-1, kernel=kernel2)
cv2.imshow('Original', image)
cv2.imshow('Kernel Blur', img)
cv2.waitKey()
cv2.imwrite('blur_kernel.jpg', img)
cv2.destroyAllWindows()
- C++
// Import dependencies
#include <opencv2/opencv.hpp>
#include <iostream>
// Using namespaces to nullify use of c::function(); syntax and std::function(); syntax
using namespace std;
using namespace cv;
int main()
{
// Read Image
Mat image = imread("test.jpg");
// Print Error message if image is null
if (image.empty())
{
cout << "Could not read image" << endl;
}
// Apply identity filter using kernel
Mat kernel1 = (Mat_<double>(3,3) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
Mat identity;
filter2D(image, identity, -1 , kernel1, Point(-1, -1), 0, 4);
imshow("Original", image);
imshow("Identity", identity);
waitKey();
imwrite("identity.jpg", identity);
destroyAllWindows();
// Blurred using kernel
// Initialize matrix with all ones
Mat kernel2 = Mat::ones(5,5, CV_64F);
// Normalize the elements
kernel2 = kernel2 / 25;
Mat img;
filter2D(image, img, -1 , kernel2, Point(-1, -1), 0, 4);
imshow("Original", image);
imshow("Kernel blur", img);
imwrite("blur_kernel.jpg", img);
waitKey();
destroyAllWindows();
}
§01 卷积核
1.1 什么是卷积核?
在图像处理中一个卷积核就是一个用于图像滤波的二维矩阵。被称为卷积矩阵的卷积核通常是方针,即M×N矩阵,其中M,N都是相同的奇数(比如,3×3,5×5,7×7)。下面是一个3×3的平滑矩阵示例。
这就是一个3×3的2D卷积核。
这样的卷积核可以用于图像中每个像素执行数据操作来实现所需效果(比如平滑,或者锐化一个图像)。但是为什么你希望对一个图像进行模糊化?这里有两个重要的原因:
- 模糊化可以减少图像特定的噪声。因为这个原因,模糊化也常被称为平滑;
- 将容易分神的背景去除,你会有意图像中部分背景,就像移动相机在“肖像”模式下的拍摄效果。
作为计算机视觉的基本处理技术,利用卷积核进行图像滤波具有广泛的应用。
1.2 如何利用卷积核来锐化或者平滑图像?
通过吧卷积核与图像进行卷积来实现对原图进行滤波。简单说通过卷积核来卷积一个图像代表着一个在卷积核与对应的图像像素之间的数学运算:
- 假定卷积核的中心位于图像中待处理的像素 p ( x , y ) p\left( {x,y} \right) p(x,y) 上面;
- 将核的每个元素与源图像中对应的像素相乘;
- 现在把这些乘积进行相加,取平均;
- 最后,加计算的平均数值作为输出图片对应的位置的数值。
当你对于源图像中每个像素,都利用上面的3×3的核进行处理之后,你便得到一个模糊化的图像。这是因为这个核对应的卷积运算具有平均的效果,它可以对图像进行平滑、模糊化。你自己很快就会了解卷积核的每个元素都代表着自然滤波,因此你也可以实现锐化的效果。这个概念简单而强大,可以应用在大量图像处理流水线过程中。
现在你了解了卷积核的用法,下面让我们看看如何在OpenCV中 实现卷积。
1.3 教程中的实验
你可以点击 这个链接 访问Colab Notebook 访问这个教程,通过它你可以运行所有的实验,而不必在你的计算机中设置相关环境。
▲ 图1.3.1 用于实验的样例图片
输入样例图片,我们将在后面的核操作中使用这个图片。
§02 图像卷积
2.1 在OpenCV中使用同等核
- Python
import cv2
import numpy as np
- C++
// Import dependencies
#include <opencv2/opencv.hpp>
#include <iostream>
// Using namespaces to nullify use of c::function(); syntax and std::function(); syntax
using namespace std;
using namespace cv;
在刻画如何实现图像的模糊和锐化之前,我们先讲讲关于同等核。 单位核是一个仿真,中间元素为1,其余都是0,。下面就是一个同等卷积矩阵:
这是一个3×3的同等卷积核。
同等卷积核的特点就是任何与其卷积的图像所得到的结构都与原图相同。下面我们展示一下如何使用OpenCV的函数使用这个同等核。在第一个例子中,我们适用上面的同等核展示滤波之后的结果与原始图像相同。
首先输入OpenCV,NumPy软件包。
- Python
import cv2
import numpy as np
- C++
// Import dependencies
#include <opencv2/opencv.hpp>
#include <iostream>
// Using namespaces to nullify use of c::function(); syntax and std::function(); syntax
using namespace std;
using namespace cv;
接下来的代码中将执行如下操作:
- 读入测试图像;
- 定义同等卷积核,使用3×3的Numpy矩阵;
- 使用OpenCV中的filter2D()函数完成线性滤波操作;
- 利用imshow() 显示 原始和滤波后的图像;
- 利用imwrite()来存储滤波后的图像;
filter2D(src, ddepth, kernel)
函数 filter2d() 需要三个输入参数:
- 第一个参数是原始图像;
- 迪若个参数是 ddepth,代表结果图像的深度。 -1 表示结果图像的深度与原始图像相同;
- 最后输入的参数是卷积核,它将被作用的输入图像中;
下面是Python和C++的实现代码:
- Python
image = cv2.imread('test.jpg')
"""
Apply identity kernel
"""
kernel1 = np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]])
# filter2D() function can be used to apply kernel to an image.
# Where ddepth is the desired depth of final image. ddepth is -1 if...
# ... depth is same as original or source image.
identity = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)
# We should get the same image
cv2.imshow('Original', image)
cv2.imshow('Identity', identity)
cv2.waitKey()
cv2.imwrite('identity.jpg', identity)
cv2.destroyAllWindows()
- C++
// Apply identity filter using kernel
Mat kernel1 = (Mat_<double>(3,3) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
Mat identity;
filter2D(image, identity, -1 , kernel1, Point(-1, -1), 0, 4);
imshow("Original", image);
imshow("Identity", identity);
waitKey();
imwrite("identity.jpg", identity);
destroyAllWindows();
下面图像就是滤波后的图像(右边),可以看到它与原始图像(左边)是一样的。
▲ 图1.4.1 原始图像与滤波后的图像
2.2 利用定制的2D卷积核来模糊化图像
下面我们展示如何把一个图像进行平滑。 我们再次定义一个定制的卷积核,然后利用OpenCV中的 filter2D() 函数在对 原始图像进行滤波。
首先定义一个仅具有1元素的5×5的矩阵。注意它也被除以25。为什么呢?那好,你在对图像进行2D矩阵卷积之前你需要保证所有的取值都归一化。这通过将卷积核的元素都除以卷积核元素的累加和来实现。本示例中,就是将卷积核除以25.这保证了输出图像的元素取值 [ 0 , 1 ] \left[ {0,1} \right] [0,1] 范围之内。
利用 filter2D() 来对图像进行滤波。正如你所见,filter2D可以使用任何用户定义的卷积核来对图像进行卷积。
- Python
"""
Apply blurring kernel
"""
kernel2 = np.ones((5, 5), np.float32) / 25
img = cv2.filter2D(src=image, ddepth=-1, kernel=kernel2)
cv2.imshow('Original', image)
cv2.imshow('Kernel Blur', img)
cv2.waitKey()
cv2.imwrite('blur_kernel.jpg', img)
cv2.destroyAllWindows()
- C++
// Blurred using kernel
// Initialize matrix with all ones
Mat kernel2 = Mat::ones(5,5, CV_64F);
// Normalize the elements
kernel2 = kernel2 / 25;
Mat img;
filter2D(image, img, -1 , kernel2, Point(-1, -1), 0, 4);
imshow("Original", image);
imshow("Kernel blur", img);
imwrite("blur_kernel.jpg", img);
waitKey();
destroyAllWindows();
对比一下下面给出的结果。可以注意到输出图像(右边)相比于原始图像(左边)已经被模糊化了。
▲ 图2.2.1 平滑后的图像
2.3 利用OpenCV内置函数来平滑图像
在OpenCV中 有一个内置 blur() 函数,专门用于对图像进行模糊化的。重要的是在使用它对图像进行平滑过程中你不必定义卷积核,而只是声明特定的卷积的的尺寸,通过 ksize 的输入参数传递给 blur() 函数。下面代码示例给出的了具体过程。blur() 函数自动会建立一个5×5的模糊卷积核应用到输入图像中。
下面的示例中使用 blur() 函数就会产生与前面 filter2D() 相同的结果。
- Python
"""
Apply blur using `blur()` function
"""
img_blur = cv2.blur(src=image, ksize=(5,5)) # Using the blur function to blur an image where ksize is the kernel size
# Display using cv2.imshow()
cv2.imshow('Original', image)
cv2.imshow('Blurred', img_blur)
cv2.waitKey()
cv2.imwrite('blur.jpg', img_blur)
cv2.destroyAllWindows()
- C++
// Blurred using OpenCV C++ blur() function
Mat img_blur;
blur(image, img_blur, Size(5,5));
imshow("Original", image);
imshow("Blurred", img_blur);
imwrite("blur.jpg", img_blur);
waitKey();
destroyAllWindows();
2.4 在OpenCV中使用高斯核对于图像进行平滑
我们下面利用OpenCV中的高斯函数来平滑图像。 在这个算法中使用高斯滤波器,它执行一个加权平均,与前面示例均匀平均不同。这种情况下,高斯平滑算法对于像素的加权值取决于它们距离核中心的距离。距离核中心越远的像素对于加权平均结果的影响与越小。下面的代码是踹死OpenCV中GaussianBlue() 函数卷积输入图像。
GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
函数 GaussianBlue() 有四个输入参数:
-
第一个参数,src,指明需要滤波的输入图像;
-
第二个参数为 ksize,定义了高斯核的尺寸。这里我们使用5×5的高斯核;
-
最后两个参数为 sigmaX, sigmaY都设置为0.这是高斯核在X(水平)和Y(垂直)方向上的标准方差。缺省的sigmaY的取值为0。 如果你讲sigmaX设置为0, 标准方差则通过核的尺寸(宽和高)分别进行计算。你也可以明确 设置他们为一个大于零的数值;
-
Python
"""
Apply Gaussian blur
"""
# sigmaX is Gaussian Kernel standard deviation
# ksize is kernel size
gaussian_blur = cv2.GaussianBlur(src=image, ksize=(5,5), \\
sigmaX=0, sigmaY=0)
cv2.imshow('Original', image)
cv2.imshow('Gaussian Blurred', gaussian_blur)
cv2.waitKey()
cv2.imwrite('gaussian_blur.jpg', gaussian_blur)
cv2.destroyAllWindows()
- C++
// Performing Gaussian Blur
Mat gaussian_blur;
GaussianBlur(image, gaussian_blur, Size(5,5), SigmaX=0, SigmaY=0);
imshow("Original", image);
imshow("Gaussian Blurred", gaussian_blur);
imwrite("gaussian_blur.jpg", gaussian_blur);
waitKey();
destroyAllWindows();
下面给出的结果,你可以看出输出结果(右边)仅仅比输入图像(左边)稍微模糊了一些。
▲ 图2.4.1 高斯平滑后的结果
2.5 使用OpenCV中的中值滤波来处理图像
你可以通过OpenCV中的 medianBlur() 函数来对图像进行中值滤波。在中值滤波中,原始图像中的每个像素都被在卷积核尺寸范围内中的像素中间取值(注意:不是平均值)来取代。
medianBlur(src, ksize)
这个函数有两个 输入参数:
-
第一个参数是原始图像;
-
第二个是核的尺寸,必须取值为奇数,正整数;
-
Python
"""
Apply Median blur
"""
# medianBlur() is used to apply Median blur to image
# ksize is the kernel size
median = cv2.medianBlur(src=image, ksize=5)
cv2.imshow('Original', image)
cv2.imshow('Median Blurred', median)
cv2.waitKey()
cv2.imwrite('median_blur.jpg', median)
cv2.destroyAllWindows()
- C++
// Apply Median Blur
Mat median_blurred;
medianBlur(image, median_blurred, (5,5));
imshow("Original", image);
imshow("Median Blurred", median_blurred);
imwrite("median_blur.jpg", median_blurred);
waitKey();
destroyAllWindows();
下面是中值滤波的结果。可以发现在相同的卷积核的大小下,中值滤波的结果相比于高斯滤波保留了更多重要的图像元素。中值滤波通常用语减少图像中的椒盐噪声。
▲ 图2.5.1 中值滤波的结果
2.6 利用定制2D卷积来锐化图像
你可以通过2D卷积核来锐化图像。首先定制一个2D卷积核,然后使用 filter2D()来对图像进行卷积。
下面的代码中,3×3的卷积核定义了锐化和。查看这个资源来了解更多的常用卷积核。
- Python
"""
Apply sharpening using kernel
"""
kernel3 = np.array([[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]])
sharp_img = cv2.filter2D(src=image, ddepth=-1, kernel=kernel3)
cv2.imshow('Original', image)
cv2.imshow('Sharpened', sharp_img)
cv2.waitKey()
cv2.imwrite('sharp_image.jpg', sharp_img)
cv2.destroyAllWindows()
- C++
// Apply sharpening using kernel
Mat sharp_img;
Mat kernel3 = (Mat_<double>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
filter2D(image, sharp_img, -1 , kernel3, Point(-1, -1), 0, BORDER_DEFAULT);
imshow("Original", image);
imshow("Sharpenned", sharp_img);
imwrite("sharp_image.jpg", sharp_img);
waitKey();
destroyAllWindows();
下面看看我们得到了什么。下面给出的结果可以看出锐化的效果还是非常明显的。右边锐化的图像将木头中的裂纹凸显,很多都是在左边被忽略的。
▲ 图2.6.1 图像锐化的结果
2.7 双边滤波器
平滑可以有效除去 图像中的噪声,但有时我们并不想吧整个图像都进行模糊化,这样会使得重要的边缘信息丢失。在这种情况下, 双边滤波器也许可以让你的生活更加轻松。
- 这种技术对相邻亮度值相似的像素选择性的执行滤波,可以保留边缘的锋利。
- 你可以控制滤波空间尺寸,也可以定义在滤波输出相邻像素的相似程度。这需要根据他发明的颜色强度变化情况以及距离滤波像素的距离来确定。
双边滤波整体上是对图像进行2D高速平滑,但同时也考虑了相邻像素的数值差异,来尽可以减少对边缘模糊化(这是我们想保留的)。这也就是说卷积核的形状与图像局部内容有关系,每一个像素位置都不同。
下面给出一个具体的例子。假设你对图像中距离边缘附近区域进行滤波。如果采用简单的高斯平滑将会吧边缘模糊化。这是因为它烤间滤波区域(靠近高斯核的中心)。但采用双边滤波器可以感知边缘存在,这是因为像素数值差一笔较大。所以他就会对边缘两侧的像素使用很小的权值,减少滤波区域对边缘的与。对于数值相对统一的区域则使用更强的平滑,这是因为它们不具有边缘元素。
感谢OpenCV 提供了bilateralFilter() 函数来实现对图像的双边滤波。
bilateralFilter(src, d, sigmaColor, sigmaSpace)
这个函数 要求四个输入参数:
- 第一个参数是图像;
- 接我下来的参数 d ,是邻近滤波像素对应的直径;
- 后面两个参数 sigmaColor , sigmaSpace 分别定义了颜色强度分布标准方差 (1D)以及2D空间分布。
- sigmaSpace参数定义了核的在x和y方向上的空间延展(与前面讨论高斯核时的概念相同)
- sigmaColor 参数定义了一个一维高斯分布,它指明能够被允许的像素强度差异的程度。
滤波图像最后(加权)值时它的空间和强度权重的成绩,因此:
-
对于滤波像素附近的相似像素对于结果影响比较大;
-
对于远离滤波中心像素对结果影响较小,这是由空间高斯定义引起的;
-
对于和中心滤波像素相差比较大的像素对于结果影响较小(这是由颜色强度高斯定义引起的),尽管他们靠近滤波中心也不行。
-
Python
"""
Apply Bilateral Filtering
"""
# Using the function bilateralFilter() where d is diameter of each...
# ...pixel neighborhood that is used during filtering.
# sigmaColor is used to filter sigma in the color space.
# sigmaSpace is used to filter sigma in the coordinate space.
bilateral_filter = cv2.bilateralFilter(src=image, d=9, sigmaColor=75, sigmaSpace=75)
cv2.imshow('Original', image)
cv2.imshow('Bilateral Filtering', bilateral_filter)
cv2.waitKey(0)
cv2.imwrite('bilateral_filtering.jpg', bilateral_filter)
cv2.destroyAllWindows()
- C++
// Apply bilateral filtering
Mat bilateral_filter;
bilateralFilter(image, bilateral_filter, 9, 75, 75);
imshow("Original", image);
imshow("Bilateral filtering", bilateral_filter);
imwrite("bilateral_filtering.jpg", bilateral_filter);
waitKey(0);
destroyAllWindows();
return 0;
下面是 双边滤波的结果。可以看到在均匀区域的像素并平滑,而保留了那些模板中裂纹(边缘)的成分。双边滤波是一个非常有效的技术,对于大的核尺寸它的计算量比较大。 所以依据你的应用合理的选择它。
※ 滤波总结 ※
本文首先讨论了卷积核的概念,以及如何用于对图像进行滤波。然后通过他们对图像进行数学运算来实现特定的效果,比如平和和锐化。展示了如何在OpenCV中实现2D滤波。 在等同卷积卷积核之后,我们创建了更多定制的核,用在OpenCV中的 filter2D()函数中。 介绍了OpenCV中的重要内置函数MediaBlur(),GaussianBlur()。 最后展示了 OpenCV中的 bilateralFilter()函数,是如何在保留图片中清晰边缘的同时又平滑了图像。
■ 相关文献链接:
● 相关图表链接:
文章来源: zhuoqing.blog.csdn.net,作者:卓晴,版权归原作者所有,如需转载,请联系作者。
原文链接:zhuoqing.blog.csdn.net/article/details/122758097
- 点赞
- 收藏
- 关注作者
评论(0)