用Python实现china-pub登录验证码的识别
文/图 luxijun
自己一直想写一个简单的验证码识别程序,能明白识别过程的原理就好,于是近日试验了一下。常去china-pub买书,发现它的验证码色彩清新数字格式工整,很适合初学者拿来练习,就从它入手吧。
我使用的是Python 2.5.1,查看其文档发现没有可以处理图像的模块,上网搜索Python Imaging Library(PIL)并下载了PIL-1.1.6.win32-py2.5.exe和pil-handbook.pdf,最终解决了问题。
图像处理
查看验证码的属性,大小为40×10,也就是说四个字符每个数字占的大小为10×10,图片生成地址asp">http://www.china-pub.com/edition06/imgchk/validatecode.asp。在识别过程中,大家可以在本地保存几张做测试。在china-pub主页多刷几次,尽量选取重复数字少的验证码图片保存到本地计算机,这样只要处理几张图片就可以得到0~9的全部数字,然后分析数字的特征和它们之间的差别。
import Image
def img2four(image):
width = 10
height = 10
left=0
upper=0
right=10
lower=10
c = 4
while(c):
box = (left, upper, right, lower)
im = Image.open(image)
region = im.crop(box)
region.convert(L).save(crop_+str(4-c)+.bmp)
left = left + 10
right = right + 10
c = c – 1
这段代码可以将验证码图像分割和去色,并分别保存为crop_0.bmp~crop_3.bmp,如将验证码3519分割为3、5、1、9并保存。也可以自己将其重命名为3.bmp、5.bmp等,方便后面代码测试时直接用for循环就可以测试10张图片。代码中的crop()用于分割图像,其参数给出分割的形状。convert(L)是把图像转化成8bit灰度图,0代表黑,255代表白,这样每个数字图片中只包含两种颜色值,字符色和背景色,方便比较和识别。在PIL中,坐标系是图片的左上角为(0,0)的。得到0~9的单个点阵图片后,我们用VC6.0全部打开,方便观察和比较,如图1所示。
图1
验证码识别
由于所有的点阵图片的背景色都是相同的,而数字颜色则是不同的,所以要想看某个数字的全部pixel值(或者你认为眼睛看的颜色不是很准的话),可以写如下程序。
def printPixel(image):
img = Image.open(image)
for y in range(0, 10):
for x in range(0, 10):
print img.getpixel((x,y)),
print
printPixel()的参数image为图片路径。通过这个函数的调用,我们可以按图的格式打印出各个像素的值,细看是可以看出相同的数值连在一起构成的“数字”的,而且可以知道背景色的值为238。
开始我是一点点用眼睛去找各个数字之间的相同和不同的,比如只有5和7经过(0,0)点,然后再找一个它俩之间的不同点就可以判断出是5还是7,然后再找其他数字的特点与差别。没想到通过if语句竟然可以识别出全部数字(太顺利倒让我不太适应了)。用if语句的好处是不用找多个关键点,只要一个一个找就好了,数字也能被一个一个的识别出来。
def cross(color):
if color !=238:
return True
else:
return False
def recognize(image):
bgcolor = 238
img = Image.open(image)
p = img.getpixel((1,8))
if cross(p):
return 7
p = img.getpixel((0,0))
if cross(p):
return 5
p = img.getpixel((2,1))
if cross(p):
return 1
p = img.getpixel((3,1))
if cross(p):
return 4
p = img.getpixel((1,1))
if cross(p):
# not 1, must be 6
return 6
p = img.getpixel((1,7))
if cross(p):
return 2
p = img.getpixel((2,5))
if cross(p):
return 9
p = img.getpixel((5,4))
if cross(p):
# not 9, must be 0
return 0
p = img.getpixel((1,4))
if cross(p):
return 8
else:
return 3
def getCode(image):
img2four(image)
n0 = recognize(crop_0.bmp)
n1 = recognize(crop_1.bmp)
n2 = recognize(crop_2.bmp)
n3 = recognize(crop_3.bmp)
# remove crop files
import os
#import time
#time.sleep(3)
#if you want to see the temp images, sleep 3 secs
if os.path.exists(crop_0.bmp):
os.remove(crop_0.bmp)
if os.path.exists(crop_1.bmp):
os.remove(crop_1.bmp)
if os.path.exists(crop_2.bmp):
os.remove(crop_2.bmp)
if os.path.exists(crop_3.bmp):
os.remove(crop_3.bmp)
return str(n0) + str(n1) + str(n2) + str(n3)
函数getCode()完成调用图片分割函数、数字识别、删除临时图片文件和返回识别处的结果的工作。识别过程是很快的,所以分割后的临时图片文件在文件夹下根本看不见,可以去掉代码中的注释符号,让线程睡眠3秒钟来查看。
总觉得用眼睛一点一点找不够智能,所以我又写了个compare.py来比较和统计从(0,0)到(9,9)这100个点都有哪些数字通过,然后打印出通过的比较少的(0~2个)那些坐标来做判断。如果某点只有一个数字经过,那么一下就可以判断出是这个数字,比如(1,8)只有7经过。根据这个统计结果,其他数字的判断点的选取也就变得非常简单了,最多两个点就可以判断一个数字。compare.py的代码如下。
import Image
def printPixel(image):
# get a bmp files all pixel
img = Image.open(image)
l=[]
for y in range(0, 10):
for x in range(0, 10):
l.append(img.getpixel((x,y)))
return l
def getxy(number):
#convert lists index to the tuple of pixel
x = number % 10
y = number / 10
return (x, y)
def findDiff():
# find different point between 0 and 9
list = []
for i in range(0, 10):
list.append(printPixel(str(i)+.bmp))
for j in range(0,100):
count = 0
num = []
for k in range(0, 10):
if list[k][j] != 238:
num.append(k)
count = count + 1
if count < 3 and count > 0:
print count = , count, , pixel is , getxy(j),, num is ,num
if __name__ == "__main__":
findDiff()
其中的printPixel()用来以list的形式返回其参数所传递进去的图像像素值,list长度为100,是以行为主的存储方式存储的。getxy()用来将printPixel()所返回的list的索引值转化为坐标值的形式,以便打印输出给用户观看。findDiff()用来对0.bmp到9.bmp这10个图像进行统计和打印结果。
GUI界面
单击“Get bmp”按钮从网上获取验证码图片文件,“Get code”调用getCode()函数获取验证码的数值并显示。初始化时使用的图片是python的图片。因为图像很简单,所以识别是可以做到100%正确的。初始化的界面如图2所示,识别后的界面如图3所示,具体的代码实现,大家可以看随文提供的源代码,这里就不贴出来了。
图2
图3
后记
第一次听说验证码识别时让我十分吃惊,不明白高手是怎么做到的。上网找资料明白是通过关键点的判断来实现的,所以花了一个下午终于完成了编写,希望能给其他想写验证码识别程序的朋友提供一点思路