新博客地址:http://gorthon.sinaapp.com/
第一:获取验证码素材
#! /usr/bin/env python # coding: u8 # file:get_img.py import urllib2 if __name__ == '__main__': [file('./%d.png'%i, 'wb').write(urllib2.urlopen('http://www.呵呵.com/captcha/').read()) for i in range(100) ]
类似如下的图片:
第二:获取背景色, 背景上的表格颜色
由于所有图片的背景色相同,背景上的表格颜色相同,而且表格在背景上的位置也是固定不变的,所以操作变得更加简单.
#! /usr/bin/env python # coding: u8 # file:getinfo.py import Image im = Image.open('./0.png') w, h = im.size for y in range(h): for x in range(w): pixel = im.getpixel((x, y)) print pixel, if x == 10: raise
得到的结果如下:
>>> (227, 218, 237) (128, 191, 255) (227, 218, 237) (227, 218, 237) (227, 218, 237) (227, 218, 237) (128, 191, 255) (227, 218, 237) (227, 218, 237) (227, 218, 237) (227, 218, 237)
对照图片可以得到:
BG_COLOR = (227, 218, 237) # 背景颜色 GRID_COLOR = (128, 191, 255) # 背景表格颜色
第三:去背景上的表格
#! /usr/bin/env python # coding: utf-8 import Image # 随便找个图片先获得以下参数: GRID_COLOR = (128, 191, 255) #背景表格颜色 BG_COLOR = (227, 218, 237) # 背景颜色 im = Image.open('./0.png') w, h = im.size im.show() # 去除背景表格前的图片 for y in range(h): for x in range(w): pixel = im.getpixel((x, y)) if pixel == GRID_COLOR: im.putpixel((x, y), BG_COLOR) # 将表格颜色设置为背景色即可清除表格 im.show() # 去除背景表格后的图片
去除背景表格前:
去除背景表格后:
第四:获得弧线的颜色
获得弧线的颜色有点困难(当然获取像素值的时候可以用其他工具比如gcolor2等来获取就可以了,这里通过PIL来实现),不过观察一下就知道其实很简单了.在我下载的100张图片当中每张图片的弧线都是相同的规律.
截取图片的后半部分:
可以看出:每一列从后往前遍历,遇到的第一个不为背景色的像素点即为弧线的末端.
def getArcColor(im): global ARC_COLOR w, h = im.size for x in range(w-1, w/2, -1): for y in range(h): pixel = im.getpixel((x, y)) if pixel != BG_COLOR: ARC_COLOR = pixel return
结果为:(128, 128, 255),多弄几个图片测试证明结果正确,故 ARC_COLOR = (128, 128, 255).
第五:去除弧线
定义全局变量
ARC_COLOR = (128, 128, 255) # 干扰线(弧线)颜色
然后在eraseGrid函数里面加上下面(有颜色的语句)即可消除弧线:
if pixel == GRID_COLOR or pixel == ARC_COLOR: im.putpixel((x, y), BG_COLOR) # 将表格及弧线颜色设置为背景色即可清除表格和弧线
效果如下:
明显地,仅仅像上面程序那样去除弧线是不行的,他把字符也擦除了!
所以在去除的时候还要加上判断,如果此弧线上或下的点为非背景色,那么将弧线的颜色设置为该非背景色.实现如下:
if pixel == GRID_COLOR: im.putpixel((x, y), BG_COLOR) # 将表格颜色设置为背景色即可清除表格 elif pixel == ARC_COLOR: up = y > 0 and im.getpixel((x, y-1)) or None down = y < h-1 and im.getpixel((x, y+1)) or None up_down = up or down im.putpixel((x, y), up_down and up_down or BG_COLOR)
结果:
注:上面是基于像素来处理的,只能征对本验证码有效.
下面的程序可以简单的处理:
import Image, ImageEnhance, ImageFilter im = Image.open('./0.png') enhancer = ImageEnhance.Contrast(im) im = enhancer.enhance(6) # 提高对比度 im = im.convert('1') # 二值化 im = im.filter(ImageFilter.MedianFilter) # 中值去噪 im.show()
结果:
可以根据需要设定对比度的的增量,然后就是按照上面的方法将弧线去除就可以了.
还有就是用聚类分析的话也可以,但是这个地方就不用那么复杂了,硬性K均值聚类(hcm)那可是相当复杂的了,当初做字幕检测的时候(用的C++)写了好长的代码啊,现在可不想再去碰那东西了.但是如果上面用了聚类的话在字符切割的时候就很方便了,直接切就是了.
整理一下代码得到以上步骤的完整代码:
#! /usr/bin/env python # coding: utf-8 import Image, ImageEnhance, ImageFilter # 随便找个图片先获得以下参数: GRID_COLOR = (128, 191, 255) #背景表格颜色 BG_COLOR = (227, 218, 237) # 背景颜色 ARC_COLOR = (128, 128, 255) # 干扰线(弧线)颜色 BLACK = (0,0,0) WHITE = (255, 255, 255) def eraseGridAndArc(im): w, h = im.size pixels = im.load() for y in range(h): for x in range(w): pixel = pixels[x, y] if pixel == BG_COLOR or pixel == GRID_COLOR: im.putpixel((x, y), WHITE) elif pixel == ARC_COLOR: up = y > 0 and pixels[x, y-1] or None down = y < h-1 and pixels[x, y+1] or None up_down = up or down im.putpixel((x, y), up_down and up_down or WHITE) enhancer = ImageEnhance.Contrast(im) im = enhancer.enhance(255) # 提高对比度,这个参数大点就好 im = im.convert('1') # 二值化 im.show() return im if __name__ == '__main__': im = Image.open('./0.png') eraseGridAndArc(im)
结果如下:
第六:字符分割
字符分割的方法有很多,如垂直投影法,联通域分析法,以上二者结合等,由于本验证码的各个字符间没有粘连现象,所以可以用简单点的垂直投影法.
垂直投影法:逐列扫描二值图片,计算当列像素点为1的点个数,累加结果即为当列投影的个数,然后画成一张图,形成波峰波谷,再以波谷(本验证码波谷为0)为界分割字符.
得到垂直投影图:
def getPoint(im, end=False): pixels = im.load() w, h = im.size range_w = end and range(w-1, 0, -1) or range(w) for x in range_w: for y in range(h): if pixels[x, y] == 0: return end and x + 1 or x def getVerticalProjection(im): ''' 得到垂直投影图, 返回投影图数据 ''' pixels = im.load() w, h = im.size start_x = getPoint(im) end_x = getPoint(im, end=True) graph = [0] * (end_x - start_x) for x in range(start_x, end_x): for y in range(h): pixel = pixels[x, y] if pixel == 0: # 此列有字符 graph[x - start_x] += 1 return start_x, end_x, graph def showVerticalProjection(graph): w = len(graph) h = max(graph) img = Image.new('1', (w, h)) for x in range(w): for y in range(h): if y <= graph[x]: img.putpixel((x, y), 255) else: break img = img.transpose(Image.FLIP_TOP_BOTTOM) img.show() if __name__ == '__main__': im = Image.open('./0.png') im = eraseGridAndArc(im) graph = getVerticalProjection(im) showVerticalProjection(graph) print graph
结果:
graph = [18, 25, 25, 25, 13, 8, 7, 7, 8, 7, 8, 18, 16, 14, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 5, 9, 12, 14, 21, 16, 13, 10, 4, 4, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 5, 6, 12, 17, 19, 21, 17, 12, 9, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 16, 17, 13, 11, 9, 3, 3, 3, 8, 14, 19, 19, 13, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 19, 23, 24, 24, 7, 6, 6, 6, 6, 6]
然后以graph中的0为界限分割即可 .
切割写的有点糟糕......如下代码:
def cut(start_x, end_x, graph, im): chars = [start_x] for i, v in enumerate(graph): if v !=0: if graph[i - 1] == 0: chars.append(i + start_x) elif graph[i - 1] !=0: chars.append(i + start_x) chars.append(end_x) result = list() for i in range(len(chars) - 1): result.append((chars[i], chars[i + 1] - 1)) result = [v for (i, v) in enumerate(result) if not i%2] # 去除波谷的0 w, h = im.size chars = list() for char_start_x, char_end_x in result: chars.append(im.crop((char_start_x, 0, char_end_x, h))) # 纵向切割之后再横向切割: result = list() for char in chars: w, h = char.size pixels = char.load() try: for y in range(h): for x in range(w): if pixels[x, y] == 0: start_y = y raise except: pass try: for y in range(h-1, -1, -1): for x in range(w): if pixels[x, y] == 0: end_y = y raise except: pass result.append(char.crop((0, start_y, w, end_y))) return result if __name__ == '__main__': im = Image.open('./0.png') im = eraseGridAndArc(im) start_x, end_x, graph = getVerticalProjection(im) chars = cut(start_x, end_x, graph, im) for char in chars: char.show()
效果如上图(图中最上面的黑色部分是标题栏,与字符无关----图片太小,标题栏没显示完整...)
最后将
第七:字符旋转
【未完待续……】
第2篇:http://blog.csdn.net/bh20077/article/details/7041400
第3篇:http://blog.csdn.net/bh20077/article/details/7311183