Skip to content

Hotfix/patch0519Merge:'hotfix/patch0519'| 二值化Agnet增强.餐馆装饰币拾取重构#287

Merged
sunyink merged 5 commits into
mainfrom
hotfix/patch0519
May 23, 2026
Merged

Hotfix/patch0519Merge:'hotfix/patch0519'| 二值化Agnet增强.餐馆装饰币拾取重构#287
sunyink merged 5 commits into
mainfrom
hotfix/patch0519

Conversation

@sunyink
Copy link
Copy Markdown
Owner

@sunyink sunyink commented May 21, 2026

Summary by Sourcery

增强基于 HSV 的形状匹配识别,使其在保持仅依赖 Pillow(无需 OpenCV)处理的前提下,支持更灵活的配置并提升鲁棒性。

新特性:

  • 通过扩展参数格式(包括 hsv_maphsv_ranges),为多个目标节点以及每个节点的多组 HSV 范围提供支持。
  • 引入可选的“边缘辅助识别”,将 HSV 掩膜与边缘掩膜结合,以在复杂场景中提升检测效果。

增强点:

  • 重构 HSV 形状匹配流水线,以复用单次截图,在多个任务之间共享 HSV 转换,并将逻辑拆分为可复用的辅助方法。
  • 改进调试工具:保存阶段化的掩膜(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:

  • Add support for multiple target nodes and multiple HSV range groups per node via extended parameter formats, including hsv_map and hsv_ranges.
  • Introduce optional edge-assisted recognition that combines HSV masks with edge masks for improved detection in challenging cases.

Enhancements:

  • Refactor the HSV shape matching pipeline to reuse a single screenshot, share HSV conversion across tasks, and factor logic into reusable helper methods.
  • Improve debug tooling by saving staged masks (HSV, edge, combined) with coverage statistics and clearer logging for each recognition attempt.

Summary by CodeRabbit

  • 新功能

    • 图像识别支持多阈值配置与按节点尝试的识别策略,新增边缘检测辅助提高未命中时的识别率。
  • 功能改进

    • 日常任务流程重构:装饰币拾取改用滑动移动并新增定位复位与旁路领取流程,增强容错与恢复能力。
    • 启动流程加入长白屏超时防御(timeout)并扩展后续跳转。
    • 多处流程新增统一的错误兜底分支,提升整体稳定性与鲁棒性。

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

PR 重构 HSV 形状匹配识别器为 Pillow 实现,支持 per-node hsv_maphsv_ranges(多阈值 OR 合并)与可选 edge_assist;同步更新装饰币流程以使用新识别参数并引入视角复位与滑动移动;为 Pass 与 StartGame 增加错误兜底与启动超时处理。

Changes

识别算法与装饰币流程重设计

Layer / File(s) Summary
HSV 识别算法重构
agent/recognition/binarymatch.py
将识别器升级为 Pillow 原生实现:一次性 BGR→PIL→HSV;新增 hsv_maphsv_rangesedge_assist/edge_threshold 与统一 _save_debug;新增私有辅助方法 _parse_tasks_map_h_compute_hsv_mask_compute_edge_mask_mask_to_bgr_try_recognition;当 HSV 未命中且 edge_assist=true 时对 HSV 掩膜与 FIND_EDGES 边缘掩膜做 AND 重试识别。
装饰币流程重设计:满量判定与错误复位
assets/resource/base/pipeline/Daily.json
Daily_Busin_DecorCoin_IsFull_Binary.custom_recognition_param 的 HSV 配置由 lower_hsv/upper_hsvhsv_ranges[],新增 edge_assist/edge_thresholdDaily_Busin_DecorCoin_PickUp.on_error 重定向为 Daily_Busin_DecorCoin_LocReset;新增 LocReset 系列与 DiaDetour 节点;Move_1/Move_2/Move_3 的动作由 Click 改为参数化 Swipe(含 begin/end/end_hold/duration)。

通行证流程错误处理加强

Layer / File(s) Summary
Pass 节点错误处理补强
assets/resource/base/pipeline/Pass.json
Pass_EnterPassPass_GetAll 新增 on_error 数组配置,分别追加 Pass_Back/Pass_EnterPassGlobal_Null_ExceptionGlobal_Null_Panic 异常类型。

游戏启动流程超时防御

Layer / File(s) Summary
启动超时与等待分支
assets/resource/base/pipeline/StartGame.json
StartGame_RunApp 新增 timeout: 60000desc 补充“长白屏超时防御”,并在 next 中追加 [JumpBack]Global_WaitingForLoading 分支。

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题涵盖了主要变更:二值化Agent(HSVShapeMatching)的增强和餐馆装饰币拾取流程的重构,与文件改动和PR目标完全相符。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/patch0519

Warning

Review ran into problems

🔥 Problems

Stopped 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 @coderabbit review after the pipeline has finished.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 21, 2026

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
Loading

File-Level Changes

Change Details Files
将 HSV 二值匹配器重构为可复用的多节点流水线,并支持边缘辅助掩膜与更丰富的调试工具。
  • 修复截图处理逻辑,仅捕获一次 argv.image,并在整个处理过程中复用该 BGR 帧。
  • 引入参数解析,既兼容旧版单节点配置,又支持新的多节点 hsv_map 配置及每个节点的多组 HSV 范围。
  • 将 OpenCV 到 Pillow 的色相映射集中到辅助函数中,并泛化 HSV 掩膜计算以支持通过 OR 组合的范围。
  • 新增可选的 edge_assist 模式,提前构建全局边缘掩膜,并在 HSV 识别失败时通过逻辑 AND 与 HSV 掩膜组合。
  • 重构调试输出,按阶段、按节点保存图像,包含覆盖率统计,并使用稳定的带时间戳命名方案。
  • 抽取辅助方法(_parse_tasks, _compute_hsv_mask, _compute_edge_mask, _mask_to_bgr, _save_debug, _try_recognition),以简化主 analyze 工作流并提升可读性。
agent/recognition/binarymatch.py
更新流水线以使用新的增强配置版二值 HSV 识别器。
  • 调整 daily 流水线配置,使其指向新的 HSV 形状匹配器,并/或为相关节点提供 hsv_map 或 hsv_ranges。
  • 更新 pass 流水线,以对齐重构后的识别器参数格式,以及与餐厅硬币拾取相关的任何新节点。
  • 修改 start-game 流水线配置,以便利用新的二值识别行为和参数。
assets/resource/base/pipeline/Daily.json
assets/resource/base/pipeline/Pass.json
assets/resource/base/pipeline/StartGame.json

Tips and commands

Interacting with Sourcery

  • 触发新评审: 在拉取请求中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在某条评审评论下回复,请求 Sourcery 从该评论创建一个 issue。你也可以在评论中回复 @sourcery-ai issue 来基于该评论生成 issue。
  • 生成拉取请求标题: 在拉取请求标题中的任意位置写上 @sourcery-ai 即可随时生成标题。你也可以在拉取请求中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成拉取请求摘要: 在拉取请求正文中的任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在拉取请求中评论 @sourcery-ai summary 来在任意时间(重新)生成摘要。
  • 生成审阅者指南: 在拉取请求中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。当你已经处理完所有评论且不希望再看到它们时非常有用。
  • 忽略所有 Sourcery 评审: 在拉取请求中评论 @sourcery-ai dismiss,以忽略所有已有的 Sourcery 评审。尤其适用于你想基于全新评审重新开始的场景——不要忘记再评论 @sourcery-ai review 以触发新的评审!

Customizing Your Experience

访问你的 dashboard 以:

  • 启用或禁用评审功能,例如 Sourcery 自动生成的拉取请求摘要、审阅者指南等。
  • 更改评审语言。
  • 添加、删除或编辑自定义评审指令。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Refactors 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 flow

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
Loading

File-Level Changes

Change Details Files
Refactor HSV binary matcher into reusable multi-node pipeline with edge-assisted masking and richer debug utilities.
  • Fixes screenshot handling by capturing argv.image once and reusing the BGR frame throughout processing.
  • Introduces parameter parsing that supports both legacy single-node configs and new multi-node hsv_map configurations with multiple HSV ranges per node.
  • Centralizes OpenCV-to-Pillow hue mapping into helper functions and generalizes HSV mask computation to support OR-composed ranges.
  • Adds optional edge_assist mode that builds a global edge mask once and combines it with HSV masks via logical AND when HSV-only recognition fails.
  • Reworks debug output to save per-stage, per-node images with coverage statistics and a stable timestamped naming scheme.
  • Extracts helper methods (_parse_tasks, _compute_hsv_mask, _compute_edge_mask, _mask_to_bgr, _save_debug, _try_recognition) to simplify the main analyze workflow and improve readability.
agent/recognition/binarymatch.py
Update pipelines to use the new binary HSV recognizer with enhanced configuration.
  • Adjusts daily pipeline configuration to point to the new HSV shape matcher and/or provide hsv_map or hsv_ranges for relevant nodes.
  • Updates pass pipeline to align with the refactored recognizer parameter format and any new nodes for restaurant coin pickup.
  • Modifies start-game pipeline configuration so that it can leverage the new binary recognition behavior and parameters.
assets/resource/base/pipeline/Daily.json
assets/resource/base/pipeline/Pass.json
assets/resource/base/pipeline/StartGame.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你好——我发现了两个问题,并留下了一些整体性的反馈:

  • _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>

Sourcery 对开源项目是免费的——如果你觉得这次评审有帮助,请考虑分享给他人 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈持续改进评审质量。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread agent/recognition/binarymatch.py
Comment on lines +238 to +246
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
  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 npfrom 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
  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.

@github-actions
Copy link
Copy Markdown
Contributor

🤖 DeepSeek 自动评审报告

模型deepseek-v4-pro 触发workflow_dispatch Diff 截断:否 输出截断:否 改动文件:4 个(其中 0 个二进制已跳过)

概览

本次 PR 主要做三件事:1)重构 HSVShapeMatching 自定义识别器,支持多阈值 OR 合并与边缘辅助识别,并优化调试输出;2)调整“餐馆装饰币拾取”流程,增加视图复位、钻石旁路等节点,将部分点击改为滑动;3)为几个任务节点补上 on_error 兜底跳转。整体代码质量较好,识别器参数解析兼容了新旧格式,逻辑清晰。但流水线 JSON 中存在若干跳转引用无法从 diff 验证、注释与实际行为不一致、以及自定义识别器内部对 only_rec 等非标准字段的潜在误用,需要进一步确认。


