Python實戰圖片驗證碼降噪處理

  • 2019 年 11 月 7 日
  • 筆記

圖片驗證碼算是網路數據採集上的一道攔路虎,雖然有諸多公開的ORC介面、雲打碼平台,一旦大規模應用起來,還是內部寫程式進行識別處理比較好。

而自己寫程式碼進行識別的話,又有很多種方案,比如最近火熱的神經網路,一頓煉丹猛如虎,識別準確率99%妥妥的。神經網路訓練模型來識別驗證碼雖然效果好,但是卻有兩個先天的缺陷:

  • 第一、需要大量的標註數據。很多公開的基於神經網路識別圖片驗證碼的程式碼都會使用一個驗證碼生成庫來生成大量的已標註的驗證碼圖片,這一步,直接把實際場景下的會消耗大量時間和經歷的步驟給忽略掉了;
  • 第二、需要巨大的算力。沒錢沒配置的小夥伴,用著低級CPU,還想訓練神經網路?還是洗洗睡吧。

所以對於一部分小夥伴而言,很現實的狀況,還是對圖片進行傳統的ORC識別比較靠譜。要對圖片進行傳統的ORC識別,對圖片進行各種降噪處理就必不可少,本文,州的先生就介紹一些實際使用到的圖片降噪處理方法。

本文所用的示例圖片來自於某網站的登錄驗證碼:

可以看到,圖片裡面大的數據混雜著小的數字和字母,人眼可以迅速地看出來真實的數字,但是對於傳統的ORC的話,可能會產生巨大的干擾。我們使用百度AI開放平台中的通用文字識別介面來測試一下:

可以看到,百度的通用文字識別介面將值為3885的數字識別成了38.852,雖然主要的數字都識別出來了,但是卻多了許多無關的字元。

我們下面藉助OpenCV的Python封裝包cv2,對其進行一些降噪處理,使得圖片更新清晰和無干擾。

二值化處理

在圖片處理中,二值化是一個很常見的操作,我們首先將圖片轉為灰度,然後再根據特定的閾值將圖片進行黑白的二值化處理。

import cv2  import matplotlib.pyplot as plt  img = cv2.imread("201910302114.png")  img2 = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)  img2 = cv2.inRange(img2, lowerb=160, upperb=255)  plt.subplot(121), plt.imshow(img)  # 原始圖片  plt.subplot(122), plt.imshow(img2)  # 降噪圖片  plt.show()

在上面的程式碼中,我們先將圖片轉換為灰度(cvtColor()步驟),得到的圖片如下所示:

左邊是原始的圖片,右邊則是灰度處理後的圖片。接著用cv2.inRange()將將閾值內的像素設置黑色,就完成了圖片二值化的轉換,如下圖所示:

閾值不同,得到的二值化圖片也會不一樣,比如我們之前設置的是160,如果我們將其設置為180,那麼得到的二值化影像又會不一樣,如下圖所示:

可以發現,很多干擾字元也出來的。如何選擇合適的閾值,需要根據具體的圖片來進行判斷。我們將效果最好的圖片保存下來,如下圖所示:

使用這個圖片,我們再去百度AI的通用文字識別介面上測試一下,可以發現相比於之前的識別出錯,現在已經完全識別正確了,如下動圖所示:

像素降噪

除了上面簡單的二值化之外,我們再來看一個更加複雜一點的例子。很多的驗證碼圖片會加上很多干擾點,不僅機器認不出來,有時候連人都認不出來,比如下圖這樣的驗證碼圖片:

干擾塊大量地附著在字元上,甚至有喧賓奪主的趨勢。這樣的驗證碼,百度的AI介面也是沒轍:

面對這樣的驗證碼,我們要怎麼處理呢。下面來看一下。

因為這個圖片的底色是白色的,所以我們直接使用cv2的threshold()方法對影像進行二值化處理,小於某個閾值直接置位白色:

import cv2  import matplotlib.pyplot as plt  img = cv2.imread("vcode_2019-10-29141155879867.png")  # 二值化  ret, img2 = cv2.threshold(img, 160, 255, cv2.THRESH_BINARY)  plt.subplot(121), plt.imshow(img)  # 原始圖片  plt.subplot(122), plt.imshow(img2)  # 降噪圖片  plt.show()

