问题
登录某网站,需要输入验证码,其中图形验证码需要按照描述的汉字顺序,点击潜藏在图片中的汉字,以完成验证
分析
如果使用神经网络去进行识别中文验证码,则因为汉字数量庞大需要比较大的算力和大数据积累,想想知难后退了。
后来,想到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)