文章目录
开发环境
- Python2.7
- Pillow4.1.1
- numpy1.15.2
- sklearn0.20.0
思路
- 对图像进行灰度化、二值化处理
- 根据图像特征进行切割,提取单个字符内容
- 针对单个字符进行机器学习
- 识别
实践
演示用的原始图片如下
图像预处理 - 灰度、二值化
首先用PIL内置的convert方法进行灰度处理,经过灰度后,每个像素点的颜色值从RGB的3维变成了1维,值的范围为0-255,值越大,颜色越倾向于白,或者说颜色更淡。
观察灰度后的图片,可以发现字符的颜色更深,背景、干扰线的颜色更浅,这一特征是后续二值化操作的关键。
二值化是在灰度图的基础上将所有像素点的颜色二值化,也就是非黑即白,这里遍历像素点,简单地根据一个预设的灰度阈值,将大于阈值的灰度值设为0,即白色,小于阈值的灰度值设为255,即黑色。(这里阈值需要经过多次试验,确定一个合适的值,如果验证码比较复杂,背景、干扰线不能很好地和字符区分,需要一些特殊处理和计算来获得合适的阈值,这点以后有机会再做深入研究)
二值化效果如下:
from PIL import Image
def remove_color(img, threshold=105):
"""
灰度、二值化
:param img: Image实例
:param threshold: 灰度阈值
:return:
"""
img_greyed = img.convert('L') # 灰度化
table = img_greyed.load()
width, height = img_greyed.size
for x in range(width):
for y in range(height):
if table[x, y] < threshold:
table[x, y] = 0
else:
table[x, y] = 255
return img_greyed
图像预处理 - 8临域法降噪
经过灰度、二值化的图片,可以看到干扰线被去得比较干净,但还剩余了零散的噪点,接下来根据8临域法来去除噪点。
8临域指的是一个像素点周围的8个点,如果一个点的周围都是白色背景,那么这个点就一定是噪点。反之,遍历各像素点,统计其8个临近点的值,如果值为255即黑色的数量大于某个阈值,则该点不是噪点,这里阈值取的是6,效果如下:
效果拔群!
def remove_noise(img, threshold=6):
"""8临域法降噪"""
table = img.load()
width, height = img.size
for x in range(1, width-1):
for y in range(1, height-1):
count = 0
if table[x, y-1] == 255:
count += 1
if table[x, y+1] == 255:
count += 1
if table[x-1, y] == 255:
count += 1
if table[x+1, y] == 255:
count += 1
if table[x-1, y-1] == 255:
count += 1
if table[x-1, y+1] == 255:
count += 1
if table[x+1, y+1] == 255:
count += 1
if table[x+1, y-1] == 255:
count += 1
if count > threshold:
table[x, y] = 255
注意x与y的遍历范围在[1, width-1]与[1, height-1]之间,这是为了避免取8临域的时候发生下标越界。
图像切割
步骤一:连通域法获取字符图像
对于连通域法的讲解,https://www.cnblogs.com/fireae/p/3723785.html 这篇文章挺不错的。
以下python代码实现了对图像的两次扫描,第一次扫描将相邻像素点进行标号,并记录标号等价关系;第二次扫描将标号替换成最小等价标号,并将像素点分组,用于后续切割。最后根据传入阈值,将面积小于阈值的连通域作为噪点剔除。
def get_domains(img_denoised, threshold=10):
"""
获取连通域
"""
table = img_denoised.load()
width, height = img_denoised.size
tab_img = Image.new('L', (width, height), 255)
tab_table = tab_img.load()
equals = [] # 存储标号等价关系,e.g. [[1, 2, 3], [4, 6], [5]]
tab = 1 # 下一个标号
# 第一次扫描
for y in range(height):
for x in range(width):
if table[x, y] == 0:
tab = set_temp_tab(tab_table, x, y, tab, equals)
# 第二次扫描
domains = {
}
for y in range(height):
for x in range(width):
if tab_table[x, y] != 255:
min_tab = get_min_tab(tab_table[x, y], equals)
if min_tab in domains:
domains[min_tab].append((x, y))
else:
domains[min_tab] = [(x, y)]
real_domains = [domains[i] for i in domains if len