微分同胚demons配准算法原理与C++/Opencv实现
moboyou 2025-05-24 14:40 51 浏览
01
前言
前文我们介绍过demons算法,以及在demons算法的基础上发展的Active demons算法和Inertial demons算法:
我们知道,demons算法是一种基于光流理论发展起来的配准方法,因此该算法与其它光流算法一样,具有小运动的约束条件。如果参考图像和浮动图像的差别很大,也即不满足小运动条件,那么demons算法的配准效果会很差。
为了解决小运动问题,Vercauteren T等人提出了微分同胚算法[1],将demons算法计算得到的位移场转换为微分同胚映射,从而对大运动或大形变都具有较理想的配准效果。本文介绍一种基于Active demons和微分同胚映射的大形变配准算法,其中使用到的微分同胚映射转换算法参考了Vercauteren T等人的文章。
首先我们来复习一下Active demons算法计算每个像素点位移的公式,其中其中Sx和Sy分别是参考图像上点(x,y)处x方向和y方向的梯度,Mx和My分别是浮动图像上点(x,y)处x方向和y方向的梯度,▽f则是点(x,y)处参考图像与浮动图像的灰度差:
本文所介绍的微分同胚demons算法,则是在以上Active demons计算公式计算得到的Ux、Uy基础上,再将Ux、Uy转换为微分同胚映射。
02
什么是微分同胚映射?
微分同胚映射,它包含了“同胚”和“微分”两个条件,也就是说必须同时满足这两个条件的映射,才是微分同胚映射。下面我们分别介绍这两个条件。
- 同胚
介绍同胚映射之前,我们先介绍一下数学中“单射”、“满射”、“双射”的概念。假设有函数y=f(x),函数f也可以称为从x到y的映射:
- 如果每个x值都有唯一的y值与之对应,那么映射f为单射。
- 如果每个y值都至少有一个x值与之对应,那么映射f为满射。
- 如果映射f同时满足单射和满射条件,那么又称之为双射,直观说就是当所有x值与所有y值一一对应的时候,映射f为双射。
单射、满射、双射的示意图如下:
其实同胚映射跟双射是一样的,都是一一对应的关系,只是同胚映射通常用于形容拓扑空间的映射关系而已,比如对于两张图像,如果它们的所有像素点都满足一一对应关系,那么这个对应关系就是同胚映射,如下图所示:
- 微分
如果映射f满足以上所说的微分条件,意味着映射f与逆映射f-1都是光滑的映射。什么又是光滑映射呢?每个x值对应到y值的映射关系,与其邻域内其它x值对应到y值的映射关系是相似的,即使不完全一样,也是连续缓慢的变化,而不是突变,那么称x到y的映射为光滑映射。如下图所示,左图中x到y的映射是连续缓慢变化的,所以是光滑映射,而右图中x4到y4的映射,大小和方向都突变了,所以右图的映射不是光滑映射。
此处讲的光滑映射可能有点抽象,下文我们再举例更加直观地说明。
03
相似度衡量
前文我们使用Active demons算法类配准的时候,每轮迭代都更新了最优位移场。实际上,并不是每轮迭代都是朝着配准效果更好的方向前进的,有的迭代配准效果反而更差,所以每轮迭代都更新最优位移场是有问题的。于是使用相似度来判断是否要更新最优位移场的方法被提了出来,流程如下图所示,其中F、M、M1依次为参考图像、浮动图像、像素重采样之后的浮动图像。
本文我们使用归一化互相关系数来衡量F、M1的相似度,假设F、M1都是M行N列矩阵,归一化互相关系数的计算公式如下:
NCC的取值范围0~1,NCC越接近1表示两张图像越相似。
考虑到在迭代过程中,图像M在像素重采样之后有可能会出现黑色边缘(例如下图),这些黑色边缘会影响相似度的准确性,所以计算NCC时应该把这些黑色边缘区域排除在外
我们首先根据计算的位移场获取黑色边缘的mask掩码矩阵,mask矩阵值为0的像素点属于黑色边缘,其它则不属于。假设每轮迭代计算得到的位移场为M行N列的矩阵Ux、Uy,对于每个像素点(x,y),判断其像素值是否满足以下条件(round为四舍五入运算),如果满足则判定点(x,y)不属于黑色边缘,并对mask(x,y)赋值255,反之则属于黑色边缘并对mask(x,y)赋值0。
基于以上得到的mask矩阵,NCC计算公式变成下式,也即只有非黑色边缘的点才加入计算,黑色边缘点排除在外:
04
梯度图的计算
计算位移场的时候,需要使用到参考图像F、浮动图像M的梯度。本文我们使用差分梯度来计算位移场。
对于图像上任意一点(x,y),其像素值为f(x,y),那么该点的x、y方向的梯度gx(x,y)、gy(x,y)可按下式计算:
按理说图像的边缘点没法按照上式计算梯度,所以我们需要对图像做边缘填充:在上、下各填充一行,左、右各填充一列。
例如,按照上式计算Lena图像所有像素点的x、y方向梯度,得到x、y方向的梯度图如下:
05
将位移场转换为微分同胚映射
这一步为微分同胚demons算法的核心。参考Vercauteren T等人的文章,可使用复合运算来实现近似微分同胚映射转换,那么什么又是复合运算呢?下面我们详细道来。
假设x、y方向的位移场分别为Ux、Uy,使用Ux、Uy分别对它们自身进行像素重采样的操作得到Ux'和Uy',然后再计算Ux+Ux'和Uy+Uy'的运算就是复合运算。对于Ux'的任意像素点(x',y'),其对应Ux上的点(x,y)的坐标可按下式计算:
得到(x,y)之后,再把点(x,y)的像素值Ux(x,y)赋值给Ux'上点(x',y'),即完成点(x,y)的像素重采样操作。
不过由于x、y都是浮点数,无法直接得到Ux(x,y)的值,所以本文使用双线性插值算法计算Ux(x,y)。关于插值算法与像素重采样的详细介绍,可参考前文:
对Ux'的所有像素点都按照上述进行像素插值,则完成了从Ux到Ux'的像素重采样,从Uy到Uy'的像素重采样也同理:
最后再计算两者之和,即完成复合运算:
以上是复合运算的介绍,接下来就是将位移场转换为近似微分同胚映射的流程:
(1)按下式计算位移场Ux、Uy的平方和矩阵,其中“.*”为点乘运算,也即两个矩阵中对应位置的像素值相乘。
(2)求矩阵norm2的最大值maxv。
(3)按下式计算n,其中ceil为向上取整运算,比如ceil(1.1)=2.0,max为取较大值运算。
(4)计算矩阵Ux1、Uy1:
(5)对矩阵Ux1、Uy1作n次复合运算,最后结果就是近似的微分同胚映射。
06
微分同胚demons算法的代码实现
(1)计算梯度代码:
void get_gradient(Mat src, Mat &Fx, Mat &Fy)
{
Mat src_board;
//边缘扩充
copyMakeBorder(src, src_board, 1, 1, 1, 1, BORDER_REPLICATE);
Fx = Mat::zeros(src.size(), CV_32FC1);
Fy = Mat::zeros(src.size(), CV_32FC1);
for (int i = 0; i < src.rows; i++)
{
float *p_Fx = Fx.ptr<float>(i);
float *p_Fy = Fy.ptr<float>(i);
for (int j = 0; j < src.cols; j++)
{
p_Fx[j] = (src_board.ptr<float>(i + 1)[j + 2] - src_board.ptr<float>(i + 1)[j]) / 2.0 ;
p_Fy[j] = (src_board.ptr<float>(i + 2)[j + 1] - src_board.ptr<float>(i)[j + 1]) / 2.0;
}
}
}(2)复合运算实现代码如下,其中调用Opencv的remap函数可以方便地实现像素重采样。
//像素重采样实现
void movepixels_2d2(Mat src, Mat &dst, Mat Tx, Mat Ty, int interpolation)
{
Mat Tx_map(src.size(), CV_32FC1, 0.0);
Mat Ty_map(src.size(), CV_32FC1, 0.0);
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
Tx_map.at<float>(i, j) = j + Tx.at<float>(i, j);
Ty_map.at<float>(i, j) = i + Ty.at<float>(i, j);
}
}
remap(src, dst, Tx_map, Ty_map, interpolation);
}
//复合运算实现
void composite(Mat& vx, Mat& vy)
{
Mat bxp, byp;
movepixels_2d2(vx, bxp, vx, vy, INTER_LINEAR);
movepixels_2d2(vy, byp, vx, vy, INTER_LINEAR);
vx = vx + bxp;
vy = vy + byp;
}(3)微分同胚映射转换实现:
void exp_field(Mat &vx, Mat &vy, Mat &vx_out, Mat &vy_out)
{
Mat normv2 = vx.mul(vx) + vy.mul(vy);
double minv, maxv;
Point pt_min, pt_max;
minMaxLoc(normv2, &minv, &maxv, &pt_min, &pt_max); //求最大最小值
float m = sqrt(maxv);
float n = ceil(log2(m / 0.5));
n = n > 0.0 ? n : 0.0;
float a = pow(2.0, -n);
vx_out = vx * a;
vy_out = vy * a;
//n次复合运算
for (int i = 0; i < (int)n; i++)
{
composite(vx_out, vy_out);
}
}(4)高斯滤波函数实现,调用Opencv的函数GaussianBlur可方便实现高斯滤波:
void imgaussian(Mat src, Mat& dst, float sigma)
{
int radius = (int)ceil(3 * sigma);
int ksize = 2 * radius + 1;
GaussianBlur(src, dst, Size(ksize, ksize), sigma);
}(5)计算Active demons位移场的代码实现:
void demons_update(Mat S, Mat M, Mat Sx, Mat Sy, float alpha, Mat& Tx, Mat& Ty)
{
Mat diff = S - M;
Tx = Mat::zeros(S.size(), CV_32FC1);
Ty = Mat::zeros(S.size(), CV_32FC1);
Mat Mx, My;
get_gradient(M, Mx, My); //求M的梯度
float alpha_2 = alpha * alpha;
for (int i = 0; i < S.rows; i++)
{
float* p_sx = Sx.ptr<float>(i);
float* p_sy = Sy.ptr<float>(i);
float* p_mx = Mx.ptr<float>(i);
float* p_my = My.ptr<float>(i);
float* p_tx = Tx.ptr<float>(i);
float* p_ty = Ty.ptr<float>(i);
float* p_diff = diff.ptr<float>(i);
for (int j = 0; j < S.cols; j++)
{
float alpha_diff = alpha_2 * p_diff[j] * p_diff[j];
float a1 = p_sx[j] * p_sx[j] + p_sy[j] * p_sy[j] + alpha_diff; //分母
float a2 = p_mx[j] * p_mx[j] + p_my[j] * p_my[j] + alpha_diff;
if (a1 > 0.00001 && a2 > 0.00001)
{
p_tx[j] = p_diff[j] * (p_sx[j] / a1 + p_mx[j] / a2);
p_ty[j] = p_diff[j] * (p_sy[j] / a1 + p_my[j] / a2);
}
}
}
}(6)计算相似度代码实现:
//获取黑色边缘的mask矩阵
void cal_mask(Mat Tx, Mat Ty, Mat &mask)
{
mask.create(Tx.size(), CV_8UC1);
for (int i = 0; i < Tx.rows; i++)
{
float *pTx = Tx.ptr<float>(i);
float *pTy = Ty.ptr<float>(i);
uchar *pm = mask.ptr<uchar>(i);
for (int j = 0; j < Tx.cols; j++)
{
int x = (int)(j + pTx[j] + 0.5);
int y = (int)(i + pTy[j] + 0.5);
if (x < 0 || x >= Tx.cols || y < 0 || y >= Tx.rows)
{
pm[j] = 0;
}
else
{
pm[j] = 255;
}
}
}
}
//计算归一化互相关系数
double cal_cc_mask(Mat S1, Mat Si, Mat Mask)
{
double result;
double sum1 = 0.0;
double sum2 = 0.0;
double sum3 = 0.0;
for (int i = 0; i < S1.rows; i++)
{
for (int j = 0; j < S1.cols; j++)
{
if (Mask.ptr<uchar>(i)[j])
{
sum1 += (double)(S1.at<uchar>(i, j)*Si.at<uchar>(i, j));
sum2 += (double)(S1.at<uchar>(i, j)*S1.at<uchar>(i, j));
sum3 += (double)(Si.at<uchar>(i, j)*Si.at<uchar>(i, j));
}
}
}
result = sum1 / sqrt(sum2*sum3); //范围0~1
return result;
}
(7)微分同胚demons算法的最终实现:
void register_diffeomorphic_demons(Mat F0, Mat M0, float alpha, float sigma_fluid, float sigma_diffusion, int niter, Mat &Mp, Mat &sx, Mat &sy)
{
Mat F, M;
F0.convertTo(F, CV_32F); //将参考图像和浮动图像都转换为浮点型矩阵
M0.convertTo(M, CV_32F);
//初始化位移场为0位移
Mat vx = Mat::zeros(F.size(), CV_32FC1);
Mat vy = Mat::zeros(F.size(), CV_32FC1);
float e_min = -9999999999.9;
Mat sx_min, sy_min;
Mat Sx, Sy;
get_gradient(F, Sx, Sy); //计算参考图像梯度图
Mat M1 = M.clone();
for (int i = 0; i < niter; i++)
{
Mat ux, uy;
//计算Active demons的位移场
demons_update(F, M1, Sx, Sy, alpha, ux, uy);
imgaussian(ux, ux, sigma_fluid); //高斯滤波
imgaussian(uy, uy, sigma_fluid); //高斯滤波
vx = vx + ux; //将位移场累加
vy = vy + uy;
imgaussian(vx, vx, sigma_diffusion); //高斯滤波
imgaussian(vy, vy, sigma_diffusion); //高斯滤波
exp_field(vx, vy, sx, sy); //将累加的位移场转换为微分同胚映射
Mat mask;
cal_mask(sx, sy, mask); //计算黑色边缘的mask掩码矩阵
movepixels_2d2(M, M1, sx, sy, INTER_LINEAR); //对M像素重采样
float e = cal_cc_mask(F, M1, mask); //计算F、M1的相似度
if (e > e_min) //如果相似度提高,则更新最佳位移场
{
printf("i=%d, e=%f\n", i, e);
e_min = e;
sx_min = sx.clone();
sy_min = sy.clone();
}
}
sx = sx_min.clone(); //得到最优微分同胚映射
sy = sy_min.clone();
movepixels_2d2(M, Mp, sx, sy, INTER_LINEAR);
Mp.convertTo(Mp, CV_8U);
}07
测试结果
为了验证我们实现的微分同胚算法对大形变图像的配准效果,我们分别使用本文实现的微分同胚demons算法、Active demons算法对以下大形变的心脏图像进行配准,并比较结果:
由前文可知,Active demons算法中的alpha参数越大,配准步长越小,反之则配准步长越大。由于是大形变图像,我们统一给alpha取一个较小值0.15,高斯滤波参数sigma_fluid和sigma_diffusion都取0.05,迭代次数设置为3000。配准结果如下:
记录每轮迭代的配准图像与参考图像的相似度,如下图:
由以上结果可知,微分同胚demons算法的配准结果比Active demons算法的好得多。Active demons算法不适用于大位移,那么我们尝试把alpha调大为1.15,使位移减小,得到Active demons算法的以下结果,发现还是不理想。
上文我们讲到,微分同胚映射是一种光滑的映射,什么是光滑映射呢?现在我们分别计算微分同胚demons算法、Active demons算法的最终位移场的位移幅度图(也即计算每个像素点的位移距离)并作伪彩色处理,同时画出每一个像素点的位移向量(白色线为位移轨迹,红点为位移终点),如下图所示,可以形象地看出来微分同胚映射的位移场是光滑的,相比Active demons算法的位移场就粗糙多了。
本文参考文献:
[1] Vercauteren T , Pennec X , Perchant A , et al. Diffeomorphic demons: efficient non-parametric image registration.[J]. NeuroImage, 2009, 45( 1):S61-S72.
- 上一篇:st/scl位置的批量赋值
- 下一篇:运动控制算法工程师想提升自己,应该怎么做?
相关推荐
- Excel技巧:SHEETSNA函数一键提取所有工作表名称批量生产目录
-
首先介绍一下此函数:SHEETSNAME函数用于获取工作表的名称,有三个可选参数。语法:=SHEETSNAME([参照区域],[结果方向],[工作表范围])(参照区域,可选。给出参照,只返回参照单元格...
- Excel HOUR函数:“小时”提取器_excel+hour函数提取器怎么用
-
一、函数概述HOUR函数是Excel中用于提取时间值小时部分的日期时间函数,返回0(12:00AM)到23(11:00PM)之间的整数。该函数在时间数据分析、考勤统计、日程安排等场景中应用广泛。语...
- Filter+Search信息管理不再难|多条件|模糊查找|Excel函数应用
-
原创版权所有介绍一个信息管理系统,要求可以实现:多条件、模糊查找,手动输入的内容能去空格。先看效果,如下图动画演示这样的一个效果要怎样实现呢?本文所用函数有Filter和Search。先用filter...
- FILTER函数介绍及经典用法12:FILTER+切片器的应用
-
EXCEL函数技巧:FILTER经典用法12。FILTER+切片器制作筛选按钮。FILTER的函数的经典用法12是用FILTER的函数和切片器制作一个筛选按钮。像左边的原始数据,右边想要制作一...
- office办公应用网站推荐_office办公软件大全
-
以下是针对Office办公应用(Word/Excel/PPT等)的免费学习网站推荐,涵盖官方教程、综合平台及垂直领域资源,适合不同学习需求:一、官方权威资源1.微软Office官方培训...
- WPS/Excel职场办公最常用的60个函数大全(含卡片),效率翻倍!
-
办公最常用的60个函数大全:从入门到精通,效率翻倍!在职场中,WPS/Excel几乎是每个人都离不开的工具,而函数则是其灵魂。掌握常用的函数,不仅能大幅提升工作效率,还能让你在数据处理、报表分析、自动...
- 收藏|查找神器Xlookup全集|一篇就够|Excel函数|图解教程
-
原创版权所有全程图解,方便阅读,内容比较多,请先收藏!Xlookup是Vlookup的升级函数,解决了Vlookup的所有缺点,可以完全取代Vlookup,学完本文后你将可以应对所有的查找难题,内容...
- 批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数
-
批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数在电商运营、物流对账等工作中,经常需要统计快递“揽收到签收”的耗时——比如判断某快递公司是否符合“3天内送达”的服务承...
- Excel函数公式教程(490个实例详解)
-
Excel函数公式教程(490个实例详解)管理层的财务人员为什么那么厉害?就是因为他们精通excel技能!财务人员在日常工作中,经常会用到Excel财务函数公式,比如财务报表分析、工资核算、库存管理等...
- Excel(WPS表格)Tocol函数应用技巧案例解读,建议收藏备用!
-
工作中,经常需要从多个单元格区域中提取唯一值,如体育赛事报名信息中提取唯一的参赛者信息等,此时如果复制粘贴然后去重,效率就会很低。如果能合理利用Tocol函数,将会极大地提高工作效率。一、功能及语法结...
- Excel中的SCAN函数公式,把计算过程理清,你就会了
-
Excel新版本里面,除了出现非常好用的xlookup,Filter公式之外,还更新一批自定义函数,可以像写代码一样写公式其中SCAN函数公式,也非常强大,它是一个循环函数,今天来了解这个函数公式的计...
- Excel(WPS表格)中多列去重就用Tocol+Unique组合函数,简单高效
-
在数据的分析和处理中,“去重”一直是绕不开的话题,如果单列去重,可以使用Unique函数完成,如果多列去重,如下图:从数据信息中可以看到,每位参赛者参加了多项运动,如果想知道去重后的参赛者有多少人,该...
- Excel(WPS表格)函数Groupby,聚合统计,快速提高效率!
-
在前期的内容中,我们讲了很多的统计函数,如Sum系列、Average系列、Count系列、Rank系列等等……但如果用一个函数实现类似数据透视表的功能,就必须用Groupby函数,按指定字段进行聚合汇...
- Excel新版本,IFS函数公式,太强大了!
-
我们举一个工作实例,现在需要计算业务员的奖励数据,右边是公司的奖励标准:在新版本的函数公式出来之前,我们需要使用IF函数公式来解决1、IF函数公式IF函数公式由三个参数组成,IF(判断条件,对的时候返...
- Excel不用函数公式数据透视表,1秒完成多列项目汇总统计
-
如何将这里的多组数据进行汇总统计?每组数据当中一列是不同菜品,另一列就是该菜品的销售数量。如何进行汇总统计得到所有的菜品销售数量的求和、技术、平均、最大、最小值等数据?不用函数公式和数据透视表,一秒就...
- 一周热门
- 最近发表
-
- Excel技巧:SHEETSNA函数一键提取所有工作表名称批量生产目录
- Excel HOUR函数:“小时”提取器_excel+hour函数提取器怎么用
- Filter+Search信息管理不再难|多条件|模糊查找|Excel函数应用
- FILTER函数介绍及经典用法12:FILTER+切片器的应用
- office办公应用网站推荐_office办公软件大全
- WPS/Excel职场办公最常用的60个函数大全(含卡片),效率翻倍!
- 收藏|查找神器Xlookup全集|一篇就够|Excel函数|图解教程
- 批量查询快递总耗时?用Excel这个公式,自动计算揽收到签收天数
- Excel函数公式教程(490个实例详解)
- Excel(WPS表格)Tocol函数应用技巧案例解读,建议收藏备用!
- 标签列表
-
- 外键约束 oracle (36)
- oracle的row number (32)
- 唯一索引 oracle (34)
- oracle in 表变量 (28)
- oracle导出dmp导出 (28)
- 多线程的创建方式 (29)
- 多线程 python (30)
- java多线程并发处理 (32)
- 宏程序代码一览表 (35)
- c++需要学多久 (25)
- css class选择器用法 (25)
- css样式引入 (30)
- css教程文字移动 (33)
- php简单源码 (36)
- php个人中心源码 (25)
- php小说爬取源码 (23)
- 云电脑app源码 (22)
- html画折线图 (24)
- docker好玩的应用 (28)
- linux有没有pe工具 (34)
- 可以上传视频的网站源码 (25)
- 随机函数如何生成小数点数字 (31)
- 随机函数excel公式总和不变30个数据随机 (33)
- 所有excel函数公式大全讲解 (22)
- 有动图演示excel函数公式大全讲解 (32)
