为什么你的大漠找图在别人电脑上全失效?聊聊特征匹配与颜色容差的门道
前阵子写了个自动登录的图色脚本,自己机器上跑得那叫一个丝滑,找图命中率 100%。美滋滋丢到群里让兄弟试,结果他那边一张都找不到,报的全是"未找到"。我一开始还嘴硬,说他分辨率没调对、DPI 没关。直到我远程看了他一眼屏幕——窗口一模一样,就是找不到。折腾一下午,根子出在俩地方:特征选得不对,和颜色容差没留余地。这俩坑,几乎每个写图色脚本的人都会踩至少一次。
先说清一件事:找图到底在找什么
无论是大漠的 FindPic 还是自己用 OpenCV 写的模板匹配,本质都是同一件事:在大图里滑动一个小图,逐像素比较相似度,超过阈值就认为是命中。关键参数有两个——相似度阈值(sim)和颜色容差(tolerance)。新人最容易犯的错,是把 sim 拉到 0.95 以上,觉得自己严谨,结果环境一变就全盘皆输。
相似度不是越高越好
sim=1.0 意味着"每一个像素都必须严丝合缝"。但现实里,抗锯齿会让边缘差几个值,录制软件/显卡输出会整体漂移,甚至窗口失焦时界面会泛灰。把 sim 卡死,等于要求"连噪点都得一模一样",这是不可能的任务。
颜色容差:屏幕上的"同一个红"其实一直在变
同一处按钮,我机器上是 (220,80,80),兄弟机器上可能是 (212,88,75)。差了不到 10,肉眼根本看不出,但按"严格相等"比的算法直接判你没找到。容差就是给每个像素一个"允许偏差多少"的宽容度。
选特征,比调参数重要十倍
我后来才懂:特征图选错了,参数怎么调都救不回来。几条我现在的选取原则:
- 选信息量大的区域:角标、图标、带纹理的按钮,比一整块纯色背景强太多。
- 避开会动态变化的部分:按钮上的文字加载慢、红点提示时有时无,拿它当特征必翻车。
- 避开阴影和高光边缘:不同 DPI、不同主题下,阴影的羽化算法不一样,边缘最不可靠。
- 特征尽量小而唯一:太大搜索慢,太普通会误命中;一个不常见的图标刚刚好。
容差怎么取?我一般分三档
- 严(sim 0.9+,容差 5~10):纯色 UI、固定主题、本机专用。命中干净,但换环境就废。
- 中(sim 0.75~0.9,容差 20~40):日常通用档,我 80% 的脚本落在这。
- 松(sim 0.6~0.75,容差 50+):环境光波动大、有抗锯齿、跨机器分发。但太松会"张冠李戴",务必先在目标环境实测。
一段能直接跑的带容差匹配
不依赖大漠也能体会这套逻辑,下面用 PIL + numpy 手搓一个,单通道容差可控:
from PIL import Image
import numpy as np
def find_with_tolerance(hay, needle, tol=30, step=2):
"""在大图 hay 里找小图 needle,tol 为单通道允许偏差"""
H = np.asarray(hay.convert("RGB")).astype(int)
N = np.asarray(needle.convert("RGB")).astype(int)
nh, nw = N.shape[:2]
best = None
for y in range(0, H.shape[0] - nh, step):
for x in range(0, H.shape[1] - nw, step):
patch = H[y:y+nh, x:x+nw]
diff = np.abs(patch - N).max(axis=2) # 每个像素最大通道差
if diff.max() <= tol: # 全部像素都在容差内
sim = 1 - diff.mean() / 255
if best is None or sim > best[0]:
best = (sim, x, y)
return best # (相似度, x, y) 或 None
# 用法
hay = Image.open("screen.png")
needle = Image.open("btn_login.png")
hit = find_with_tolerance(hay, needle, tol=35)
print(hit) # (0.91, 412, 308) 表示在 (412,308) 命中,相似度 0.91
- 只截了按钮中间,结果按钮文字是异步加载的,慢半秒就找不到——后来改成截图标而非文字。
- 用了带圆角阴影的图当特征,别人机器 DPI=125% 时阴影算法不同,边缘全偏。
- 没考虑任务栏自动隐藏,窗口 Y 偏移了 40px,找图范围没覆盖到,死活找不到。
回过头看,找图这件事的精髓不是"算法多牛",而是你对人体工学和显示环境的理解——知道哪块屏幕区域稳定、哪种颜色会漂、哪个特征不随状态变。把特征选对、容差留口活气,你的脚本就能从"我机器能跑"进化到"发群里大家都能用"。这中间的差距,就是新手和老手的分水岭。
