corrmatch 0.1.0

CPU-first template matching with ZNCC/SSD and coarse-to-fine pyramid search
Documentation

corrmatch

CI Python Tests Security Audit crates.io docs.rs license

CorrMatch is a CPU-first template matching library for grayscale images. It implements a coarse-to-fine pyramid search with optional rotation and two metrics: ZNCC and SSD. The focus is deterministic, reproducible matching with minimal dependencies.

Install

[dependencies]
corrmatch = "0.1"

Quickstart (library)

use corrmatch::{
    CompileConfig, MatchConfig, Matcher, RotationMode, Template, ImageView,
};

# fn run(image: &[u8], width: usize, height: usize, tpl: Vec<u8>, tw: usize, th: usize)
#     -> corrmatch::CorrMatchResult<corrmatch::Match> {
let template = Template::new(tpl, tw, th)?;
let compiled = template.compile(CompileConfig::default())?;
let matcher = Matcher::new(compiled).with_config(MatchConfig {
    rotation: RotationMode::Enabled,
    ..MatchConfig::default()
});
let image_view = ImageView::from_slice(image, width, height)?;
matcher.match_image(image_view)
# }

Match results (match_image vs match_image_topk)

  • Matcher::match_image(image) returns the single best Match.
  • Matcher::match_image_topk(image, k) returns up to k matches sorted best-first by score.

Match fields:

  • x, y: top-left placement coordinates in the original image (level 0). These are f32 because the final refinement can be subpixel.
  • angle_deg: rotation angle in degrees (clockwise, with image coordinates x→ right, y↓ down). When rotation is disabled this is 0.0.
  • score: ZNCC is roughly [-1, 1] (higher is better). SSD is reported as negative SSE (higher is better).

To retrieve multiple results:

# use corrmatch::{CompileConfig, MatchConfig, Matcher, RotationMode, Template, ImageView};
# fn run(image: &[u8], width: usize, height: usize, tpl: Vec<u8>, tw: usize, th: usize)
#     -> corrmatch::CorrMatchResult<Vec<corrmatch::Match>> {
let template = Template::new(tpl, tw, th)?;
let compiled = template.compile(CompileConfig::default())?;
let matcher = Matcher::new(compiled).with_config(MatchConfig {
    rotation: RotationMode::Enabled,
    ..MatchConfig::default()
});
let image_view = ImageView::from_slice(image, width, height)?;
let matches = matcher.match_image_topk(image_view, 5)?;
for (i, m) in matches.iter().enumerate() {
    println!("{i}: x={}, y={}, angle_deg={}, score={}", m.x, m.y, m.angle_deg, m.score);
}
Ok(matches)
# }

If you do not need rotation support, compile a lighter template:

# use corrmatch::{CompileConfigNoRot, CompiledTemplate, CorrMatchResult, Template};
# fn compile_no_rot(tpl: Vec<u8>, w: usize, h: usize) -> CorrMatchResult<CompiledTemplate> {
let template = Template::new(tpl, w, h)?;
CompiledTemplate::compile_unrotated(&template, CompileConfigNoRot::default())
# }

CLI (corrmatch-cli)

The workspace includes a JSON-driven CLI.

  • Build: cargo build -p corrmatch-cli
  • Run: cargo run -p corrmatch-cli -- --config config.json
  • Print schema: cargo run -p corrmatch-cli -- --print-schema
  • Print example: cargo run -p corrmatch-cli -- --print-example

The schema lives at corrmatch-cli/config.schema.json, and an example config is at corrmatch-cli/config.example.json.

Concepts

  • Template: owned template pixels (contiguous grayscale).
  • CompiledTemplate: precomputed template pyramid plus optional angle banks.
  • Matcher: runs coarse-to-fine search using MatchConfig.
  • Metric: Zncc or Ssd.
  • RotationMode: Disabled (fast path) or Enabled (masked rotation search).
  • Coordinates: results are top-left placement coordinates at level 0.

