Skip to content

Commit d3b97ea

Browse files
committed
Add HSV colour-space segmentation (lighting-robust colour location)
1 parent a28c95a commit d3b97ea

15 files changed

Lines changed: 392 additions & 1 deletion

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) — HSV 色彩空间分割
4+
5+
不论光照都能找出「任一色阶的红色」。完整参考:[`docs/source/Zh/doc/new_features/v139_features_doc.rst`](../docs/source/Zh/doc/new_features/v139_features_doc.rst)
6+
7+
- **`dominant_hue_regions` / `segment_hsv` / `color_mask`**(`AC_dominant_hue_regions``AC_segment_hsv`):`find_color_region` 在 RGB 以各通道 ± 框遮罩——无法匹配「同一颜色但不同亮度」(状态灯、强调色、主题色调)。HSV 把色相与亮度分离,因此「色相带 + 饱和度 / 明度下限」可在不同光照下捕捉所有色阶。`dominant_hue_regions(hue=…)` 自动处理红色 0/180 环绕;`segment_hsv` 接受明确带;两者皆返回 `{x,y,width,height,area,center}` 区块并沿用共用连通元件辅助函数。可注入 haystack → 无头可测。
8+
39
## 本次更新 (2026-06-23) — 融合并排序屏幕元素框
410

511
把原始的 OCR + 图标 + a11y 框转成一份干净、已编号的元素列表。完整参考:[`docs/source/Zh/doc/new_features/v138_features_doc.rst`](../docs/source/Zh/doc/new_features/v138_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) — HSV 色彩空間分割
4+
5+
不論光照都能找出「任一色階的紅色」。完整參考:[`docs/source/Zh/doc/new_features/v139_features_doc.rst`](../docs/source/Zh/doc/new_features/v139_features_doc.rst)
6+
7+
- **`dominant_hue_regions` / `segment_hsv` / `color_mask`**(`AC_dominant_hue_regions``AC_segment_hsv`):`find_color_region` 在 RGB 以各通道 ± 框遮罩——無法比對「同一顏色但不同亮度」(狀態燈、強調色、主題色調)。HSV 把色相與亮度分離,因此「色相帶 + 飽和度 / 明度下限」可在不同光照下捕捉所有色階。`dominant_hue_regions(hue=…)` 自動處理紅色 0/180 環繞;`segment_hsv` 接受明確帶;兩者皆回傳 `{x,y,width,height,area,center}` 區塊並沿用共用連通元件輔助函式。可注入 haystack → 無頭可測。
8+
39
## 本次更新 (2026-06-23) — 融合並排序螢幕元素框
410

511
把原始的 OCR + 圖示 + a11y 框轉成一份乾淨、已編號的元素清單。完整參考:[`docs/source/Zh/doc/new_features/v138_features_doc.rst`](../docs/source/Zh/doc/new_features/v138_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) — HSV Colour-Space Segmentation
4+
5+
Find "any shade of red" regardless of lighting. Full reference: [`docs/source/Eng/doc/new_features/v139_features_doc.rst`](docs/source/Eng/doc/new_features/v139_features_doc.rst).
6+
7+
- **`dominant_hue_regions` / `segment_hsv` / `color_mask`** (`AC_dominant_hue_regions`, `AC_segment_hsv`): `find_color_region` masks in RGB with a per-channel ± box — it can't match "the same colour at a different brightness" (status lights, highlights, theme tints). HSV separates hue from brightness, so a hue band + saturation/value floor catches every shade across lighting. `dominant_hue_regions(hue=…)` handles red's 0/180 wrap automatically; `segment_hsv` takes an explicit band; both return `{x,y,width,height,area,center}` blobs reusing the shared connected-components helper. Injectable haystack → headless-testable.
8+
39
## What's new (2026-06-23) — Fuse & Order On-Screen Element Boxes
410

