Skip to content

Commit b15c51d

Browse files
authored
Merge pull request #342 from Integration-Automation/feat/shape-locator-batch
Add edge/contour shape locator (find rectangles/shapes without a template)
2 parents 980a3a3 + 0a36c35 commit b15c51d

15 files changed

Lines changed: 398 additions & 2 deletions

File tree

README/WHATS_NEW_zh-CN.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 以边缘 / 轮廓定位 UI 元素(免模板)
4+
5+
在从未见过的画面上找出可点击的方框。完整参考:[`docs/source/Zh/doc/new_features/v132_features_doc.rst`](../docs/source/Zh/doc/new_features/v132_features_doc.rst)
6+
7+
- **`find_shapes` / `find_rectangles`**(`AC_find_shapes``AC_find_rectangles`):其他定位器都需要一个寻找对象——模板、颜色或文字。这两个什么都不需要:Canny 边缘检测 + 轮廓提取返回各个形状的边界框(`{x,y,width,height,area,center,aspect}`,由大到小),让脚本能结构性地列举卡片 / 按钮 / 输入框并点击第 N 个。`find_rectangles` 只保留凸四边形,并加上 `aspect_range=(min,max)` 宽高比过滤(`(1.5,8)` 取宽按钮)。可注入 haystack → 无头可测。
8+
39
## 本次更新 (2026-06-23) — ORB 特征匹配(对旋转 / 缩放 / 主题稳健)
410

511
即使目标旋转、缩放或换主题也能找到。完整参考:[`docs/source/Zh/doc/new_features/v131_features_doc.rst`](../docs/source/Zh/doc/new_features/v131_features_doc.rst)

README/WHATS_NEW_zh-TW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# 本次更新 — AutoControl
22

3+
## 本次更新 (2026-06-23) — 以邊緣 / 輪廓定位 UI 元素(免模板)
4+
5+
在從未見過的畫面上找出可點擊的方框。完整參考:[`docs/source/Zh/doc/new_features/v132_features_doc.rst`](../docs/source/Zh/doc/new_features/v132_features_doc.rst)
6+
7+
- **`find_shapes` / `find_rectangles`**(`AC_find_shapes``AC_find_rectangles`):其他定位器都需要一個尋找對象——模板、顏色或文字。這兩個什麼都不需要:Canny 邊緣偵測 + 輪廓擷取回傳各個形狀的邊界框(`{x,y,width,height,area,center,aspect}`,由大到小),讓腳本能結構性地列舉卡片 / 按鈕 / 輸入框並點擊第 N 個。`find_rectangles` 只保留凸四邊形,並加上 `aspect_range=(min,max)` 寬高比過濾(`(1.5,8)` 取寬按鈕)。可注入 haystack → 無頭可測。
8+
39
## 本次更新 (2026-06-23) — ORB 特徵比對(對旋轉 / 縮放 / 主題穩健)
410

511
即使目標旋轉、縮放或換主題也能找到。完整參考:[`docs/source/Zh/doc/new_features/v131_features_doc.rst`](../docs/source/Zh/doc/new_features/v131_features_doc.rst)

WHATS_NEW.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# What's New — AutoControl
22

3+
## What's new (2026-06-23) — Locate UI Elements by Edge / Contour (No Template)
4+
5+
Find the clickable boxes on a screen you have never seen. Full reference: [`docs/source/Eng/doc/new_features/v132_features_doc.rst`](docs/source/Eng/doc/new_features/v132_features_doc.rst).
6+
7+
- **`find_shapes` / `find_rectangles`** (`AC_find_shapes`, `AC_find_rectangles`): every other locator needs something to look *for* — a template, a colour, some text. These need nothing: Canny edge detection + contour extraction returns the bounding boxes (`{x,y,width,height,area,center,aspect}`, largest first) of the distinct shapes, so a script can enumerate cards / buttons / input fields structurally and click the Nth one. `find_rectangles` keeps only convex quads and adds an `aspect_range=(min,max)` w/h filter (`(1.5,8)` wide buttons). Injectable haystack → headless-testable.
8+
39
## What's new (2026-06-23) — ORB Feature Matching (Rotation / Scale / Theme Robust)
410

