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

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

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

识色

定点比色

python 复制代码
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色值,然后再与传进来的参数对比,得出结果。

多点比色

python 复制代码
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来截图

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

找色

单点找色

python 复制代码
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列表。

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

多点找色

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

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

识图

单模版匹配

python 复制代码
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归一化相关系数匹配法为例,公式计算过程详解:

假设有一张大图:

以及一张小图:

然后写一个简单的代码进行目标匹配并显示结果
代码:

python 复制代码
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()

示例调用

python 复制代码
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的相似度甚至更高,总之使用时不建议相似度设置得太低

多模板匹配

python 复制代码
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方法筛选出符合相似度的结果,并返回的是一个数组。

前台找图

python 复制代码
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

完整代码

python 复制代码
#! /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

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

相关推荐
__lost21 分钟前
Python图像变清晰与锐化,调整对比度,高斯滤波除躁,卷积锐化,中值滤波钝化,神经网络变清晰
python·opencv·计算机视觉
海绵波波10726 分钟前
玉米产量遥感估产系统的开发实践(持续迭代与更新)
python·flask
漫谈网络39 分钟前
基于 Netmiko 的网络设备自动化操作
运维·自动化·netdevops·netmiko
逢生博客1 小时前
使用 Python 项目管理工具 uv 快速创建 MCP 服务(Cherry Studio、Trae 添加 MCP 服务)
python·sqlite·uv·deepseek·trae·cherry studio·mcp服务
堕落似梦1 小时前
Pydantic增强SQLALchemy序列化(FastAPI直接输出SQLALchemy查询集)
python
坐吃山猪2 小时前
Python-Agent调用多个Server-FastAPI版本
开发语言·python·fastapi
Bruce-li__2 小时前
使用Django REST Framework快速开发API接口
python·django·sqlite
小兜全糖(xdqt)2 小时前
python 脚本引用django中的数据库model
python·django
Arenaschi3 小时前
SQLite 是什么?
开发语言·网络·python·网络协议·tcp/ip
纪元A梦3 小时前
华为OD机试真题——推荐多样性(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
java·javascript·c++·python·华为od·go·华为od机试题