Skip to content

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 Model instance.
  • 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 inline roi takes precedence over roi_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; use 1 unless you specifically need multiple simultaneous instances).
  • class_names: [OK_label, NG_label] — NG is reported when no match clears best_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 _roi.json beside the image.

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".