如下图所示,有时候参考图像与浮动图像的灰度区别很大,但是它们又有某一个小区域比较相似,这种情况下直接通过特征点匹配或形变优化来配准的效果并不理想。
这个时候我们可以尝试使用模板匹配的方法来配准,首先在参考图像上选取一个相似区域作为模板,然后在浮动图像上搜索与模板最相似的区域(匹配区域),最后通过计算模板在参考图像上位置与匹配区域在浮动图像上位置的坐标偏移,作为参考图像与浮动图像的整体坐标偏移。
所以模板匹配的核心思路是:搜索最相似的区域作为匹配区域。
既然核心思路是搜索相似区域,那么首先必须有一个合适的指标来衡量模板T与搜索区域I的相似度。假设参考图像上模板的左上角x坐标、y坐标分别为x、y,浮动图像上搜索区域的左上角x坐标、y坐标分别为x'、 y',且假设x' - x = dx,y' - y = dy,同时它们的宽、高都是w、h,下面我们分别介绍Opencv中几种常用的相似度衡量指标。
差值平方和:TM_SQDIFF
通过计算模板与搜索区域中所有对应位置像素点的像素平方差之和,来衡量两者相似度,计算结果越小表示越相似,当计算结果为0时表示两者完全相同。
归一化差值平方和:TM_SQDIFF_NORMED
该指标与以上差值平方和类似,只不过加了一个归一化的操作。
归一化操作的作用是什么呢?就是为了确保当T和I的亮度都乘以某个系数a之后R值保持不变,也即当T(i,j)与I(i+dy,j+dx)分别变成a*T(i,j)与a*I(i+dy,j+dx),那么R值保持不变。
同样,计算结果越小表示越相似,当计算结果为0时表示两者完全相同。
相关系数:TM_CCORR
采用T和I的相关系数作为衡量指标,相关系数越大表示两者越相似。
归一化相关系数:TM_CCORR_NORMED
对比相关系数,归一化相关系数多了一个归一化操作,也是为了确保当T和I的亮度都乘以某个系数a之后相似度保持不变:
归一化相关系数越大表示两者越相似。
去均值相关系数:TM_CCOEFF
去均值相关系数与相关系数的区别在于:计算去均值相关系数时T和I都减去各自的均值,而相关系数则不减去均值。这样做是为了消除T和I亮度不同对计算结果的影响,比如T和I的结构非常相似但亮度有很大区别,那么它们分别减去各自的均值,使用每个像素点的亮度偏移来计算,而不是使用亮度本身来计算,因此消除了亮度不同的影响。
去均值相关系数越大表示两者越相似。
去均值归一化相关系数:TM_CCOEFF_NORMED
同样,相比去均值相关系数,去均值归一化相关系数多了归一化操作:
去均值归一化相关系数越大表示两者越相似。
调用Opencv的matchTemplate函数,可以方便实现模板匹配,下面介绍一下该函数的参数以及调用方法。
函数原型及参数说明:
/*
image -- 待匹配图像
templ -- 模板
result -- 结果矩阵,保存每个搜索位置的相似度结果,假设image的宽、高分别为W、H,templ的宽、高分别为 w、h,那么result的宽、高分别为W-w+1、H-h+1
method -- 相似度指标:
差值平方和:TM_SQDIFF
归一化差值平方和:TM_SQDIFF_NORMED
相关系数:TM_CCORR
归一化相关系数:TM_CCORR_NORMED
去均值相关系数:TM_CCOEFF
去均值归一化相关系数:TM_CCOEFF_NORMED
mask -- 计算相似度的掩码区域,默认整个模板窗口都计算
*/
void matchTemplate( InputArray image, InputArray templ, OutputArray result, int method, InputArray mask = noArray() );
调用matchTemplate函数得到result矩阵之后,在result矩阵中查找最大值和最小值,以及最大最小值对应的位置。不过需要注意,使用TM_SQDIFF、TM_SQDIFF_NORMED指标时,result中最小值的位置表示匹配位置,如果使用TM_CCORR、TM_CCORR_NORMED、TM_CCOEFF、TM_CCOEFF_NORMED指标,则result中最大值的位置表示匹配位置。
通过模板匹配来实现图像配准的实现代码如下:
/*
img0 -- 参考图像
img1 -- 浮动图像
re -- 模板的左上角x坐标、y坐标、宽、高
out -- 配准后图像
*/
Mat tempalte_match(Mat img0, Mat img1, Rect re, Mat &out)
{
Mat temp;
img0(re).copyTo(temp); //根据模板的左上角x坐标、y坐标、宽、高从参考图像中截取模板
Mat result;
//使用TM_CCOEFF_NORMED作为相似度衡量指标,进行模板匹配
matchTemplate(img1, temp, result, TM_CCOEFF_NORMED);
Point max_index, min_index;
double min_value, max_value;
//寻找result中的最大值,及其对应位置作为匹配位置
minMaxLoc(result, &min_value, &max_value, &min_index, &max_index);
Mat T(2, 3, CV_64FC1); //定义一个只有平移变换的仿射变换矩阵
T.ptr(0)[0] = 1.0;
T.ptr(0)[1] = 0.0;
T.ptr(0)[2] = re.x - max_index.x; //根据模板位置与匹配位置计算x坐标偏移
T.ptr(1)[0] = 0.0;
T.ptr(1)[1] = 1.0;
T.ptr(1)[2] = re.y - max_index.y; //根据模板位置与匹配位置计算y坐标偏移
//使用仿射变换矩阵对浮动图像进行仿射变换,得到配准图像
warpAffine(img1, out, T, img1.size(), INTER_CUBIC);
return T;
}
以上代码中,因为使用TM_CCOEFF_NORMED作为相似度,故选择result矩阵中最大值的位置max_index作为浮动图像上的匹配位置。
总结模板匹配流程为:
1. 在参考图像选择模板,可以通过调用Opencv的selectROI手动选择;
2. 调用matchTemplate函数搜索匹配区域;
3. 调用minMaxLoc函数寻找匹配结果的最大最小值,以及对应位置;
4. 根据所使用的相似度指标,选择最大值位置或最小值位置作为匹配位置;
5. 计算坐标偏移,得到仿射变换矩阵;
6. 调用warpAffine函数对浮动图像进行仿射变换,得到配准图像。
我们使用模板匹配的配准方法对一组荧光成像图片进行配准:
Mat read_img_u16(char *str, Mat &img_u16)
{
Mat img1 = imread(str, CV_LOAD_IMAGE_ANYDEPTH);
img1 = img1 * 16; //乘以16是为了可视化,不然看起来图像是黑的
img1.copyTo(img_u16);
normalize(img1, img1, 0, 255, NORM_MINMAX); //将16位图转换为8位图
img1.convertTo(img1, CV_8U);
return img1; //返回8位图
}
void templ_match_test()
{
Mat img0_u16;
//读参考图像
Mat img0 = read_img_u16("img3/c2/0000.tif", img0_u16);
imwrite("img3/c2_res/0.tif", img0);
Rect roi = selectROI(img0); //选择模板区域
for (int i = 1; i <= 128; i++)
{
char str[100] = { 0 };
sprintf(str, "img3/c2/%04d.tif", i);
cout << "i:" << i << endl;
Mat img_u16;
Mat img = read_img_u16(str, img_u16); //读浮动图像
Mat out;
//模板匹配
Mat T = tempalte_match(img0, img, roi, out);
char str1[100] = { 0 };
sprintf(str1, "img3/c2_res/%d.tif", i);
imwrite(str1, out); //写配准图像
imshow("out", out); //显示配准图像
imshow("img0 - img", abs(img0 - img)); //显示参考图像与浮动图像的差值图
imshow("img0 - out", abs(img0 - out)); //显示参考图像与配准图像的差值图
waitKey(1);
}
}
运行以上代码,将配准结果做成视频如下,可以看到配准后的图像得到对齐。
配准前图像
配准后图像
没有适用于任何情况的完美配准方法,只能说根据图像的情况特点来选择一种合适的方法对其进行配准。
当参考图像与浮动图像的灰度区别很大,并且它们有某一个小区域比较相似时,则可以使用模板匹配来配准,但如果图像存在旋转、缩放、局部非刚性形变的情况下,以上介绍的方法则不适用。
对于局部非刚性形变的图像,也可以先使用模板匹配进行整体平移的粗配准,然后再使用FFD、TPS等形变做局部形变配准。