Configuration

  • CompileConfig controls template pyramid depth and rotation grid. When rotation is disabled, only max_levels is used.
  • MatchConfig controls the search strategy (beam width, ROI size, NMS radius, and angle neighborhood). For SSD, min_var_i is ignored.

Feature flags

  • rayon: parallel search execution.
  • simd: SIMD-accelerated kernels (currently: unmasked translation-only path).
  • image-io: file I/O helpers via the image crate.

Python bindings (corrmatch-py)

The workspace includes PyO3 bindings in corrmatch-py.

  • Build locally: cd corrmatch-py && maturin develop --release
  • Run tests: python -m pytest python/tests

Python API notes:

  • Matcher.match_image(image) returns a corrmatch.Match with fields x, y, angle_deg, score.
  • Matcher.match_topk(image, k) returns a Python list[corrmatch.Match] sorted best-first by score.
  • Angle convention matches Rust: positive angles are clockwise.

Example (match + visualize):

import numpy as np
from PIL import Image

import corrmatch
import corrmatch.viz as viz

image = np.asarray(Image.open("synthetic_cases/rotation_fine_22_5deg/image.png").convert("L"), dtype=np.uint8)
template = np.asarray(Image.open("synthetic_cases/rotation_fine_22_5deg/template.png").convert("L"), dtype=np.uint8)

compile_cfg = corrmatch.CompileConfig(max_levels=4, coarse_step_deg=30.0, min_step_deg=7.5)
match_cfg = corrmatch.MatchConfig(rotation="enabled", metric="zncc")

tpl = corrmatch.Template(template)
compiled = tpl.compile(compile_cfg)
matcher = compiled.matcher(match_cfg)

best = matcher.match_image(image)
top = matcher.match_topk(image, k=3)  # sorted best-first

viz.show_matches(image, template, top, compile_cfg=compile_cfg, match_cfg=match_cfg)
print("best:", best.x, best.y, best.angle_deg, best.score)

Low-level API

Advanced hooks live in corrmatch::lowlevel, including template plans, kernel traits, scan helpers, and rotation utilities. These are intended for custom pipelines and experimentation.

Image I/O (feature image-io)

# #[cfg(feature = "image-io")]
# {
use corrmatch::io::load_gray_image;
let template = load_gray_image("template.png")?;
# }

Benchmarks and tests

  • cargo test
  • cargo test --features rayon
  • cargo bench

Validation and performance tracking

  • Synthetic validation suite (ground truth cases): docs/VALIDATION.md
  • Criterion benchmarks and latest numbers: performance.md
  • Release checklist (crates.io + PyPI): docs/RELEASE_CHECKLIST.md

Synthetic case examples

This repo includes ground-truth synthetic cases in synthetic_cases/.

Note: the per-case cli_config.json uses relative paths like image.png, so run the CLI from inside the case directory (or use the Python helper below).

Quick visual impressions (templates + saved overlays in book/images/):

Case Template Detection overlay
blur_sigma_1_5
rotation_fine_22_5deg
distractors_topk

blur_sigma_1_5 (translation-only, blurred)

  • CLI:
    • (cd synthetic_cases/blur_sigma_1_5 && cargo run --manifest-path ../../Cargo.toml -p corrmatch-cli -- --config cli_config.json)
  • Visualize (requires corrmatch-py installed):
    • ./tools/viz_case.sh blur_sigma_1_5 1

rotation_fine_22_5deg (rotation enabled)

  • CLI:
    • (cd synthetic_cases/rotation_fine_22_5deg && cargo run --manifest-path ../../Cargo.toml -p corrmatch-cli -- --config cli_config.json)
  • Visualize:
    • ./tools/viz_case.sh rotation_fine_22_5deg 1

distractors_topk (multiple candidates)

  • CLI:
    • (cd synthetic_cases/distractors_topk && cargo run --manifest-path ../../Cargo.toml -p corrmatch-cli -- --config cli_config.json)
  • Visualize top-4 overlays:
    • ./tools/viz_case.sh distractors_topk 4

Status

Core matcher types, the JSON-driven CLI, and Python bindings are implemented. See ROADMAP.md for upcoming milestones.