A near drop-in replacement for OpenCV's cv::findTransformECC (the ECC image
alignment of Evangelidis & Psarakis, PAMI 2008) that reduces the per-iteration
warps from three to one. Same iteration math, fewer resampling passes.
// before
cv::findTransformECC(templ, input, W, cv::MOTION_AFFINE, criteria);
// after — just change the namespace
fastecc::findTransformECC(templ, input, W, cv::MOTION_AFFINE, criteria);Not affiliated with or endorsed by OpenCV. Derived from
modules/video/src/ecc.cpp(BSD-3). See NOTICE and LICENSE.
Template in green, input in magenta — color fringes mean misalignment. After
fastecc::findTransformECC (affine, ρ = 0.9997) the overlay collapses to gray.
Reproduce with examples/align_pair.cpp.
OpenCV's ECC warps three images every iteration: the input image and its two
gradient images ∂I/∂x, ∂I/∂y (plus a cheap nearest-neighbour mask warp).
warpAffine/warpPerspective with bilinear interpolation is the dominant cost of the
inner loop.
This version warps only the image, then obtains the gradients it needs from the
warped image with a filter2D and a single addWeighted recombination — so the two
bilinear gradient warps per iteration disappear.
| image warp | gradient warps | mask warp | |
|---|---|---|---|
cv::findTransformECC |
1 | 2 | 1 |
fastecc::findTransformECC |
1 | 0 (filter2D + addWeighted) | 1 |
ECC maximizes the normalized correlation between the template and the warped input
I(W(x; p)). The Gauss–Newton step needs the steepest-descent images, i.e. the
image gradient evaluated at the warped location.
The key identity is the chain rule. For a warp with linear part A,
∂/∂x [ I(W(x; p)) ] = Aᵀ · (∇I)(W(x; p))
So the finite-difference gradient of the already-warped image is the warped-image
gradient pre-multiplied by Aᵀ. Re-combining it with the warp's linear part
(warp_gradients_*_ECC) recovers the quantity the Jacobian assembly expects, without
warping the gradient images separately. For translation/euclidean the linear part is
orthonormal so the recombination introduces no directional distortion; for
affine/homography it uses the warp's global linear part (a close approximation).
This method takes gradients of the warped image, whereas OpenCV warps gradients
of the original image. Bilinear resampling and finite differencing do not commute
exactly, so the result is not bit-identical to cv::findTransformECC, even for pure
translation. What matters in practice is the recovered transform: across motion types it
matches OpenCV's accuracy against ground truth — and in the bundled benchmark it is
consistently a little closer to ground truth, because the gradients are consistent
with the actually-resampled intensities. The shipped equivalence test asserts fastecc is
no less accurate than cv::findTransformECC (corner RMS vs ground truth) for all four
motion types.
Synthetic 512×512 image, best of 25 runs, Release build (MSVC, OpenCV 4.x).
Numbers are hardware-dependent — reproduce on your machine with ./build/bench.
| motion | cv (ms) | fast (ms) | speedup | errCv (px) | errFast (px) |
|---|---|---|---|---|---|
| TRANSLATION | 18.3 | 16.4 | 1.12× | 0.667 | 0.172 |
| EUCLIDEAN | 38.6 | 35.1 | 1.10× | 0.710 | 0.169 |
| AFFINE | 55.8 | 44.6 | 1.25× | 0.921 | 0.293 |
| HOMOGRAPHY | 115.9 | 104.7 | 1.11× | 1.066 | 0.380 |
errCv / errFast are the corner RMS (in pixels) of the recovered warp against the
known ground-truth warp — i.e. accuracy, lower is better. The speed-up comes from
dropping two bilinear warps per iteration; the exact factor depends on image size,
motion type and how warp-bound your build is.
git clone https://github.com/ysmz334/fast-ecc.git
cd fast-ecc
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure # equivalence vs cv::findTransformECC
./build/bench # timing table
./build/align_pair template.png input.png aligned.png affineRequires OpenCV (core, imgproc, and video for the MOTION_* enum) — any 4.x.
On Debian/Ubuntu: sudo apt-get install libopencv-dev.
The library is a single .cpp + header. Either add this repo via CMake
add_subdirectory(fast-ecc) and link fast_ecc, or just drop include/fast_ecc.hpp
and src/fast_ecc.cpp into your build.
#include <fast_ecc.hpp>
cv::Mat W = cv::Mat::eye(2, 3, CV_32F);
double rho = fastecc::findTransformECC(
templateGray, inputGray, W, cv::MOTION_AFFINE,
cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 50, 1e-4));The signature mirrors cv::findTransformECC exactly (same arguments, same defaults,
same MOTION_* types, same warpMatrix conventions).
- Inputs are single-channel
CV_8UC1orCV_32FC1, like OpenCV. - The speed-up is largest where warps dominate the inner loop (i.e. plain ECC). If you add heavy per-iteration work on top (robust losses, extra parameters), the relative gain shrinks.
- For affine/homography the gradient recombination is an approximation; if you need bit-exact agreement with OpenCV for those motions, prefer the stock function.
- Pairs well with a coarse-to-fine pyramid (align low-res first) to cut warped pixels.
@article{evangelidis2008ecc,
author = {Evangelidis, Georgios D. and Psarakis, Emmanouil Z.},
title = {Parametric Image Alignment Using Enhanced Correlation Coefficient Maximization},
journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence},
volume = {30}, number = {10}, pages = {1858--1865}, year = {2008}
}BSD-3-Clause. Derived from OpenCV ecc.cpp (Copyright © 2000 Intel Corporation);
modifications © 2026 ysmz334. See LICENSE and NOTICE.
