美文网首页
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+opencv 模板暴力匹配

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