游戏自动化里 OCR 是个又爱又恨的东西。
爱的是它”啥都能读”——按钮文字、对话内容、分数、排名,丢进去给你识别个七七八八。 恨的是它慢。一张 1920×1080 的全屏截图扔给 OCR 引擎,CPU 单线程上要花 200~500ms。每次决策都跑一遍,UI 卡得跟 PPT 似的。
更糟的是——全屏 OCR 召回一堆你根本不关心的文字。游戏里到处都是漂浮的提示、装饰文字、状态栏,OCR 全识别出来,你再写正则去过滤,吃力不讨好。
正经的做法是先把”目标在哪儿”框出来,再对那个小区域做 OCR。一个 30×100 像素的区域,OCR 大概 10~20ms,差一个数量级。
问题是——目标在哪儿?
这就是这篇要聊的主角:HSV 颜色蒙版。
为什么是 HSV,不是 RGB
游戏 UI 里很多元素颜色高度一致——分数的橙色字、排名的蓝色背景条、警告的红色边框。这些”颜色明确”的元素是天然的”信标”。
直接在 RGB 空间挑颜色阈值很难——光照、Alpha 混合、压缩都会让 R/G/B 三个分量同时漂移。比如同样一抹”游戏里的橙色”,在不同截图里可能是 (255, 165, 0)、(248, 158, 12)、(250, 170, 5) 这种小幅波动,写 RGB 范围特别难调。
HSV 空间就友好得多:
- H(色相):颜色本身,最稳定的维度
- S(饱和度):颜色鲜艳程度
- V(明度):颜色明暗
游戏里的橙色 H 大概在 [10, 25] 这个区间,S 和 V 给一个宽范围(比如 [100, 255] 和 [100, 255])就能稳稳框住。不管亮一点暗一点,H 不太会跑出去。
转换一步到位:
1 | |
mask 是个二值图——目标颜色的像素是白,其他是黑。
从蒙版到 ROI
光有蒙版还不够,你要的是”位置”,不是”颜色像素”。
接下来通常这几步:
1 | |
每个 roi 就是一个 (x, y, w, h)——目标颜色在画面里的一块区域。
接下来对每个 ROI 切一小块送 OCR:
1 | |
一张全屏 OCR 200ms+,换成”HSV 蒙版定位 + 几个小区域 OCR”,总耗时通常压到 50ms 以内,而且只读你关心的内容,不混进噪音。
一个具体例子
举个项目里的真实场景——竞技场结算后要读排名:
画面上排名的数字是橙色背景白字,整张画面到处都是其他文字(玩家昵称、分数、奖励列表)。全屏 OCR 会把所有文字一锅端,你还得从结果里猜哪个是排名。
我们的做法(简化版):
1 | |
这套流程跑完十几毫秒,准确率比全屏 OCR 高得多——因为输入就只有”排名”那一块。
对应项目里的代码可以看 src/utils/contest_overlay_tools.py,思路一致,多了几层鲁棒性兜底。
几个常见坑
坑一:HSV 颜色范围调不准
老老实实拿真实截图调。OpenCV 自带个交互工具不算好用,最方便的还是写个小脚本——加滑动条,实时看蒙版效果:
1 | |
拖几下滑动条,立马看到颜色范围对不对。比闷头猜数字快十倍。
项目里有个 devtools/hsv_tools.py 就是这个用途,专门用来快速调阈值。
坑二:颜色范围要带样本验证
调出来一组阈值,绝对不能只在一张图上验证就上线。
游戏里不同场景的同种颜色可能略有差异——白天关卡和夜晚关卡的”橙色按钮”,HSV 值能差出去十几度。同一种 UI 元素至少要在 10~20 张不同场景截图上验证,确保都能稳定召回。
我们的规矩是:新加一个 HSV 蒙版规则,必须附带至少 10 张样本测试,跑过才能合进主线。
坑三:颜色冲突
游戏里有时候多个不同 UI 元素用了相近的颜色。HSV 蒙版会同时召回所有——你以为找到了 A,结果是 B。
解决办法不止一个:
- 加形状约束:A 是横长条,B 是方块——用宽高比过滤
- 加位置约束:A 总在屏幕上半部分——直接对 y 范围加限制
- 加面积约束:A 比 B 大很多——按面积区间过滤
- 多颜色融合:A 的标志性边缘有蓝色,B 没有——两个蒙版做与运算
实战里通常至少两个约束叠加才稳,单一颜色蒙版很容易误识别。
坑四:截图压缩带来的颜色漂移
如果截图来源经过 JPG 压缩(比如某些投屏方案),颜色会有可见的偏移和锯齿。HSV 阈值得放宽,宁可多召回一些再过滤。
更稳的做法是在管线源头就避免 JPG——能拿 PNG / 原始 numpy 数组就别走 JPG。但有时候是底层方案限制,没得选,那就阈值放宽 + 后续约束严格。
什么时候还是该用全屏 OCR
不是所有场景都能 HSV 蒙版。有几类情况老老实实全屏 OCR:
- 目标颜色没有显著特征——比如普通对话框的黑字白底,颜色不独特
- 目标位置完全不可预测——比如某个事件提示可能出现在屏幕任意位置
- 第一次见的新页面,还没分析过 UI 结构——先全屏 OCR 拿到所有文本,分析完再写局部规则
但这些场景应该是少数。主流程的每一步都该有”先框定区域再 OCR”的优化,留全屏 OCR 给真正必要的地方。
收个尾
写自动化项目时间长了你会发现,“性能优化”这个话题往往不是优化代码本身,而是减少不必要的工作量。
OCR 跑全屏,单次 200ms;HSV 蒙版定位后跑局部,10ms。差 20 倍。
模型本身没变,参数也没调,就是”先用便宜的方法收窄范围”这一个小改动。
这种思路在视觉自动化里到处适用——能用 YOLO 框定的就别让 CLIP 全图比对;能用形状判断的就别让 OCR 读字;能用本地缓存的就别每次去查服务端。让昂贵操作只对必要数据生效,是这类项目最重要的工程嗅觉。
下次写到 ocr_engine.infer(full_screen_img) 之前,先问一下自己——能不能用 HSV / YOLO / 颜色统计先收一收?多半能。