Feature Matching
A feature-based (keypoint) template-matching model for the ECOS AI framework. It locates a stored template inside an image using OpenCV SIFT, ORB, or AKAZE keypoints and estimates the object's pose with a RANSAC-fitted partial affine transform (rotation + uniform scale + translation).
Overview
The feature_matching module extends the BaseModel class. Unlike match_tool (pyramid NCC with an explicit rotation/scale search), rotation and scale here fall out of the affine fit automatically — no angle_range/scale_range sweep is needed. That makes it a better fit when the object's rotation/scale isn't known ahead of time, at the cost of needing enough distinctive keypoints on the template to match against.
- Detectors/matchers: SIFT, ORB, or AKAZE (default).
- Pose estimation via a RANSAC partial-affine fit; the reported confidence is the RANSAC inlier ratio.
- Angle is exponentially smoothed across successive calls on the same
Modelinstance. - Single template, single ROI per instance (no pattern-keyed multi-template config).
Installation
Please install ecos_core.
Requirements
Beyond ecos_core's base install (OpenCV, NumPy), this module also needs torch and albumentations.
Usage
Configuration
Configuration is managed through a JSON file (opt.json):
{
"template_path": "./templates/template.png",
"roi": {"x": 90, "y": 352, "w": 966, "h": 1696},
"method": "AKAZE",
"max_distance": 0.7,
"best_matched_points": 10,
"fast_threshold": 5,
"edge_threshold": 10,
"max_matches": 1
}
With:
template_path: path to the template image to search for.roi/roi_path: search window —{"x","y","w","h"}or[x1,y1,x2,y2]. An inlineroitakes precedence overroi_path(a JSON file in either of the same formats). Omit both for full-frame search.method: keypoint detector/matcher to use —"SIFT","ORB", or"AKAZE"(default).max_distance: Lowe's ratio-test threshold for the SIFT/AKAZE nearest-vs-second-nearest descriptor distance (lower = stricter).best_matched_points: for AKAZE, the minimum number of good matches required to attempt a pose fit — below this, the image is reported as NG (no exception raised); for ORB, the number of best (lowest-distance) matches to keep.fast_threshold,edge_threshold: ORB detector parameters (unused for SIFT/AKAZE).max_matches: number of independent detection passes to run per image (each pass re-runs keypoint matching + pose estimation; use1unless you specifically need multiple simultaneous instances).class_names:[OK_label, NG_label]— NG is reported when no match clearsbest_matched_points(AKAZE) or the affine pose fit fails. Defaults to["OK", "NG"].
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.feature_matching.feature_matching 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 of the best match — empty when no match clears the configured thresholds). score/confidence is the RANSAC inlier ratio (inliers / good_matches); angle is the fitted rotation in degrees, exponentially smoothed across successive process_predictions calls on the same Model instance. An image with no valid match is reported as NG rather than raising.
Call model.reset_state() between unrelated images (e.g. in a benchmark loop processing images from different scenes/objects with one Model instance) to clear the angle EMA — otherwise each image's reported angle is smoothed toward whatever the previous image's angle was.
Template / ROI selection helpers
crop_and_save_template(image_path, save_path): interactively select a region (OpenCV GUI, or a CLI x/y/w/h prompt if no GUI is available) and save it as the template image.select_and_save_roi(image_path, roi_path=None): interactively select the search ROI and save it as{"x","y","w","h"}JSON.load_template(template_path)/load_roi(roi_path): load either from an existing file without the interactive picker.
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
crop_and_save_template(image_path, save_path)
Select a template region and save it.
If OpenCV GUI is available, opens an interactive ROI selector: - Click and drag to draw a selection box. - Press ENTER or SPACE to confirm. - Press C to redraw. Press ESC to abort.
If GUI is not available (e.g. headless OpenCV), falls back to a CLI prompt asking for x, y, w, h coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image_path |
str
|
Path to the source image. |
required |
save_path |
str
|
File path to save the cropped template image. |
required |
Returns: bool: True if the template was saved, False if the user aborted.
get_selected_pattern()
Get the selected pattern from the options Returns: The selected pattern
get_transform()
Get the data transformation function Returns: The data transformation function
load_roi(roi_path)
Load the ROI from a *_roi.json file. Args: roi_path (str): Path to the ROI json file.
load_template(template_path)
Load the template image from the specified path. Args: template_path (str): Path to the template image file.
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.
reset_state()
Clear the angle EMA state so the next prediction is not biased by
a previous, unrelated image. Call this between independent test
cases (e.g. in a benchmark harness) since _smooth_angle otherwise
carries state across calls.
select_and_save_roi(image_path, roi_path=None)
Interactively select a ROI from the image and save it to a dedicated *_roi.json file.
If OpenCV GUI is available, opens an interactive ROI selector. Falls back to CLI input if GUI is unavailable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image_path |
str
|
Path to the source image to select ROI from. |
required |
roi_path |
str
|
Path to save the ROI json file. Defaults to
self._roi_path if set, otherwise derives from image_path
as |
None
|
Returns: bool: True if the ROI was saved, False if the user aborted.
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".