在
点的某个邻域内具有任意阶导数,则
在 点处的泰勒展开式为: ,其中 , 。
。
我们一般将其表示为:
尺度空间理论的基本思想是:在图像信息处理模型中引入一个被视为尺度的参数,通过连续变化尺度参数获得多尺度下的尺度空间表示序列,对这些序列进行尺度空间主轮廓的提取,并以该主轮廓作为一种特征向量,实现边缘、角点检测和不同分辨率上的特征提取等。
尺度空间理论的特点是:将传统的单尺度图像信息处理技术纳入尺度不断变化的动态分析框架中,更容易获取图像的本质特征。尺度空间中各尺度图像的模糊程度逐渐变大,能够模拟人在距离目标由近到远时目标在视网膜上的形成过程。
尺度空间理论的一个直观理解:我们人在远近不同距离上对同一物体,都可以实现辨识。
高斯卷积核是实现尺度变换的唯一线性核(高斯函数可以称为最伟大和最重要的函数)。
一幅图像的尺度空间可以定义为:
对全图直接做偏导操作 = 对原图以特定的高斯核做卷积
基于这个推论,对于图的Hessian运算将极大地降低计算量,同时提高运算速度。
|a b|
|c d|
这个是一元二次方程,可以计算得到有两个解,分别为
λ1=(a+d+√((a-d)^2+4bc))/2
λ2=(a+d-√((a-d)^2+4bc))/2
二维高斯分布,其曲线图如下:
根据定义,我们可以求得一下内容。
二维高斯函数的一阶偏导数:
二维高斯函数的二阶偏导数
这里从原函数到二阶偏导的推断都是本科的知识,建议大家自己推一下,很简单,对于这个问题的深入认识很有帮助,后面我们在代码部分将再一次提及。
二、“血管增强”算法的原理
Frangi A F, Niessen W J, Vincken K L, et al. 《Multiscale vessel enhancement filtering》[C]//International Conference on Medical Image Computing and Computer-Assisted Intervention. Springer Berlin Heidelberg, 1998: 130-137.
//使用默认参数设定Frangi
frangi2d_opts_t opts;
frangi2d_createopts(&opts);
//读取图片,进行处理
Mat input_img = imread("E:/template/51.bmp", 0);
Mat input_img_fl;
//转换为单通道,浮点运算
input_img.convertTo(input_img_fl, CV_32FC1);
//进行处理
Mat vesselness, scale, angles;
frangi2d(input_img_fl, vesselness, scale, angles, opts);
//显示结果
vesselness.convertTo(vesselness, CV_8UC1, 255);
scale.convertTo(scale, CV_8UC1, 255);
angles.convertTo(angles, CV_8UC1, 255);
imshow("result", vesselness);
}
//Frangi filter//
/////////////////
//frangi滤波主要过程
void frangi2d(const cv::Mat &src, cv::Mat &J, cv::Mat &scale, cv::Mat &directions, frangi2d_opts_t opts);
////////////////////
//Helper functions//
////////////////////
//计算Hessian矩阵 with parameter sigma on src, save to Dxx, Dxy and Dyy.
void frangi2d_hessian(const cv::Mat &src, cv::Mat &Dxx, cv::Mat &Dxy, cv::Mat &Dyy, float sigma);
//参数设置 (sigma_start = 3, sigma_end = 7, sigma_step = 1, BetaOne = 1.6, BetaTwo 0.08)
void frangi2d_createopts(frangi2d_opts_t *opts);
//计算特征值 from Dxx, Dxy, Dyy. Save results to lambda1, lambda2, Ix, Iy.
void frangi2_eig2image(const cv::Mat &Dxx, const cv::Mat &Dxy, const cv::Mat &Dyy, cv::Mat &lambda1, cv::Mat &lambda2, cv::Mat &Ix, cv::Mat &Iy);
void frangi2d_hessian(const cv::Mat &src, cv::Mat &Dxx, cv::Mat &Dxy, cv::Mat &Dyy, float sigma);
j=0;
for (int y = -round(3*sigma); y <= round(3*sigma); y++){
kern_xx_f[i*n_kern_y + j] = 1.0f/(2.0f*M_PI*sigma*sigma*sigma*sigma) * (x*x/(sigma*sigma) - 1) * exp(-(x*x + y*y)/(2.0f*sigma*sigma));
kern_xy_f[i*n_kern_y + j] = 1.0f/(2.0f*M_PI*sigma*sigma*sigma*sigma*sigma*sigma)*(x*y)*exp(-(x*x + y*y)/(2.0f*sigma*sigma));
j++;
}
i++;
}
for (int j=0; j < n_kern_y; j++){
for (int i=0; i < n_kern_x; i++){
kern_yy_f[j*n_kern_x + i] = kern_xx_f[i*n_kern_x + j];
}
}
这里我们就是首先把“特定的高斯核”计算出来。然后我们回忆当时介绍的二维高斯函数的二阶偏导数
那么它翻译成代码是什么样子的了?matlab版本
//DGaussxx = 1/(2*pi*Sigma^4) * (X.^2/Sigma^2 - 1) .* exp(-(X.^2 + Y.^2)/(2*Sigma^2));
//DGaussxy = 1/(2*pi*Sigma^6) * (X .* Y) .* exp(-(X.^2 + Y.^2)/(2*Sigma^2));
//DGaussyy = DGaussxx';
cv::Mat tmp2 = -1*((EWM(X,X)+EWM(Y,Y))/(2*Sigma*Sigma));
exp(tmp2,tmp2);
Mat tmp, tmp2;
tmp2 = Dxx - Dyy;
sqrt(tmp2.mul(tmp2) + 4*Dxy.mul(Dxy), tmp);
Mat v2x = 2*Dxy;
Mat v2y = Dyy - Dxx + tmp;
//normalize
Mat mag;
sqrt((v2x.mul(v2x) + v2y.mul(v2y)), mag);
Mat v2xtmp = v2x.mul(1.0f/mag);
v2xtmp.copyTo(v2x, mag != 0);
Mat v2ytmp = v2y.mul(1.0f/mag);
v2ytmp.copyTo(v2y, mag != 0);
//eigenvectors are orthogonal
Mat v1x, v1y;
v2y.copyTo(v1x);
v1x = -1*v1x;
v2x.copyTo(v1y);
//compute eigenvalues
Mat mu1 = 0.5*(Dxx + Dyy + tmp);
Mat mu2 = 0.5*(Dxx + Dyy - tmp);
for (float sigma = opts.sigma_start; sigma <= opts.sigma_end; sigma += opts.sigma_step){……}
//多尺度叠加
for (int i=1; i < ALLfiltered.size(); i++){
maxVals = max(maxVals, ALLfiltered[i]);
whatScale.setTo(sigma, ALLfiltered[i] == maxVals);
ALLangles[i].copyTo(outAngles, ALLfiltered[i] == maxVals);
sigma += opts.sigma_step;
}
程序最外面的框架告诉我们,整个程序是多次运算(尺度)的叠加。
Dyy = Dyy*sigma*sigma;
Dxy = Dxy*sigma*sigma;
//calculate (abs sorted) eigenvalues and vectors
Mat lambda1, lambda2, Ix, Iy;
frangi2_eig2image(Dxx, Dxy, Dyy, lambda1, lambda2, Ix, Iy);
在每次计算中,首先计算出的值,代码中对于的变量叫做lambda1,lambda2。
lambda2.setTo(nextafterf(0, 1), lambda2 == 0);
Mat Rb = lambda1.mul(1.0/lambda2);
Rb = Rb.mul(Rb);
Mat S2 = lambda1.mul(lambda1) + lambda2.mul(lambda2);
Mat tmp1, tmp2;
exp(-Rb/beta, tmp1);
exp(-S2/c, tmp2);
float c = 2*opts.BetaTwo*opts.BetaTwo;