by 閑歡
前面我們學習了 Python 的圖像處理庫 PIL,學會了一些相關的圖像處理方法,好多人心里會問:有什么用呢?這一節我們就拿實際的例子來回答大家。
識別驗證碼的原理
現在大多數網站登錄不再是簡單地輸入用戶名密碼了,一般都伴隨着此二者之外的驗證手段,目的是阻止一些居心不良的行為。而圖片驗證碼是其中一種比較常用的手段。所謂道高一尺魔高一丈,在 IT 行業中,對於這種安全防守,肯定會有針對性地破解勢力。對於圖片驗證碼的識別破解,目前已經有了很多成熟的方法。我想大概是從自動搶火車票興起之后快速發展而來的吧。
首先我們來看一張未處理的驗證碼圖片:
想要識別驗證碼,我們需要有一套圖片識別算法(這個目前已經有成熟的應用,大家可以自行搜索),然后拿到足夠多的樣本去喂養它,讓它不斷地自我學習,不斷提升識別准確率。在喂養算法之前,我們首先要做的就是對原始圖片進行處理,一般包括的步驟是:
- 將彩色圖片轉換成灰度圖
- 將灰度圖二值化處理
- 去除圖片噪點
經過這三步處理之后,一般圖片的驗證碼數字或者字母會比較明顯好辨別了。
下面我們以上面那張簡單的驗證碼圖片為例,來運用 Python 的 PIL 庫的方法對圖片進行去噪處理。
1. 彩色圖片轉換成灰度圖
什么事灰度圖呢?灰度圖,也可以認為是黑白圖。我們知道彩色圖片是有不同的顏色的像素組合到一起的,灰度圖可以類似的認為是由不同灰度值的像素組合在一起后呈現出來的。
任何顏色都有紅、綠、藍三原色組成,假如原來某點的顏色為 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),用它替換原來的RGB(R,G,B)就是灰度圖了。
我們用代碼實現非常簡單:
from PIL import Image
# 打開原始圖片
im = Image.open('vc.png')
# 展示原始圖片
im.show()
# 將原始圖片灰度化
grey_im = im.convert('L')
# 展示灰度化圖片
grey_im.show()
# 保存灰度化圖片
grey_im.save('grey.png')
運行上面代碼后,我們可以看到轉換后的灰度圖了,如下所示:
2. 將灰度圖片二值化
我們已經得到了灰度圖,接下來就是將灰度圖二值化。所謂二值化就是將灰度圖像轉換成由黑白二色組成的圖像。思路就是確定一個閾值,大於閾值的像素表示為白色,小於閾值的像素表示為黑色,以此將圖片的像素(灰度值)划分為兩部分:0和1,例如0代表黑色,1代表白色,然后我們就可以用一串0和1組成的數字來表示一張圖片。
from PIL import Image
# 二值處理
# 設定閾值threshold,像素值小於閾值,取值0,像素值大於閾值,取值1
# 閾值具體多少需要多次嘗試,不同閾值效果不一樣
def get_table(threshold=115):
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
return table
# 打開灰度化圖片並進行二值處理
binary_im = Image.open('grey.png').point(get_table(120), "1")
# 展示二值化圖片
binary_im.show()
# 保存二值化圖片
binary_im.save('binary.png')
我們首先定義了一個二值處理的方法,該方法就是根據傳入的一個閾值,將0到256之間的數進行分類,大於這個閾值取1,小於閾值取0。然后我們使用 Image 的 point 方法,該方法針對傳入的函數對每一個像素點進行操作。我們傳入二值處理方法,對每個像素點進行二值化處理,將圖片轉換成二值圖片。
這里的閾值是需要大家嘗試之后才能確定的,不同的圖片,在閾值不同時會出現不同的處理效果,大家需要用不同的閾值去處理,查看處理之后的效果圖,找到比較合理的閾值。本例中使用的是120。
經過二值化處理之后,我們的圖片變成了下面這樣:
3. 對圖片進行降噪處理
我們看二值化后的圖片,可以看到還有一些干擾線,這些線條也會影響算法的識別准確率,所以我們需要想辦法去掉這些干擾線。
降噪的方法有很多,主要難點是判斷哪些點是噪點。由於我們這張驗證碼圖片上的數字和字母的線條比干擾線的線條粗,因此我們認為字母和數字線條上的點周圍8個點范圍內黑色點的個數應該比干擾線上的點要多。因此我們這里采用的思路是:
根據一個點 A 的 RGB 值,與周圍的8個點的 RBG 值比較,設定一個值 N(0 <N <8),當 A 的 RGB 值與周圍8個點的 RGB 相等數小於 N 時,此點為噪點。
對應的程序代碼為:
from PIL import Image, ImageDraw
# 判斷噪點,如果確認是噪點,用該點的上面一個點的灰度進行替換
# 根據一個點A的RGB值,與周圍的8個點的RBG值比較,設定一個值 N(0 <N <8),當A的RGB值與周圍8個點的RGB相等數小於N時,此點為噪點
# x, y: 像素點坐標
# G: 圖像二值化閥值
# N: 降噪率 0 < N <8
def get_pixel(image, x, y, G, N):
# 獲取像素值
L = image.getpixel((x, y))
# 與閾值比較
if L > G:
L = True
else:
L = False
nearDots = 0
if L == (image.getpixel((x - 1, y - 1)) > G):
nearDots += 1
if L == (image.getpixel((x - 1, y)) > G):
nearDots += 1
if L == (image.getpixel((x - 1, y + 1)) > G):
nearDots += 1
if L == (image.getpixel((x, y - 1)) > G):
nearDots += 1
if L == (image.getpixel((x, y + 1)) > G):
nearDots += 1
if L == (image.getpixel((x + 1, y - 1)) > G):
nearDots += 1
if L == (image.getpixel((x + 1, y)) > G):
nearDots += 1
if L == (image.getpixel((x + 1, y + 1)) > G):
nearDots += 1
if nearDots < N:
return image.getpixel((x, y - 1))
else:
return None
# 降噪
# Z: 降噪次數
def clear_noise(image, G, N, Z):
draw = ImageDraw.Draw(image)
for i in range(0, Z):
for x in range(1, image.size[0] - 1):
for y in range(1, image.size[1] - 1):
color = get_pixel(image, x, y, G, N)
if color is not None:
draw.point((x, y), color)
# 打開二值化圖片
b_im = Image.open('binary.png')
# 將二值化圖片降噪
clear_noise(b_im, 50, 4, 4)
# 展示降噪后的圖片
b_im.show()
# 保存降噪后的圖片
b_im.save('result.png')
在本例中,我們設置的二值化閾值為50,降噪率為4,降噪次數為4.這幾個參數也是不同的圖片會有不同的值,大家需要根據不同的圖片自行設定。
降噪后的圖片效果如下:
我們可以看到,經過上面的處理之后,圖片上的字母和數字已經很清晰了,再使用圖片識別算法,准確率應該會很高。
除了上面的步驟,我們還可以通過 PIL 庫的 ImageEnhance 和 ImageFilter 對圖片做其他處理,例如增加對比度、亮度、銳化等,最終的目的都是去除圖片的噪點,是圖片更容易辨別。大家如果感興趣的話可以試試看。
總結
本節我們通過使用 PIL 庫的一些簡單方法,對驗證碼圖片進行一系列的處理,從而達到降噪的目標。通過本節的學習,大家應該要學會學以致用,運用我們學習的一些理論知識去解決工作或生活中遇到的實際問題。 PIL 庫還有很多其他的方法都可以用來對圖片進行不同的處理,大家可以自己去探索。
參考
https://www.osgeo.cn/pillow/reference/
文中示例代碼:https://github.com/JustDoPython/python-100-day/tree/master/day-098
關注公眾號:python技術,回復"python"一起學習交流