1. 도입: 두 사진을 완벽하게 겹치고 싶을 때
우리가 사진을 찍다 보면 미세하게 손이 떨리거나, 각도가 틀어질 때가 있습니다. 만약 이 두 사진을 합쳐서 노이즈를 줄이거나 변화를 관찰해야 한다면 어떻게 해야 할까요? 단순히 위치만 옮기는 것으로는 부족합니다. 미세한 회전과 비틀림까지 잡아내야 하죠. 이때 필요한 것이 바로 OpenCV의 findTransformECC입니다.
2. 비유로 이해하기: "투명 종이 겹치기 장인"
findTransformECC를 한마디로 정의하면 "투명 종이 겹치기 장인"입니다.
- 상황: 아래에는 고정된 사진(Template)이 있고, 위에는 투명한 종이에 인쇄된 사진(Input)이 있습니다.
- 미션: 위에 있는 투명 종이를 이리저리 움직이고, 돌리고, 살짝 늘려서 아래 사진과 '완벽하게' 겹치게 만드는 것입니다.
- 방법: 장인은 눈을 가늘게 뜨고 두 사진의 밝기 패턴(상관계수)을 확인합니다. 그리고 0.1mm씩 아주 미세하게 종이를 옮겨가며 두 사진이 가장 일치하는 지점을 찾아냅니다.

3. 핵심 동작 원리 (간략히)
이 함수는 ECC(Enhanced Correlation Coefficient)라는 지표를 사용합니다. 단순히 픽셀 값이 같은지를 보는 게 아니라, 두 이미지 사이의 상관관계를 분석합니다.
- 장점: 조명이 갑자기 변하거나 전체적인 밝기가 달라도 매우 정확하게 정렬을 수행합니다.
- 특징: 하위 픽셀(Sub-pixel) 단위까지 계산하므로, 육안으로 구별하기 힘든 미세한 차이까지 잡아낼 수 있습니다.
4. 어떤 변환까지 가능할까? (Motion Models)
장인에게 어떤 도구를 줄지 결정하는 것과 같습니다.
- Translation: 상하좌우로만 밀기 (가장 간단)
- Euclidean: 밀기 + 회전하기
- Affine: 밀기 + 회전 + 크기 조절 + 비틀기
- Homography: 멀고 가까운 원근감까지 조절하기 (가장 강력)
5. 함수 인터페이스
double cv::findTransformECC(
InputArray templateImage, // 기준 이미지 (8-bit 또는 32-bit 부동소수점)
InputArray inputImage, // 정렬할 이미지
InputOutputArray warpMatrix, // [중요] 초기 변환 행렬 및 결과가 저장될 곳
int motionType, // 변환 모델 (MOTION_AFFINE 등)
TermCriteria criteria, // 종료 조건 (반복 횟수 및 정밀도)
InputArray inputMask = noArray(), // 마스크 (선택)
int gaussFiltSize = 5 // 가우시안 필터 크기
);
6. 실제 예제
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 1. 이미지 로드 (그레이스케일 필수)
cv::Mat refImg = cv::imread("reference.bmp", cv::IMREAD_GRAYSCALE);
cv::Mat targetImg = cv::imread("target.bmp", cv::IMREAD_GRAYSCALE);
if (refImg.empty() || targetImg.empty()) return -1;
// 2. 변환 행렬 초기화 (Affine: 2x3 행렬)
// MOTION_HOMOGRAPHY를 쓴다면 3x3 행렬이어야 합니다.
int motionType = cv::MOTION_AFFINE;
cv::Mat warpMatrix = cv::Mat::eye(2, 3, CV_32F);
// 3. 종료 조건 설정
// 최대 50회 반복하거나, 변화량이 0.001 이하일 때 멈춤
cv::TermCriteria criteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 50, 0.001);
try {
// 4. ECC 실행
// 결과값으로 두 이미지 간의 상관계수(Correlation Coefficient)를 반환합니다.
double cc = cv::findTransformECC(refImg, targetImg, warpMatrix, motionType, criteria);
std::cout << "Matching Score (CC): " << cc << std::endl;
// 5. 결과 적용 (Warping)
cv::Mat alignedResult;
cv::warpAffine(targetImg, alignedResult, warpMatrix, refImg.size(),
cv::INTER_LINEAR + cv::WARP_INVERSE_MAP);
cv::imshow("Aligned Result", alignedResult);
cv::waitKey(0);
}
catch (const cv::Exception& e) {
std::cerr << "ECC failed to converge: " << e.what() << std::endl;
}
return 0;
}
7. C++ 사용자를 위한 인수의 핵심 포인트
- warpMatrix의 자료형: 반드시 CV_32F (float) 타입을 사용해야 합니다. CV_64F나 다른 타입을 넣으면 런타임 에러가 발생할 수 있습니다.
- WARP_INVERSE_MAP 플래그: findTransformECC가 찾아주는 행렬은 Target → Template 방향의 역변환 행렬입니다. 따라서 warpAffine이나 warpPerspective 함수를 쓸 때 이 플래그를 반드시 포함해야 이미지가 기준점 쪽으로 올바르게 이동합니다.
- 예외 처리 (try-catch): 이 함수는 알고리즘이 수렴하지 못하면 내부적으로 에러를 발생시키는 경우가 잦습니다. 실무용 코드라면 반드시 예외 처리를 통해 프로그램이 죽지 않도록 방어 코드를 짜야 합니다.
8. 마무리하며
findTransformECC는 연산량이 조금 많아 속도는 느릴 수 있지만, 그만큼 정교한 결과를 보장합니다. 의료 영상 분석, 위성 사진 정렬, 혹은 흔들린 사진 보정 프로그램을 만들고 싶다면 이 함수가 여러분의 강력한 무기가 될 것입니다.
'OpenCV' 카테고리의 다른 글
| 왜 OpenCV는 빠르고, 왜 때로는 SIMD가 더 빠를까? (0) | 2026.05.06 |
|---|