511
Turn raw OCR + icon + a11y boxes into one clean, numbered element list. Full reference: [`docs/source/Eng/doc/new_features/v138_features_doc.rst`](docs/source/Eng/doc/new_features/v138_features_doc.rst).
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
HSV Colour-Space Segmentation
2+
=============================
3+
4+
``find_color_region`` masks in RGB with a per-channel ± tolerance box, which fails
5+
the canonical case: a status light, highlight or theme tint that is "the same colour"
6+
but at a different *brightness*. HSV separates hue from saturation/value, so a hue
7+
band with a saturation/value floor catches every shade of a colour across lighting.
8+
This adds HSV masking and blob boxes, reusing the shared connected-component helper,
9+
with correct hue-wrap handling for red (which straddles the 0/180 boundary).
10+
11+
Runs on an injectable ``haystack`` (ndarray / path / PIL, RGB), so it is headless-
12+
testable on synthetic arrays. OpenCV + NumPy come in via ``je_open_cv``. Imports no
13+
``PySide6``.
14+
15+
Headless API
16+
------------
17+
18+
.. code-block:: python
19+
20+
from je_auto_control import dominant_hue_regions, segment_hsv, color_mask
21+
22+
# Every red region — bright or dark — regardless of lighting (red wrap handled).
23+
for r in dominant_hue_regions(hue=0, hue_tol=10, sat_min=80, val_min=80):
24+
click(*r["center"])
25+
26+
# Or an explicit HSV band (H 0-179, S/V 0-255).
27+
greens = segment_hsv(lower_hsv=[40, 80, 80], upper_hsv=[80, 255, 255])
28+
mask = color_mask(lower_hsv=[40, 80, 80], upper_hsv=[80, 255, 255])
29+
30+
``dominant_hue_regions`` constrains only the hue (± ``hue_tol``) plus a ``sat_min`` /
31+
``val_min`` floor to skip greys, returning ``{x, y, width, height, area, center}`` per
32+
blob largest first — so it finds a colour at any brightness, unlike the RGB box.
33+
``segment_hsv`` takes an explicit ``lower_hsv`` / ``upper_hsv`` band; ``color_mask``
34+
returns the raw uint8 mask.
35+
36+
Executor commands
37+
-----------------
38+
39+
``AC_segment_hsv`` (``lower_hsv`` / ``upper_hsv`` / ``min_area`` / ``region``) and
40+
``AC_dominant_hue_regions`` (``hue`` / ``hue_tol`` / ``sat_min`` / ``val_min`` /
41+
``min_area`` / ``region``), both returning ``{count, regions, best}``. They are
42+
exposed as the MCP tools ``ac_segment_hsv`` / ``ac_dominant_hue_regions`` and as
43+
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
@@ -161,6 +161,7 @@ Comprehensive guides for all AutoControl features.
161161
doc/new_features/v136_features_doc
162162
doc/new_features/v137_features_doc
163163
doc/new_features/v138_features_doc
164+
doc/new_features/v139_features_doc
164165
doc/ocr_backends/ocr_backends_doc
165166
doc/observability/observability_doc
166167
doc/operations_layer/operations_layer_doc
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
HSV 色彩空間分割
2+
================
3+
4+
``find_color_region`` 在 RGB 以各通道 ± 容差框遮罩,這在經典情境會失效:狀態燈、強調色或主題色調是「同一個顏色」
5+
但*亮度*不同。HSV 把色相與飽和度 / 明度分離,因此「色相帶 + 飽和度 / 明度下限」可在不同光照下捕捉某顏色的所有色階。
6+
本功能加入 HSV 遮罩與區塊框,沿用共用的連通元件輔助函式,並正確處理紅色的色相環繞(跨越 0/180 邊界)。
7+
8+
在可注入的 ``haystack``(ndarray / 路徑 / PIL,RGB)上執行,因此可對合成陣列做無頭測試。OpenCV + NumPy 透過
9+
``je_open_cv`` 引入。不匯入 ``PySide6``。
10+
11+
無頭 API
12+
--------
13+
14+
.. code-block:: python
15+
16+
from je_auto_control import dominant_hue_regions, segment_hsv, color_mask
17+
18+
# 每個紅色區域——不論明暗、不論光照(已處理紅色環繞)。
19+
for r in dominant_hue_regions(hue=0, hue_tol=10, sat_min=80, val_min=80):
20+
click(*r["center"])
21+
22+
# 或明確的 HSV 帶(H 0-179、S/V 0-255)。
23+
greens = segment_hsv(lower_hsv=[40, 80, 80], upper_hsv=[80, 255, 255])
24+
mask = color_mask(lower_hsv=[40, 80, 80], upper_hsv=[80, 255, 255])
25+
26+
``dominant_hue_regions`` 只限制色相(± ``hue_tol``)再加 ``sat_min`` / ``val_min`` 下限以略過灰階,為每個區塊回傳
27+
``{x, y, width, height, area, center}``,由大到小——因此能在任何亮度下找到某顏色,不像 RGB 框。``segment_hsv`` 接受
28+
明確的 ``lower_hsv`` / ``upper_hsv`` 帶;``color_mask`` 回傳原始 uint8 遮罩。
29+
30+
執行器命令
31+
----------
32+
33+
``AC_segment_hsv``(``lower_hsv`` / ``upper_hsv`` / ``min_area`` / ``region``)與
34+
``AC_dominant_hue_regions``(``hue`` / ``hue_tol`` / ``sat_min`` / ``val_min`` / ``min_area`` / ``region``),
35+
皆回傳 ``{count, regions, best}``。它們以 MCP 工具 ``ac_segment_hsv`` / ``ac_dominant_hue_regions`` 以及 Script
36+
Builder 中 **Image** 分類下的命令提供。

