refactor(halcon): single-skill plugin layout → invoke as /halcon (not /halcon:halcon)

Move SKILL.md + references/ scripts/ evals/ from skills/halcon/ up to the
plugin root. Per Claude Code plugins-reference, a plugin with SKILL.md at its
root and no skills/ subdir is auto-loaded as a single-skill plugin (v2.1.142+),
so the invocation name = frontmatter name = halcon → clean /halcon.
Bump plugin.json 2.0.0 → 2.0.1 so existing installs receive the update.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 23:14:39 +08:00
parent 36eadffa87
commit 3fe919e37c
44 changed files with 1 additions and 1 deletions
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<hdevelop file_version="1.2" halcon_version="24.11.1.0">
<procedure name="main">
<interface/>
<body>
<l>Values := [1, 2, 3, 4, 10]</l>
<l>Total := sum(Values)</l>
<l>gen_image_const (Img, 'byte', 20, 10)</l>
<l>threshold (Img, Region, 0, 128)</l>
<l>area_center (Region, Area, RowC, ColC)</l>
<l>Message := 'driven by HDevEngine'</l>
</body>
<docu id="main">
<parameters/>
</docu>
</procedure>
</hdevelop>
@@ -0,0 +1,10 @@
"""Remote-control a .hdev program via HDevEngine: run it, read variables back."""
from halcon.hdevengine import HDevProgram, HDevProgramCall
program = HDevProgram("demo_prog.hdev")
call = HDevProgramCall(program)
call.execute() # runs the exact same code HDevelop would run
# pull any control variable back out by name
for name in ("Total", "Area", "RowC", "ColC", "Message"):
print(f"{name} =", call.get_control_var_by_name(name))
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<hdevelop file_version="1.2" halcon_version="24.11.1.0">
<procedure name="find_circles">
<interface>
<ic>
<par name="ImageFile" base_type="ctrl" dimension="0"/>
<par name="RoiFile" base_type="ctrl" dimension="0"/>
</ic>
<oc>
<par name="FitRow" base_type="ctrl" dimension="0"/>
<par name="FitCol" base_type="ctrl" dimension="0"/>
<par name="MetroRow" base_type="ctrl" dimension="0"/>
<par name="MetroCol" base_type="ctrl" dimension="0"/>
</oc>
</interface>
<body>
<l>read_image (Image1, ImageFile)</l>
<l>read_region (Rectangle, RoiFile)</l>
<l>get_image_size (Image1, Width, Height)</l>
<l>reduce_domain (Image1, Rectangle, ImageReduced)</l>
<l>edges_sub_pix (ImageReduced, Edges, 'canny', 3, 10, 40)</l>
<l>select_contours_xld (Edges, SelectedContours1, 'contour_length', 50, 500, -0.5, 0.5)</l>
<l>union_adjacent_contours_xld (SelectedContours1, UnionContours, 10, 1, 'attr_keep')</l>
<l>select_contours_xld (UnionContours, SelectedContours, 'contour_length', 300, 400, -0.5, 0.5)</l>
<l>sort_contours_xld (SelectedContours, SortedContours, 'character', 'true', 'column')</l>
<l>fit_circle_contour_xld (SortedContours, 'algebraic', -1, 0, 0, 3, 2, FitRow, FitCol, Radius, StartPhi, EndPhi, PointOrder)</l>
<l>if (|FitRow| != 16)</l>
<l> throw ('fit circle count != 16, got ' + |FitRow|)</l>
<l>endif</l>
<l>CircleRadiusTolerance := 15</l>
<l>create_metrology_model (MetrologyHandle)</l>
<l>set_metrology_model_image_size (MetrologyHandle, Width, Height)</l>
<l>add_metrology_object_circle_measure (MetrologyHandle, FitRow, FitCol, Radius, CircleRadiusTolerance, 5, 1, 30, [], [], MetrologyCircleIndices)</l>
<l>set_metrology_object_param (MetrologyHandle, MetrologyCircleIndices, 'num_instances', 1)</l>
<l>set_metrology_object_param (MetrologyHandle, MetrologyCircleIndices, 'measure_transition', 'positive')</l>
<l>set_metrology_object_param (MetrologyHandle, MetrologyCircleIndices, 'min_score', 0.3)</l>
<l>apply_metrology_model (Image1, MetrologyHandle)</l>
<l>get_metrology_object_result (MetrologyHandle, MetrologyCircleIndices, 'all', 'result_type', 'all_param', CircleParameter)</l>
<l>Sequence := [0:3:|CircleParameter| - 1]</l>
<l>MetroRow := CircleParameter[Sequence]</l>
<l>MetroCol := CircleParameter[Sequence + 1]</l>
<l>if (|MetroRow| != 16)</l>
<l> throw ('metrology circle count != 16, got ' + |MetroRow|)</l>
<l>endif</l>
<l>clear_metrology_model (MetrologyHandle)</l>
</body>
<docu id="find_circles">
<parameters>
<parameter id="ImageFile"/>
<parameter id="RoiFile"/>
<parameter id="FitRow"/>
<parameter id="FitCol"/>
<parameter id="MetroRow"/>
<parameter id="MetroCol"/>
</parameters>
</docu>
</procedure>
</hdevelop>
@@ -0,0 +1,79 @@
"""HDevEngine 全自动驱动:读 roi.hobj,循环所有 png 跑 find_circles,
对比 拟合法 vs 计量模型法 两种找圆结果,输出偏差统计,并写 CSV。"""
import math
import csv
from halcon.hdevengine import HDevEngine, HDevProcedure, HDevProcedureCall, HDevEngineError
import halcon as ha
PROJ = r"C:\workspace\agent-studio\halcon-001"
IMG_DIR = r"C:\工作文档\2025.10.03_精度实验数据分析\两种找圆算法对比\初始状态-往复-右侧上视左标定片阵列Mark定位"
ROI_FILE = PROJ + r"\roi.hobj"
CSV_OUT = PROJ + r"\compare_result.csv"
def as_list(v):
return list(v) if isinstance(v, (list, tuple)) else [v]
# 引擎 + procedure
eng = HDevEngine()
eng.set_procedure_path(PROJ)
proc = HDevProcedure.load_external("find_circles")
call = HDevProcedureCall(proc)
# 图片列表
files = ha.list_files(IMG_DIR, "files")
pngs = sorted(ha.tuple_regexp_select(files, r"\.png$"))
print(f"待处理图片: {len(pngs)}")
all_dists = [] # 所有 图×圆 的中心欧氏距离
per_image_mean = [] # 每张图的平均偏差
skipped = 0
rows_csv = [("image", "circle_idx", "fit_row", "fit_col", "metro_row", "metro_col", "dist_px")]
for path in pngs:
call.reset()
call.set_input_control_param_by_name("ImageFile", path)
call.set_input_control_param_by_name("RoiFile", ROI_FILE)
try:
call.execute()
except HDevEngineError as e:
skipped += 1
if skipped <= 3:
print(f"[skip] {path.rsplit(chr(92),1)[-1]}: {e}")
continue
fr = as_list(call.get_output_control_param_by_name("FitRow"))
fc = as_list(call.get_output_control_param_by_name("FitCol"))
mr = as_list(call.get_output_control_param_by_name("MetroRow"))
mc = as_list(call.get_output_control_param_by_name("MetroCol"))
name = path.rsplit("\\", 1)[-1]
dists = []
for i in range(16):
d = math.hypot(fr[i] - mr[i], fc[i] - mc[i])
dists.append(d)
all_dists.append(d)
rows_csv.append((name, i, f"{fr[i]:.4f}", f"{fc[i]:.4f}",
f"{mr[i]:.4f}", f"{mc[i]:.4f}", f"{d:.4f}"))
per_image_mean.append(sum(dists) / len(dists))
# 统计
def stats(xs):
n = len(xs)
m = sum(xs) / n
sd = math.sqrt(sum((x - m) ** 2 for x in xs) / n)
return n, m, sd, min(xs), max(xs)
with open(CSV_OUT, "w", newline="", encoding="utf-8-sig") as f:
csv.writer(f).writerows(rows_csv)
print("=" * 60)
print(f"成功处理: {len(per_image_mean)} 张 跳过(圆数!=16): {skipped}")
if all_dists:
n, m, sd, lo, hi = stats(all_dists)
print(f"两算法圆心偏差 (像素),样本 {n} 个圆:")
print(f" 均值 = {m:.4f}")
print(f" 标准差 = {sd:.4f}")
print(f" 最小 = {lo:.4f}")
print(f" 最大 = {hi:.4f}")
_, mm, _, _, _ = stats(per_image_mean)
print(f" 每图平均偏差的均值 = {mm:.4f}")
print(f"明细已写: {CSV_OUT}")
@@ -0,0 +1,30 @@
"""ROI 示教工具:显示一张代表图,人工画一次矩形,存成 roi.hobj。运行一次即可。"""
import halcon as ha
IMG_DIR = r"C:\工作文档\2025.10.03_精度实验数据分析\两种找圆算法对比\初始状态-往复-右侧上视左标定片阵列Mark定位"
ROI_FILE = r"C:\workspace\agent-studio\halcon-001\roi.hobj"
# 取目录里第一张 png 作为代表图
files = ha.list_files(IMG_DIR, "files")
pngs = sorted(ha.tuple_regexp_select(files, r"\.png$"))
if not pngs:
raise SystemExit("目录里没有 png")
img = ha.read_image(pngs[0])
width, height = ha.get_image_size(img)
width, height = width[0], height[0]
# 开窗口,映射到整幅图坐标(这样画出来的框就是图像坐标)
win = ha.open_window(0, 0, 1024, 768, 0, "visible", "")
ha.set_part(win, 0, 0, height - 1, width - 1)
ha.disp_obj(img, win)
ha.set_color(win, "green")
print("请在弹出的窗口里用鼠标左键拖出 ROI 矩形,松开即可 ...", flush=True)
row1, col1, row2, col2 = ha.draw_rectangle1(win) # 阻塞,等人工画
rect = ha.gen_rectangle1(row1, col1, row2, col2)
ha.write_region(rect, ROI_FILE)
print(f"ROI 已保存: {ROI_FILE}")
print(f"矩形坐标 Row1={row1:.1f} Col1={col1:.1f} Row2={row2:.1f} Col2={col2:.1f}")
ha.close_window(win)