一个蹩脚的图形中文验证码自动识别实现

问题

登录某网站,需要输入验证码,其中图形验证码需要按照描述的汉字顺序,点击潜藏在图片中的汉字,以完成验证

分析

如果使用神经网络去进行识别中文验证码,则因为汉字数量庞大需要比较大的算力和大数据积累,想想知难后退了。

后来,想到openCV中存在模板匹配度的接口cv.matchTemplate,而且此登录网站的字体比较规整,没有任何倾斜和模糊,可能能够使用opencv完成识别

解决方案

采集网站提示的中文点击顺序,然后逐汉字形成匹配模板,最后,对于验证码图片作为输入源,进行模板匹配。

特别地,在调用opencv后得到匹配位置上,需要再通过模板图片大小,来计算鼠标中心位置。

虽然,此解决方案的识别率并不是很高,可能只在10%左右。但好在网站没有对于反复的尝试登录,采取封IP或者账户的“冰冻”纵深防御技术,所以,虽然有点蹩脚,但也是暂时可以使用的:)


附录

import os,sys,shutil,math
import cv2 as cv
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import base64,re

# 'TM_CCORR' is not so good
methods  = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']

# openCV识别图片存在某些左上角点存在比较小的差异,可以认为是同一个位置
def reduceSmallDifference(posList):
    uniqueSet = set(posList)
    #ColorLog.info("original posList: {}, uniqueSet: {}".format(posList, uniqueSet))

    tmpList   = list(uniqueSet)
    setSize   = len(uniqueSet)

    if setSize == 2 and math.dist(tmpList[0], tmpList[1]) > 2 :
        #ColorLog.info("only has two item and distance is bigger")
        return posList

    while setSize > 1 :
        unique = uniqueSet.pop()

        for i,e in enumerate(posList):
            dist = math.dist(unique, e)
            if dist > 0 and dist < 2 :
                posList[i]  = unique
                if e in uniqueSet:
                    uniqueSet.remove(e)

        setSize  = len(uniqueSet)

    return posList

# openCV模板匹配
def recognizeTemplate(img, template):
    h,w,l       = template.shape[::]
    # All the 6 methods for comparison in a list
    topLeftSet  = set()
    topLeftList = [];

    for meth in methods:
    
         method = eval(meth)
         # Apply template Matching
         res    = cv.matchTemplate(img, template, method)
         min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
         
         # If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
         if method in [cv.TM_SQDIFF, cv.TM_SQDIFF_NORMED]:
            top_left = min_loc
         else:
            top_left = max_loc
         
         bottom_right = (top_left[0] + w, top_left[1] + h)
 
         topLeftList.append(top_left)
 
    topLeftSet          = set(topLeftList)
    #ColorLog.info('topLeftList:{},topLeftSet:{}'.format(topLeftList, topLeftSet))
    reduceSmallDifference(topLeftList)
    topLeftSet          = set(topLeftList)
    #ColorLog.info('reduce topLeftList:{}, topLeftSet:{}'.format(topLeftList, topLeftSet))

    max_percent        = 0.0
    max_percent_unique = (0, 0)
   
 
    for unique in topLeftSet:
        stat = [ x for x in topLeftList if x == unique ]
        max_percent_tmp     = (len(stat)/float(len(methods)))
        # always has value when fail can produce random
        max_percent_unique  = unique

        if max_percent_tmp > 0.5:
          
          max_percent            = max_percent_tmp
        
          break

   
    #ColorLog.info('max_percent: {}, topLeft: {}'.format(max_percent, max_percent_unique))
    return max_percent, max_percent_unique

# 从汉字形成模板图片
def formChineseTemplate(text, templateSize=(34, 34), textSize=24):
    img      = np.uint8(np.zeros(templateSize))
    img      = Image.fromarray(img)
    img_draw = ImageDraw.Draw(img)
    ttf      = ImageFont.truetype("font/msyh.ttc", size=textSize, encoding="utf-8")
    img_draw.text((1, 0),  text, font=ttf, fill=255, stroke_width=1)
    img      = np.asarray(img)
  
    return img

def recognizeChineseChar(img, text):
    template          = formChineseTemplate(text)
    # to three channels
    template          = cv.cvtColor(template, cv.COLOR_GRAY2BGR)
    percent, top_left = recognizeTemplate(img, template)

    center  = (0,0)

    if top_left != (0,0):
        center = (int(top_left[0] + template.shape[1]/2 - 5), int(top_left[1] + template.shape[0]/2 - 5))

    return center

# 如果从网站上截图,可以采用base64编码,以避免读写IO
def base642np(base64_str):
    base64_str = re.sub('^data:image/.+;base64,', '', base64_str)
    #print('base64_str:', type(base64_str))

    # base64解码
    img_data_bytes  = base64.b64decode(base64_str)

    # 转换为np数组
    img_array      = np.fromstring(img_data_bytes, np.uint8)
    # 转换成opencv可用格式

    img            = cv.imdecode(img_array, cv.COLOR_RGB2BGR)
    
    return img


# 对于图片截取验证码区域
def recognizeChineseCharBase64(base64_str, checkcodeRect, text, savedFile=None):

    img          = base642np(base64_str)

    img          = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    ret,  img    = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
    img          = cv.cvtColor(img, cv.COLOR_GRAY2BGR)

    img          = img[int(checkcodeRect['y']):int(checkcodeRect['height'] + checkcodeRect['y']), int(checkcodeRect['x']):int(checkcodeRect['width'] + checkcodeRect['x']), :]
    

    #print('img.shape', img.shape)
    if savedFile :
        cv.imwrite(savedFile, img)

    return recognizeChineseChar(img ,text)

参考资料

Template Matching

你可能感兴趣的:(笔记,小工具,opencv,python,中文验证码,自动识别,图形中文验证码)