最近在做二维码的识别,其实就是调用微信二维码的opencv接口,但是遇到一些问题,有部分的二维码无法识别,大概是1100张里面有将近70张左右,感觉概率还挺高的。
而且有的二维码直接用手机微信的二维码扫描是可以识别的,但是在程序里是没办法识别的,这就让人摸不着头脑。按道理来说应该用的是一样的代码(如果微信没有偷偷优化的话)。没理由手机能识别,而程序里的没办法识别。经过观察发现,手机微信在静止状态下,其实也很难识别,得是运动状态,带些抖动,造成一点运动模糊,才能快速地将二维码识别出来。又观察了一下扫描上来的二维码,锐化程度很高,而且可能细节有些缺失,所以导致的识别失败。
于是我就在思考,我可不可以在程序里模仿这种运动模糊,来提升识别的成功率。便试着加了一个高斯模糊,发现确实识别成功率提升了,1100多张图片,大概只有7张无法识别了。
虽然,识别成功率确实大幅度提升了,但是我感觉太玄学了(笑),还不清楚其中的原理,如果各位大佬知道原理的话请告诉我。
下面是我接触线性滤波时候的一些笔记。
关键字:相关操作(correlation),2D高斯核生成,
我们在模糊一张图片的时候,我们使用的是高斯模糊GaussianBlur
。
下面是C++和python中的函数方法:
C++:
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )
Python:
cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) → dst
可以看出来,在python中有6个参数:
src
:要处理的源图,通道数无所谓,但是深度要CV_8U, CV_16U, CV_16S, CV_32F或者CV_64Fksize
:高斯核尺寸sigmaX
:x轴方向高斯核的标准差sigmaY
:y轴方向高斯核的标准差dst
:输出图片(和源图一样的尺寸和类型)borderType
:边缘处的处理类型一般来说,src
,ksize
,sigmaX
这三个参数是必填的,剩下的sigmaY
,dst
,borderType
是选填的。
ksize
必须是奇数,高宽可以不一样,值可以为0,这样就会从sigmaX
和sigmaY
中计算得到宽高.sigmaY
是0的话,就会被设定为等于sigmaX
,但是如果它们两者都是0,则会从高斯核的宽高中计算得到。 dst = cv2.GaussianBlur(src, (3, 3), 0)
上面就是最简单的高斯模糊的代码,不过为了避免未知错误,最好还是把ksize
,sigmaX
和sigmaY
几个值都设定好。
既然名字叫高斯滤波,高斯体现在哪里呢?
在概率论的时候我们都学过一维高斯函数,也叫正态分布,其表达式是:
f ( x ) = 1 2 π σ e − ( x − μ ) 2 2 σ 2 , − ∞ < x < ∞ f(x) = \frac{1}{\sqrt{2\pi } \sigma } e^{-\frac{(x-\mu )^2 }{2 \sigma ^2} } , -\infty < x < \infty f(x)=2πσ1e−2σ2(x−μ)2,−∞<x<∞
所以实际可以设定的参数大概只有 σ \sigma σ,设定好了 σ \sigma σ就可以根据 x x x得到 f ( x ) f(x) f(x)的值了。
因为我们的处理对象是图像,有着两个维度,所以我们需要的是二维的高斯函数:
f ( x , y ) = 1 2 π σ x σ y 1 − ρ 2 exp [ − 1 2 ( 1 − ρ 2 ) ( ( x − μ x ) 2 σ x 2 − 2 ρ ( x − μ x ) ( y − μ y ) σ x σ y + ( y − μ y ) 2 σ y 2 ) ] f(x, y) = \frac{1}{2\pi \sigma_x \sigma _y \sqrt[]{1-\rho^2} } \exp \left[-\frac{1}{2(1- \rho ^2)}\left ( \frac{(x- \mu_x)^2}{\sigma _x^2} - \frac{2\rho (x-\mu_x )(y-\mu_y)}{\sigma _x \sigma _y} +\frac{(y- \mu_y)^2}{\sigma _y^2} \right ) \right ] f(x,y)=2πσxσy1−ρ21exp[−2(1−ρ2)1(σx2(x−μx)2−σxσy2ρ(x−μx)(y−μy)+σy2(y−μy)2)]
我们的高斯卷积核的生成就是根据这个公式来的。在使用GausssianBlur
函数的时候,想要得到一个高斯核的话,就肯定要知道 σ x \sigma_x σx和 σ y \sigma_y σy。
多元高斯分布完全解析
getGaussianKernel
:生成1D高斯核的函数,参数是ksize和 σ \sigma σ标准差
我们回到一维,就像官方文档里写的,两个 σ \sigma σ值可以通过高斯核的尺寸来获得,也就是官方提供了一个预设值,这个预设值和尺寸有着某种数量关系,在getGaussianKernel
函数里可以发现:
σ = 0.3 ⋅ ( ( ksize − 1 ) ⋅ 0.5 − 1 ) + 0.8 \sigma = 0.3 \cdot ((\text{ksize}-1) \cdot 0.5 - 1) + 0.8 σ=0.3⋅((ksize−1)⋅0.5−1)+0.8这样的话,在一个维度上,一维高斯函数可以表示为: f ( x ) = α ⋅ e − ( x − ksize-1 2 ) 2 2 σ 2 f(x) = \alpha \cdot e ^{-\frac{\left(x-\frac{\text{ksize-1}}{2} \right)^2}{2 \sigma ^2} } f(x)=α⋅e−2σ2(x−2ksize-1)2
为什么要进行归一化?
目标图像的亮度:如果卷积核的各行各列元素之和为1,则经过处理后输出的目标图像和源图像的亮度一致。
通过 x x x和 y y y两个方向的1D高斯核的相乘,我们可以生成一个2D高斯核,如果两者是相同的话,就相当于:与自己的转置相乘。
举个例子,生成ksize为3, σ \sigma σ为1的2D高斯核
print(cv2.getGaussianKernel(3, 1).T * cv2.getGaussianKernel(3, 1))
[[0.07511361 0.1238414 0.07511361]
[0.1238414 0.20417996 0.1238414 ]
[0.07511361 0.1238414 0.07511361]]
*这个只是测试,和GausssianBlur
生成的高斯核肯定是不一样的。
相关与卷积
在高斯模糊中采用的是滑动加权平均,是一种协相关操作(correlation)。
这里涉及到我们在图像处理中常见的两种操作:协相关(correlation)和卷积(convolution)
均值滤波,和高通滤波一样,也是一种低通滤波,用于模糊图像。
原理也是非常简单,计算一个窗口内像素的平均值,然后输出到目标图像的锚点。
其实本质和高斯滤波一样,都要对高斯核内的数值进行归一化,只不过高斯滤波的卷积核为不同的区域根据高斯分布分配了不同的权重,而均匀滤波的卷积核中全部区域的数值都是相等的。
C++和python中的函数方法:
C++:
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
Python:
cv2.blur(src, ksize[, dst[, anchor[, borderType]]]) → dst
其他的参数都已经比较熟悉了,单独来说一下anchor
anchor
:有一个默认值 ( − 1 , − 1 ) (-1, -1) (−1,−1),表示锚点在核的中心既然均值滤波本身没啥可以讲的,我们可以拓展的说一下高通和低通滤波。就我们所知,均值滤波和高斯滤波都属于低通滤波,所以会对图像进行模糊处理。(与之相反,高通滤波,则会对图像进行锐化处理)
高通和低通其实都是频域上的概念,通过对核函数求傅里叶变化,就可以得到卷积核的频域表达形式,也就能直观的看出滤波器的特性了。
如果想相比较直观的看出一个滤波器是高通还是低通,还是得看频域的曲线图,以后有时间再补吧。
这么看的话,边缘信息算是高频的信息了,在经过低通滤波器的处理之后,会被去除。