Python自动化(6)——图像模块

本文所述的方法都是基于前几章的后台点击,因此同样需要绑定窗口句柄。

Python自动化(6)——图像模块

识色

定点比色

def cv2CompareColorOneMatch(self, x, y, hexColor, _similar=0, border=None):
    startX = 0
    startY = 0
    similar = _similar + self.colorOffset
    if border:
        startX = border[0]
        startY = border[1]
    color = self.Hex2RGB(hexColor)
    screenQImg = self.screen.captureScreen(None, border)
    if int(x)-startX <= 0:
        print('cv2CompareColorOneMatch error x: '+str(x)+', startX: '+str(startX))
    if int(y)-startY <= 0:
        print('cv2CompareColorOneMatch error y: '+str(y)+', startY: '+str(startY))
    res = QColor(screenQImg.pixel(int(x)-startX, int(y)-startY)).getRgb()
    print('cv2CompareColorOneMatch x: '+str(x)+', y: '+str(y)+', re: '+str(res))
    if abs(res[0] - color[0]) < similar and abs(res[1] - color[1]) < similar and abs(res[2] - color[2]) < similar:
        return True
    else:
        return False

参数:
前两个传输是绑定的窗口的x,y坐标
hexColor:16进制的色值(传字符串,例如:“#fffbeb”)
_similar:色值偏移值,默认为0,一般会传3~5
border:截图范围,默认截全屏。截图截少一点,(理论上)性能好一点,使用时一般只会截那个点周围的十来个像素
返回值:如果截图判断的点与传进来的色值相减,RGB每个值都在色值偏移范围内,返回True,否则返回False
其中,self.Hex2RGB是将16进制色值转换为RGB值的方法,可以在最下面的全部代码看到。
self.colorOffset是全局变量,用于设置全局的色值偏差。因为某些屏幕会有色差,所以需要这个设置

定点比色的核心代码是通过QImage类的pixel方法获取到对应的像素点数据,然后转换为QColor对象,再通过QColor对象的getRgb方法获取到对应像素点的RGB色值,然后再与传进来的参数对比,得出结果。

多点比色

def cv2CompareColorMoreMatch(self, lists, _similar=0, border=None, screenQImg=None, isIgnoreBorder=False):
    if screenQImg == None:
        screenQImg = self.screen.captureScreen(None, border)
    startX = 0
    startY = 0
    similar = _similar + self.colorOffset
    if not isIgnoreBorder and border:
        startX = border[0]
        startY = border[1]
    # print('cv2CompareColorMoreMatch')
    for x, y, hexColor in lists:
        color = self.Hex2RGB(hexColor)
        if int(x)-startX <= 0:
            print('cv2CompareColorOneMatch error x: '+str(x)+', startX: '+str(startX))
        if int(y)-startY <= 0:
            print('cv2CompareColorOneMatch error y: '+str(y)+', startY: '+str(startY))
        res = QColor(screenQImg.pixel(int(x)-startX, int(y)-startY)).getRgb()
        if abs(res[0] - color[0]) > similar or abs(res[1] - color[1]) > similar or abs(res[2] - color[2]) > similar:
            return False
    return True

参数:
lists:需要比较色值点的列表,例如:[[998,262,’#fffbeb’], [999,329,’#fffbeb’]]
_similar:色值偏移值,同定点比色
border:截图范围,默认截全屏。同定点比色
screenQImg:截的图片,格式是QImage,默认为空,为空时会根据border截图
isIgnoreBorder:是否忽略截图范围,默认为false。当已有一张全屏图的时候,可以用此参数。例如:graph.cv2CompareColorMoreMatch(pointList,5,border,screenshot,screenshot!=None)
这样就是如果有全屏图就忽略border,否则根据border来截图

多点比色实际上只是支持了多个点对比,核心代码同定点比色。

找色

单点找色

def cv2FindColor(self, hexColor, border=None):
    color = list(self.Hex2RGB(hexColor))
    screenImg = self.screen.captureScreen(None, border)
    array = numpy.array(Image.fromqimage(screenImg))
    res = numpy.argwhere(numpy.all(array == color, axis=2)).tolist()
    print('cv2FindColor res: '+str(res))
    return res

参数:
hexColor:字符串,16进制色值(带#号)
border:截屏范围,默认为全屏
返回值:返回全部色值相同位置的数组

单点找色的核心逻辑,其实就是先将QImage转换为PIL库的Iamge对象,然后通过numpy库的array方法将Image转换为数组以便进行数值操作。
接着使用numpy.all方法比较numpy数组中的每个像素值与指定的RGB色值,返回一个bool数组,表示哪些像素匹配指定颜色。
最后使用numpy.argwhere方法,返回bool数组中值为True的索引,然后通过tolist方法将numpy数组转换为python列表。

一般来说,这个方法比较少用,限制比较多

多点找色

def cv2FindColors(self, hexColorListStr, border=None):
    screenImg = self.screen.captureScreen(None, border)
    array = numpy.array(Image.fromqimage(screenImg))
    startX = 0
    startY = 0
    w = None
    h = None
    if border == None:
        left, top, right, bottom = win32gui.GetWindowRect(self.hwnd)
        w = right-left
        h = bottom-top
    else:
        startX = border[0]
        startY = border[1]
        w = border[2]-border[0]
        h = border[3]-border[1]
    rgby = []
    ps = []
    a = 0
    firstXY = []
    res = numpy.empty([0, 2])
    hexColorStr = hexColorListStr.split(',')
    for i in hexColorStr:
        rgb_y = i[-13:]
        r = int(rgb_y[0:2], 16)
        g = int(rgb_y[2:4], 16)
        b = int(rgb_y[4:6], 16)
        y = int(rgb_y[-2:])
        rgby.append([r,g,b,y])
    for i in range(1, len(hexColorStr)):
        ps.append([int(hexColorStr[i].split('|')[0]), int(hexColorStr[i].split('|')[1])])
    for i in rgby:
        result = numpy.logical_and(abs(array[:, :, 0:1] - i[0]) < i[3], abs(array[:, :, 1:2] - i[1]) < i[3], abs(array[:, :, 2:3] - i[2]) < i[3])
        results = numpy.argwhere(numpy.all(result == True, axis=2)).tolist()
        if a == 0:
            firstXY = copy.deepcopy(results)
        else:
            nextnextXY = copy.deepcopy(results)
            for index in nextnextXY:
                index[0] = int(index[0]) - ps[a - 1][1]
                index[1] = int(index[1]) - ps[a - 1][0]
            q = set([tuple(t) for t in firstXY])
            w = set([tuple(t) for t in nextnextXY])
            matched = numpy.array(list(q.intersection(w)))
            if len(matched)==0:
                return -1,-1
            res = numpy.append(res, matched, axis=0)
        a += 1
    res = res.tolist()
    for i in res:
        if res.count(i) == len(hexColorStr) - 1:
            print('cv2FindColors res: '+str(res))
            return i[1] + startX, i[0] + startY
    print('cv2FindColors not find')
    return -1,-1

多点找色其实就是大漠插件里的多点找色实现的,其核心还是上述的找色逻辑,这里不再赘述。

识图

单模版匹配

def cv2OneMatchFindImage(self, rect, temp, qimg=None, similar=0.85):
    img = None
    if qimg:
        img = cv2.cvtColor(numpy.asarray(Image.fromqimage(qimg)),cv2.COLOR_RGB2BGR)
    else:
        img = cv2.cvtColor(numpy.asarray(Image.fromqimage(self.screen.captureScreen(None,rect))),cv2.COLOR_RGB2BGR)
    template = cv2.cvtColor(numpy.asarray(temp),cv2.COLOR_RGB2BGR)
    h, w = template.shape[:2]
    # 匹配模板
    res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    if max_val >= similar:
        # 计算矩形左边
        top_left = max_loc
        bottom_right = (top_left[0] + w, top_left[1] + h)
        # 返回rect数组,参数分别是topLeft,topRight,bottomLeft,bottomRight,中心点x,中心点y
        rect = (top_left[0], top_left[1], top_left[0]+w, top_left[1]+h, int(int(2*top_left[0]+w)/2), int(int(2*top_left[1]+h)/2))
        print('cv2OneMatchFindImage rect: '+str(rect))
        print('cv2OneMatchFindImage max_val: '+str(max_val))
        return rect
    else:
        return None

参数:
rect:截图范围,为空截全屏
temp:被查找的图片,小图,PIL库的Image对象
qimg:大图,在这张图上找temp那张图,QImage对象
similar:相似度,默认0.85

模板匹配实际上就是在一张大图中找小图。其核心是基于OpenCV库的cv2.matchTemplate方法。
首先,将两张图片的颜色空间都从RGB转换为BGR,OpenCV使用BGR作为默认颜色空间,然后获取模板图像的高度h和宽度w。
然后通过cv2.matchTemplate方法进行模板匹配,在大图中寻找小图temp的位置,并返回一个二维数组,表示每个位置的匹配结果(此方法有多个不同的匹配方式,试了一下大差不差吧,没有什么最准的)。接着通过cv2.minMaxLoc方法找到最小值和最大值及其对应的位置:
min_val:最小匹配值
max_val:最大匹配值
min_loc:最小值的位置
max_loc:最大值的位置
如果匹配结果的最大匹配值满足相似度要求,则计算顶点和中心点的位置并返回。否则返回空(None)。

注意:单模板匹配只会返回最匹配的一个结果,多模板匹配会返回全部满足相似度要求的结果。

另外,这里要说明一下模板匹配的实现以及问题:
实际使用的情况中,会有时候得不到正确的结果。因此研究了一下内部逻辑,这里简单说一下。
首先看一下OpenCV文档:https://docs.opencv.org/3.4/de/da9/tutorial_template_matching.html
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(文档是英文,这里为了方便翻译为中文截图)
匹配方法有6种:
TM_SQDIFF:平方差匹配法
TM_SQDIFF_NORMED:归一化平方差匹配法
TM_CCORR:相关匹配法
TM_CCORR_NORMED:归一化相关匹配法
TM_CCOEFF:系数匹配法
TM_CCOEFF_NORMED:归一化相关系数匹配法
这里以TM_CCOEFF_NORMED归一化相关系数匹配法为例,公式计算过程详解:
假设有一张大图:
在这里插入图片描述
以及一张小图:
在这里插入图片描述
然后写一个简单的代码进行目标匹配并显示结果
代码:

import cv2
import numpy as np
import matplotlib.pyplot as plt

def match_and_display(image_path, template_path, method, similarity_threshold=0.9):
    img = cv2.imread(image_path, cv2.IMREAD_COLOR)
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
    
    h, w = template.shape
    res = cv2.matchTemplate(img_gray, template, method)
    loc = np.where(res >= similarity_threshold)
    
    # 在目标图像上绘制匹配区域的矩形框
    for pt in zip(*loc[::-1]):
        top_left = pt
        bottom_right = (pt[0] + w, pt[1] + h)
        cv2.rectangle(img, top_left, bottom_right, (0, 255, 0), 2)
    
    # 使用 Matplotlib 显示结果图像
    plt.figure(figsize=(6, 6))
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.title('Matched Results')
    plt.axis('off')
    plt.show()

示例调用

image_path = './source.png'
template_path = './temp.png'
method = cv2.TM_CCOEFF_NORMED
match_and_display(image_path, template_path, method, similarity_threshold=0.95)

结果:
TM_SQDIFF(平方差匹配法):
在这里插入图片描述
TM_SQDIFF_NORMED(归一化平方差匹配法):
在这里插入图片描述
TM_CCORR(相关匹配法):
在这里插入图片描述
TM_CCORR_NORMED(归一化相关匹配法):
在这里插入图片描述
TM_CCOEFF:系数匹配法):
在这里插入图片描述
TM_CCOEFF_NORMED:归一化相关系数匹配法)
在这里插入图片描述
从结果可以看到,很多结果都把大图中两个相似的点都识别出来了,甚至还有的匹配方法识别失败了,TM_CCOEFF_NORMED匹配方法看起来是对了,不过当我把相似度降低到0.9时,一样会把大图中左上角的也匹配进结果中:
在这里插入图片描述
从官方文档可以知道,cv2进行模板匹配时,是以模板大小的搜索框依次遍历整张大图的。假设小图宽高为(w,h),大图宽高为(W,H),那么遍历时就绪遍历(W-w+1)次,每列需要遍历(H-h+1)次。
以下列的矩阵为例:
在这里插入图片描述
假设小图的矩阵为:
在这里插入图片描述
根据公式:
在这里插入图片描述
对比公式得出,完全匹配会得到1,完全负相关匹配会得到-1,完全不匹配会得到0
假设匹配的是第一个点,首先两边同时减去各自的均值,得到公式中的T ‘和I’:
在这里插入图片描述 =》 在这里插入图片描述
在这里插入图片描述
=》 在这里插入图片描述
然后求两个矩阵的内积,以及两个矩阵内元素平方和的平方的乘积再开根号:

在这里插入图片描述
result = 6/7.7459 = 0.7746
类似的,我们可以得出,当模板匹配到下面两个矩阵的时候,得出的值也是很接近1的
在这里插入图片描述 =》在这里插入图片描述 =》 在这里插入图片描述
result = 1(完全匹配)
在这里插入图片描述 =》在这里插入图片描述=》在这里插入图片描述
result = -5/5 = -1(完全不匹配)

那么,为什么上述的结果中,当相似度设置为0.9时,会把完全负相关的那一块也匹配到呢。
经过我的计算,当矩阵为
0 0
4 3 时,得到的结果:
在这里插入图片描述
result = 6.5/7.9843 = 0.8140
从结果可以看出,完全负相关周围的矩阵,其实还是有可能匹配到相似度比较高的结果,因此,cv2的模板匹配是有可能不准的。
不过,一般来说,只要取最匹配的值,一般来说结果还是可靠的。
但是,通过这次的探究,使用模板匹配时建议设置的值不低于0.65,这个是我认为比较安全的值,因为按照模板匹配的算法,可能不相关的矩阵也能算出来有0.5的相似度甚至更高,总之使用时不建议相似度设置得太低

多模板匹配

def cv2MoreMatch(self, imagePath, tempImgPath, similar=0.9):
    img = cv2.imread(imagePath)
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    template = cv2.imread(tempImgPath, 0)
    h, w = template.shape[:2]
    res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
    # numpy.where返回的坐标值(x,y)是(h,w),注意h,w的顺序
    loc = numpy.where(res >= similar)
    rects = []
    for pt in zip(*loc[::-1]):
        top_left = pt
        bottom_right = (pt[0] + w, pt[1] + h)
        # 返回rect数组,参数分别是topLeft,topRight,bottomLeft,bottomRight,中心点x,中心点y
        rect = (top_left[0], top_left[1], top_left[0]+w, top_left[1]+h, int(int(2*top_left[0]+w)/2), int(int(2*top_left[1]+h)/2))
        rects.append(rect)
        print('cv2MoreMatch rect: '+str(rect))
    return rects

多模板匹配的参数其实与单模板匹配相同,也是传入大图、小图以及相似度。
不同的是多模板匹配使用了numpy.where方法筛选出符合相似度的结果,并返回的是一个数组。

前台找图

def pyAutoGUIMatch(self, imagePath, rect=None, similar=0.9, grayscale=False):
    rectInWindow = None
    if rect == None:
        left, top, right, bottom = win32gui.GetWindowRect(self.hwnd)
        rectInWindow = (left, top, right-left, bottom-top)
        print('rectInWindow: '+str(rectInWindow))
    else:
        rectInWindow = (rect[0], rect[1], rect[2]-rect[0], rect[3]-rect[1])
    pos = pyautogui.locateOnScreen(imagePath, region=rectInWindow, confidence=similar, grayscale=grayscale)
    print('pyAutoGUIMatch pos: '+str(pos))
    return pos

前台找图是通过pyautogui.locateOnScreen方法实现的,需要注意的是,如果自己的电脑连接了多个屏幕时,此方法无法在第二个屏幕上截图,如果传入的x1值大于屏幕的宽度,会导致报错needle dimension(s) exceed the haystack image or region dimensions

完整代码

#! /usr/bin env python3
# -*- coding:utf-8 -*-
# 图形处理模块
 
import numpy
import cv2
import pyautogui
import win32gui
from Screen import Screen
from PyQt5.QtGui import QColor
from PIL import Image
import copy

class Graph():
    def __init__(self):
        self.screen = Screen()
        self.colorOffset = 0
        print('Graph init')

    def bind(self, hwnd):
        self.hwnd = hwnd
        self.screen.bind(hwnd)

    def setColorOffset(self, colorOffset):
        self.colorOffset = colorOffset

    # 图形处理方法1——使用cv2(默认)  ############################################

    # 单个模板匹配
    def cv2OneMatch(self, imagePath, tempImgPath):
        img = cv2.imread(imagePath)
        template = cv2.imread(tempImgPath)
        h, w = template.shape[:2]
        # 匹配模板
        res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        # 计算矩形左边
        top_left = max_loc
        bottom_right = (top_left[0] + w, top_left[1] + h)
        # 返回rect数组,参数分别是topLeft,topRight,bottomLeft,bottomRight,中心点x,中心点y
        rect = (top_left[0], top_left[1], top_left[0]+w, top_left[1]+h, int(int(2*top_left[0]+w)/2), int(int(2*top_left[1]+h)/2))
        print('cv2OneMatch rect: '+str(rect))
        return rect

    # 单个模板匹配
    # @rect 需要被截图的范围(left, right, top, bottom),为空则全窗口截图
    # @temp 小图,PIL.Image格式
    # @qimg 
    def cv2OneMatchFindImage(self, rect, temp, qimg=None, similar=0.85):
        img = None
        if qimg:
            img = cv2.cvtColor(numpy.asarray(Image.fromqimage(qimg)),cv2.COLOR_RGB2BGR)
        else:
            img = cv2.cvtColor(numpy.asarray(Image.fromqimage(self.screen.captureScreen(None,rect))),cv2.COLOR_RGB2BGR)
        template = cv2.cvtColor(numpy.asarray(temp),cv2.COLOR_RGB2BGR)
        h, w = template.shape[:2]
        # 匹配模板
        res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        if max_val >= similar:
            # 计算矩形左边
            top_left = max_loc
            bottom_right = (top_left[0] + w, top_left[1] + h)
            # 返回rect数组,参数分别是topLeft,topRight,bottomLeft,bottomRight,中心点x,中心点y
            rect = (top_left[0], top_left[1], top_left[0]+w, top_left[1]+h, int(int(2*top_left[0]+w)/2), int(int(2*top_left[1]+h)/2))
            print('cv2OneMatchFindImage rect: '+str(rect))
            print('cv2OneMatchFindImage max_val: '+str(max_val))
            return rect
        else:
            return None

    # 多个模板匹配
    def cv2MoreMatch(self, imagePath, tempImgPath, similar=0.9):
        img = cv2.imread(imagePath)
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        template = cv2.imread(tempImgPath, 0)
        h, w = template.shape[:2]
        res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
        # numpy.where返回的坐标值(x,y)是(h,w),注意h,w的顺序
        loc = numpy.where(res >= similar)
        rects = []
        for pt in zip(*loc[::-1]):
            top_left = pt
            bottom_right = (pt[0] + w, pt[1] + h)
            # 返回rect数组,参数分别是topLeft,topRight,bottomLeft,bottomRight,中心点x,中心点y
            rect = (top_left[0], top_left[1], top_left[0]+w, top_left[1]+h, int(int(2*top_left[0]+w)/2), int(int(2*top_left[1]+h)/2))
            rects.append(rect)
            print('cv2MoreMatch rect: '+str(rect))
        return rects

    def Hex2RGB(self, hex):
        r = int(hex[1:3], 16)
        g = int(hex[3:5], 16)
        b = int(hex[5:7], 16)
        return r, g, b

    # 定点比色
    def cv2CompareColorOneMatch(self, x, y, hexColor, _similar=0, border=None):
        startX = 0
        startY = 0
        similar = _similar + self.colorOffset
        if border:
            startX = border[0]
            startY = border[1]
        color = self.Hex2RGB(hexColor)
        screenQImg = self.screen.captureScreen(None, border)
        if int(x)-startX <= 0:
            print('cv2CompareColorOneMatch error x: '+str(x)+', startX: '+str(startX))
        if int(y)-startY <= 0:
            print('cv2CompareColorOneMatch error y: '+str(y)+', startY: '+str(startY))
        res = QColor(screenQImg.pixel(int(x)-startX, int(y)-startY)).getRgb()
        print('cv2CompareColorOneMatch x: '+str(x)+', y: '+str(y)+', re: '+str(res))
        if abs(res[0] - color[0]) < similar and abs(res[1] - color[1]) < similar and abs(res[2] - color[2]) < similar:
            return True
        else:
            return False

    # 多点比色
    def cv2CompareColorMoreMatch(self, lists, _similar=0, border=None, screenQImg=None, isIgnoreBorder=False):
        if screenQImg == None:
            screenQImg = self.screen.captureScreen(None, border)
        startX = 0
        startY = 0
        similar = _similar + self.colorOffset
        if not isIgnoreBorder and border:
            startX = border[0]
            startY = border[1]
        # print('cv2CompareColorMoreMatch')
        for x, y, hexColor in lists:
            color = self.Hex2RGB(hexColor)
            if int(x)

完整自动化工程代码:https://gitee.com/chj-self/PythonRobotization

大佬们找到问题欢迎拍砖~

  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值