511
Find a target even when it is rotated, rescaled or re-themed. Full reference: [`docs/source/Eng/doc/new_features/v131_features_doc.rst`](docs/source/Eng/doc/new_features/v131_features_doc.rst).
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Locate UI Elements by Edge / Contour (No Template)
2+
==================================================
3+
4+
Every locator so far needs something to look *for*: ``match_template`` and
5+
``feature_match`` need a reference image, ``find_color_region`` needs a colour,
6+
``locate_text`` needs the text. None of them answers the structural question
7+
"where are the clickable boxes on this screen?". ``find_shapes`` and
8+
``find_rectangles`` run Canny edge detection plus contour extraction and return
9+
the bounding boxes of the distinct shapes — so a script can enumerate the cards,
10+
buttons or input fields on a screen it has never seen and act on the Nth one,
11+
without ever supplying a sample.
12+
13+
Both run on an injectable ``haystack`` image (ndarray / path / PIL), so they are
14+
unit-testable on synthetic arrays without a real screen. OpenCV + NumPy come in
15+
via ``je_open_cv``. Imports no ``PySide6``.
16+
17+
Headless API
18+
------------
19+
20+
.. code-block:: python
21+
22+
from je_auto_control import find_shapes, find_rectangles
23+
24+
# Every distinct shape, largest first.
25+
for shape in find_shapes(min_area=500):
26+
print(shape["x"], shape["y"], shape["width"], shape["height"])
27+
28+
# Just the wide, button-shaped rectangles, then click the first.
29+
buttons = find_rectangles(min_area=800, aspect_range=(1.5, 8.0))
30+
if buttons:
31+
click(*buttons[0]["center"])
32+
33+
``find_shapes`` returns ``{x, y, width, height, area, center, aspect}`` for every
34+
contour (``area`` is the bounding-box area), largest first; ``min_area`` /
35+
``max_area`` drop specks and the full-frame border. ``find_rectangles`` keeps only
36+
contours that approximate to a convex quadrilateral (``epsilon`` is the
37+
``approxPolyDP`` tolerance as a fraction of the perimeter) and adds an optional
38+
``aspect_range`` ``(min, max)`` width/height filter — ``(1.5, 8)`` for wide
39+
buttons, ``(0.8, 1.2)`` for square icons.
40+
41+
Executor commands
42+
-----------------
43+
44+
``AC_find_shapes`` (``region`` / ``min_area`` / ``max_area`` → ``{count, shapes}``)
45+
and ``AC_find_rectangles`` (also ``aspect_range`` / ``epsilon`` →
46+
``{count, rectangles}``). They are exposed as the MCP tools ``ac_find_shapes`` /
47+
``ac_find_rectangles`` and as Script Builder commands under **Image**.

docs/source/Eng/eng_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ Comprehensive guides for all AutoControl features.
154154
doc/new_features/v129_features_doc
155155
doc/new_features/v130_features_doc
156156
doc/new_features/v131_features_doc
157+
doc/new_features/v132_features_doc
157158
doc/ocr_backends/ocr_backends_doc
158159
doc/observability/observability_doc
159160
doc/operations_layer/operations_layer_doc
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
以邊緣 / 輪廓定位 UI 元素(免模板)
2+
====================================
3+
4+
目前所有定位器都需要一個*尋找對象*:``match_template`` 與 ``feature_match`` 需要參考影像、``find_color_region``
5+
需要顏色、``locate_text`` 需要文字。它們都無法回答結構性問題「這個畫面上可點擊的方框在哪裡?」。``find_shapes``
6+
與 ``find_rectangles`` 執行 Canny 邊緣偵測加輪廓擷取,回傳各個形狀的邊界框——讓腳本能在從未見過的畫面上列舉
7+
卡片、按鈕或輸入框,並對第 N 個操作,完全不需提供樣本。
8+
9+
兩者都在可注入的 ``haystack`` 影像(ndarray / 路徑 / PIL)上執行,因此可在無真實螢幕下對合成陣列做單元測試。
10+
OpenCV + NumPy 透過 ``je_open_cv`` 引入。不匯入 ``PySide6``。
11+
12+
無頭 API
13+
--------
14+
15+
.. code-block:: python
16+
17+
from je_auto_control import find_shapes, find_rectangles
18+
19+
# 每個獨立形狀,由大到小。
20+
for shape in find_shapes(min_area=500):
21+
print(shape["x"], shape["y"], shape["width"], shape["height"])
22+
23+
# 只取寬的按鈕形矩形,點擊第一個。
24+
buttons = find_rectangles(min_area=800, aspect_range=(1.5, 8.0))
25+
if buttons:
26+
click(*buttons[0]["center"])
27+
28+
``find_shapes`` 為每個輪廓回傳 ``{x, y, width, height, area, center, aspect}``(``area`` 為邊界框面積),由大到小;
29+
``min_area`` / ``max_area`` 去除雜點與整框邊界。``find_rectangles`` 只保留近似凸四邊形的輪廓(``epsilon`` 是
30+
``approxPolyDP`` 以周長比例表示的容差),並加上選用的 ``aspect_range``(min, max)寬高比過濾——``(1.5, 8)`` 取寬
31+
按鈕、``(0.8, 1.2)`` 取方形圖示。
32+
33+
執行器命令
34+
----------
35+
36+
``AC_find_shapes``(``region`` / ``min_area`` / ``max_area`` → ``{count, shapes}``)與 ``AC_find_rectangles``
37+
(另有 ``aspect_range`` / ``epsilon`` → ``{count, rectangles}``)。它們以 MCP 工具 ``ac_find_shapes`` /
38+
``ac_find_rectangles`` 以及 Script Builder 中 **Image** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ AutoControl 所有功能的完整使用指南。
154154
doc/new_features/v129_features_doc
155155
doc/new_features/v130_features_doc
156156
doc/new_features/v131_features_doc
157+
doc/new_features/v132_features_doc
157158
doc/ocr_backends/ocr_backends_doc
158159
doc/observability/observability_doc
159160
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@
290290
# ORB feature matching (rotation / scale / theme-robust template location)
291291
from je_auto_control.utils.feature_match import feature_match
292292
from je_auto_control.utils.feature_match import FeatureMatch
293+
# Locate UI elements by edge/contour detection (rectangles / shapes, no template)
294+
from je_auto_control.utils.shape_locator import (
295+
find_rectangles, find_shapes,
296+
)
293297
# CI workflow annotations (GitHub Actions)
294298
from je_auto_control.utils.ci_annotations import (
295299
emit_annotations, format_annotation,
@@ -1091,6 +1095,8 @@ def start_autocontrol_gui(*args, **kwargs):
10911095
"ssim_changed_regions",
10921096
"feature_match",
10931097
"FeatureMatch",
1098+
"find_shapes",
1099+
"find_rectangles",
10941100
"emit_annotations", "format_annotation",
10951101
"ClipboardHistory", "default_clipboard_history",
10961102
"analyze_heal_log", "heal_stats", "scan_secrets",

je_auto_control/gui/script_builder/command_schema.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,30 @@ def _add_image_specs(specs: List[CommandSpec]) -> None:
354354
),
355355
description="Locate a template under rotation / scale / theme change.",
356356
))
357+
specs.append(CommandSpec(
358+
"AC_find_shapes", "Image", "Find Shapes",
359+
fields=(
360+
FieldSpec("region", FieldType.STRING, optional=True,
361+
placeholder="[left, top, right, bottom]"),
362+
FieldSpec("min_area", FieldType.INT, optional=True, default=400),
363+
FieldSpec("max_area", FieldType.INT, optional=True),
364+
),
365+
description="Locate distinct shapes by edge/contour detection (no template).",
366+
))
367+
specs.append(CommandSpec(
368+
"AC_find_rectangles", "Image", "Find Rectangles",
369+
fields=(
370+
FieldSpec("region", FieldType.STRING, optional=True,
371+
placeholder="[left, top, right, bottom]"),
372+
FieldSpec("min_area", FieldType.INT, optional=True, default=400),
373+
FieldSpec("max_area", FieldType.INT, optional=True),
374+
FieldSpec("aspect_range", FieldType.STRING, optional=True,
375+
placeholder="[1.5, 8.0]"),
376+
FieldSpec("epsilon", FieldType.FLOAT, optional=True, default=0.04,
377+
min_value=0.0, max_value=1.0),
378+
),
379+
description="Locate rectangular regions (buttons / cards / input fields).",
380+
))
357381

