你有没有试过在Photoshop或移动应用程序的帮助下模糊或锐化图像?如果是,那么您已经使用过卷积核。在这里,我们将解释如何在OpenCV中使用卷积进行图像滤波。
您将使用2d卷积核和OpenCV计算机视觉库应用不同的模糊和锐化技术的图像。我们将向您展示如何在Python和c++中实现这些技术。
我们将使用下面的图像为我们所有的编码操作。
让我们先看一看将用于过滤图像的代码。我们将详细讨论每一行代码,以便您完全理解它。
(1)Python
import cv2
import numpy as np
image = cv2.imread('test.jpg')
# 如果图像为空,则打印错误信息
if image is None:
print('Could not read image')
# 应用恒等核
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()
# 应用均值滤波器
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()
(2)C++
// 导入依赖
#include
#include
// 命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 应用恒等核
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();
// 使用均值滤波器
// 创建全一矩阵
Mat kernel2 = Mat::ones(5,5, CV_64F);
// 规范化元素
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();
}
在图像处理中,卷积核是一个用于过滤图像的二维矩阵。卷积核也被称为卷积矩阵,它通常是一个正方形的MxN矩阵,其中M和N都是奇整数(例如3×3, 5×5, 7×7等)。
这种核可以用于对图像的每个像素执行数学运算,以达到预期的效果(如模糊或锐化图像)。但为什么要模糊图像呢?这里有两个重要的原因:
图像的滤波是通过核与图像的卷积来实现的。简单地说,图像与核的卷积代表了核与图像中相应元素之间的简单数学运算。
一旦使用上面的3×3内核对源图像中的每个像素执行此操作,得到的过滤图像将显得模糊。这是因为与这个核的卷积操作有一个平均效果,这往往是平滑或模糊的图像。您很快就会看到内核中单个元素的值如何决定过滤的性质。例如,通过更改核元素的值,还可以实现锐化效果。这个概念简单却非常强大,因此被用于许多图像处理管道中。
现在您已经学会了使用卷积核,让我们来探索如何在OpenCV中实现卷积核。
下面的代码执行了以下步骤
3x3 NumPy
数组定义恒等卷积核filter2D()
函数来执行线性滤波操作imshow()
显示原始和过滤后的图像imwrite()
将过滤后的图像保存到磁盘filter2D(src, ddepth, kernel)
需要三个输入参数:
ddepth
,它表示得到的图像的深度。值为-1表示最终图像也将具有与源图像相同的深度kernel
,我们将它应用到源图像import cv2
import numpy as np
image = cv2.imread('test.jpg')
"""
恒等卷积
"""
kernel1 = np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]])
# filter2D() 函数可用于将核应用于图像
# ddepth表示得到的图像的深度。值为-1表示最终图像也将具有与源图像相同的深度
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()
(2)C++
// 导入依赖
#include
#include
//命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 使用恒等卷积
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();
}
接下来,我们将演示如何模糊图像。在这里,我们也将定义一个自定义核,并使用OpenCV中的filter2D()函数对源图像应用过滤操作。
首先定义一个5×5核,它只包含一个核。注意,我们还将核除以25。这是为什么呢?在你使用2D卷积矩阵对图像进行卷积之前,你需要确保所有的值都是标准化的。这是通过将核中的每个元素除以内核中的元素数来完成的,在这种情况下,内核中的元素数是25。这确保了所有值都在[0,1]范围内。
现在使用filter2D()
函数来过滤图像。如您所见,可以使用filter2D()
对任意用户定义的核进行卷积。
(1)Python
import cv2
import numpy as np
image = cv2.imread('test.jpg')
"""
图像模糊
"""
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()
(2)C++
// 导入依赖
#include
#include
//命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 图像模糊
// 使用全一初始化矩阵
Mat kernel2 = Mat::ones(5,5, CV_64F);
// 标准化元素
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();
}
你也可以使用OpenCV内置的blur()
函数来模糊图像。使用它来模糊图像,在这里您不需要明确定义内核。只需使用ksize输入参数指定内核大小,如下面的代码所示。模糊函数将在内部创建5×5模糊核,并将其应用到源图像。
下面的示例使用了 blur()
函数,将生成与上面使用 filter2d()
函数的示例完全相同的输出。
(1)Python
import cv2
import numpy as np
image = cv2.imread('test.jpg')
"""
图像模糊
"""
img_blur = cv2.blur(src=image, ksize=(5,5)) # 使用模糊函数模糊一个图像,其中ksize是内核大小
# 显示使用cv2.imshow ()
cv2.imshow('Original', image)
cv2.imshow('Blurred', img_blur)
cv2.waitKey()
cv2.imwrite('blur.jpg', img_blur)
cv2.destroyAllWindows()
(2)C++
// 导入依赖
#include
#include
//命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 使用OpenCV c++ blur()函数进行模糊
Mat img_blur;
blur(image, img_blur, Size(5,5));
imshow("Original", image);
imshow("Blurred", img_blur);
imwrite("blur.jpg", img_blur);
waitKey();
destroyAllWindows();
}
现在我们将使用OpenCV对图像应用高斯模糊。该技术使用高斯滤波器,它执行加权平均,而不是第一个示例中描述的均匀平均。在这种情况下,高斯模糊权重像素值,基于它们到核中心的距离。离中心越远的像素对加权平均的影响越小。下面的代码使用OpenCV中的GaussianBlur()
函数卷积图像。GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
函数GaussianBlur()
需要四个输入参数:
sigmaX
和sigmaY
,它们都被设置为0。这些是高斯核在X(水平)和Y(垂直)方向上的标准差。sigmaY
的默认设置是0。如果您简单地将sigmaX
设置为0,那么将从核大小(分别为宽度和高度)计算标准偏差。您还可以显式地将每个参数的大小设置为大于零的正值。(1)Python
import cv2
import numpy as np
image = cv2.imread('test2.png')
"""
Gaussian模糊
"""
# sigmaX是高斯核标准差
# Ksize是核大小
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()
(2)C++
// 导入依赖
#include
#include
//命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 实现高斯模糊
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();
}
我们也可以使用中值模糊,使用OpenCV中的medianBlur()
函数。中值滤波是将源图像中的每个像素替换为图像在核区域中像素的中值。
medianBlur(src, ksize)
这个函数只有两个必需参数:
import cv2
import numpy as np
image = cv2.imread('test2.png')
"""
中值滤波
"""
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()
(2)C++
// 导入依赖
#include
#include
//命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 中值滤波
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();
}
见下图中值滤波的结果。注意,对于相同的核大小,中值滤波的效果比高斯模糊更突出。中值滤波通常用于减少图像中的椒盐噪声,如图所示。
你也可以用2D卷积核锐化图像。首先定义一个自定义的2D核,然后使用filter2D()
函数对图像应用卷积操作。
在下面的代码中,定义了一个3×3锐化核。
(1)Python
import cv2
import numpy as np
image = cv2.imread('test2.png')
"""
锐化
"""
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()
(2)C++
// 导入依赖
#include
#include
//命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 锐化
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();
}
那么,我们会得到什么样的结果呢?请看下面的图表。锐化效果非常令人印象深刻。右图的锐化图像显示了之前看不见的木头裂缝。
虽然模糊是降低图像噪声的一种有效方法,但通常不希望对整个图像进行模糊,因为重要的细节和锐利的边缘可能会丢失。在这种情况下,可以使用双边滤波器。
双边滤波实质上是对图像应用二维高斯(加权)模糊,同时也考虑相邻像素强度的变化,以最小化边缘附近的模糊(这是我们希望保持的)。这意味着核的形状实际上取决于每个像素位置的局部图像内容。
这是一个具体的例子。假设,您正在过滤图像中靠近边缘的一个区域。一个简单的高斯模糊过滤器会模糊边缘,因为它位于过滤区域附近(接近高斯过滤器的中心)。但双边滤波器可以感知边缘,因为它也考虑像素强度的差异。因此,它将为横跨边缘的像素计算一个更低的权重,从而减少它们对过滤区域的影响。强度较均匀的区域模糊较重,因为它们与强边缘无关。
幸运的是,OpenCV提供了bilateralFilter()
函数来过滤图像。bilateralFilter(src, d, sigmaColor, sigmaSpace)
d
,定义用于过滤的像素邻域的直径。sigmaColor
和sigmspace
分别定义了(1D)颜色强度分布和(2D)空间分布的标准偏差。sigmaSpace
参数定义了核在x和y方向上的空间范围(就像前面描述的高斯模糊过滤器)。sigmaColor
参数定义一维高斯分布,它指定像素强度差异可以容忍的程度。过滤后的图像中像素的最终(加权)值是其空间权重和强度权重的乘积。因此,
(1)Python
import cv2
import numpy as np
image = cv2.imread('test2.png')
"""
双边滤波器
"""
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()
(2)C++
// 导入依赖
#include
#include
//命名空间
using namespace std;
using namespace cv;
int main()
{
// 读取图像
Mat image = imread("test.jpg");
// 如果图像为空,则打印错误信息
if (image.empty())
{
cout << "Could not read image" << endl;
}
// 双边滤波器
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滤波。在理解了identity核之后,我们继续创建更多的自定义核,这些核可以与OpenCV中的filter2D()
函数一起使用。探索了OpenCV中一些重要的内置滤波函数,如MedianBlur()
和GaussianBlur()
。最后,演示了OpenCV中的bilateralFilter()
,看看它是如何在保持清晰边缘的同时平滑图像的。