運行上述程式碼,我們可以得到如下所示的圖片:

對比與原始影像,經過二值化後的影像銳利了很多,邊緣不再有過渡性的顏色。下一步,我們把那些孤立在影像上的像素點清除掉即可。

如何清除影像中的孤立像素,我們可以選用效果較好的鄰域降噪演算法。鄰域降噪演算法通過計算一個像素點鄰域的非白色數量來判斷是否將其置為白色。其Python程式碼的實現如下所示:

# 計算鄰域非白色個數  def calculate_noise_count(img_obj, w, h):      """      計算鄰域非白色的個數      Args:          img_obj: img obj          w: width          h: height      Returns:          count (int)      """      count = 0      width, height,s = img_obj.shape      for _w_ in [w - 1, w, w + 1]:          for _h_ in [h - 1, h, h + 1]:              if _w_ > width - 1:                  continue              if _h_ > height - 1:                  continue              if _w_ == w and _h_ == h:                  continue              if (img_obj[_w_, _h_,0] < 233) or (img_obj[_w_, _h_,1] < 233) or (img_obj[_w_, _h_,2] < 233):                  count += 1      return count      # k鄰域降噪  def operate_img(img,k):      w,h,s = img.shape      # 從高度開始遍歷      for _w in range(w):          # 遍歷寬度          for _h in range(h):              if _h != 0 and _w != 0 and _w < w-1 and _h < h-1:                  if calculate_noise_count(img, _w, _h) < k:                      img.itemset((_w,_h,0),255)                      img.itemset((_w, _h,1), 255)                      img.itemset((_w, _h,2), 255)        return img

我們將讀取的影像傳入operate_img()函數,其就會返回降噪處理後的影像,而且這個函數還可以重複進行調用,每一次調用都是在前一次降噪的基礎上進行降噪。

img2 = operate_img(img2, 4)

在進行第一次鄰域降噪後,影像如下圖所示:

可以看到,影像上已經減少了很多孤立像素塊了。

img2 = operate_img(img2, 4)  img2 = operate_img(img2, 4)

對二值化後的影像調用兩次鄰域降噪,降噪的效果更加明顯了,如下圖所示:

可以發現,影像中孤立的像素點都清除掉了,但是影像四周邊緣的噪點還是很頑固。我們再來編寫一個簡單的演算法,將影像四周全部置為白色,程式碼如下所示:

# 四周置白色  def around_white(img):      w, h, s = img.shape      for _w in range(w):          for _h in range(h):              if (_w <= 5) or (_h <= 5) or (_w >= w-5) or (_h >= h-5):                  img.itemset((_w, _h, 0), 255)                  img.itemset((_w, _h, 1), 255)                  img.itemset((_w, _h, 2), 255)      return img

然後,我們再來編寫一個簡單的處理演算法,就是如果一個像素點上下左右四個連接的像素都跟像素點不是同一個顏色,那麼我們將其置為白色:

# 鄰域非同色降噪  def noise_unsome_piexl(img):      '''          查找像素點上下左右相鄰點的顏色,如果是非白色的非像素點顏色,則填充為白色      :param img:      :return:      '''      # print(img.shape)      w, h, s = img.shape      for _w in range(w):          for _h in range(h):              if _h != 0 and _w != 0 and _w < w - 1 and _h < h - 1:# 剔除頂點、底點                  center_color = img[_w, _h] # 當前坐標顏色                  # print(center_color)                  top_color = img[_w, _h + 1]                  bottom_color = img[_w, _h - 1]                  left_color = img[_w - 1, _h]                  right_color = img[_w + 1, _h]                  cnt = 0                  if all(top_color == center_color):                      cnt += 1                  if all(bottom_color == center_color):                      cnt += 1                  if all(left_color == center_color):                      cnt += 1                  if all(right_color == center_color):                      cnt += 1                  if cnt < 1:                      img.itemset((_w, _h, 0), 255)                      img.itemset((_w, _h, 1), 255)                      img.itemset((_w, _h, 2), 255)      return img

最後,我們結合上面的各種降噪演算法,最後得到一個處理得比較好的影像,如下圖所示:

用它再去百度AI介面上測試,發現已經完全識別正確的:

以上就是本文介紹的兩種驗證碼圖片降噪處理方法,歡迎留言討論:)