358382

359383
def _add_ocr_specs(specs: List[CommandSpec]) -> None:

je_auto_control/utils/executor/action_executor.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3315,6 +3315,36 @@ def _feature_match(template: str, region: Any = None, max_features: Any = 500,
33153315
"match": match.to_dict() if match else None}
33163316

33173317

3318+
def _find_shapes(region: Any = None, min_area: Any = 400,
3319+
max_area: Any = None) -> Dict[str, Any]:
3320+
"""Adapter: bounding boxes of all distinct on-screen shapes, largest first."""
3321+
import json
3322+
from je_auto_control.utils.shape_locator import find_shapes
3323+
if isinstance(region, str):
3324+
region = json.loads(region) if region.strip() else None
3325+
shapes = find_shapes(region=region, min_area=int(min_area),
3326+
max_area=int(max_area) if max_area is not None else None)
3327+
return {"count": len(shapes), "shapes": shapes}
3328+
3329+
3330+
def _find_rectangles(region: Any = None, min_area: Any = 400, max_area: Any = None,
3331+
aspect_range: Any = None, epsilon: Any = 0.04
3332+
) -> Dict[str, Any]:
3333+
"""Adapter: boxes of the ~rectangular shapes (buttons / cards), largest first."""
3334+
import json
3335+
from je_auto_control.utils.shape_locator import find_rectangles
3336+
if isinstance(region, str):
3337+
region = json.loads(region) if region.strip() else None
3338+
if isinstance(aspect_range, str):
3339+
aspect_range = json.loads(aspect_range) if aspect_range.strip() else None
3340+
rects = find_rectangles(
3341+
region=region, min_area=int(min_area),
3342+
max_area=int(max_area) if max_area is not None else None,
3343+
aspect_range=tuple(aspect_range) if aspect_range else None,
3344+
epsilon=float(epsilon))
3345+
return {"count": len(rects), "rectangles": rects}
3346+
3347+
33183348
def _with_modifiers(modifiers: Any, actions: Any) -> Dict[str, Any]:
33193349
"""Adapter: run nested actions while modifier keys are held down."""
33203350
import json
@@ -5039,6 +5069,8 @@ def __init__(self):
50395069
"AC_ssim_compare": _ssim_compare,
50405070
"AC_ssim_changed_regions": _ssim_changed_regions,
50415071
"AC_feature_match": _feature_match,
5072+
"AC_find_shapes": _find_shapes,
5073+
"AC_find_rectangles": _find_rectangles,
50425074
"AC_find_color_region": _find_color_region,
50435075
"AC_detect_drift": _detect_drift,
50445076
"AC_categorical_drift": _categorical_drift,

0 commit comments

Comments
 (0)