如果你有想要交流的想法、技术,欢迎在评论区留言。
本篇博客继续学习 OpenCV 的二值化操作。
局部二值化方法
局部二值化方法也叫自适应阈值法。在上篇文章中,咱学会的是一种全局性的阈值,只需要设定一个阈值,整个图像都和这个阈值比较。
而自适应阈值可以看成一种局部性的阈值,通过设定一个区域大小,比较这个点与区域大小里面像素点 的平均值(或者其他特征)的大小关系确定这个像素点的情况。
说白了就是在一个范围内进行二值化操作。
方法的语法格式如下:
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst
其中各参数如下:
- src - 输入图像(8 位单通道图像)
- maxValue - 使用 THRESH_BINARY 和 THRESH_BINARY_INV 的最大值
- adaptiveMethod - 自适应阈值算法,平均 (ADAPTIVE_THRESH_MEAN_C)或高斯(ADAPTIVE_THRESH_GAUSSIAN_C)
- thresholdType - 阈值类型,必须为 THRESH_BINARY 或 THRESH_BINARY_INV 的阈值类型
- blockSize - 块大小(奇数且大于 1 )
- C-常数,从平均值或加权平均值中减去的数。 通常情况下,这是正值,但也可能为零或负值
其中最关键的参数是 adaptiveMethod
,具体内容如下:
adaptiveMethod:决定如何计算阈值
- cv2.ADAPTIVE_THRESH_MEAN_C:阈值是邻域的平均值
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值的加权和,其中权重是高斯窗口
当然在网络上还找到关于 blockSize
最好的一段解释。
在使用平均和高斯两种算法情况下,通过计算每个像素周围 blockSize
x blockSize
大小像素块的加权均值并减去常量 C 即可得到自适应阈值。
如果使用平均的方法,则所有像素周围的权值相同;
如果使用高斯的方法,则每个像素周围像素的权值则根据其到中心点的距离通过高斯方程得到。
该方法的返回值是:dst - 经函数处理过的图像。
测试代码如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 局部二值化
def local_threshold(src):
old_img = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(src, cv2.COLOR_RGB2GRAY)
# 平均
binary1 = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 25, 10)
# 高斯处理
binary2 = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 25, 10)
# 建立一个 2 行 2 列的图
plt.subplot(2, 2, 1)
# 原图
plt.imshow(old_img, 'gray')
plt.title("old_img")
# 灰度图
plt.subplot(2, 2, 2)
plt.imshow(gray, 'gray')
plt.title("gray")
# 添加第三幅图
plt.subplot(2, 2, 3)
plt.imshow(binary1, 'gray')
plt.title("MEAN")
# 添加第四幅图
plt.subplot(2, 2, 4)
plt.imshow(binary2, 'gray')
plt.title("GAUSSIAN")
plt.show()
if __name__ == "__main__":
# 读取图像并转换为灰度图
img = cv2.imread('7.jpg')
cv2.imshow("aaa", img)
local_threshold(img)
运行之后对比,发现局部二值化确实比全局二值化呈现效果好。
相同的效果,再把全局二值化得到的结果做一下对比。
代码如下,上篇博客已经做了相关说明,本文不再补充。
def global_threshold(src):
# 手动二值化处理
# 设置阈值大小 threshold
thresh = 125
# 设置超过阈值像素值的最大值
maxval = 255
# THRESH_BINARY:超过阈值为maxval,否则为0
# THRESH_BINARY_INV:超过阈值为0,否则为maxval(相当于上个参数取反)
# THRESH_TRUNC:超过阈值为thresh,低于阈值灰度值不变
# THRESH_TOZERO:超过阈值灰度值不变,否则为0
# THRESH_TOZERO_INV:超过阈值为0,低于阈值灰度值不变
ret, thresh1 = cv2.threshold(src, thresh, maxval, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(src, thresh, maxval, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(src, thresh, maxval, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(src, thresh, maxval, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(src, thresh, maxval, cv2.THRESH_TOZERO_INV)
titles = ['original image', 'Binary',
'binary-inv', 'trunc', 'tozero', 'tozero-inv']
images = [src, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
plt.subplot(2, 3, i+1)
plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([])
plt.yticks([])
plt.show()
局部二值化更好的描绘了图片的轮廓。
对于二值化的学习,橡皮擦一直在好奇这个东西用在哪里比较合适,由于接触时间短,毕竟才开始 7 天,我能想到的只有验证码识别和扫描图像识别了。
验证码处理
先对验证码进行处理,例如下面的验证码。
先使用全局的看一下二值化的结果。
对比之后发现效果还可以,如果应用于 OCR 技术会大幅度提高识别率。但是如果我将验证码修改成下图中间区域,发现有的就无法正常识别了。
对于上图二维码,进行局部二值化操作,看一下效果。
如果验证码再次增加难度,例如下面的混合验证码,得到的结果又是怎样的呢?可以查阅一下。
全局阈值确实容易丢失细节。
扫描文件识别
如果你要处理一些扫描到电脑里的文件,二值化看起来也是非常棒的一种选择,可以自行试试,效果也是一样的。
全局阈值进行二值化之后如下图所示。
局部二值化处理之后的效果对比。
上篇博客中的函数改造
在上篇博客中,用到计算像素点矩阵中的所有像素点的灰度值的平均值 avg,用来做阈值,今天在检索中发现了一个其他的函数,分享给大家。
# 自己求图像平均阈值,一个常见的办法
# 求出图像均值作为阈值来二值化
def custom_image(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
h, w = gray.shape[:2]
# 将图像转一维数组,一行,w*h 列,转换维度要保证其 size 不变
m = np.reshape(gray, [1, w*h])
# 求平均值来当做阈值,来分割图像
mean = m.sum() / (w*h)
print("mean: ", mean)
ret, binary = cv2.threshold(gray, mean, 255, cv2.THRESH_BINARY)
plt.imshow(binary, 'gray')
plt.title("binary")
plt.show()
OpenCV 尾声
1 个小时又过去了,对 Python OpenCV 图像写入相关的知识点,你掌握了吗?
空闲之余,可以订阅橡皮擦的爬虫百例课程学习爬虫知识。
想学 Python 爬虫,可以订阅橡皮擦专栏哦~
🈲🈲🈲🈲 点击发现惊喜 🈲🈲🈲🈲