Match Tool
A pyramid NCC (normalized cross-correlation) template-matching wrapper for the ECOS AI framework, ported from a C++ MatchToolDlg-style industrial vision tool. It locates a stored template inside an image with rotation search and optional sub-pixel refinement, and reports an OK/NG result plus the matched pose.
Overview
The match_tool module extends the BaseModel class. Compared to a plain cv2.matchTemplate, it adds:
- Rotation search — the template is matched across a symmetric range of angles (
angle_range), so a rotated part is still found. - Pyramid (coarse-to-fine) matching — matches are first found on down-scaled pyramid levels, then refined on finer levels, keeping the rotation search affordable.
- Optional scale search — a range of template scales can be searched (
scale_range/scale_steps) for when the physical object is smaller/larger than the stored template, with a cheap coarse-to-fine pre-filter so the extra search stays affordable. - Optional sub-pixel refinement of the final position and angle.
- Pattern-keyed configuration — multiple templates and/or multiple ROI positions per named
pattern, for panels with several identical parts to inspect in one image.
For a keypoint-based alternative that infers rotation and scale automatically (no explicit sweep), see feature_matching.
Installation
Please install ecos_core.
Requirements
No extra dependencies beyond ecos_core's base install (OpenCV, NumPy) — see requirements.txt.
Usage
Configuration
Configuration is managed through a JSON file (opt.json). Two modes are supported.
Simple mode — one template, one ROI:
{
"template_path": "./templates/template.png",
"roi": {"x": 90, "y": 352, "w": 966, "h": 1696},
"max_pos": 1,
"max_overlap": 0.2,
"threshold": 0.45,
"angle_range": 15.0,
"min_area": 256,
"subpixel": false,
"scale_range": [0.9, 1.1],
"scale_steps": 5,
"scale_prefilter_top_k": 2,
"class_names": ["OK", "NG"],
"visualization": true
}
Pattern-keyed mode — multiple templates and/or ROI positions selected by pattern (falls back to a "default" key, or is skipped entirely, when pattern is not set):
{
"pattern": "1",
"template_folder": "./template_folder",
"template_name": {"1": ["part_a.png", "part_b.png"]},
"roi": {"1": [[794, 590, 1159, 1604], [487, 595, 883, 1594]]},
"threshold": 0.45,
"angle_range": 15.0
}
Every (template, ROI) combination configured for the active pattern is searched; the highest-scoring match across all of them wins. pattern can be changed after construction (e.g. model.opt.pattern = "2", or via update_opt) — the template(s)/ROI(s) for the new pattern are re-resolved automatically the next time predict() runs.
With:
template_path/template_name+template_folder+pattern: which template(s) to load (see the two modes above).template_pathaccepts either a single path or a list of paths (all searched, same as multipletemplate_nameentries).roi/roi_path: search window(s) —{"x","y","w","h"},[x1,y1,x2,y2], or a list of either for multiple positions. Omit for full-frame search.threshold: minimum NCC match score to accept (alias:score).angle_range: rotation search tolerance in degrees, symmetric around 0 (alias:tolerance_angle).min_area: stop building pyramid levels once the template's area would drop below this (alias:min_reduce_area).subpixel: enable sub-pixel position/angle refinement at the finest pyramid level (alias:use_subpixel).max_pos,max_overlap: max instances to keep per (template, ROI) pass, and the NMS overlap threshold between them.scale_range,scale_steps: template scale factors to search (e.g.[0.9, 1.1]over 5 steps) when the physical object's size can differ from the stored template. Defaults to[1.0, 1.0]/ 1 step (no scale search).scale_prefilter_top_k: when scale-searching, only the top-K cheaply-ranked scale candidates get the full (expensive) match pass. Higher = safer but slower; 1 disables the safety margin.class_names:[OK_label, NG_label]— NG is reported when no match clearsthreshold.visualization: whether to draw the best match's outline on the saved output image.
Inference
import sys
import os
import json
from glob import glob
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), './../../')))
from ecos_core.match_tool.match_tool import Model
from easydict import EasyDict
if __name__ == "__main__":
with open("./opt.json", "r") as f:
opt = EasyDict(json.load(f))
model = Model(opt)
model.build_model()
for image_path in glob("./input/*"):
model.process_predictions(
net_output=None,
raw_image_path=image_path,
raw_image_mat=None,
image_transform=None,
save_image_path=os.path.join("./output", os.path.basename(image_path)),
)
Each call writes the annotated output image plus a {save_image_path}.json sidecar with predict_index, class_name, bbox, confidence, and extra_info (center_x, center_y, angle, scale of the best match).
APIs
Model
Bases: BaseModel
__call__(x)
Inference function Args: x: input data Returns: Inference results
__init__(opt)
Initialize the model with options Args: opt (Namespace/Dict): options for the model
build_model()
Build the model Returns: The built model
get_transform()
Get the data transformation function Returns: The data transformation function
predict(image)
Run matching against every configured (template, ROI) combination. Args: image (str | np.ndarray): Image file path, or an already-loaded image as a NumPy array (grayscale or BGR). Returns: list[dict]: All matches found, sorted by score descending. Each entry has keys x, y, w, h, score, angle, scale, center, corners (bbox and center/corners are in full-image pixel coordinates).
process_predictions(net_output, raw_image_path, raw_image_mat, image_transform, save_image_path)
Process the predictions and save the results. Args: net_output: the output from the model inference (unused) raw_image_path: path to the raw input image raw_image_mat: the raw input image in matrix form (unused, re-read from disk) image_transform: the transformed input image (unused) save_image_path: path to save the processed image Returns: str: save_image_path, once the annotated image and its JSON sidecar ({save_image_path}.json) have been written.
update_opt(option, output_path='./opt.json')
Update config Args: option (Namespace/Dict): option to update output_path (str, optional): path to save opt json. Defaults to "./opt.json".