Rotation- and Scale-Tolerant Template Matching
match_template searches a template across scales (DPI / zoom tolerance) but
assumes it is axis-aligned — OpenCV’s matchTemplate is not rotation-invariant,
so a control rendered at a slight skew, a rotated icon, or a dial/knob at a different
angle is missed. match_rotated adds a rotation sweep: each angle is applied to
the template with cv2.warpAffine, crossed with a np.linspace scale-space, and
the best-correlating (scale, angle) is returned — so the caller also learns the
recovered pose.
It reuses visual_match’s grayscale loaders, scale resize, correlation-method
table and non-maximum suppression, so no matching or geometry code is duplicated.
The haystack is injectable (ndarray / path / PIL), so the search is unit-testable
on synthetic arrays; only the default (grab the screen) is device-bound. Imports no
PySide6.
Headless API
from je_auto_control import match_rotated, match_rotated_all, scale_space
# find a knob that may be turned to any of these angles, at any of these scales
hit = match_rotated("knob.png", angles=[-15, 0, 15, 30],
scales=scale_space(0.9, 1.1, 3), min_score=0.85)
if hit:
print(hit.angle, hit.scale, hit.score, hit.center)
# every rotated occurrence, overlaps merged by NMS
for m in match_rotated_all("arrow.png", angles=[0, 90, 180, 270]):
print(m.center, m.angle)
match_rotated returns a single RotatedMatch (x / y / width /
height / score / scale / angle + center) or None;
match_rotated_all returns every hit at or above min_score with overlapping
detections from neighbouring angles / scales collapsed by NMS, ordered by score.
scale_space(min, max, steps) is a helper returning evenly spaced scales.
Executor commands
AC_match_rotated (template / min_score / angles / scales /
region / method → {found, match}) and AC_match_rotated_all (adds
max_results / nms_iou → {count, matches}). They are exposed as the MCP
tools ac_match_rotated / ac_match_rotated_all (read-only) and as Script
Builder commands Match Template (rotated) / Match Template All (rotated)
under Image.