Python PIL库实现验证码去噪:从灰度化到二值化的完整步骤
本文详细介绍利用Python PIL库处理字符型验证码的完整流程。内容涵盖将彩色图片转换为灰度图的多种算法、通过设定阈值实现图像二值化,以及采用九宫格算法去除孤立噪点的具体代码实现与原理分析,旨在为验证码识别前的图像预处理提供清晰的技术参考。
Python PIL验证码处理流程概述
关于字符型图片验证码识别的完整流程及Python实现,博主从该领域前辈的经验中汲取了大量知识。
在进行验证码识别之前,若需收集足够样本,首要任务是对原始验证码图片进行预处理。这一过程通常包含三个核心步骤:将彩色图像转换为灰度图、对灰度图进行二值化处理,以及去除图像噪点。本文将以较为简单的验证码为例,演示如何利用Python的PIL库完成图像去噪操作。
首先观察未经任何处理的原始验证码图片:

图像预处理主要依赖于PIL库中的Image类。
彩色图像转换为灰度图
1. 彩色图片转换为灰度图
首先调用Image类的open方法打开上述图片,获取一个PIL.Image.Image对象。随后,便可利用该对象提供的convert、filter、point及putpixel等方法对图像进行进一步处理。
利用convert方法,可以将上述彩色图片转换为灰度图像:
# encoding=utf8
from PIL import Image
def main():
image = Image.open('RandomPicture.png')
imgry = image.convert('L')
imgry.save('gray.png')
if __name__ == '__main__':
main()
运行结果如下:

通过保存后的图片可见,原彩色图已转换为灰度图(或称黑白图)。所谓灰度图,是指由不同灰度值的像素组合而成的图像。任何颜色均可由红、绿、蓝三原色构成,若某点原始颜色为RGB(R, G, B),则可通过以下几种算法将其转换为灰度值:
1. 浮点算法:Gray = R * 0.3 + G * 0.59 + B * 0.11 2. 整数方法:Gray = (R * 30 + G * 59 + B * 11) / 100 3. 移位方法:Gray = (R * 76 + G * 151 + B * 28) >> 8; 4. 平均值法:Gray = (R + G + B) / 3; 5. 仅取绿色分量:Gray = G;
通过上述任一方法计算出Gray值后,将原RGB(R, G, B)中的R、G、B统一替换为Gray,形成新的颜色RGB(Gray, Gray, Gray),以此替换原像素颜色,即得到灰度图。
以下通过代码实例进行演示:
# encoding=utf8
from PIL import Image
def main():
image = Image.open('RandomPicture.png')
print 'image mode: ', image.mode
print image.getpixel((0, 0))
print '-' * 40
imgry = image.convert('L')
print 'imgry mode: ', imgry.mode
print imgry.getpixel((0, 0))
if __name__ == '__main__':
main()
运行结果:
image mode: RGB
(21, 10, 26)
----------------------------------------
imgry mode: L
15
代码说明:
通过image.mode属性可获取当前PIL.Image.Image对象(即当前打开的图片)的mode值。该mode值表示图片的颜色单位是由RGB三个分量组成,还是由单一的灰度值组成;
而getpixel方法用于获取特定像素点的RGB值或灰度值。图像由众多像素构成,每个像素在图像中对应特定的坐标(x, y),其中“(0, 0)”代表图像左上角顶点的像素。
由上述运行结果可知,在将图片转换为灰度图之前,坐标“(0, 0)”代表的像素颜色由RGB组成,具体值为(21, 10, 26);而在通过convert方法将彩色图片转换为灰度后,坐标“(0, 0)”代表的像素值变为单一数值“15”。通过打印imgry.mode亦可确认,此时图片已转换为灰度图,其每个像素的颜色均表现为一个灰度值。
此时,亦可简单计算验证:将前述浮点算法代入上述(21, 10, 26)三个值进行计算:
>>> 21*0.3+10*0.59+26*0.11
15.059999999999999
结果显示,确实通过浮点算法将RGB值转换为了灰度值。
灰度图像二值化处理
2. 灰度图二值化
在获得灰度图后,下一步是进行二值化处理。所谓二值化,即将灰度图像转换为仅由黑白两色构成的图像。其思路是确定一个阈值,大于该阈值的像素表示为白色,小于该阈值的像素表示为黑色,从而将图像的像素(灰度值)划分为两部分:0和1。例如,0代表黑色,1代表白色,进而可用一串由0和1组成的数字序列来表示一张图片。
将灰度图二值化需使用point方法,该方法接收一个灰度转二值的映射表table。具体原理暂不详述,代码实现过程如下:
# encoding=utf8
from PIL import Image
def get_bin_table(threshold=115):
'''
获取灰度转二值的映射table
0表示黑色,1表示白色
'''
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
return table
def main():
image = Image.open('RandomPicture.png')
imgry = image.convert('L')
table = get_bin_table()
binary = imgry.point(table, '1')
binary.save('binary.png')
if __name__ == '__main__':
main()
运行结果:

