8.1 KiB
8.1 KiB
Arrow Out 自动求解研发文档(Java,输入为图片)
1. 目标与范围
- 输入:一张关卡截图(包含 UI、背景、彩色箭头线条与箭头头部)。
- 输出:
List<Arrow>:每个箭头的id、离散坐标(gx, gy)、方向dir(8方向)。List<Integer>:一条可消除序列(按id返回)。
- 不在范围:
- 自动点击/自动操作游戏。
- 复杂的通用 OCR/UI 识别;本方案仅提取箭头(位置+方向)。
2. 规则抽象(用于建模与求解)
2.1 箭头属性
每个箭头只保留两类信息:
- 位置:
(x, y)(工程上最终用离散坐标(gx, gy))。 - 朝向:8 方向之一(
N, NE, E, SE, S, SW, W, NW)。
颜色、线条粗细、形状风格不影响解题,忽略。
2.2 “可消除”判定
对任意箭头 A:
- 从
A的箭头尖端(tip)沿其朝向作射线。 - 若该射线上存在任意其他箭头(不论颜色与距离),则
A不可消除。 - 若该射线直到边界都没有遇到任何箭头,则
A可消除。
消除后会改变其他箭头的判定,因为阻挡关系会变化。
3. 总体架构
分两层:
Vision(识别层):图片 → 箭头列表(像素坐标 tip + 方向)→ 离散化网格坐标(gx, gy)。Solver(求解层):箭头列表(gx,gy,dir)→ 消除序列。
接口建议:
VisionExtractor.extract(Mat screenshot) -> List<Arrow>ArrowOutSolver.solve(List<Arrow> arrows) -> List<Integer>solveFromImage(String imagePath) -> List<Integer>(端到端封装)
4. Vision:从图片提取 (x,y,dir)(OpenCV Java)
4.1 坐标系约定
- OpenCV 图像坐标:
x向右增大,y向下增大。 - 求解模型可以沿用该坐标系,也可以转换为数学坐标系(
y向上增大)。 - 要求:方向枚举与坐标系必须一致。
建议:内部统一用 OpenCV 坐标系,方向定义也按“y 向下”为正。
4.2 预处理:裁剪棋盘 ROI(去 UI)
目的:减少误检(顶部文字/按钮/底部工具栏等)。
推荐方案(鲁棒):利用“彩色线条像素”的饱和度/亮度特征定位主区域。
- 转 HSV:
cvtColor(bgr, hsv, COLOR_BGR2HSV) - 前景掩码:
mask = (S > s0) && (V > v0)(只抓彩色高饱和区域) - 形态学:
close填洞、open去噪 - 找最大连通区域或最大外接矩形 contour,取其外接矩形为
boardRoi
输出:Mat board(棋盘区域图像)。
4.3 二值化:生成箭头像素掩码
目标:得到只包含箭头线条/箭头头部的 arrowMask。
- 在
board上按 HSV 阈值生成二值图(仍然只依赖 S/V):arrowMask = (S > s0) && (V > v0)
- 对
arrowMask做morphology close/open,让箭头头部轮廓更连续。
输出:Mat arrowMask(0/255)。
4.4 检测箭头头部:定位 tip 与方向
核心思想:箭头头部通常呈“尖角三角形/箭头尖”,几何特征稳定;方向可由“尖点指向”得到。
步骤:
findContours(arrowMask, ...)获取轮廓集合- 对每个 contour 过滤:
- 面积在
[amin, amax](避免把长线条当头部,或把噪声当头部) approxPolyDP多边形近似
- 面积在
- 取 tip(尖点)
- 对多边形各顶点计算内角,选择 内角最小 的顶点作为
tip
- 对多边形各顶点计算内角,选择 内角最小 的顶点作为
- 取中心
center- 用
moments计算轮廓质心
- 用
- 方向向量
vec = tip - centerangle = atan2(vec.y, vec.x)(按 OpenCV 坐标系)
- 角度量化到 8 方向
- 每 45° 一档;误差超过 22.5° 的标记为“不确定”进入兜底
- 去重/聚类
- 按
tip的欧式距离把相近候选聚成一簇(阈值 5~15 像素) - 每簇保留面积最大的候选
- 按
输出(像素级):List<ArrowPixel>{ tipX, tipY, dir }。
4.5 离散化:从像素坐标到网格坐标 (gx, gy)
求解层需要稳定的“同一行/列/对角线”关系,建议把像素 tip 坐标离散化到网格。
推荐做法:对 x 与 y 分别做 1D 容差聚类。
- 收集全部
tipX,排序 - 从左到右扫描:相邻差值 <
tx的归为同一簇 - 每簇中心取均值
cx,簇序号即gx - 对
tipY同理得到gy
参数建议:
tx/ty可取“箭头头部平均宽度”的 0.5~1 倍。- 或先验经验值(需按分辨率调参):10~25 像素。
输出:List<Arrow>{ id, gx, gy, dir }。
4.6 识别质量校验(必须做)
- 方向置信度:量化前角度与目标方向夹角 > 22.5° 视为不可信。
- 唯一性:同一
(gx,gy)不应出现多个箭头(出现则说明去重/聚类失败)。 - Debug 可视化:在
board上画 tip 点与方向短线,输出调试图,便于快速调参。
4.7 兜底方案(可选增强)
若几何检测不稳:
- 模板匹配:8 个方向头部模板,匹配得到方向与 tip。
- 轻量检测模型:训练在 Python,推理可用 ONNXRuntime Java 或 OpenCV DNN。
5. Solver:由 (gx,gy,dir) 求消除序列
5.1 blocker/blockedBy 核心结构
对每个箭头 i 维护:
blocker[i]:沿自身方向最近的箭头 id;若不存在为-1。blockedBy[i]:所有当前把i当作 blocker 的箭头集合(反向索引,用于增量更新)。ready:所有blocker == -1且未删除的箭头队列。
流程:不断从 ready 取出可消除箭头删除,并只更新受影响的箭头(即 blockedBy[removed])。
5.2 最近阻挡者查询(4 类直线索引)
8 方向只会落在 4 类直线:
- 行:
y = const,按x排序 - 列:
x = const,按y排序 - 主对角线:
x - y = const,按x排序 - 副对角线:
x + y = const,按x排序
建议索引结构:
Map<Integer, TreeMap<Integer, Integer>> rows; // y -> (x -> id)Map<Integer, TreeMap<Integer, Integer>> cols; // x -> (y -> id)Map<Integer, TreeMap<Integer, Integer>> diags; // (x-y) -> (x -> id)Map<Integer, TreeMap<Integer, Integer>> antiDiags; // (x+y) -> (x -> id)
computeBlocker(i):
- 根据
dir选择索引与外层 key - 使用
TreeMap.higherEntry(index)或lowerEntry(index)找最近邻 - 找到则返回其 id,否则返回
-1
删除箭头时同步从 4 套索引中移除,确保后续查询不会返回已删除对象。
5.3 多候选时的选择策略
- 优先消除
blockedBy[u].size()最大的箭头(更可能解锁更多箭头)。 - 或用简单队列先做一版;若出现卡死再引入回溯(见 6)。
6. 无解与回溯(增强项)
纯“可消除就删”的贪心可能在某些关卡卡死(ready 为空但仍有剩余)。
可选策略:
A(先落地):贪心卡死即返回“无解/需回溯”。B(增强):在ready多候选时做 DFS 回溯;需实现状态回滚(操作日志优于全量拷贝)。C(启发式):结合入度优先等启发式减少回溯概率。
7. Java 类型与接口建议
7.1 数据结构
enum Direction { N, NE, E, SE, S, SW, W, NW }class Arrow { int id; int gx, gy; Direction dir; boolean removed; }
7.2 核心类
class VisionExtractor { List<Arrow> extract(Mat screenshot); }class ArrowOutSolver { List<Integer> solve(List<Arrow> arrows); }class Pipeline { List<Integer> solveFromImage(String path); }
8. 测试与验收标准
8.1 Vision 层
- 能从给定截图中输出合理数量的箭头(不漏检、不明显误检)。
- 输出的
(gx,gy)唯一且稳定(同一箭头在同一位置)。 - 输出方向
dir与肉眼一致(允许少量不确定,但需可追踪)。
8.2 Solver 层
- 对构造的小规模数据集:
solve输出序列长度等于箭头数量。 - 删除序列合法:每一步删除的箭头在当步满足
blocker == -1。 - 若卡死:能明确返回失败(或触发回溯策略)。
9. 调参建议(研发流程)
- 先固定 ROI(或实现自动 ROI)
- 调
s0/v0使arrowMask只保留彩色线条 - 调形态学核大小使箭头头部轮廓连续
- 调
amin/amax让头部候选稳定 - 调
tx/ty让网格离散化不抖动 - 用 debug 图逐轮验证 tip 与方向