在这篇文章中,你会了解到关于什么是自适应阈值,并且如何对图像使用自适应阈值。这篇文章中,除了介绍OpenCV中经典的cv2.adaptiveThreshold
函数,还有我根据halcon中的local_threshold
函数实现的自适应阈值方法。
在普通的二值化阈值算法中,是对图片中所有的像素都应用同样的一个阈值,大津算法(Otsu)也是如此,只不过这个算法自动计算了一个能让图片尽量分为两个部分的阈值,总之,都是全局阈值(global threshold)。但在实际需求中,由于光线明暗的变化,阴影等等,仅仅是全局阈值,可能并没有办法得到我们想要的结果。
这个时候,先不要急于使用深度学习分割方法(实例分割或者语义分割),你应该先试试自适应阈值的方法。
自适应阈值,顾名思义,自适应阈值就是通过每个像素相邻的像素,来计算出这个像素位置的阈值,每一个像素的阈值,都是私人定制!
现在我们通过一张图片来观察一下全局二值化和私人定制自适应阈值处理一张图片的效果。
为了方便进行阈值处理,我们直接把这张图片按照灰度图片来读取 :
img = cv2.imread('chart.JPG', 0)
这里第二个变量的位置写0,就代表按照黑白图片模式来读取一张图片。
可以看到这张表格有一些行的背景颜色是灰色的,而且还存在光照不均的问题,如果我们使用128作为全局阈值,进行二值化处理:
_,res3 = cv2.threshold(img,128,255,cv2.THRESH_BINARY)
cv2.imshow('binary threshold',res3)
cv2.waitKey()
我们的目的是提取出这张表格上的文字,这样的效果显然不能让我们满意。
otsu_thres,res4 = cv2.threshold(img,128,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imshow('otsu threshold',res4)
cv2.waitKey()
大津算法为我们计算出来的全局阈值是95.0,居然还是一个浮点数我笑了。
再来看看效果:
比我们自己随便设的128好一些,不过也并不能满足我们。
再来看一下OpenCV中cv2.adaptiveThreshold
函数的效果:
res2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 21, 15)
cv2.imshow('adaptive threshold',res2)
cv2.waitKey()
看起来很妙,不是吗?
在解读参数之前,来看看这个算子背后的数学。
T
=
m
e
a
n
(
I
L
)
−
C
T = mean(I_{L} )-C
T=mean(IL)−C
T代表了某个像素位置计算出来的阈值,
I
L
I_{L}
IL代表了这个像素的邻域像素,
m
e
a
n
(
I
L
)
mean(I_{L} )
mean(IL)代表对邻域像素取均值,OpenCV中提供了两种均值,一种是普通的均值cv2.ADAPTIVE_THRESH_MEAN_C
,另一种是高斯均值cv2.ADAPTIVE_THRESH_GAUSSIAN_C
,也就是按照高斯分布那样,离得越近的像素灰度值对均值的贡献越大,越远越小。这两种方法几乎同样受欢迎。
现在我们再回头看一下这个函数的输入变量,第一个输入img
代表输入图片不用说了,第二个输入255代表目标区域画成白色,第三个代表我们用的普通均值,第四个代表我们要暗色区域,第五个代表我们要
21
×
21
21\times21
21×21这个范围作为邻域,最后一个15代表均值算完之后再减掉15作为这个像素的私人定制阈值。
现在请你用高斯均值来处理一下这张图片。
现在再来看看halcon中是什么思路处理类似的问题的。halcon中的dyn_threshold
函数与上面OpenCV中的cv2.adaptiveThreshold
如出一辙,但halcon还提供了一个叫local_threshold
的函数
T
(
r
,
c
)
=
μ
(
r
,
c
)
(
1
+
k
(
σ
(
r
,
c
)
R
−
1
)
)
T(r,c)=\mu (r,c)(1+k(\frac{\sigma (r,c)}{R} -1))
T(r,c)=μ(r,c)(1+k(Rσ(r,c)−1))
这个公式稍稍复杂些,
μ
(
r
,
c
)
\mu (r,c)
μ(r,c)代表邻域的均值,k是一个系数,
σ
(
r
,
c
)
\sigma (r,c)
σ(r,c)是邻域的标准差,对于一般的uint8图片来说,这个值小于等于128。所以
σ
(
r
,
c
)
R
−
1
\frac{\sigma (r,c)}{R} -1
Rσ(r,c)−1绝对会是一个小于0的数,因为我们是要提取暗色的区域,因此k越大提取的区域越小。
当要提取图片中的亮色部分时,我们先对图片取反,然后再用相同的方法计算。halcon文档:
https://www.mvtec.com/doc/halcon/12/en/local_threshold.html
我们要如何计算邻域的标准差??这是一个好问题,但你的概率论老师现在想哭
σ
=
E
[
(
X
−
μ
)
2
]
=
E
[
X
2
]
−
(
E
[
X
]
)
2
\sigma =\sqrt{E[(X-\mu )^{2} ]} =\sqrt{E[X^{2} ]-(E[X])^{2} }
σ=E[(X−μ)2]
=E[X2]−(E[X])2
所谓的E,不过就是图像处理里面的均值滤波,这下子你又会了,说干就干:
def gen_std_img(img, mask_size):
img = np.array(img, dtype=float)
img2 = img ** 2
s = cv2.blur(img, (mask_size, mask_size))
s2 = cv2.blur(img2, (mask_size, mask_size))
return np.sqrt(s2 - s ** 2)
一鼓作气
def local_threshold(img, mode='dark', mask_size=15, scale=0.2, range=128):
if mode == 'dark':
img = np.array(img, dtype=float)
else:
ones = np.ones(img.shape)
img = np.array(img, dtype=float)
img = 255 * ones - img
mean_img = cv2.blur(img, (mask_size, mask_size))
std_img = gen_std_img(img, mask_size)
threshold_img = mean_img*(1 + scale * (std_img / range - 1))
tmp_img = img - threshold_img
_, res = cv2.threshold(tmp_img, 0, 255, 1)
return res
用OpenCV实现这个算子!
这个算子对于纹理的保存效果更好,但这也可能是噪声,anyway,所以还是要看使用的场合。
所以我用的参数是多少?你猜到了吗??
这篇帖子看得不过瘾?为什么不了解一下我的OpenCV视频课?🤔
丰富的图像处理知识,绝对让你成为高手
https://study.163.com/course/introduction/1209967299.htm?inLoc=ss_ssjg_tjlb_opencv
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)