Hotfix/patch0519Merge:'hotfix/patch0519'| 二值化Agnet增强.餐馆装饰币拾取重构#287
Conversation
📝 WalkthroughWalkthroughPR 重构 HSV 形状匹配识别器为 Pillow 实现,支持 per-node Changes识别算法与装饰币流程重设计
通行证流程错误处理加强
游戏启动流程超时防御
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reviewer's Guide将 HSV 二值形状识别器重构为可复用的多节点、多范围流水线,支持可选的边缘辅助掩膜和更丰富的调试输出,并通过 JSON 配置更新将其接入 daily/pass/start-game 流水线。 更新后的 HSV 二值识别器分析流程时序图sequenceDiagram
actor Caller
participant BinaryMatch
participant Context
Caller->>BinaryMatch: analyze(argv)
Note over BinaryMatch: 0. 固定截图 original_bgr
BinaryMatch->>BinaryMatch: _parse_tasks(params)
alt no_tasks
BinaryMatch-->>Caller: None
else tasks_found
BinaryMatch->>BinaryMatch: compute pil_img, hsv_np
opt edge_assist
BinaryMatch->>BinaryMatch: _compute_edge_mask(pil_img, edge_threshold)
end
loop for each (node_name, ranges)
BinaryMatch->>BinaryMatch: _compute_hsv_mask(hsv_np, ranges)
BinaryMatch->>BinaryMatch: _mask_to_bgr(original_bgr, hsv_mask)
opt debug_mode
BinaryMatch->>BinaryMatch: _save_debug(processed_hsv, node_name, ts, 1_hsv, hsv_mask)
end
BinaryMatch->>BinaryMatch: _try_recognition(Context, node_name, processed_hsv, HSV)
BinaryMatch->>Context: run_recognition(node_name, processed_hsv)
Context-->>BinaryMatch: detail
alt HSV_hit
BinaryMatch-->>Caller: AnalyzeResult
else HSV_miss_and_edge_assist
BinaryMatch->>BinaryMatch: and_mask = hsv_mask & edge_mask
BinaryMatch->>BinaryMatch: _mask_to_bgr(original_bgr, and_mask)
opt debug_mode
BinaryMatch->>BinaryMatch: _save_debug(processed_and, node_name, ts, 3_and, and_mask)
end
BinaryMatch->>BinaryMatch: _try_recognition(Context, node_name, processed_and, AND)
BinaryMatch->>Context: run_recognition(node_name, processed_and)
Context-->>BinaryMatch: detail
alt AND_hit
BinaryMatch-->>Caller: AnalyzeResult
end
end
end
BinaryMatch-->>Caller: None
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideRefactors the HSV binary-shape recognizer into a reusable, multi-node, multi-range pipeline with optional edge-assisted masking and richer debug output, and wires it into daily/pass/start-game pipelines via JSON config updates. Sequence diagram for updated HSV binary recognizer analyze flowsequenceDiagram
actor Caller
participant BinaryMatch
participant Context
Caller->>BinaryMatch: analyze(argv)
Note over BinaryMatch: 0. 固定截图 original_bgr
BinaryMatch->>BinaryMatch: _parse_tasks(params)
alt no_tasks
BinaryMatch-->>Caller: None
else tasks_found
BinaryMatch->>BinaryMatch: compute pil_img, hsv_np
opt edge_assist
BinaryMatch->>BinaryMatch: _compute_edge_mask(pil_img, edge_threshold)
end
loop for each (node_name, ranges)
BinaryMatch->>BinaryMatch: _compute_hsv_mask(hsv_np, ranges)
BinaryMatch->>BinaryMatch: _mask_to_bgr(original_bgr, hsv_mask)
opt debug_mode
BinaryMatch->>BinaryMatch: _save_debug(processed_hsv, node_name, ts, 1_hsv, hsv_mask)
end
BinaryMatch->>BinaryMatch: _try_recognition(Context, node_name, processed_hsv, HSV)
BinaryMatch->>Context: run_recognition(node_name, processed_hsv)
Context-->>BinaryMatch: detail
alt HSV_hit
BinaryMatch-->>Caller: AnalyzeResult
else HSV_miss_and_edge_assist
BinaryMatch->>BinaryMatch: and_mask = hsv_mask & edge_mask
BinaryMatch->>BinaryMatch: _mask_to_bgr(original_bgr, and_mask)
opt debug_mode
BinaryMatch->>BinaryMatch: _save_debug(processed_and, node_name, ts, 3_and, and_mask)
end
BinaryMatch->>BinaryMatch: _try_recognition(Context, node_name, processed_and, AND)
BinaryMatch->>Context: run_recognition(node_name, processed_and)
Context-->>BinaryMatch: detail
alt AND_hit
BinaryMatch-->>Caller: AnalyzeResult
end
end
end
BinaryMatch-->>Caller: None
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
你好——我发现了两个问题,并留下了一些整体性的反馈:
- 在
_compute_hsv_mask中,如果ranges里的lower/upper(或lower_hsv/upper_hsv)缺失或格式错误,会导致比较隐晦的运行时错误;建议对每个 range 做校验,并记录清晰的警告日志或跳过无效条目,而不是假设这些键一定存在。 _save_debug现在无条件调用os.makedirs(debug_dir, exist_ok=True),且没有任何错误处理,而之前的版本会记录失败日志;建议恢复最小化的异常处理/日志记录,这样文件系统问题就不会悄悄导致调试输出失效。
提供给 AI Agents 的提示
Please address the comments from this code review:
## Overall Comments
- 在 `_compute_hsv_mask` 中,如果 `ranges` 里的 `lower`/`upper`(或 `lower_hsv`/`upper_hsv`)缺失或格式错误,会导致比较隐晦的运行时错误;建议对每个 range 做校验,并记录清晰的警告日志或跳过无效条目,而不是假设这些键一定存在。
- `_save_debug` 现在无条件调用 `os.makedirs(debug_dir, exist_ok=True)`,且没有任何错误处理,而之前的版本会记录失败日志;建议恢复最小化的异常处理/日志记录,这样文件系统问题就不会悄悄导致调试输出失效。
## Individual Comments
### Comment 1
<location path="agent/recognition/binarymatch.py" line_range="260-268" />
<code_context>
+ result[mask] = [0, 0, 0]
+ return result
+
+ def _save_debug(self, bgr_img: np.ndarray, node_name: str, ts: str,
+ stage: str, mask: np.ndarray) -> None:
+ """保存调试图,并在日志打印像素覆盖率。"""
+ debug_dir = "debug_images"
+ os.makedirs(debug_dir, exist_ok=True)
+ safe_node = node_name.replace('/', '_').replace('\\', '_')
+ filename = f"{debug_dir}/debug_{safe_node}_{ts}_{stage}.png"
+ Image.fromarray(bgr_img[..., ::-1]).save(filename)
+ hit = int(np.sum(mask))
+ pct = hit / mask.size * 100
+ mfaalog.info(f"[HSVShapeMatching] [{stage}] {node_name} 覆盖 {hit}px ({pct:.1f}%) → {filename}")
</code_context>
<issue_to_address>
**issue (bug_risk):** 处理创建调试目录失败的情况,避免影响识别流程
`_save_debug` 现在调用 `os.makedirs` 并写入文件,但没有任何错误处理,而之前的版本只是在失败时记录日志。如果 `debug_images` 目录无法创建或写入(例如权限问题、只读文件系统),在启用 `debug_mode` 时会导致识别流程抛出异常并中断。请将目录创建和保存操作包裹在 `try/except OSError` 中,在失败时记录警告日志,并继续执行,以避免调试相关问题影响主识别逻辑。
</issue_to_address>
### Comment 2
<location path="agent/recognition/binarymatch.py" line_range="238-246" />
<code_context>
+ val = h_cv * (255.0 / 180.0)
+ return min(255, int(np.ceil(val))) if is_upper else max(0, int(np.floor(val)))
+
+ def _compute_hsv_mask(self, hsv_np: np.ndarray, ranges: list) -> np.ndarray:
+ """多组 HSV 阈值 OR 合并,返回 bool mask。"""
+ combined = np.zeros(hsv_np.shape[:2], dtype=bool)
+ for rng in ranges:
+ lo = rng.get("lower") or rng.get("lower_hsv")
+ hi = rng.get("upper") or rng.get("upper_hsv")
+ lower_pil = np.array([self._map_h(lo[0], False), lo[1], lo[2]])
+ upper_pil = np.array([self._map_h(hi[0], True), hi[1], hi[2]])
+ combined |= np.all((hsv_np >= lower_pil) & (hsv_np <= upper_pil), axis=-1)
+ return combined
+
</code_context>
<issue_to_address>
**suggestion:** 防御性地处理格式错误或异常的 HSV 范围配置条目
`_compute_hsv_mask` 假定每个 `rng` 是一个字典,且其中的 `lower`/`upper`(或 `lower_hsv`/`upper_hsv`)指向长度为 3 的序列。当前,对于配置错误的条目(例如非字典、长度不足的数组或 `None`),在调用 `rng.get(...)` 或访问 `lo[0]` / `hi[0]` 时会抛出异常。建议对 `rng` 进行校验(例如检查 `isinstance(rng, dict)` 以及 `lo`/`hi` 的长度),并在日志中记录信息后跳过无效的范围条目,这样单个错误配置就不会导致整个识别流程失败。
Suggested implementation:
```python
def _map_h(self, h_cv: int, is_upper: bool = False) -> int:
"""OpenCV H(0-180) → Pillow H(0-255),下界 floor,上界 ceil。"""
val = h_cv * (255.0 / 180.0)
return min(255, int(np.ceil(val))) if is_upper else max(0, int(np.floor(val)))
def _compute_hsv_mask(self, hsv_np: np.ndarray, ranges: list) -> np.ndarray:
"""多组 HSV 阈值 OR 合并,返回 bool mask。
ranges: 每个元素应为 dict,包含
- "lower" / "upper" 或
- "lower_hsv" / "upper_hsv"
对于配置错误的条目(类型错误、缺少字段、长度不足等),会跳过并记录日志。
"""
combined = np.zeros(hsv_np.shape[:2], dtype=bool)
for idx, rng in enumerate(ranges):
# 1. 基本类型检查
if not isinstance(rng, dict):
mfaalog.warning(
"Skip HSV range[%d]: expected dict, got %r",
idx,
type(rng),
)
continue
lo = rng.get("lower") or rng.get("lower_hsv")
hi = rng.get("upper") or rng.get("upper_hsv")
# 2. 存在性检查
if lo is None or hi is None:
mfaalog.warning(
"Skip HSV range[%d]: missing lower/upper keys: %r",
idx,
rng,
)
continue
# 3. 序列 / 长度检查
try:
# 支持 list/tuple/np.ndarray 等
if len(lo) < 3 or len(hi) < 3:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper length < 3: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
except TypeError:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper not sequence: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
try:
lower_pil = np.array(
[self._map_h(int(lo[0]), False), int(lo[1]), int(lo[2])]
)
upper_pil = np.array(
[self._map_h(int(hi[0]), True), int(hi[1]), int(hi[2])]
)
except (ValueError, TypeError, IndexError) as exc:
mfaalog.warning(
"Skip HSV range[%d]: invalid numeric values: lower=%r, upper=%r, err=%s",
idx,
lo,
hi,
exc,
)
continue
combined |= np.all(
(hsv_np >= lower_pil) & (hsv_np <= upper_pil),
axis=-1,
)
return combined
from maa.agent.agent_server import AgentServer
from maa.custom_recognition import CustomRecognition
from utils import mfaalog
```
1. 此实现假定 `mfaalog` 暴露了一个与标准 logging API 兼容的 `warning` 函数(`mfaalog.warning(msg, *args)`)。
如果在你的代码库中,日志是通过 logger 实例完成的(例如 `logger = mfaalog.get_logger(__name__)`),请将 `mfaalog.warning(...)` 替换为对应的 logger(例如 `logger.warning(...)`),并确保该 logger 在模块或类作用域中初始化。
2. 如果当前文件或模块中尚未声明类型注解和 NumPy 导入,请根据项目的整体风格添加 `import numpy as np` 和 `from typing import List`(如果你希望更严格的类型标注)。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈持续改进评审质量。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- In
_compute_hsv_mask, missing or malformedlower/upper(orlower_hsv/upper_hsv) entries inrangeswill cause cryptic runtime errors; consider validating each range and logging a clear warning or skipping invalid entries instead of assuming these keys always exist. _save_debugnow unconditionally callsos.makedirs(debug_dir, exist_ok=True)without any error handling, whereas the previous version logged failures; consider restoring minimal exception handling/logging so filesystem issues don’t silently break debug output.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_compute_hsv_mask`, missing or malformed `lower`/`upper` (or `lower_hsv`/`upper_hsv`) entries in `ranges` will cause cryptic runtime errors; consider validating each range and logging a clear warning or skipping invalid entries instead of assuming these keys always exist.
- `_save_debug` now unconditionally calls `os.makedirs(debug_dir, exist_ok=True)` without any error handling, whereas the previous version logged failures; consider restoring minimal exception handling/logging so filesystem issues don’t silently break debug output.
## Individual Comments
### Comment 1
<location path="agent/recognition/binarymatch.py" line_range="260-268" />
<code_context>
+ result[mask] = [0, 0, 0]
+ return result
+
+ def _save_debug(self, bgr_img: np.ndarray, node_name: str, ts: str,
+ stage: str, mask: np.ndarray) -> None:
+ """保存调试图,并在日志打印像素覆盖率。"""
+ debug_dir = "debug_images"
+ os.makedirs(debug_dir, exist_ok=True)
+ safe_node = node_name.replace('/', '_').replace('\\', '_')
+ filename = f"{debug_dir}/debug_{safe_node}_{ts}_{stage}.png"
+ Image.fromarray(bgr_img[..., ::-1]).save(filename)
+ hit = int(np.sum(mask))
+ pct = hit / mask.size * 100
+ mfaalog.info(f"[HSVShapeMatching] [{stage}] {node_name} 覆盖 {hit}px ({pct:.1f}%) → {filename}")
</code_context>
<issue_to_address>
**issue (bug_risk):** Handle failures when creating the debug directory to avoid breaking recognition
`_save_debug` now calls `os.makedirs` and writes the file without any error handling, whereas previously failures were only logged. If `debug_images` cannot be created or written (e.g. permission issues, read-only FS), enabling `debug_mode` will cause the recognition flow to raise and abort. Please wrap the directory creation and save in a `try/except OSError`, log a warning on failure, and continue so that debug issues don’t break the main recognition logic.
</issue_to_address>
### Comment 2
<location path="agent/recognition/binarymatch.py" line_range="238-246" />
<code_context>
+ val = h_cv * (255.0 / 180.0)
+ return min(255, int(np.ceil(val))) if is_upper else max(0, int(np.floor(val)))
+
+ def _compute_hsv_mask(self, hsv_np: np.ndarray, ranges: list) -> np.ndarray:
+ """多组 HSV 阈值 OR 合并,返回 bool mask。"""
+ combined = np.zeros(hsv_np.shape[:2], dtype=bool)
+ for rng in ranges:
+ lo = rng.get("lower") or rng.get("lower_hsv")
+ hi = rng.get("upper") or rng.get("upper_hsv")
+ lower_pil = np.array([self._map_h(lo[0], False), lo[1], lo[2]])
+ upper_pil = np.array([self._map_h(hi[0], True), hi[1], hi[2]])
+ combined |= np.all((hsv_np >= lower_pil) & (hsv_np <= upper_pil), axis=-1)
+ return combined
+
</code_context>
<issue_to_address>
**suggestion:** Defensively handle malformed or unexpected HSV range entries
`_compute_hsv_mask` assumes each `rng` is a dict with `lower`/`upper` (or `lower_hsv`/`upper_hsv`) pointing to 3-element sequences. Misconfigured entries (e.g., non-dicts, shorter arrays, or `None`) will currently raise when calling `rng.get(...)` or indexing `lo[0]` / `hi[0]`. Consider validating `rng` (e.g., `isinstance(rng, dict)` and checking `lo`/`hi` length) and skipping invalid ranges with a log entry so a single bad entry doesn’t break the whole recognition flow.
Suggested implementation:
```python
def _map_h(self, h_cv: int, is_upper: bool = False) -> int:
"""OpenCV H(0-180) → Pillow H(0-255),下界 floor,上界 ceil。"""
val = h_cv * (255.0 / 180.0)
return min(255, int(np.ceil(val))) if is_upper else max(0, int(np.floor(val)))
def _compute_hsv_mask(self, hsv_np: np.ndarray, ranges: list) -> np.ndarray:
"""多组 HSV 阈值 OR 合并,返回 bool mask。
ranges: 每个元素应为 dict,包含
- "lower" / "upper" 或
- "lower_hsv" / "upper_hsv"
对于配置错误的条目(类型错误、缺少字段、长度不足等),会跳过并记录日志。
"""
combined = np.zeros(hsv_np.shape[:2], dtype=bool)
for idx, rng in enumerate(ranges):
# 1. 基本类型检查
if not isinstance(rng, dict):
mfaalog.warning(
"Skip HSV range[%d]: expected dict, got %r",
idx,
type(rng),
)
continue
lo = rng.get("lower") or rng.get("lower_hsv")
hi = rng.get("upper") or rng.get("upper_hsv")
# 2. 存在性检查
if lo is None or hi is None:
mfaalog.warning(
"Skip HSV range[%d]: missing lower/upper keys: %r",
idx,
rng,
)
continue
# 3. 序列 / 长度检查
try:
# 支持 list/tuple/np.ndarray 等
if len(lo) < 3 or len(hi) < 3:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper length < 3: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
except TypeError:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper not sequence: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
try:
lower_pil = np.array(
[self._map_h(int(lo[0]), False), int(lo[1]), int(lo[2])]
)
upper_pil = np.array(
[self._map_h(int(hi[0]), True), int(hi[1]), int(hi[2])]
)
except (ValueError, TypeError, IndexError) as exc:
mfaalog.warning(
"Skip HSV range[%d]: invalid numeric values: lower=%r, upper=%r, err=%s",
idx,
lo,
hi,
exc,
)
continue
combined |= np.all(
(hsv_np >= lower_pil) & (hsv_np <= upper_pil),
axis=-1,
)
return combined
from maa.agent.agent_server import AgentServer
from maa.custom_recognition import CustomRecognition
from utils import mfaalog
```
1. This implementation assumes `mfaalog` exposes a `warning` function compatible with the standard logging API (`mfaalog.warning(msg, *args)`).
If, in your codebase, logging is done via a logger instance (e.g., `logger = mfaalog.get_logger(__name__)`), replace `mfaalog.warning(...)` with the appropriate logger (e.g., `logger.warning(...)`) and ensure that logger is initialized at module or class scope.
2. If type hints and NumPy imports are not already present in this file or module, ensure `import numpy as np` and `from typing import List` (if you want stricter typing) are declared consistently with the rest of the project.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def _compute_hsv_mask(self, hsv_np: np.ndarray, ranges: list) -> np.ndarray: | ||
| """多组 HSV 阈值 OR 合并,返回 bool mask。""" | ||
| combined = np.zeros(hsv_np.shape[:2], dtype=bool) | ||
| for rng in ranges: | ||
| lo = rng.get("lower") or rng.get("lower_hsv") | ||
| hi = rng.get("upper") or rng.get("upper_hsv") | ||
| lower_pil = np.array([self._map_h(lo[0], False), lo[1], lo[2]]) | ||
| upper_pil = np.array([self._map_h(hi[0], True), hi[1], hi[2]]) | ||
| combined |= np.all((hsv_np >= lower_pil) & (hsv_np <= upper_pil), axis=-1) |
There was a problem hiding this comment.
suggestion: 防御性地处理格式错误或异常的 HSV 范围配置条目
_compute_hsv_mask 假定每个 rng 是一个字典,且其中的 lower/upper(或 lower_hsv/upper_hsv)指向长度为 3 的序列。当前,对于配置错误的条目(例如非字典、长度不足的数组或 None),在调用 rng.get(...) 或访问 lo[0] / hi[0] 时会抛出异常。建议对 rng 进行校验(例如检查 isinstance(rng, dict) 以及 lo/hi 的长度),并在日志中记录信息后跳过无效的范围条目,这样单个错误配置就不会导致整个识别流程失败。
Suggested implementation:
def _map_h(self, h_cv: int, is_upper: bool = False) -> int:
"""OpenCV H(0-180) → Pillow H(0-255),下界 floor,上界 ceil。"""
val = h_cv * (255.0 / 180.0)
return min(255, int(np.ceil(val))) if is_upper else max(0, int(np.floor(val)))
def _compute_hsv_mask(self, hsv_np: np.ndarray, ranges: list) -> np.ndarray:
"""多组 HSV 阈值 OR 合并,返回 bool mask。
ranges: 每个元素应为 dict,包含
- "lower" / "upper" 或
- "lower_hsv" / "upper_hsv"
对于配置错误的条目(类型错误、缺少字段、长度不足等),会跳过并记录日志。
"""
combined = np.zeros(hsv_np.shape[:2], dtype=bool)
for idx, rng in enumerate(ranges):
# 1. 基本类型检查
if not isinstance(rng, dict):
mfaalog.warning(
"Skip HSV range[%d]: expected dict, got %r",
idx,
type(rng),
)
continue
lo = rng.get("lower") or rng.get("lower_hsv")
hi = rng.get("upper") or rng.get("upper_hsv")
# 2. 存在性检查
if lo is None or hi is None:
mfaalog.warning(
"Skip HSV range[%d]: missing lower/upper keys: %r",
idx,
rng,
)
continue
# 3. 序列 / 长度检查
try:
# 支持 list/tuple/np.ndarray 等
if len(lo) < 3 or len(hi) < 3:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper length < 3: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
except TypeError:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper not sequence: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
try:
lower_pil = np.array(
[self._map_h(int(lo[0]), False), int(lo[1]), int(lo[2])]
)
upper_pil = np.array(
[self._map_h(int(hi[0]), True), int(hi[1]), int(hi[2])]
)
except (ValueError, TypeError, IndexError) as exc:
mfaalog.warning(
"Skip HSV range[%d]: invalid numeric values: lower=%r, upper=%r, err=%s",
idx,
lo,
hi,
exc,
)
continue
combined |= np.all(
(hsv_np >= lower_pil) & (hsv_np <= upper_pil),
axis=-1,
)
return combined
from maa.agent.agent_server import AgentServer
from maa.custom_recognition import CustomRecognition
from utils import mfaalog- 此实现假定
mfaalog暴露了一个与标准 logging API 兼容的warning函数(mfaalog.warning(msg, *args))。
如果在你的代码库中,日志是通过 logger 实例完成的(例如logger = mfaalog.get_logger(__name__)),请将mfaalog.warning(...)替换为对应的 logger(例如logger.warning(...)),并确保该 logger 在模块或类作用域中初始化。 - 如果当前文件或模块中尚未声明类型注解和 NumPy 导入,请根据项目的整体风格添加
import numpy as np和from typing import List(如果你希望更严格的类型标注)。
Original comment in English
suggestion: Defensively handle malformed or unexpected HSV range entries
_compute_hsv_mask assumes each rng is a dict with lower/upper (or lower_hsv/upper_hsv) pointing to 3-element sequences. Misconfigured entries (e.g., non-dicts, shorter arrays, or None) will currently raise when calling rng.get(...) or indexing lo[0] / hi[0]. Consider validating rng (e.g., isinstance(rng, dict) and checking lo/hi length) and skipping invalid ranges with a log entry so a single bad entry doesn’t break the whole recognition flow.
Suggested implementation:
def _map_h(self, h_cv: int, is_upper: bool = False) -> int:
"""OpenCV H(0-180) → Pillow H(0-255),下界 floor,上界 ceil。"""
val = h_cv * (255.0 / 180.0)
return min(255, int(np.ceil(val))) if is_upper else max(0, int(np.floor(val)))
def _compute_hsv_mask(self, hsv_np: np.ndarray, ranges: list) -> np.ndarray:
"""多组 HSV 阈值 OR 合并,返回 bool mask。
ranges: 每个元素应为 dict,包含
- "lower" / "upper" 或
- "lower_hsv" / "upper_hsv"
对于配置错误的条目(类型错误、缺少字段、长度不足等),会跳过并记录日志。
"""
combined = np.zeros(hsv_np.shape[:2], dtype=bool)
for idx, rng in enumerate(ranges):
# 1. 基本类型检查
if not isinstance(rng, dict):
mfaalog.warning(
"Skip HSV range[%d]: expected dict, got %r",
idx,
type(rng),
)
continue
lo = rng.get("lower") or rng.get("lower_hsv")
hi = rng.get("upper") or rng.get("upper_hsv")
# 2. 存在性检查
if lo is None or hi is None:
mfaalog.warning(
"Skip HSV range[%d]: missing lower/upper keys: %r",
idx,
rng,
)
continue
# 3. 序列 / 长度检查
try:
# 支持 list/tuple/np.ndarray 等
if len(lo) < 3 or len(hi) < 3:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper length < 3: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
except TypeError:
mfaalog.warning(
"Skip HSV range[%d]: lower/upper not sequence: lower=%r, upper=%r",
idx,
lo,
hi,
)
continue
try:
lower_pil = np.array(
[self._map_h(int(lo[0]), False), int(lo[1]), int(lo[2])]
)
upper_pil = np.array(
[self._map_h(int(hi[0]), True), int(hi[1]), int(hi[2])]
)
except (ValueError, TypeError, IndexError) as exc:
mfaalog.warning(
"Skip HSV range[%d]: invalid numeric values: lower=%r, upper=%r, err=%s",
idx,
lo,
hi,
exc,
)
continue
combined |= np.all(
(hsv_np >= lower_pil) & (hsv_np <= upper_pil),
axis=-1,
)
return combined
from maa.agent.agent_server import AgentServer
from maa.custom_recognition import CustomRecognition
from utils import mfaalog- This implementation assumes
mfaalogexposes awarningfunction compatible with the standard logging API (mfaalog.warning(msg, *args)).
If, in your codebase, logging is done via a logger instance (e.g.,logger = mfaalog.get_logger(__name__)), replacemfaalog.warning(...)with the appropriate logger (e.g.,logger.warning(...)) and ensure that logger is initialized at module or class scope. - If type hints and NumPy imports are not already present in this file or module, ensure
import numpy as npandfrom typing import List(if you want stricter typing) are declared consistently with the rest of the project.
🤖 DeepSeek 自动评审报告模型: 概览本次 PR 主要做三件事:1)重构 阻塞性问题(必须修改)无 建议改进(非阻塞)
疑问 / 需要作者确认
本评论由 GitHub Actions + DeepSeek 自动生成;最终判断以人工审查为准。 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@agent/recognition/binarymatch.py`:
- Around line 263-267: The debug filename creation can fail on Windows because
safe_node only replaces '/' and '\\' but node names may contain reserved
characters like <>:"/\\|?*; update the sanitization used when building filename
(the safe_node assignment) to replace or strip all Windows-reserved characters
(at least the set <>:\"/\\|?*) before composing filename; ensure the change is
applied where filename is constructed (the variables debug_dir, safe_node, ts,
stage and the Image.fromarray(...).save call remain the same) so save() no
longer throws on Windows.
- Around line 149-199: HSVShapeMatching.analyze currently builds
hsv_mask/edge_mask/and_mask over the full argv.image and never applies argv.roi,
and _save_debug only strips "/" and "\" causing Windows filename errors; to fix,
after computing each mask from _compute_hsv_mask/_compute_edge_mask/_mask_to_bgr
but before calling _try_recognition or context.run_recognition, intersect the
mask with argv.roi (crop or bitwise-and the global-size mask with an ROI mask
while preserving the original canvas size so coordinates remain consistent) and
pass the ROI-limited processed image to _try_recognition/_run_recognition;
additionally, harden HSVShapeMatching._save_debug by sanitizing node_name for
all Windows-illegal characters (e.g., <>:"/\\|?*) or use a stable hash/ID
derived from node_name for filenames to avoid save failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cdd46312-ae4f-4aeb-adf6-db40379fd905
📒 Files selected for processing (4)
agent/recognition/binarymatch.pyassets/resource/base/pipeline/Daily.jsonassets/resource/base/pipeline/Pass.jsonassets/resource/base/pipeline/StartGame.json
| # 0. 固定截图,全程复用 | ||
| original_bgr = argv.image | ||
| ts = f"{time.time():.3f}".replace('.', '_') | ||
|
|
||
| # 1. 解析参数 | ||
| raw = argv.custom_recognition_param | ||
| if isinstance(raw, dict): | ||
| params = raw | ||
| else: | ||
| params = json.loads(str(raw)) | ||
|
|
||
| recognition_node = params.get("target_node") or params.get("recognition") | ||
| debug_mode = params.get("debug", False) | ||
|
|
||
| if not recognition_node: | ||
| mfaalog.error("[HSVShapeMatching] 参数错误: 未找到 'target_node'") | ||
| params = raw if isinstance(raw, dict) else json.loads(str(raw)) | ||
|
|
||
| debug_mode = params.get("debug", False) | ||
| edge_assist = params.get("edge_assist", False) | ||
| edge_threshold = params.get("edge_threshold", 15) | ||
|
|
||
| # 2. BGR → PIL → HSV(只做一次) | ||
| pil_img = Image.fromarray(original_bgr[..., ::-1]) # BGR → RGB → PIL | ||
| hsv_np = np.array(pil_img.convert("HSV")) | ||
|
|
||
| # 3. 解析任务列表 [(node_name, [ranges])] | ||
| tasks = self._parse_tasks(params) | ||
| if not tasks: | ||
| mfaalog.error("[HSVShapeMatching] 参数错误:未找到有效节点配置") | ||
| return None | ||
|
|
||
| # img 是 BGR 格式的 Numpy 数组 | ||
| img_bgr = argv.image | ||
|
|
||
| # 2. 预处理:BGR -> RGB | ||
| # ------------------------------------------------- | ||
| # OpenCV 图片是 BGR,Pillow 需要 RGB。 | ||
| # 使用 numpy 切片反转通道,速度极快。 | ||
| img_rgb = img_bgr[..., ::-1] | ||
|
|
||
| # Numpy -> PIL Image | ||
| pil_img = Image.fromarray(img_rgb) | ||
|
|
||
| # 转 HSV (Pillow 标准: H:0-255, S:0-255, V:0-255) | ||
| hsv_pil = pil_img.convert("HSV") | ||
| hsv_np = np.array(hsv_pil) | ||
|
|
||
| # 3. 核心逻辑:阈值映射与过滤 | ||
| # ------------------------------------------------- | ||
| # 获取用户配置的 OpenCV 标准阈值 (H: 0-180) | ||
| user_lower = params.get("lower_hsv", [0, 0, 120]) | ||
| user_upper = params.get("upper_hsv", [180, 50, 255]) | ||
|
|
||
| # --- [关键算法:坐标系映射] --- | ||
| # 目的:将用户输入的 0-180 映射到 PIL 的 0-255 | ||
| # 策略:Min向下取整,Max向上取整,确保范围只大不小 | ||
|
|
||
| def map_h_opencv_to_pillow(h_opencv, is_upper_bound=False): | ||
| # 转换系数 | ||
| ratio = 255.0 / 180.0 | ||
| val = h_opencv * ratio | ||
| if is_upper_bound: | ||
| # 上限:向上取整 (Ceil),防止浮点误差切掉边缘 | ||
| return min(255, int(np.ceil(val))) | ||
| else: | ||
| # 下限:向下取整 (Floor) | ||
| return max(0, int(np.floor(val))) | ||
|
|
||
| # 构建 PIL 标准的阈值数组 | ||
| # H 通道做映射,S/V 通道保持不变 (两者都是 0-255) | ||
| lower_hsv_pil = np.array([ | ||
| map_h_opencv_to_pillow(user_lower[0], is_upper_bound=False), | ||
| user_lower[1], | ||
| user_lower[2] | ||
| ]) | ||
|
|
||
| upper_hsv_pil = np.array([ | ||
| map_h_opencv_to_pillow(user_upper[0], is_upper_bound=True), | ||
| user_upper[1], | ||
| user_upper[2] | ||
| ]) | ||
|
|
||
| # 生成掩码 (利用 Numpy 广播机制) | ||
| # 逻辑:(Pixel >= Lower) AND (Pixel <= Upper) | ||
| mask = np.all((hsv_np >= lower_hsv_pil) & (hsv_np <= upper_hsv_pil), axis=-1) | ||
|
|
||
| # 4. 二值化与输出构建 | ||
| # ------------------------------------------------- | ||
| # 创建全白底图 (注意:输出需要 BGR 格式给 MAA,所以直接用 shape 即可) | ||
| # 这里我们直接创建一个和原图一样大小的白色 BGR 图片 | ||
| processed_bgr = np.full_like(img_bgr, 255) | ||
|
|
||
| # 将掩码区域(目标)涂黑 [0, 0, 0] | ||
| processed_bgr[mask] = [0, 0, 0] | ||
|
|
||
| # 5. 调试输出 | ||
| # ------------------------------------------------- | ||
| if debug_mode: | ||
| debug_dir = "debug_images" | ||
| if not os.path.exists(debug_dir): | ||
| try: os.makedirs(debug_dir, exist_ok=True) | ||
| except OSError as e: | ||
| mfaalog.debug(f"[HSVShapeMatching] 创建调试目录失败: {e}") | ||
|
|
||
| timestamp = f"{time.time():.3f}".replace('.', '_') | ||
| safe_node_name = recognition_node.replace('/', '_').replace('\\', '_') | ||
|
|
||
| # 在文件名里标记这是 PIL 处理的,方便区分 | ||
| filename = f"{debug_dir}/debug_pil_{safe_node_name}_{timestamp}.png" | ||
|
|
||
| # 保存调试图 | ||
| # 注意:processed_bgr 是 BGR 格式,保存前要转回 RGB 给 PIL 存 | ||
| # 或者如果你有 cv2 可以用 cv2.imwrite,但在无 cv2 环境下必须用 PIL | ||
| debug_save_img = Image.fromarray(processed_bgr[..., ::-1]) | ||
| debug_save_img.save(filename) | ||
|
|
||
| mfaalog.info(f"[HSVShapeMatching] 调试图已保存: {filename} (H范围: {lower_hsv_pil[0]}~{upper_hsv_pil[0]})") | ||
|
|
||
| # 6. 移交识别 | ||
| # ------------------------------------------------- | ||
| # 这里的 processed_bgr 已经是标准的 BGR numpy 数组 | ||
| # 且已经是【白底黑图】,完全符合 Core 节点的预期 | ||
| reco_detail = context.run_recognition(recognition_node, processed_bgr) | ||
|
|
||
| if reco_detail and reco_detail.hit: | ||
| if reco_detail.best_result: | ||
| mfaalog.debug(f"[HSVShapeMatching] 命中目标: {recognition_node}") | ||
| return CustomRecognition.AnalyzeResult( | ||
| box=reco_detail.box, | ||
| detail=reco_detail.raw_detail | ||
| ) | ||
|
|
||
| # 4. 预计算 edge_mask(全局只算一次) | ||
| edge_mask = None | ||
| if edge_assist: | ||
| edge_mask = self._compute_edge_mask(pil_img, edge_threshold) | ||
| if debug_mode: | ||
| edge_vis = self._mask_to_bgr(original_bgr, edge_mask) | ||
| self._save_debug(edge_vis, "global", ts, "2_edge", edge_mask) | ||
|
|
||
| # 5. 逐节点尝试 | ||
| for node_name, ranges in tasks: | ||
| # 生成 HSV 掩膜(多范围 OR) | ||
| hsv_mask = self._compute_hsv_mask(hsv_np, ranges) | ||
| processed_hsv = self._mask_to_bgr(original_bgr, hsv_mask) | ||
|
|
||
| if debug_mode: | ||
| self._save_debug(processed_hsv, node_name, ts, "1_hsv", hsv_mask) | ||
|
|
||
| # 尝试 HSV 掩膜识别 | ||
| result = self._try_recognition(context, node_name, processed_hsv, "HSV") | ||
| if result: | ||
| return result | ||
|
|
||
| # edge_assist 升级:AND 掩膜 | ||
| if edge_assist and edge_mask is not None: | ||
| and_mask = hsv_mask & edge_mask | ||
| processed_and = self._mask_to_bgr(original_bgr, and_mask) | ||
| if debug_mode: | ||
| self._save_debug(processed_and, node_name, ts, "3_and", and_mask) | ||
| result = self._try_recognition(context, node_name, processed_and, "AND") |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 定位并查看 binarymatch.py 相关代码段
FILE="agent/recognition/binarymatch.py"
if [ -f "$FILE" ]; then
echo "=== $FILE: lines 1-260 ==="
nl -ba "$FILE" | sed -n '1,260p'
else
echo "File not found: $FILE"
fi
# 2) 检查 HSVShapeMatching 类是否还有其他与 roi 相关逻辑
rg -n "roi|argv\.roi|AnalyzeArg|CustomRecognition|run_recognition|_save_debug" agent/recognition/binarymatch.py || true
# 3) 搜索 Context.run_recognition 和 Custom 回调参数定义
rg -n "def run_recognition|run_recognition\(" -S agent source binding || true
rg -n "CustomRecognition|AnalyzeArg|roi" -S source/binding/Python/maa agent source || true
# 4) 定位 Python binding 中 roi 的字段/用法(只做少量上下文)
rg -n "class .*AnalyzeArg|AnalyzeArg" -S source/binding/Python/maa || trueRepository: sunyink/MFABD2
Length of output: 173
🌐 Web query:
site:github.com MaaXYZ/MaaFramework roi CustomRecognition AnalyzeArg
💡 Result:
In MaaXYZ/MaaFramework, CustomRecognition 的回调 analyze 通过 AnalyzeArg 里的 roi 字段来接收“识别区域 ROI”;roi 定义识别边界(只在该区域内进行相关图像处理),并可与 roi_offset 共同决定最终识别范围。[1][2] 你在自定义识别实现中会拿到 AnalyzeArg.roi(以及 roi_offset 未直接暴露在该 AnalyzeArg 定义片段中,但在文档对识别范围的说明里与 roi_offset 联合)。[3][1] 关键点(对应你查询里的“roi CustomRecognition AnalyzeArg”): 1) CustomRecognition 回调签名与 AnalyzeArg - Python 绑定中,CustomRecognition.analyze(context, argv) 的 argv 类型为 AnalyzeArg。[3] - AnalyzeArg 明确包含:custom_recognition_name、custom_recognition_param(JSON 字符串)、image(BGR)、roi(Rect)。[3] 2) roi 的语义:识别区域/识别边界 - 文档说明 roi 是 Region of Interest (ROI),用来定义图像识别边界,相关图像处理只在该区域内进行。[1] - 另外文档还指出“识别范围由 roi 和 roi_offset 共同决定”。[1] 3) roi 的形态/来源与传递 - 文档对 roi 的类型给出说明:可为 array<int,4>(识别区域坐标)或 string(可使用 anchor 相关格式/与目标关联)。[2] - roi 会“传递给 MaaCustomRecognitionCallback.roi”(即你自定义识别回调在参数里能看到 roi)。[1] 如何使用(你实现 CustomRecognition.analyze 时): - 在你的 analyze 里读取 argv.roi(Rect),对 argv.image 做裁剪/限制处理,然后返回识别结果(AnalyzeResult.box + detail,或直接返回 box / None)。[3] - CustomRecognition 示例(如何注册自定义识别器、以及 pipeline_override 用法)可参考官方 Quick Started/boilerplate 文档中的示例:注册 @AgentServer.custom_recognition("MyReco"), 并在实现里可通过 context.run_recognition(...) 使用 pipeline_override 进一步覆盖 roi 等识别参数。[4] 补充:v5.6 的 roi 坐标扩展 - 与 roi/roi_target 相关的支持里,文档/提交里提到:array<int,4> 的 roi 坐标支持负值含义(例如负的 x/y 从右/下边缘计算;w/h 为 0 表示扩展到边缘;负值会把 (x,y) 当作右下角并取绝对值等),以便更灵活地指定 ROI。[2]
Citations:
- 1: MaaXYZ/MaaFramework@6a97392
- 2: MaaXYZ/MaaFramework@e78524c
- 3: https://github.com/MaaXYZ/MaaFramework/blob/main/source/binding/Python/maa/custom_recognition.py
- 4: https://github.com/MaaXYZ/MaaFramework/blob/main/docs/en_us/1.1-QuickStarted.md
🌐 Web query:
site:github.com MaaXYZ/MaaFramework docs zh_cn roi 识别边界 Custom 节点 回调
💡 Result:
在 MaaFramework 中,Custom 节点用于实现自定义识别逻辑,其 ROI(感兴趣区域)设置、识别边界及回调处理机制说明如下: 1. ROI 与识别边界 ROI(Region of Interest)用于定义图像识别的边界,MaaFramework 仅会在该区域内进行相关的图像处理 [1][2]。 - 配置方式:在 Pipeline 的 Custom 节点配置中,roi 参数支持 array<int, 4> 格式 [x, y, w, h] 或 string 格式(填写节点名或 [Anchor]锚点名以引用之前节点的识别范围) [1][2]。 - 动态偏移:roi_offset 参数允许在 roi 的基础上进行额外的坐标偏移 [1][2]。 - 识别范围计算:最终的识别范围由 roi 和 roi_offset 共同决定 [1][2]。 2. 自定义识别回调 (Custom Recognition Callback) Custom 节点通过回调机制与外部实现的识别逻辑进行交互 [3][4]。 - 注册与绑定:开发者通过 MaaResourceRegisterCustomRecognition 接口注册自定义识别器,传入识别名和回调 [5][6]。 - 参数传递:Custom 节点配置中的 custom_recognition_name、custom_recognition_param 以及 roi 等参数,会通过回调结构体 MaaCustomRecognitionCallback 传递给回调函数 [1][2]。 - 回调实现:以 Python 绑定为例,开发者需实现 analyze 方法。该方法接收 context 和包含 roi、图像数据等信息的 argv 参数,并返回识别结果(AnalyzeResult 或识别到的 RectType) [7]。 3. 自定义动作回调 (Custom Action Callback) 与识别类似,Custom 动作节点通过 MaaCustomActionCallback 机制实现 [3][4]。执行时,框架会调用注册的回调函数,并传入包括任务上下文、节点参数及之前的识别详情(reco_detail)等信息,回调函数通过执行逻辑并返回结果(如成功或失败) [8]。 建议参考官方文档中的 Pipeline 协议说明及对应的 C API/Python 绑定源码以获取最新的接口定义 [1][8]。
Citations:
- 1: https://github.com/MaaXYZ/MaaFramework/blob/main/docs/zh_cn/3.1-%E4%BB%BB%E5%8A%A1%E6%B5%81%E6%B0%B4%E7%BA%BF%E5%8D%8F%E8%AE%AE.md
- 2: https://github.com/MaaXYZ/MaaFramework/blob/main/docs/en_us/3.1-PipelineProtocol.md
- 3: https://github.com/MaaXYZ/MaaFramework/blob/main/AGENTS.md
- 4: https://github.com/MaaXYZ/MaaFramework/blob/c26fe1acace2ce4242c62c120ae2a6d0288f2c65/AGENTS.md
- 5: https://github.com/MaaXYZ/MaaFramework/blob/main/docs/zh_cn/2.2-%e9%9b%86%e6%88%90%e6%8e%a5%e5%8f%a3%e4%b8%80%e8%a7%88.md
- 6: https://github.com/MaaXYZ/MaaFramework/blob/main/docs/en_us/2.2-IntegratedInterfaceOverview.md
- 7: https://github.com/MaaXYZ/MaaFramework/blob/main/source/binding/Python/maa/custom_recognition.py
- 8: https://github.com/MaaXYZ/MaaFramework/blob/main/source/binding/Python/maa/custom_action.py
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 查看 binarymatch.py 全部相关实现(含 roi / 掩膜 / debug)
FILE="agent/recognition/binarymatch.py"
if [ -f "$FILE" ]; then
echo "=== $FILE ==="
nl -ba "$FILE" | sed -n '1,320p'
else
echo "File not found: $FILE" >&2
fi
# 2) 只做精确检索:roi 是否被用到、processed 图是否裁剪、run_recognition 是否会自动处理 roi
rg -n "argv\.roi|roi_mask|roi\b|run_recognition|CustomRecognition|AnalyzeArg|_save_debug" agent/recognition/binarymatch.py || true
# 3) 框架侧:定位 run_recognition、CustomRecognition、AnalyzeArg、roi 契约
# (只搜关键符号,避免输出爆炸)
rg -n "def run_recognition\(|run_recognition\(" -S source/binding/Python/maa source agent || true
rg -n "CustomRecognition|AnalyzeArg|roi" -S source/binding/Python/maa source agent || true
rg -n "Context\.run_recognition|class .*Context" -S source/binding/Python/maa source agent || trueRepository: sunyink/MFABD2
Length of output: 160
🌐 Web query:
site:github.com/MaaXYZ/MaaFramework roi CustomRecognition AnalyzeArg
💡 Result:
In MaaFramework, roi, CustomRecognition, and AnalyzeArg are components related to the custom recognition system, which allows users to define custom image processing or recognition logic within an Agent process [1][2]. 1. ROI (Region of Interest): This defines the specific boundary area within an image where recognition or processing should occur [3]. It is commonly represented as an array of four integers [x, y, w, h] or a string [3]. Recent updates have added support for anchors, allowing ROI coordinates to be relative to specific elements or screen edges [3][4]. 2. CustomRecognition: This is an extensible mechanism for implementing specialized recognition logic [1][2]. Users define a class inheriting from CustomRecognition (in Python) and implement an analyze method [1][2]. This method is invoked by the framework when a task requires custom logic [2]. 3. AnalyzeArg: This object acts as a container for arguments passed to the analyze method [1]. It provides context to the custom recognition logic, such as access to the task environment [1][5]. In the Python binding, AnalyzeArg provides data necessary for the execution of the recognition logic [1]. Notably, performance-related discussions in the MaaFramework repository have highlighted that AnalyzeArg may contain properties like task_detail, which should be accessed lazily to avoid unnecessary performance overhead (specifically O(N) degradation) in Agent Server mode [5]. In practice, these components are used together by defining a custom recognition ID in the task configuration (JSON) and registering the corresponding class in the Python Agent script using decorators like @AgentServer.custom_recognition("ID") [2]. The framework then handles the communication between the main execution process and the Agent process where the custom analyze logic resides [1][2].
Citations:
- 1: https://github.com/MaaXYZ/MaaFramework/blob/main/source/binding/Python/maa/custom_recognition.py
- 2: https://github.com/MaaXYZ/MaaFramework/blob/master/docs/en_us/1.1-QuickStarted.md
- 3: MaaXYZ/MaaFramework@e78524c
- 4: MaaXYZ/MaaFramework@fc6fa87
- 5: [Bug] Agent Server 模式下自定义识别/动作回调因 get_task_detail() 导致 O(N) 性能退化 MaaXYZ/MaaFramework#1118
🌐 Web query:
site:github.com/MaaXYZ/MaaFramework/docs/zh_cn roi 识别边界 Custom 回调
💡 Result:
在 MaaFramework 的 Pipeline(docs/zh_cn 对应内容)里:
-
**ROI(识别边界)**用
roi字段表示:只在该区域内做图像处理/识别;默认[0,0,0,0]表示全屏。格式为[x, y, w, h],并且 v5.6 起支持负数(如x/y<0表示从右/下边缘反算;w/h=0表示延伸到边缘;w/h<0时取绝对值并把(x,y)视为右下角)。另外roi也可以写 节点名(或[Anchor]锚点名)来引用之前节点的识别结果范围。 (maafw.xyz) -
当你把识别算法设为
Custom时,需要在节点里配置:custom_recognition:自定义识别器名字(要和MaaResourceRegisterCustomRecognition注册时一致)custom_recognition_param:任意自定义参数(可选)roi/roi_offset:同上面的 ROI/偏移
这些字段会分别通过MaaCustomRecognitionCallback的custom_recognition_name、custom_recognition_param、roi传到你的 Custom 回调里。 (maafw.xyz)
Citations:
- 1: https://maafw.xyz/docs/3.1-PipelineProtocol.html
- 2: https://maafw.xyz/docs/3.1-PipelineProtocol.html
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 安全读取 agent/recognition/binarymatch.py 的关键片段(替代 nl:避免 nl 不存在)
FILE="agent/recognition/binarymatch.py"
echo "=== $FILE: lines 120-260 ==="
sed -n '120,260p' "$FILE" | cat -n
echo "=== $FILE: _try_recognition / _save_debug 附近 ==="
rg -n "def _try_recognition|def _save_debug" "$FILE"
# 展示各函数附近 80 行
for m in _try_recognition _save_debug; do
line=$(rg -n "def ${m}" "$FILE" | head -n1 | cut -d: -f1)
start=$((line-20))
end=$((line+120))
echo "--- $m at ~line $line ---"
sed -n "${start},${end}p" "$FILE" | cat -n
done
# 2) 检查是否任何位置使用了 argv.roi
echo "=== argv.roi 使用情况 ==="
rg -n "argv\.roi|roi\b" "$FILE" || true
# 3) 检查 Daily.json 中是否存在带 < > 的节点名(用你提到的那段做精确检索)
JSON="assets/resource/base/pipeline/Daily.json"
if [ -f "$JSON" ]; then
echo "=== $JSON: nodes containing '<' or '>' (first 50 matches) ==="
rg -n "\".*<.*>.*\"" "$JSON" | head -n 50 || true
echo "=== $JSON: search for Rec_<Daily_Busin_DecorCoin_PickUp>_Tpl_01 (if exists) ==="
rg -n "Rec_<Daily_Busin_DecorCoin_PickUp>_Tpl_01" "$JSON" || true
else
echo "File not found: $JSON" >&2
fiRepository: sunyink/MFABD2
Length of output: 11897
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 4) 再定位:processed_hsv / processed_and 是否在 _try_recognition 里被 roi 影响
FILE="agent/recognition/binarymatch.py"
rg -n "run_recognition|pipeline_override|roi_offset|roi\s*[:=]" "$FILE" || trueRepository: sunyink/MFABD2
Length of output: 129
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 5) 框架侧:检查 Python binding 自定义识别回调是否把 roi 用于裁剪或只是提供给回调
rg -n "class .*AnalyzeArg|custom_recognition.*AnalyzeArg|AnalyzeArg" source/binding/Python/maa/custom_recognition.py
sed -n '1,260p' source/binding/Python/maa/custom_recognition.py | cat -nRepository: sunyink/MFABD2
Length of output: 146
使 HSVShapeMatching 真正遵守 argv.roi,并修复 Windows debug 文件名非法字符问题
agent/recognition/binarymatch.py的HSVShapeMatching.analyze在计算hsv_mask/edge_mask/and_mask时始终基于整张argv.image,完全未引用argv.roi;因此该 Custom 节点的掩膜预处理阶段无法按“识别边界 ROI”限制到 ROI 区域,ROI 配置不会生效(至少不会限定掩膜覆盖范围)。需要在掩膜生成后与argv.roi相交/裁剪,再把限定后的掩膜渲染图传给context.run_recognition(保留原画布尺寸以避免坐标漂移)。HSVShapeMatching._save_debug只替换了/和\。assets/resource/base/pipeline/Daily.json中节点名包含<>(如Rec_<Daily_Busin_DecorCoin_PickUp>_Tpl_01),Windows 下保存 debug 图片可能因非法文件名字符失败;该异常会被analyze外层捕获并直接return None,导致开启debug时识别失败。应对node_name进行完整的 Windows 非法字符清洗,或改用 hash/编号生成文件名。
🧰 Tools
🪛 Ruff (0.15.13)
[warning] 149-149: Comment contains ambiguous , (FULLWIDTH COMMA). Did you mean , (COMMA)?
(RUF003)
[warning] 161-161: Comment contains ambiguous ( (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?
(RUF003)
[warning] 161-161: Comment contains ambiguous ) (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?
(RUF003)
[warning] 168-168: String contains ambiguous : (FULLWIDTH COLON). Did you mean : (COLON)?
(RUF001)
[warning] 171-171: Comment contains ambiguous ( (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?
(RUF003)
[warning] 171-171: Comment contains ambiguous ) (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?
(RUF003)
[warning] 181-181: Comment contains ambiguous ( (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?
(RUF003)
[warning] 181-181: Comment contains ambiguous ) (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?
(RUF003)
[warning] 193-193: Comment contains ambiguous : (FULLWIDTH COLON). Did you mean : (COLON)?
(RUF003)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@agent/recognition/binarymatch.py` around lines 149 - 199,
HSVShapeMatching.analyze currently builds hsv_mask/edge_mask/and_mask over the
full argv.image and never applies argv.roi, and _save_debug only strips "/" and
"\" causing Windows filename errors; to fix, after computing each mask from
_compute_hsv_mask/_compute_edge_mask/_mask_to_bgr but before calling
_try_recognition or context.run_recognition, intersect the mask with argv.roi
(crop or bitwise-and the global-size mask with an ROI mask while preserving the
original canvas size so coordinates remain consistent) and pass the ROI-limited
processed image to _try_recognition/_run_recognition; additionally, harden
HSVShapeMatching._save_debug by sanitizing node_name for all Windows-illegal
characters (e.g., <>:"/\\|?*) or use a stable hash/ID derived from node_name for
filenames to avoid save failures.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
agent/recognition/binarymatch.py (1)
172-200:⚠️ Potential issue | 🟠 Major | ⚡ Quick win让掩膜预处理真正遵守
argv.roi。这里始终基于整张
argv.image生成hsv_mask/edge_mask,后续也直接把整张processed_*送进context.run_recognition,所以 Custom 节点自己的roi在预处理阶段完全没生效。MaaFramework 文档把roi定义为“识别边界,仅在该区域内进行相关图像处理”,并且Custom节点会把这个值传到AnalyzeArg.roi;当前实现会把 ROI 外噪声也混进 HSV/边缘掩膜,导致误识别风险和覆盖率日志失真。(github.com)🛠️ 建议修改
+ def _apply_roi_mask(self, mask: np.ndarray, roi: RectType) -> np.ndarray: + x, y, w, h = roi + if w <= 0 or h <= 0: + return mask + x1 = max(0, x) + y1 = max(0, y) + x2 = min(mask.shape[1], x + w) + y2 = min(mask.shape[0], y + h) + limited = np.zeros_like(mask, dtype=bool) + if x1 < x2 and y1 < y2: + limited[y1:y2, x1:x2] = mask[y1:y2, x1:x2] + return limited + def analyze( self, context: Context, argv: CustomRecognition.AnalyzeArg, ) -> Union[CustomRecognition.AnalyzeResult, Optional[RectType]]: @@ - hsv_mask = self._compute_hsv_mask(hsv_np, ranges) + hsv_mask = self._apply_roi_mask( + self._compute_hsv_mask(hsv_np, ranges), + argv.roi, + ) processed_hsv = self._mask_to_bgr(original_bgr, hsv_mask) @@ - and_mask = hsv_mask & edge_mask + and_mask = hsv_mask & self._apply_roi_mask(edge_mask, argv.roi) processed_and = self._mask_to_bgr(original_bgr, and_mask)As per coding guidelines,
务必以 MaaFramework 官方 docs/zh_cn 为准。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@agent/recognition/binarymatch.py` around lines 172 - 200, The precomputed masks ignore each node's ROI so HSV/edge preprocessing uses the full image and feeds full-image processed_* into context.run_recognition; update the loop in the tasks processing so that for each (node_name, ranges) you compute per-node masks clipped to the node's ROI (use argv.roi / AnalyzeArg.roi passed into the node), i.e. call _compute_hsv_mask and _compute_edge_mask on the ROI-subimage (or mask the returned full-image mask with the node ROI) before converting with _mask_to_bgr and before calling _try_recognition; ensure edge_mask precomputation still happens globally but is intersected with the node ROI (or recomputed per-node if edge_threshold differs) so that processed_hsv/processed_and respect the node's roi when passed to _try_recognition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@agent/recognition/binarymatch.py`:
- Around line 172-200: The precomputed masks ignore each node's ROI so HSV/edge
preprocessing uses the full image and feeds full-image processed_* into
context.run_recognition; update the loop in the tasks processing so that for
each (node_name, ranges) you compute per-node masks clipped to the node's ROI
(use argv.roi / AnalyzeArg.roi passed into the node), i.e. call
_compute_hsv_mask and _compute_edge_mask on the ROI-subimage (or mask the
returned full-image mask with the node ROI) before converting with _mask_to_bgr
and before calling _try_recognition; ensure edge_mask precomputation still
happens globally but is intersected with the node ROI (or recomputed per-node if
edge_threshold differs) so that processed_hsv/processed_and respect the node's
roi when passed to _try_recognition.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 654a2b87-877d-42cf-9cc5-2e147622d2b5
📒 Files selected for processing (1)
agent/recognition/binarymatch.py
Summary by Sourcery
增强基于 HSV 的形状匹配识别,使其在保持仅依赖 Pillow(无需 OpenCV)处理的前提下,支持更灵活的配置并提升鲁棒性。
新特性:
hsv_map和hsv_ranges),为多个目标节点以及每个节点的多组 HSV 范围提供支持。增强点:
Original summary in English
Summary by Sourcery
Enhance the HSV-based shape matching recognition to support more flexible configuration and robustness while keeping Pillow-only, OpenCV-free processing.
New Features:
Enhancements:
Summary by CodeRabbit
新功能
功能改进