从结果可看出,我们已将最初的彩色图经由灰度图转换为仅由黑白二色组成的图片,实现了二值化。此处需说明,threshold参数值针对当前验证码图片较为合适,该值需根据验证码类型的不同进行调试以确定。
接着查看坐标(0, 0)代表的像素颜色值:
# encoding=utf8
from PIL import Image
def get_bin_table(threshold=115):
'''
获取灰度转二值的映射table
0表示黑色,1表示白色
'''
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
return table
def main():
image = Image.open('RandomPicture.png')
print 'image mode: ', image.mode
print image.getpixel((0, 0))
co = image.getcolors()
print co
print '-' * 40
imgry = image.convert('L')
print 'imgry mode: ', imgry.mode
print imgry.getpixel((0, 0))
co = imgry.getcolors()
print co
print '-' * 40
table = get_bin_table()
binary = imgry.point(table, '1')
print 'binary mode: ', binary.mode
print binary.getpixel((0, 0))
co = binary.getcolors()
print co
if __name__ == '__main__':
main()
运行结果:
image mode: RGB
(21, 10, 26)
None
----------------------------------------
imgry mode: L
15
[(1, 2), (2, 3), (1, 4), (1, 5), (4, 6), (3, 8), (3, 9), (6, 10), (4, 11), (4, 12), (7, 13), (8, 14), (3, 15), (12, 16), (7, 17), (6, 18), (5, 19), (13, 20), (9, 21), (9, 22), (4, 23), (5, 24), (7, 25), (3, 26), (6, 27), (7, 28), (3, 29), (3, 30), (3, 31), (5, 32), (1, 33), (3, 35), (2, 36), (2, 37), (2, 38), (2, 39), (1, 41), (3, 42), (1, 43), (2, 44), (7, 45), (3, 46), (5, 47), (1, 48), (3, 49), (3, 50), (3, 51), (5, 52), (4, 53), (1, 54), (7, 55), (7, 56), (10, 57), (4, 58), (5, 59), (6, 60), (5, 61), (12, 62), (7, 63), (10, 64), (12, 65), (14, 66), (15, 67), (11, 68), (9, 69), (11, 70), (7, 71), (9, 72), (5, 73), (10, 74), (5, 75), (5, 76), (5, 77), (8, 78), (7, 79), (3, 80), (5, 81), (6, 82), (5, 83), (3, 84), (3, 85), (6, 86), (2, 87), (3, 88), (2, 90), (3, 91), (1, 93), (2, 94), (3, 95), (1, 96), (3, 97), (2, 99), (3, 100), (3, 101), (1, 102), (3, 104), (4, 105), (1, 106), (3, 108), (4, 110), (4, 111), (4, 112), (3, 113), (3, 114), (5, 115), (2, 116), (3, 117), (8, 118), (8, 119), (8, 120), (7, 121), (9, 122), (9, 123), (11, 124), (11, 125), (2, 126), (10, 127), (9, 128), (7, 129), (13, 130), (11, 131), (11, 132), (9, 133), (16, 134), (11, 135), (12, 136), (8, 137), (14, 138), (12, 139), (13, 140), (20, 141), (22, 142), (19, 143), (14, 144), (23, 145), (17, 146), (10, 147), (18, 148), (13, 149), (11, 150), (26, 151), (16, 152), (14, 153), (11, 154), (17, 155), (10, 156), (12, 157), (12, 158), (20, 159), (18, 160), (16, 161), (22, 162), (20, 163), (16, 164), (13, 165), (14, 166), (13, 167), (11, 168), (17, 169), (8, 170), (16, 171), (20, 172), (12, 173), (10, 174), (10, 175), (10, 176), (11, 177), (7, 178), (8, 179), (7, 180), (5, 181), (7, 182), (4, 183), (7, 184), (4, 185), (4, 186), (5, 187), (6, 188), (2, 189), (1, 190), (4, 191), (6, 192), (12, 193), (8, 194), (10, 195), (3, 196), (13, 197), (9, 198), (19, 199), (18, 200), (20, 201), (16, 202), (18, 203), (24, 204), (33, 205), (25, 206), (33, 207), (38, 208), (31, 209), (46, 210), (39, 211), (53, 212), (54, 213), (33, 214), (42, 215), (54, 216), (60, 217), (50, 218), (36, 219), (48, 220), (32, 221), (45, 222), (28, 223), (24, 224), (21, 225), (19, 226), (21, 227), (13, 228), (12, 229), (12, 230), (13, 231), (5, 232), (8, 233), (4, 234), (5, 235), (1, 236), (1, 237), (2, 238), (1, 239), (1, 240), (1, 242), (1, 243)]
----------------------------------------
binary mode: 1
0
[(503, 0), (1993, 1)]
代码说明:
通过binary mode的值可知,二值化后得到的图片像素值由0或1表示,且当前(0, 0)代表的像素值为0,代表黑色。从上方图片亦可确认,左上角顶点确为黑色。
上述代码中还使用了getcolors方法,该方法用于返回像素信息,结果为一个包含元素的列表:[(该种像素的数量, (该种像素)), ...]。当该列表特别大时,它会返回None,这也是为何上方彩色图片调用getcolors时返回None的原因。而[(503, 0), (1993, 1)]则表示我们得到的二值化黑白图片,由503个黑色像素点和1993个白色像素点组成。
通过binary.size可获取二值化后黑白图片的width和height值:(78, 32)。这表示该图片由78x32个像素点组成,正好等于503+1993的和。(78, 32)也说明该图片横向共有32行,每行有78个像素点。将由0和1表示的图片打印出来观察:
# encoding=utf8
from PIL import Image
def get_bin_table(threshold=115):
'''
获取灰度转二值的映射table
0表示黑色,1表示白色
'''
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
return table
def main():
image = Image.open('RandomPicture.png')
imgry = image.convert('L')
table = get_bin_table()
binary = imgry.point(table, '1')
width, height = binary.size
lis = binary.getdata() # 返回图片所有的像素值,要使用list()才能显示出具体数值
lis = list(lis)
start = 0
step = width
for i in range(height):
for p in lis[start: start+step]:
if p == 1: # 将白色的点变成空格,方便人眼看
p = ' '
print p,
print
start += step
if __name__ == '__main__':
main()
运行结果:

