美文网首页
python+opencv 模板暴力匹配

python+opencv 模板暴力匹配

作者: WangLane | 来源:发表于2019-03-03 02:16 被阅读0次

背景

在做微博自动登陆部分的时候, 发现有的账号需要验证码, 微博使用的是极验验证码,开始尝试用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)

matchTemplate手册在这里

结果识别成了这个样子


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

相关文章

  • Python + selenium 破解极验点击验证码

    0x00 尝试 本来是打算截图定位的,模板匹配的函数都写好了,参见python+opencv 暴力模板匹配,但是后...

  • python+opencv 模板暴力匹配

    背景 在做微博自动登陆部分的时候, 发现有的账号需要验证码, 微博使用的是极验验证码,开始尝试用css直接定位过去...

  • Python+OpenCV教程16:模板匹配

    主站:http://ex2tron.wang原文:Python+OpenCV教程16:模板匹配 学习使用模板匹配在...

  • 027-Opencv笔记-模板匹配

    模板匹配 模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域。所以模板匹配首先需要一个模板图像T(给定的子图...

  • OpenCV-Python学习(十三):模板匹配

    目录: 1.模板匹配原理 2.模板匹配操作1)单对象匹配:原图中仅有一个与模板匹配2)多对象匹配:原图中有多个与模...

  • KMP 算法

    KMP 算法 1. 暴力匹配算法 在分析KMP算法前, 先看看暴力匹配算法是如何工作的.暴力匹配算法的基本思想是:...

  • 第 9 章 描述和匹配兴趣点

    本章包括以下内容: 局部模板匹配; 描述并匹配局部强度值模式; 用二值描述子匹配关键点。 9.2 局部模板匹配 本...

  • 模板匹配(单模板多匹配))

    说明 直接使用别人的opencv代码,多是只找出最匹配的轮廓,但是有时候我么需要找出多个,那就需要稍微进行一点点操...

  • 模板匹配要实现的匹配方法

    相关度匹配:输入模板,匹配待匹配图,返回rect+score+旋转量(做旋转不错缩放) 灰度图:输入灰度图模板, ...

  • 模板匹配

网友评论

      本文标题:python+opencv 模板暴力匹配

      本文链接:https://www.haomeiwen.com/subject/wucuuqtx.html