背景:朋友在為"關山口男子職業技術學校"寫一款校園應用,於是找MoonXue寫一個學生選課系統的登錄接口.為了搞定這個接口,不得不先搞定這個系統的驗證碼.
驗證碼大概是這個樣子
看上去不怎么難,沒有干擾線沒有粘連沒有扭曲.但還是沒能用pytesser直接將它識別出來,因為當中有噪點和其他背景噪聲的存在.MoonXue的工作就是去掉這些討厭的東西
先介紹一下,我們的工具:
1.Pytesser 它是基於一個c語言實現名為tesser的識別工具的python封裝.可惜比較笨,只能做最簡單的識別而且不認識漢字
2.Requests 它是我們喜歡寫爬蟲的孩子的最愛,提供人性化的接口,代價是失去了一點效率(寫python就別考慮效率啦)
3.BeautifulSoup 它和Requests是一對好機油,讓提取文檔中所需的內容變成一件簡單的事情
4.PIL 它是今天的主角,PIL是專門用作圖像處理的庫,很好很強大.熟練的人甚至可以用它來P圖
如何寫爬蟲去實現模擬登錄此處不細說,下面說說怎么解決驗證碼識別
解決思路如下:
1.先用PIL對圖像做一次圖像增強,因為原圖中數字的邊緣和背景中的噪聲並不是太分明,做了增強之后能將兩者分離.如果不分離,可能會在去噪點的時候導致數字中有部分會缺失
im = Image.open("randomimage/randomImage11.jpg") im = ImageEnhance.Sharpness(im).enhance(3)
參數為3是經過實驗之后感覺比較理想的值,太強不好,太弱也不好
2.做完預處理之后,就是去背景噪聲了.背景噪聲指的是背景中各種明暗變換的色塊,肉眼也許不會注意到這個.但是它的存在會給識別帶來影響.我最初的做法是將圖像轉換為只有黑白兩色,這樣自然就將噪聲轉換成了噪點.
效果如圖
但我希望能去掉噪點,成為這樣
最先想到的是種子染色法 ,什么是種子染色法請參看這個鏈接
為了防止壞鏈,此處做部分轉載
種子染色法英文叫做Flood Fill ,實際上Flood Fill這個名稱更貼切一點,因為這個方法作用在一個圖的結點上時恰似洪水一樣“淹沒”與之相連的其他結點並以相同的方式蔓延出去,這個方法通常用於計算一個圖的極大連通子圖(這里的“圖”是圖論的概念)。設想一個無向圖,我們從這個圖中一個未標號(“標號”可以理解為“染色”)的結點開始,將此結點和從這個結點出發可達的所有結點都賦予相同的標號(染上相同的顏色),那么我們就得到了這些被標號的結點所組成的一個極大連通子圖,搜索下一個未標號的結點並重復上述過程我們便可以找到所有的極大連通子圖。“染色”的過程可以用DFS或者BFS實現,如果結點數為V,邊數為E,因為我們在Flood Fill過程中“造訪”每個結點兩次,“造訪”每條邊兩次,所以得到所有極大連通子圖的時間復雜度為o(V+E) 。
來自Wikipedia的一個示例:
想象每個白色方塊為圖中的結點,相鄰的方塊(上下左右)有邊相連,那么這個圖就有三個極大連通子圖,這演示了Flood Fill查找其中一個極大連通子圖的過程。
在這是借要用種子染色法計算每塊的面積,然后把小體積的塊當作噪點去除.
代碼在這

def check(j,i): try: if pix[j,i] == 0 and matrix[j][i] != -1: return True else: return False except: return False def juli(r,s): return abs(r[0]-s[0])+abs(r[1]-s[1])+abs(r[2]-s[2]) for i in range(w): for j in range(h): r = [0,0,0] s = [0,0,0] if pix[j,i] == 0: if check(j-1,i): r[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j-1,i)) print r print s print "-"*55 if juli(r,s) <=l: matrix[j][i] = matrix[j-1][i] maps[str(matrix[j][i])]+=1 elif check(j-1,i-1): r[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j-1,i-1)) if juli(r,s) <=l: matrix[j][i] = matrix[j-1][i-1] maps[str(matrix[j][i])]+=1 elif check(j,i-1): r[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j-1,i)) if juli(r,s) <=l: matrix[j][i] = matrix[j][i-1] maps[str(matrix[j][i])]+=1 elif check(j+1,i+1): r[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j+1,i+1)) if juli(r,s) <=l: matrix[j][i] = matrix[j+1][i+1] maps[str(matrix[j][i])]+=1 elif check(j,i+1): r[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j,i+1)) if juli(r,s) <=l: matrix[j][i] = matrix[j][i+1] maps[str(matrix[j][i])]+=1 elif check(j-1,i+1): pr[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j-1,i+1)) if juli(r,s) <=l: matrix[j][i] = matrix[j-1][i+1] maps[str(matrix[j][i])]+=1 elif check(j+1,i-1): r[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j+1,i-1)) if juli(r,s) <=l: matrix[j][i] = matrix[j+1][i-1] maps[str(matrix[j][i])]+=1 elif check(j+1,i): r[0],r[1],r[2] = im2.getpixel((j,i)) s[0],s[1],s[2] = im2.getpixel((j+1,i)) if juli(r,s) <=l: matrix[j][i] = matrix[j+1][i] maps[str(matrix[j][i])]+=1 else: n+=1 maps[str(n)]=1 matrix[j][i] = n for i in range(w): for j in range(h): if matrix[j][i]!=-1 and maps[str(matrix[j][i])]<=2: im.putpixel((j,i),255)
結果呢,不是很理想因為這個體積參數設小了,噪點沒去干凈,設大了數字部分可能也去了一小塊.最重要的是這里噪點的大小不是很規律,很難找到一個不錯的面積參數.
失敗只是暫時的,經過觀察發現背景噪聲顏色明顯比數字要淺的多.這也意味着它的RGB值要比數字小的多,通過分析RGB值能去掉大部分噪聲,剩下來的噪點可以再通過種子染色法處理.也就是說,分別在兩張圖片(分別是黑白和彩色)上獲取信息,在一張圖片上做處理最后做識別
核心代碼在這
r[0],r[1],r[2] = im2.getpixel((j,i)) if r[0]+r[1]+r[2]>=400 or r[0]>=250 or r[1]>=250 or r[2]>=250 : im2.putpixel((j,i),(255,255,255))
至此,本次識別的問題就搞定啦,成功率在50%以上基本滿足接口的需求
想交流或想要源碼的朋友可以郵件聯系