微分同胚demons配准算法原理与C++/Opencv实现
01
—
前言
02
—
什么是微分同胚映射?
同胚
如果每个x值都有唯一的y值与之对应,那么映射f为单射。
如果每个y值都至少有一个x值与之对应,那么映射f为满射。
如果映射f同时满足单射和满射条件,那么又称之为双射,直观说就是当所有x值与所有y值一一对应的时候,映射f为双射。
单射非满射
满射非单射
双射
同胚映射
微分
03
—
相似度衡量
本文我们使用归一化互相关系数来衡量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)可按下式计算:
05
—
将位移场转换为微分同胚映射
06
—
微分同胚demons算法的
代码实现
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;}}}
//像素重采样实现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++){j) = j + Tx.at<float>(i, j);j) = i + Ty.at<float>(i, j);}}dst, Tx_map, Ty_map, interpolation);}//复合运算实现void composite(Mat& vx, Mat& vy){Mat bxp, byp;bxp, vx, vy, INTER_LINEAR);byp, vx, vy, INTER_LINEAR);vx = vx + bxp;vy = vy + byp;}
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);}}
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);}
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);}}}}
//获取黑色边缘的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~1return result;}
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
—
测试结果
