【爬蟲系列】1. 無事,Python驗證碼識別入門
阿新 • • 發佈:2021-08-07
最近在匯入某站資料(正經需求),看到他們的登入需要驗證碼,
本來並不想折騰的,然而Cookie有效期只有一天。
已經收到了幾次夜間報警推送之後,實在忍不住。
得嘞,還是得研究下模擬登入。
於是,禿頭了兩個小時gang出來了。
預警
- 二值化、普通降噪、8鄰域降噪
- tesseract、tesserocr、PIL
如果都瞭解這些東西,這文章就不用看了,直接跳到參考文獻咯。
程式碼地址:https://github.com/liguobao/python-verify-code-ocr
開始搞事
批量下載驗證碼圖片
import shutil
import requests
from loguru import logger
for i in range(100):
url = 'http://xxxx/create/validate/image'
response = requests.get(url, stream=True)
with open(f'./imgs/{i}.png', 'wb') as out_file:
response.raw.decode_content = True
shutil.copyfileobj(response.raw, out_file)
logger.info(f"download {i}.png successfully.")
del response
第一步,直接上識別程式碼看看效果。
from PIL import Image import tesserocr img = Image.open("./imgs/98.png") img.show() img_l = img.convert("L")# 灰階圖 img_l.show() verify_code1 = tesserocr.image_to_text(img) verify_code2 = tesserocr.image_to_text(img_l) print(f"verify_code1:{verify_code1}") print(f"verify_code2:{verify_code2}")
毫無疑問,無論是原圖還是灰階圖,一無所有。
折騰降噪、去幹擾
第一個找到有用的文章是這個,沒記錯的話幾年前也看到過。
from PIL import Image # https://www.cnblogs.com/jhao/p/10345853.html Python圖片驗證碼降噪 — 8鄰域降噪 def noise_remove_pil(image_name, k): """ 8鄰域降噪 Args: image_name: 圖片檔案命名 k: 判斷閾值 Returns: """ def calculate_noise_count(img_obj, w, h): """ 計算鄰域非白色的個數 Args: img_obj: img obj w: width h: height Returns: count (int) """ count = 0 width, height = img_obj.size 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.getpixel((_w_, _h_)) < 230: # 這裡因為是灰度影象,設定小於230為非白色 count += 1 return count img = Image.open(image_name) # 灰度 gray_img = img.convert('L') w, h = gray_img.size for _w in range(w): for _h in range(h): if _w == 0 or _h == 0: gray_img.putpixel((_w, _h), 255) continue # 計算鄰域非白色的個數 pixel = gray_img.getpixel((_w, _h)) if pixel == 255: continue if calculate_noise_count(gray_img, _w, _h) < k: gray_img.putpixel((_w, _h), 255) return gray_img if __name__ == '__main__': image = noise_remove_pil("./imgs/1.png", 4) image.show()
跑起來看下效果。
嘖嘖嘖,很是可以。
不過扔過去識別...
依舊不太行。
研讀了一下程式碼,有了思路。
新思路
這邊的干擾線是從某個點發出來的紅色線條,
其實我只需要把紅色的畫素點都幹掉,這個線條也會被去掉。
from PIL import Image
import tesserocr
img = Image.open("./imgs/98.png")
img.show()
# 嘗試去掉紅畫素點
w, h = img.size
for _w in range(w):
for _h in range(h):
o_pixel = img.getpixel((_w, _h))
if o_pixel == (255, 0, 0):
img.putpixel((_w, _h), (255, 255, 255))
img.show()
img_l = img.convert("L")
# img_l.show()
verify_code1 = tesserocr.image_to_text(img)
verify_code2 = tesserocr.image_to_text(img_l)
print(f"verify_code1:{verify_code1}")
print(f"verify_code2:{verify_code2}")
看起來OK,上面還有零星的藍色畫素掉,也可以用同樣的方法一起去掉。
甚至OCR都直接出效果了。
好了,完結撒花。
不過,後面發現,有些紅色線段和藍色點,是和驗證碼重合的。
這個時候,如果直接填成白色,就容易把字母切開,導致識別效果變差。
想起這個文章的做法,所以改進了一下:
當前點是紅色或者藍色,判斷周圍點是不是超過兩個畫素點是黑色。
是,填充為黑色。
否,填充成白色。
最終完整程式碼:
from PIL import Image
import tesserocr
from loguru import logger
class VerfyCodeOCR():
def __init__(self) -> None:
pass
def ocr(self, img):
""" 驗證碼OCR
Args:
img (img): imgObject/imgPath
Returns:
[string]: 識別結果
"""
img_obj = Image.open(img) if type(img) == str else img
self._remove_pil(img_obj)
verify_code = tesserocr.image_to_text(img_obj)
return verify_code.replace("\n", "").strip()
def _get_p_black_count(self, img: Image, _w: int, _h: int):
""" 獲取當前位置周圍畫素點中黑色元素的個數
Args:
img (img): 影象資訊
_w (int): w座標
_h (int): h座標
Returns:
int: 個數
"""
w, h = img.size
p_round_items = []
# 超過了橫縱座標
if _w == 0 or _w == w-1 or 0 == _h or _h == h-1:
return 0
p_round_items = [img.getpixel(
(_w, _h-1)), img.getpixel((_w, _h+1)), img.getpixel((_w-1, _h)), img.getpixel((_w+1, _h))]
p_black_count = 0
for p_item in p_round_items:
if p_item == (0, 0, 0):
p_black_count = p_black_count+1
return p_black_count
def _remove_pil(self, img: Image):
"""清理干擾識別的線條和噪點
Args:
img (img): 影象物件
Returns:
[img]: 被清理過的影象物件
"""
w, h = img.size
for _w in range(w):
for _h in range(h):
o_pixel = img.getpixel((_w, _h))
# 當前畫素點是紅色(線段) 或者 綠色(噪點)
if o_pixel == (255, 0, 0) or o_pixel == (0, 0, 255):
# 周圍黑色數量大於2,則把當前畫素點填成黑色;否則用白色覆蓋
p_black_count = self._get_p_black_count(img, _w, _h)
if p_black_count >= 2:
img.putpixel((_w, _h), (0, 0, 0))
else:
img.putpixel((_w, _h), (255, 255, 255))
logger.info(f"_remove_pil finish.")
# img.show()
return img
if __name__ == '__main__':
verfyCodeOCR = VerfyCodeOCR()
img_path = "./imgs/51.png"
img= Image.open(img_path)
img.show()
ocr_result = verfyCodeOCR.ocr(img)
img.show()
logger.info(ocr_result)
總結:
- 識別率大概是80%左右,部分連起來的字元會被識別錯誤,需要切割字元後單獨識別
- 降噪演算法只適用於當前圖片,其他場景需要自行適配
程式碼地址:https://github.com/liguobao/python-verify-code-ocr
參考文章:
釋出於剛剛