docs/source/Zh/zh_index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ AutoControl 所有功能的完整使用指南。
161161
doc/new_features/v136_features_doc
162162
doc/new_features/v137_features_doc
163163
doc/new_features/v138_features_doc
164+
doc/new_features/v139_features_doc
164165
doc/ocr_backends/ocr_backends_doc
165166
doc/observability/observability_doc
166167
doc/operations_layer/operations_layer_doc

je_auto_control/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@
316316
from je_auto_control.utils.element_parse import (
317317
fuse_elements, iou, merge_boxes, reading_order,
318318
)
319+
# HSV colour-space segmentation (lighting-robust colour masking + blob boxes)
320+
from je_auto_control.utils.hsv_segment import (
321+
color_mask, dominant_hue_regions, segment_hsv,
322+
)
319323
# CI workflow annotations (GitHub Actions)
320324
from je_auto_control.utils.ci_annotations import (
321325
emit_annotations, format_annotation,
@@ -1149,6 +1153,9 @@ def start_autocontrol_gui(*args, **kwargs):
11491153
"merge_boxes",
11501154
"fuse_elements",
11511155
"reading_order",
1156+
"segment_hsv",
1157+
"color_mask",
1158+
"dominant_hue_regions",
11521159
"emit_annotations", "format_annotation",
11531160
"ClipboardHistory", "default_clipboard_history",
11541161
"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
@@ -391,6 +391,30 @@ def _add_image_specs(specs: List[CommandSpec]) -> None:
391391
),
392392
description="Clean up an image for OCR / matching (grayscale/binarize/…).",
393393
))
394+
specs.append(CommandSpec(
395+
"AC_segment_hsv", "Image", "Segment by HSV",
396+
fields=(
397+
FieldSpec("lower_hsv", FieldType.STRING, placeholder="[40, 80, 80]"),
398+
FieldSpec("upper_hsv", FieldType.STRING, placeholder="[80, 255, 255]"),
399+
FieldSpec("min_area", FieldType.INT, optional=True, default=50),
400+
FieldSpec("region", FieldType.STRING, optional=True,
401+
placeholder="[left, top, right, bottom]"),
402+
),
403+
description="Locate regions inside an HSV band (lighting-robust).",
404+
))
405+
specs.append(CommandSpec(
406+
"AC_dominant_hue_regions", "Image", "Find Hue Regions",
407+
fields=(
408+
FieldSpec("hue", FieldType.INT, placeholder="0=red, 60=green, 120=blue"),
409+
FieldSpec("hue_tol", FieldType.INT, optional=True, default=10),
410+
FieldSpec("sat_min", FieldType.INT, optional=True, default=80),
411+
FieldSpec("val_min", FieldType.INT, optional=True, default=80),
412+
FieldSpec("min_area", FieldType.INT, optional=True, default=50),
413+
FieldSpec("region", FieldType.STRING, optional=True,
414+
placeholder="[left, top, right, bottom]"),
415+
),
416+
description="Locate any shade of a hue, any brightness (handles red wrap).",
417+
))
394418
specs.append(CommandSpec(
395419
"AC_fuse_elements", "Image", "Fuse Element Boxes",
396420
fields=(

je_auto_control/utils/executor/action_executor.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3505,6 +3505,38 @@ def _reading_order(elements: Any, row_tol: Any = 12) -> Dict[str, Any]:
35053505
return {"count": len(ordered), "elements": ordered}
35063506

35073507

3508+
def _segment_hsv(lower_hsv: Any, upper_hsv: Any, min_area: Any = 50,
3509+
region: Any = None) -> Dict[str, Any]:
3510+
"""Adapter: locate blobs inside an explicit HSV band on the screen."""
3511+
import json
3512+
from je_auto_control.utils.hsv_segment import segment_hsv
3513+
if isinstance(lower_hsv, str):
3514+
lower_hsv = json.loads(lower_hsv)
3515+
if isinstance(upper_hsv, str):
3516+
upper_hsv = json.loads(upper_hsv)
3517+
if isinstance(region, str):
3518+
region = json.loads(region) if region.strip() else None
3519+
boxes = segment_hsv(region=region, lower_hsv=list(lower_hsv),
3520+
upper_hsv=list(upper_hsv), min_area=int(min_area))
3521+
return {"count": len(boxes), "regions": boxes,
3522+
"best": boxes[0] if boxes else None}
3523+
3524+
3525+
def _dominant_hue_regions(hue: Any, hue_tol: Any = 10, sat_min: Any = 80,
3526+
val_min: Any = 80, min_area: Any = 50,
3527+
region: Any = None) -> Dict[str, Any]:
3528+
"""Adapter: locate any-brightness regions near a hue on the screen."""
3529+
import json
3530+
from je_auto_control.utils.hsv_segment import dominant_hue_regions
3531+
if isinstance(region, str):
3532+
region = json.loads(region) if region.strip() else None
3533+
boxes = dominant_hue_regions(region=region, hue=int(hue), hue_tol=int(hue_tol),
3534+
sat_min=int(sat_min), val_min=int(val_min),
3535+
min_area=int(min_area))
3536+
return {"count": len(boxes), "regions": boxes,
3537+
"best": boxes[0] if boxes else None}
3538+
3539+
35083540
def _with_modifiers(modifiers: Any, actions: Any) -> Dict[str, Any]:
35093541
"""Adapter: run nested actions while modifier keys are held down."""
35103542
import json
@@ -5237,6 +5269,8 @@ def __init__(self):
52375269
"AC_wait_actionable": _wait_actionable,
52385270
"AC_fuse_elements": _fuse_elements,
52395271
"AC_reading_order": _reading_order,
5272+
"AC_segment_hsv": _segment_hsv,
5273+
"AC_dominant_hue_regions": _dominant_hue_regions,
52405274
"AC_tile_rect": _tile_rect,
52415275
"AC_grid_rects": _grid_rects,
52425276
"AC_cascade_rects": _cascade_rects,

0 commit comments

Comments
 (0)