gzq830510 发表于 2022-3-19 17:57

非深度学习非调用API过猿人学第八题点选验证

本帖最后由 gzq830510 于 2022-3-19 19:22 编辑

原题地址:https://match.yuanrenxue.com/match/8
解决过程中参考了大佬这篇:https://blog.csdn.net/qq_38017966/article/details/119221282, 感谢!

这道题主要是考察验证码识别,网上资源基本上都是如何去干扰降噪的,由于里面很多生僻、繁体字甚至包括偏旁部首等,文字识别相对有难度,如果需要准确识别的话需要搜集大量样本进行学习训练,成本和工作量比较高,也有调用腾讯、百度API的,虽然识别率不高,但只解题的话多试几次总会搞定,当然也有手动打码的……
我开始也尝试了几个常用的轮子,但效果一般,后来想识别图片文字的难度虽然较高,但如果只是点选的话我是无需识别全部文字的,只要能找到待点选文字的位置就行,也就是说只要找出九宫格中哪个区域的图像和文字相似度最高就可以了,具体思路是把待点选文字转成图片,然后对两张图片进行相似度比较,具体细节需要边测边调,选最优值。
   
1. 原始图片如上,肯定需要降噪,先去随机黑点噪声,再去跨宫的干扰线,原本想用八邻域求众数的算法,发现效果一般,所以还是直接替换像素颜色了。
2. 降噪后效果如上,黑底白字主要是想用opencv取轮廓进行比较来着,但汉字的轮廓比较复杂,而且有干扰,不好实现。
3. PILLOW 和 opencv这两个图像处理工具各有优势,如前者能添加自定义字体文字,后者则有现成的腐蚀/膨胀函数,因此我都用了,只是二者互相调用计算结果时要注意其区别。
4. 调用文字生成图片的字体最好选用相似的黑体,我是用的微软雅黑粗体。
5. 由于图片文字右偏,单个汉字的分隔位置一定要先估算好然后进行调整,可以先生成分隔图片保存以便观察,同时最右侧要补全让汉字居中。
6. 图像比较算法可以用像素相同数量或余弦距离,两个准确率都很高,连试10次基本没一次错误。
下面直接上代码了,萌新首次发问=文代码不太规范,供参考,大家轻拍。

import numpy as np
from collections import Counter
import requests, re, base64, cv2
from PIL import ImageFont, ImageDraw, Image
#单字图片大小为85*85
boxsize = 85
num_list = []
pages = list(range(1,6))
#最右侧背景补全矩阵
blank_array = np.zeros(85*5*3, np.uint8).reshape(85, 5, 3)
#九宫典型位置
block_list =
#汉字分割初始坐标,需尝试找比较接近的
fontboxlist = [, , , , , , , , ]
#自定义字体位置和字体大小
fontpath = r'C:\Users\win10\YaheiCu.ttf'
font = ImageFont.truetype(fontpath, 88)
headers = {
'user-agent': 'yuanrenxue project',
'referer': 'https://match.yuanrenxue.com/match/8',
'cookie': 'sessionid=你的ID'
}

#超过10 个点跨域才算干扰处理,避免字体相同颜色噪点导致文字消失
def noiseline(imgarray, color):
    c = 0
    row, col = imgarray.shape[:2]
    for u in range(row):
      for v in range(col):
            if (imgarray == color).all():
                c += 1
    return c > 10
#相同像素数量统计,也可用余弦相似度
def image_similarity_count(image1, image2):
    tt = 0
    for x in range(85):
      for y in range(85):
            if (image1 == image2).all():
                tt += 1
    return tt

session = requests.Session()
session.headers.update(headers)
for i in pages:
    while(True):
      answer = ''
      r = session.get('https://match.yuanrenxue.com/api/match/8_verify')
      char_list = re.findall('<p>(\S)</p>', r.json()['html'])
      img = base64.b64decode(re.search('base64,(\S+)\\" alt=\\"\\">', r.json()['html']).group(1))
      im = cv2.imdecode(np.frombuffer(img, np.uint8), cv2.IMREAD_COLOR)
      color_list = Counter().most_common()
      for j in color_list:
#这里小于200的肯定是噪声干扰,需要取舍,太小宫内干扰线去不掉,太大偏旁部首会消失
            if j > 5000 or j < 200 or noiseline(im[:,:15,:], j) or noiseline(im[:,105:120,:], j) or noiseline(im[:,205:220,:], j) or noiseline(im[:9,:,:], j) or noiseline(im, j) or noiseline(im, j):
                im, axis=-1)] = (0, 0, 0)
            else:
                im, axis=-1)] = (255, 255, 255)
      kernel = np.ones((3, 3), np.uint8)
      dilate_img = cv2.dilate(im, kernel, iterations=2)
      for flag in range(4):
            image = Image.new('RGB', (85, 85), (0, 0, 0))
            draw = ImageDraw.Draw(image)
#这里文字坐标也需要反复尝试
            draw.text((5, -12), char_list, (255, 255, 255), font=font)
            image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
            similar_list = []
            for q in range(9):
#最右侧补全
                if (q % 3) != 2:
                  image2 = dilate_img:fontboxlist+boxsize,fontboxlist:fontboxlist+boxsize,:]
                else:
                  image2 = np.hstack((dilate_img:fontboxlist+boxsize,fontboxlist:300,:], blank_array))
                result = image_similarity_count(image, image2)
                similar_list.append(result)
            answer += f'{block_list}|'
      params = {'page':i, 'answer':answer}
      response = requests.get(f'https://match.yuanrenxue.com/api/match/8', headers=headers, params=params)
      if response.status_code == 200:
            num_list.extend( for num in response.json()['data']])
            break
      else:
            print('运气欠佳,识别失败!')
print(Counter(num_list).most_common(1))



页: [1]
查看完整版本: 非深度学习非调用API过猿人学第八题点选验证