阻塞性问题(必须修改)


建议改进(非阻塞)

  • 文件assets/resource/base/pipeline/Daily.jsonDaily_Busin_DecorCoin_Move_2 节点)
    问题focus 字段的内容为 "点击'员工',移动画面",但该节点动作已从 Click 改为 Swipe,不再具有“点击”行为。
    原因:项目规范第 1 条要求 doc(及 focus/desc 等开发者注释)必须与实际 action / next 行为一致,否则视为陈旧注释。
    建议:将 focus 修改为准确描述当前滑动动作,例如 "滑动'员工',移动画面" 或更明确的业务注释。

  • 文件assets/resource/base/pipeline/Daily.jsonDaily_Busin_DecorCoin_IsFull_Binary 节点)
    问题:该节点 next 列表中包含 [JumpBack]Daily_Busin_GiftGet,但此次 diff 未包含 Daily_Busin_GiftGet 的定义。若该锚点未设置或指向的节点不存在,运行时会被跳过,可能导致流程提前结束。
    原因:流水线协议规定,[Anchor] 引用的锚点名若未设置会被跳过(参见属性字段 anchor 说明)。虽然 Daily_Busin_GiftGet 可能在文件其他位置定义,但审查无法核验。
    建议:确认 Daily_Busin_GiftGet 节点存在且能正确设置锚点;如果该回跳是已废弃的逻辑,应清理引用。

  • 文件assets/resource/base/pipeline/Daily.json(新增 Daily_Busin_DecorCoin_LocReset 节点)
    问题:该节点 next 同时包含 Daily_Busin_DecorCoin_LocReset_SubUI 和自身 Daily_Busin_DecorCoin_LocReset,没有 max_hit 限制,也未设置 timeout(使用默认 20 秒)。在 SubUI 始终未命中时,会反复重新识别自身,直到超时后因无 on_error 而终止。虽不会导致死循环,但可能使任务长时间无意义轮询。
    原因:流水线执行逻辑(执行逻辑)允许节点自引用,但缺乏保护可能降低效率。
    建议:如果期望的视图复位大概率在 1~2 次内完成,可增设 max_hit 或适当缩短 timeout,避免长时间无效等待。

  • 文件assets/resource/base/pipeline/Daily.json(新增 Daily_Busin_DecorCoin_LocReset 节点)
    问题:节点中出现了 "only_rec": true 字段。该字段在官方流水线协议(v1/v2)及变更记录中均未出现,不知是否有框架扩展支持。如果 MaaFW 核心忽略此字段,则该节点仍会执行 action: Click,可能导致意外点击。
    原因:文档无此属性,无法确认其行为;若该字段无效则节点实际会执行点击,与注释 desc“餐厅视图复位系列流程”的意图可能不一致。
    建议:明确 only_rec 是否为本地自定义扩展;若非框架支持,则移除或改为 action: DoNothing 并依赖 next 进行跳转;若是扩展,请补充文档说明。

  • 文件assets/resource/base/pipeline/Pass.jsonPass_CheckPassPagePass_ClaimReward 新增 on_error
    问题on_error 中引用了 Pass_BackPass_EnterPassGlobal_Null_ExceptionGlobal_Null_Panic 等节点,但这些节点的定义未出现在本次 diff 中。
    原因:流水线要求所有引用的节点必须存在于资源文件中,否则运行时会导致流程卡死或异常。
    建议:确认上述节点是否已在其他 JSON 文件中定义;若尚未定义,请补充完整,或改用已存在的恢复/异常处理节点。

  • 文件assets/resource/base/pipeline/StartGame.jsonStartGame_RunApp 节点)
    问题next 新增了 [JumpBack]Global_WaitingForLoading,该锚点对应节点未在 diff 中展示。
    原因:同上述锚点引用问题。
    建议:确认 Global_WaitingForLoading 节点已在某处正确设置锚点。

  • 文件agent/recognition/binarymatch.py_compute_hsv_mask 方法)
    问题:读取范围参数时兼容了 rng.get("lower_hsv")rng.get("upper_hsv") 旧键名,但新版配置仅使用 lower/upper。这种容错容易让开发者误用旧格式,且文档注释并未说明 hsv_ranges 项内也支持 lower_hsv/upper_hsv
    原因:数据-逻辑分离原则鼓励明确的参数契约,避免隐式兼容导致混乱。
    建议:要么在注释中明确声明兼容旧格式,要么仅支持新键名以减少歧义。


