背景
在做微博自动登陆部分的时候, 发现有的账号需要验证码, 微博使用的是极验验证码,开始尝试用css直接定位过去, 但是人家验证码这么简单也就不叫验证码了. 于是尝试换路子,看到不少人使用的超级鹰平台,但是啊,要钱啊。搞不起搞不起。看了下想法,他们也就是截图定位,于是打算自己搞。对了, 验证码这个样子的:
大图开始搞
先观察,我发现每个验证码都一样,中间一堆文字,那干脆把那一堆文字截图下来作为识别的特征。
小图
那问题来了,怎么在大图片中找到小图片?简单搜索下,发现,opencv有个函数matchTemplate()叫做模板匹配,直接可以匹配,对了,事先记得装opencv模块
import cv2 as cv
#读取图片,第二个参数可以直接读取成灰度图,省的转换了。
big = cv.imread('big.png', cv.IMREAD_GRAYSCALE)
small = cv.imread('small.png', cv.IMREAD_GRAYSCALE)
#第三个参数是用相关匹配方法,一共有六种具体看手册
res = cv.matchTemplate(big, small, cv.TM_CCOEFF)
# minMaxLoc 顾名思义,获得最小值最大值位置,返回四个值,分别是矩阵中最小值,最大值,最小值的位置,最大值的位置。
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
结果识别成了这个样子
res.png
颜色越亮匹配程度越高。看来识别的不行啊。六种近似算法都尝试匹配了,结果都找不到。
自己撸一个吧
直接自己搞一份代码了,取小图片为窗口,逐个对比像素点,超过给定比例的像素相同就认为找到了。
由于文字空白部分比较多, 我们重新选特征图片,选小一点,特别一点的。
然后开始撸代码的就行了。
import cv2 as cv
import sys
def matchTemplate_bypixel(big, small, pctg):
'''
big是大图片,small是小图片,pctg是匹配百分百
注意big 和small要传入灰度模式
'''
# pctg简单的合法性检查
if pctg > 100 or pctg < 0:
print('pctg should be in (0,100)')
sys.exit()
# 获取大小图片的宽度和高度
big_h, big_w = big.shape[:2]
small_h, small_w = small.shape[:2]
# 设定相同像素的阈值,达到之后停止扫描
threshold = int(small_h*small_w*pctg/100)
# 获取大图片的比较窗口
def get_window(left_top_point):
x,y = left_top_point
return big[x:x+small_h,y:y+small_w]
# 比较两个窗口矩阵,返回相同像素的个数
def same_pixel_num(m1, m2):
if m1.shape != m2.shape:
print('shape not same')
print(m1.shape)
print(m2.shape)
return
return sum(sum(m1-m2==0))
for i in range(big_h-small_h-1):
# 输出行数,方便调试
if i % 50 == 0:
print(i)
# 逐个像素移动窗口进行匹配
for j in range(big_w-small_w-1):
same_num = same_pixel_num(get_window((i,j)), small)
if same_num > threshold:
print('found match point: (',i,j, ') match value:', same_num)
cv.rectangle(big, (j,i), (j+small_w,i+small_h), (0,0,255))
cv.imwrite('aaaaa.png',big)
print('write success')
sys.exit()
if __name__ == '__main__':
big = cv.imread('big.png', cv.IMREAD_GRAYSCALE)
small = cv.imread('small.png', cv.IMREAD_GRAYSCALE)
matchTemplate_bypixel(big, small, 20)
匹配到的位置还算可以,虽然有些偏差但是大体上可以找得到位置了,但是有个问题,原图的那个圆圈会随着鼠标转圈圈,所以我截图的部分不是百分百一样,但是就算那个阴影部分变成两倍,把百分百的参数调成50的话应该完全没有问题,但是在测试中发现,参数写成50根本匹配不到,20倒是可以,但是这可逼死强迫症啊。于是我决定找找问题出在哪里。
aaaaa.png
第一波优化
我尝试随便找两个矩阵模拟一下过程,似乎发现了什么
In [92]: m1
Out[92]:
array([[255, 255, 255],
[255, 255, 255],
[255, 255, 255]], dtype=uint8)
In [93]: m2
Out[93]:
array([[254, 254, 254],
[254, 254, 254],
[253, 254, 253]], dtype=uint8)
In [94]: m1-m2
Out[94]:
array([[1, 1, 1],
[1, 1, 1],
[2, 1, 2]], dtype=uint8)
In [95]: m2-m1
Out[95]:
array([[255, 255, 255],
[255, 255, 255],
[254, 255, 254]], dtype=uint8)
其实,这里的m1和m2我取的就是左上角的空白区域,但是,仔细观察发现灰度值有波动啊,这就说明,同样的颜色在转换之后可能会有轻微的改变,于是,我将same_pixel_num函数中的判定条件稍微修改了一下,将m1-m2==0改为m1-m2<3。
def same_pixel_num(m1, m2):
if m1.shape != m2.shape:
print('shape not same')
print(m1.shape)
print(m2.shape)
return
return sum(sum(m1-m2<3))
判定条件改了,那就有问题了, 矩阵类型是uint8,无符号!! 所以说当出现1-2这种情况的时候,会发生溢出啊。于是转个类型再减,顺便加个绝对值,于是,这个函数变成了
def same_pixel_num(m1, m2):
if m1.shape != m2.shape:
print('shape not same')
print(m1.shape)
print(m2.shape)
return
b1 = m1.astype('int16')
b2 = m2.astype('int16')
return sum(sum(abs(b1-b2)))
看看优化之后的效果,已经可以非常准确的找到位置了,所以总体上到这里已经可以识别啦。但是别急,还有问题。
fin.png
再优化
如果真的这样子找下去太慢了吧。识别一次需要耗时十几秒,这还不是全屏的截图,背景图片再大一些恐怕还要慢一点。点个验证码要十几秒,这效率堪比蜗牛。想办法优化咯。想到个办法,这个图形比较特别,他的颜色只有这一部分有,那不妨直接根据灰度值定位过来,反正,周围一片空白。
优化后的的代码:
import cv2 as cv
import sys
from scipy import stats
import datetime
def printTime(f):
def wrapper(*args, **kwargs):
a = datetime.datetime.now()
f(*args, **kwargs)
b = datetime.datetime.now()
print('running time: ', (b-a).seconds)
def matchTemplate_bypixel(big, small, pctg):
if pctg > 100 or pctg < 0:
print('pctg should be in (0,100)')
sys.exit()
big_h, big_w = big.shape[:2]
small_h, small_w = small.shape[:2]
threshold = int(small_h*small_w*pctg/100)
print('threshold : ', threshold)
def get_window(left_top_point):
x,y = left_top_point
return big[x:x+small_h,y:y+small_w]
def same_pixel_num(m1, m2):
if m1.shape != m2.shape:
print('shape not same')
print(m1.shape)
print(m2.shape)
return
b1 = m1.astype('int16')
b2 = m2.astype('int16')
return sum(sum(abs(b1-b2)<3))
# 获取开始匹配的点
def getStartPoint():
range_h = range(big_h-small_h-1)
range_w = range(big_w-small_w-1)
# 利用 scipy的stats模块获得众数,即小图中最多的灰度值
modenum = stats.mode(small.flatten())[0][0]
startx = 0
starty = 0
for i in range_h:
for j in range_w:
if big[i,j] == modenum == big[i-1,j] == big[i+1,j] == big[i,j+1] == big[i,j-1]:
startx = i
starty = j
break
if startx or starty:
break;
startx = startx-small_w
if startx < 0:
satrtx = 0
starty = starty-small_h
if starty < 0:
starty = 0
return (startx,starty)
startx,starty = getStartPoint()
range_h = range(startx,big_h-small_h-1)
range_w = range(starty,big_w-small_w-1)
for i in range_h:
if i % 50 == 0:
print(i)
for j in range_w:
same_num = same_pixel_num(get_window((i,j)), small)
if same_num > threshold:
print('found match point: (',i,j, ') match value:', same_num)
cv.rectangle(big, (j,i), (j+small_w,i+small_h), (0,0,255))
cv.imwrite('aaaaa.png',big)
print('write success')
return
if __name__ == '__main__':
big = cv.imread('big.png', cv.IMREAD_GRAYSCALE)
small = cv.imread('small.png', cv.IMREAD_GRAYSCALE)
a = datetime.datetime.now()
matchTemplate_bypixel(big, small, 50)
b = datetime.datetime.now()
print('running time: ', (b-a).seconds)
测试了下,速度已经提升到了3s
image.png
网友评论