通过上述结果已大致可看出,该图片表示的内容为“959c”。
基于九宫格算法去除噪点
3. 去除噪点
由上述结果不难发现,除了表示“959c”的“0”之外,图片中还存在其他由“0”代表的噪点。我们需要尽可能去除这些噪点,以便后续的识别训练。
对于去除噪点,此处借鉴了字符型图片验证码识别完整过程及Python实现中提到的“九宫格”方法,代码实现如下:
# encoding=utf8
from PIL import Image
def sum_9_region_new(img, x, y):
'''确定噪点 '''
cur_pixel = img.getpixel((x, y)) # 当前像素点的值
width = img.width
height = img.height
if cur_pixel == 1: # 如果当前点为白色区域,则不统计邻域值
return 0
# 因当前图片的四周都有黑点,所以周围的黑点可以去除
if y < 3: # 本例中,前两行的黑点都可以去除
return 1
elif y > height - 3: # 最下面两行
return 1
else: # y不在边界
if x < 3: # 前两列
return 1
elif x == width - 1: # 右边非顶点
return 1
else: # 具备9领域条件的
sum = img.getpixel((x - 1, y - 1)) \
+ img.getpixel((x - 1, y)) \
+ img.getpixel((x - 1, y + 1)) \
+ img.getpixel((x, y - 1)) \
+ cur_pixel \
+ img.getpixel((x, y + 1)) \
+ img.getpixel((x + 1, y - 1)) \
+ img.getpixel((x + 1, y)) \
+ img.getpixel((x + 1, y + 1))
return 9 - sum
def collect_noise_point(img):
'''收集所有的噪点'''
noise_point_list = []
for x in range(img.width):
for y in range(img.height):
res_9 = sum_9_region_new(img, x, y)
if (0 < res_9 < 3) and img.getpixel((x, y)) == 0: # 找到孤立点
pos = (x, y)
noise_point_list.append(pos)
return noise_point_list
def remove_noise_pixel(img, noise_point_list):
'''根据噪点的位置信息,消除二值图片的黑点噪声'''
for item in noise_point_list:
img.putpixel((item[0], item[1]), 1)
def get_bin_table(threshold=115):
'''获取灰度转二值的映射table,0表示黑色,1表示白色'''
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
return table
def main():
image = Image.open('RandomPicture.png')
imgry = image.convert('L')
table = get_bin_table()
binary = imgry.point(table, '1')
noise_point_list = collect_noise_point(binary)
remove_noise_pixel(binary, noise_point_list)
binary.save('finaly.png')
if __name__ == '__main__':
main()
运行结果:

通过截图可知,我们已成功去除图片四周的噪点以及一些孤立的噪点。
还需补充说明的是,除上述步骤外,还可通过PIL的ImageEnhance和ImageFilter对图像进行其他处理(例如增加对比度、亮度、锐化等)。此处不再举例说明,因为不同的图片经过这些处理后,效果可能各异。
最后,感谢上述博主的分享,为本人学习验证码识别相关知识提供了诸多参考:
字符型图片验证码识别完整过程及Python实现
Python3.5+sklearn 使用SVM自动识别字母验证码
python简单验证码识别
基于Python的PIL库学习(一)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