疑问 / 需要作者确认

  1. only_rec 字段的来源与预期行为是什么?是 MFABD2 项目内部的框架补丁,还是某个未被文档记载的 MaaFW 特性?若无效,Daily_Busin_DecorCoin_LocReset 等节点将执行点击,可能与“视角复位”的意图冲突。
  2. 新增的 Daily_Busin_DiaDetour 等节点中引用的 "Daily_GetBusinRewardBack""Daily_Busin_Entered""Sub_Ocr_Enable_Clr" 等子节点是否已在其他流水线文件中定义且参数合法?建议提供完整依赖的节点清单或补充定义。
  3. Pass.jsonStartGame.json 新增/修改中涉及的 Pass_BackGlobal_WaitingForLoading 等节点是否已存在且正确配置?请确认后回复。
  4. Daily_Busin_DecorCoin_Move_2focus 注释是否应随动作变更同步修改?还是该注释另有所指?

本评论由 GitHub Actions + DeepSeek 自动生成;最终判断以人工审查为准。

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e657739 and 9dec43a.

📒 Files selected for processing (4)
  • agent/recognition/binarymatch.py
  • assets/resource/base/pipeline/Daily.json
  • assets/resource/base/pipeline/Pass.json
  • assets/resource/base/pipeline/StartGame.json

Comment on lines +149 to +199
# 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")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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 || true

Repository: 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:


🌐 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:


🏁 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 || true

Repository: 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:


🌐 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/偏移
      这些字段会分别通过 MaaCustomRecognitionCallbackcustom_recognition_namecustom_recognition_paramroi 传到你的 Custom 回调里。 (maafw.xyz)

Citations:


🏁 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
fi

Repository: 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" || true

Repository: 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 -n

Repository: sunyink/MFABD2

Length of output: 146


使 HSVShapeMatching 真正遵守 argv.roi,并修复 Windows debug 文件名非法字符问题

  • agent/recognition/binarymatch.pyHSVShapeMatching.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.

Comment thread agent/recognition/binarymatch.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9dec43a and 8396881.

📒 Files selected for processing (1)
  • agent/recognition/binarymatch.py

@sunyink sunyink merged commit 09d9058 into main May 23, 2026
21 checks passed
@sunyink sunyink deleted the hotfix/patch0519 branch May 23, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant