OpenCV图像卷积之cv.filter2D()函数详解
API
照例,我们搬一下官网的 API:
C++
void cv::filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1, -1), double delta=0, int borderType=BORDER_DEFAULT )
Python
dst=cv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
函数详解
这个函数一般是用于图像的卷积,但 OpenCV 文档里说这个函数不完全等于图像卷积。这一点说实话我看到的时候也震惊到了,我一直是拿它当卷积来用的。但是仔细考虑后,我认为这一点完全是定义上的差异,日常用的图像卷积定义和这个函数的功能实际上是一致的。
HelloWorld
直接上手做往往能给人最直观的感受。因此,最开始这里我要放一个使用这个函数的最小化程序,可以称之为该函数的 hello world 程序。这个程序跑通了,就可以很方便地尝试其他参数的作用了。
# HelloWrold Program of cv.filter2D # by Aling on 2021/1/18 import numpy as np import cv2 as cv def main(): img = cv.imread("你想读的图片") # 定义卷积核 kernel = np.ones((10, 10)) / 100 # 执行滤波 avg_filtered = cv.filter2D(img, -1, kernel) # 显示图片 cv.imshow("Average filtered", avg_filtered) cv.waitKey(0) cv.destroyAllWindows() if __name__ == "__main__": main()
这个程序读取一张图片,并将其通过10 x 10 的卷积核作均值滤波并显示结果。
参数详解
同样的,这里把各个参数打一张表:
参数 | 类型 | 是否必须指定(默认值) | 具体含义 |
---|---|---|---|
src | numpy.ndarray | 是 | 原图像 |
ddepth | int | 是 | 目标图像深度(指数据类型) |
kernel | numpy.ndarray | 是 | 卷积核 |
anchor | tuple | 否(卷积核中心) | 卷积锚点 |
delta | 是数据类就行 | 否(0) | 偏移量,卷积结果要加上这个数字 |
borderType | int (实际上是 enum 类) | 否(cv.BORDER_DEFALUT ) | 边缘类型 |
src
这个参数没什么好说的,就是原图像。它可以是任何色彩模式,这就意味着如果你把原本送到这个函数里的图片从黑白变成了彩色(单通道变成了 3 通道),你并不需要更改其他参数。本身,对多通道的图像,卷积就是以通道为单位进行的。
ddepth
这个参数有点费解了。大部分情况下不需要管它是干嘛的,直接把它设成 -1 就没有任何问题。
参数名 ddepth
,英文是 desired depth,即期望深度。什么意思呢?我们来看它的可能取值表(来自这里):
输入深度(src.depth() ) | 期望深度(ddepth ) |
---|---|
CV_8U | -1/CV_16S/CV_32F/CV_64F |
CV_16U/CV_16S | -1/CV_32F/CV_64F |
CV_32F | -1/CV_32F/CV_64F |
CV_64F | -1/CV_64F |
这个表里的一大串 CV
打头的符号到底是什么意思呢?实际上这些符号的末尾字母对应了数据类型:
U == Unsigned int # 无符号整型 S == Signed int # 有符号整型 F == Float # 浮点型
中间的数字很显然代表了数据类型所占用的空间(bit)。所以,所谓深度,其实指的是数据类型。
那就好说了,你会发现这里其实是规定了输出数据的类型,包括每个通道的每个像素占用多少空间。输出数据的类型必须根据上面的表格中输入对应的类型指定。这里 -1 表示输出类型和输入相同。
不过,值得注意的是,似乎有些数据类型无法通过 cv2.imshow
正常显示,可以用 matplotlib.pyplot.imshow
来代替。
但是,还是注意,没有关于数据类型的特别要求时,这个功能是不需要的,取 -1 即可。
kernel & anchor
这两个参数都是卷积相关的,因此放在一节里面讲述。接下来的内容假设你已经了解了图像卷积。
这里,kernel
很显然表示的是卷积核,这是一个 numpy.ndarray
类型的矩阵。这个矩阵的生成可以用 numpy
自带的函数,但是对于复杂一些的卷积核,OpenCV 内部的一些函数显然更合适。如 getStructuringElement
,getGaussianKernel
,前者用于获取特定形状的核,后者则是高斯核生生成器(不过要注意生成的是一个向量)。
# 方法一示例 kernel = cv.getStructuringElement(cv.MORPH_RECT, (11, 11)) # 方法二示例 vector = cv.getGaussianKernel(11, -1) kernel = vector @ vector.T
anchor
则表示锚点。什么叫锚点呢?看下面这张图:
可以说,锚点 anchor
决定了卷积核相对于生成目标点的位置。虽然锚点是相对于卷积核来定义的,但是卷积的过程更像是通过锚点去寻找卷积核。遍历图像中的每一个像素,以每一个像素为锚点,按照相对位置生成卷积范围,和卷积核对应元素相乘再求和得到目标图像中对应像素的值。可以用公式表示成:
这实际上就是一般的图像卷积计算方法。OpenCV 文档里面叙述的卷积定义则是需要将卷积核围绕锚点对称变换,再用上面的公式计算。这种方法更接近卷积原始的定义,但是对图像的卷积一般的应用而言(滤波、深度学习)这两种定义实际上没有什么区别。、
锚点用一个元组指定,是相对于卷积核左上角的坐标,从 0 开始:
# 替换掉 HelloWorld 程序的对应行。 avg_filtered = cv.filter2D(img, -1, kernel, (1, 2))
delta
这个参数的存在其实有些费解,它的效果很简单,就是把卷积的结果加上一个固定的数字。直观上将,它将整个图像变亮或者变暗了。从应用上来说,它实际上将卷积过程扩展成了一般的线性运算( ∗ * ∗ 表示卷积):
这个线性运算可以将结果限定在某一符合要求的范围内(比如大于 0),而且不会阻断梯度的传递。
borderType
这个参数更改的是 border 的生成方式。这个 border,也就是边缘,是在靠近边缘的部分卷积时用到的,参考上面那张图。无论 anchor
是什么,总有些边缘上的点对应的卷积范围无法完全落在原图内,这就需要对原图进行扩展。扩展的方法就是我们这里参数的含义。
这个参数取值是 OpenCV 里的 cv::BorderTypes
enum 类定义的值,其可能取值及其对于边缘的作用结果如下图所示(图片来自 OpenCV Python 教程):
要注意几点:
cv.BORDER_WARP
在这个函数里面是不支持的;cv.BORDER_CONSTANT
会将边缘取为 0(黑色),而且没法改,因为原函数并没有留出相关的接口。
扩展——滤波相关函数
图像滤波是一个很常用的功能,对此,OpenCV 也定义了很多函数。这里介绍的 cv.filter2D
是这些函数中可控性最高的,因为你可以用自定义的核进行卷积。但是一些常用的滤波,我们可以不必自己定义相应的核,直接用定义好的函数就可以了。
均值滤波
dst = cv.blur(img, (11, 11)) # 等效于: dst = cv.filter2D(img, -1, np.ones((11, 11))/11**2)
高斯滤波
dst = cv.GaussianBlur(img, (11, 11), -1) # 等效于 vector = cv.getGaussianKernel(11, -1) kernel = vector @ vector.T dst = cv.filter2D(img, -1, kernel)
中值滤波
dst = cv.medianBlur(img, 11)
注意中值滤波是取中位数作为目标值,是一个非线性滤波子,因此无法通过线性滤波的 cv.filter2D
来等效实现。
参考链接
总结
到此这篇关于OpenCV图像卷积之cv.filter2D()函数详解的文章就介绍到这了,更多相关OpenCV图像卷积cv.filter2D()函数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
最新评论