首先,我對驗證碼做了初步的觀察分析。總結如下:
驗證碼中的字符位數始終為6位,並且是灰度圖像;
字符之間的間隔看起來始終保持相同的間隔;
每個字符都是完全定義的;
圖像有許多雜散的暗像素,以及穿過圖像的線條
我決定下載一個圖片驗證碼,並藉助 這款工具 以二進制可視化圖像(0表示黑色,1表示白色像素)。
我的觀察是正確的 – 圖像尺寸為45×180,每個字符被分配一個30像素的空間來擬合,從而使它們均勻間隔。
我將其包裝在一個循環中,寫了一個簡單的腳本,從該站點獲取500個驗證碼圖像,並將所有裁剪後的字符保存到一個文件夾中。
第三次觀察 – 每個字符都有明確的定義。為了“清理”圖像中的裁剪字符(刪除不必要的線和點),我使用了以下方法。
字符中的所有像素都是純黑色(0)。我用了一個簡單的邏輯 – 如果它不是完全黑色的,就視為白色。因此,對於值大於0的每個像素,將其重新分配為255。使用load()函數將圖像轉換為45×180矩陣,然後對其進行處理。
pixel_matrix = cropped_image.load()for col in range(0, cropped_image.height): for row in range(0, cropped_image.width): if pixel_matrix[row, col] != 0: pixel_matrix[row, col] = 255image.save("thresholded_image.png")
為了更加清晰,我將代碼應用至原始圖像上。
原圖:
處理後:
可以看到處理後的圖像中的非純黑像素都已被移除,其中包括穿插圖像的線條。
直到項目完成後,我才知道上述方法被稱為圖像處理中的閾值處理。
第四次觀察 – 圖像中有許多雜散像素。
循環遍歷圖像矩陣,如果相鄰像素為白色,與相鄰像素相對的像素也為白色,且中心像素為黑色的,則使中心像素為白色。
for column in range(1, image.height - 1): for row in range(1, image.width - 1): if pixel_matrix[row, column] == 0 \ and pixel_matrix[row, column - 1] == 255 and pixel_matrix[row, column + 1] == 255 : pixel_matrix[row, column] = 255 if pixel_matrix[row, column] == 0 \ and pixel_matrix[row - 1, column] == 255 and pixel_matrix[row + 1, column] == 255: pixel_matrix[row, column] = 255
一個將按照字符排序的相似圖像分組(約束條件:暗像素數量,相似度>= 90 – 95 %)
一個從每個分組字符中獲得最佳圖像
因此現在已生成了庫圖像。將它們轉換為像素矩陣,並將“位圖”存儲為JSON文件。
最後,這是解決任何新的驗證碼圖像的算法。
使用相同的算法減少新圖像中不必要的干擾
對於新驗證碼圖像中的每個字符,通過我生成的JSON位圖強制執行。根據對應的暗像素匹配來計算相似度。
這意味著,如果一個像素為暗像素,在圖像中的位置為(4,8),並且如果該像素在我們的骨架圖像/位圖中的相同位置處為暗像素,則計數值會遞增1。
該計數與骨架圖像中暗像素的數量相比,用於計算百分比匹配。
選擇匹配率最高的字符。
import json characters = "123456789abcdefghijklmnpqrstuvwxyz" captcha = "" with open("bitmaps.json", "r") as f: bitmap = json.load(f) for j in range(image.width/6, image.width + 1, image.width/6): character_image = image.crop((j - 30, 12, j, 44)) character_matrix = character_image.load() matches = {} for char in characters: match = 0 black = 0 bitmap_matrix = bitmap[char] for y in range(0, 32): for x in range(0, 30): if character_matrix[x, y] == bitmap_matrix[y][x] and bitmap_matrix[y][x] == 0: match += 1 if bitmap_matrix[y][x] == 0: black += 1 perc = float(match) / float(black) matches.update({perc: char[0].upper()}) try: captcha += matches[max(matches.keys())] except ValueError: print("failed captcha") captcha += "0" print captcha
最終得到的結果如下:
可以看到驗證碼被成功識別為Z5M3MQ!
總結
這個項目對於我本人而言,也是一次非常好的學習經歷。我還開發了一個 Chrome插件 ,目前有1800+的用戶,歡迎大家安裝使用!此外,如果你有任何的意見和建議也歡迎向我提出。以上的代碼我已託管在GitHub,你可以
在這裡找到 。閱讀更多 繁華落盡and曲終人散 的文章