先导知识:高斯模糊
疑问:上图中的c和d?
我本来比较疑惑这一点,就是c所示的灰色的边界是经怎样处理得到的,d中细细的黑边又是怎样得到的?然后我搜了一下,这篇博客解决了我的疑惑:
高斯模糊实现小结_高斯模糊边缘处理_zddhub的博客-CSDN博客
代码如下(搬运,侵删)
高斯核确定(灰色边缘):
//高斯平滑
//未使用sigma,边缘无处理
void GaussianTemplateSmooth(const Mat &src, Mat &dst, double sigma)
{
//高斯模板(7*7),sigma = 0.84089642,归一化后得到
static const double gaussianTemplate[7][7] =
{
{0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067},
{0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292},
{0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117},
{0.00038771, 0.01330373, 0.11098164, 0.22508352, 0.11098164, 0.01330373, 0.00038771},
{0.00019117, 0.00655965, 0.05472157, 0.11098164, 0.05472157, 0.00655965, 0.00019117},
{0.00002292, 0.00078633, 0.00655965, 0.01330373, 0.00655965, 0.00078633, 0.00002292},
{0.00000067, 0.00002292, 0.00019117, 0.00038771, 0.00019117, 0.00002292, 0.00000067}
};
dst.create(src.size(), src.type());
uchar* srcData = src.data;
uchar* dstData = dst.data;
for(int j = 0; j < src.cols-7; j++)
{
for(int i = 0; i < src.rows-7; i++)
{
double acc = 0;
double accb = 0, accg = 0, accr = 0;
for(int m = 0; m < 7; m++)
{
for(int n = 0; n < 7; n++)
{
if(src.channels() == 1)
acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * gaussianTemplate[m][n];
else
{
accb += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 0) * gaussianTemplate[m][n];
accg += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 1) * gaussianTemplate[m][n];
accr += *(srcData + src.step * (i+n) + src.channels() * (j+m) + 2) * gaussianTemplate[m][n];
}
}
}
if(src.channels() == 1)
*(dstData + dst.step * (i+3) + dst.channels() * (j+3))=(int)acc;
else
{
*(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 0)=(int)accb;
*(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 1)=(int)accg;
*(dstData + dst.step * (i+3) + dst.channels() * (j+3) + 2)=(int)accr;
}
}
}
}
由sigma求高斯核(灰色边缘):
void GaussianSmooth2D(const Mat &src, Mat &dst, double sigma)
{
if(src.channels() != 1)
return;
//确保sigma为正数
sigma = sigma > 0 ? sigma : 0;
//高斯核矩阵的大小为(6*sigma+1)*(6*sigma+1)
//ksize为奇数
int ksize = cvRound(sigma * 3) * 2 + 1;
// dst.create(src.size(), src.type());
if(ksize == 1)
{
src.copyTo(dst);
return;
}
dst.create(src.size(), src.type());
//计算高斯核矩阵
double *kernel = new double[ksize*ksize];
double scale = -0.5/(sigma*sigma);
const double PI = 3.141592653;
double cons = -scale/PI;
double sum = 0;
for(int i = 0; i < ksize; i++)
{
for(int j = 0; j < ksize; j++)
{
int x = i-(ksize-1)/2;
int y = j-(ksize-1)/2;
kernel[i*ksize + j] = cons * exp(scale * (x*x + y*y));
sum += kernel[i*ksize+j];
// cout << " " << kernel[i*ksize + j];
}
// cout <<endl;
}
//归一化
for(int i = ksize*ksize-1; i >=0; i--)
{
*(kernel+i) /= sum;
}
//图像卷积运算,无边缘处理
for(int j = 0; j < src.cols-ksize; j++)
{
for(int i = 0; i < src.rows-ksize; i++)
{
double acc = 0;
for(int m = 0; m < ksize; m++)
{
for(int n = 0; n < ksize; n++)
{
acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * kernel[m*ksize+n];
}
}
*(dstData + dst.step * (i + (ksize - 1)/2) + (j + (ksize -1)/2)) = (int)acc;
}
}
delete []kernel;
}
换成边缘处理(细细的黑边):
int center = (ksize-1) /2;
//图像卷积运算,处理边缘
for(int j = 0; j < src.cols; j++)
{
for(int i = 0; i < src.rows; i++)
{
double acc = 0;
for(int m = -center, c = 0; m <= center; m++, c++)
{
for(int n = -center, r = 0; n <= center; n++, r++)
{
if((i+n) >=0 && (i+n) < src.rows && (j+m) >=0 && (j+m) < src.cols)
{
acc += *(srcData + src.step * (i+n) + src.channels() * (j+m)) * kernel[r*ksize+c];
}
}
}
*(dstData + dst.step * (i) + (j)) = (int)acc;
}
}
我基础不好,也不怎么聪明,直接看看不明白,就写了写画了画:
以上是关于高斯模糊,实际用opencv就一句代码就解决了。
Sift第一步—— 建立高斯差分金字塔
1.尺度空间
2.高斯金字塔
尺度空间在实现时,可用高斯金字塔来表示。
高斯金字塔的两步构建:对图像做不同尺度的高斯模糊;对图像进行降采样(隔点取点)
- 同一组 或者叫 同一大层(Octave):(不同层)图像间进行高斯模糊的sigma不同;由下到上,sigma变大,图像变模糊。
- 不同组:下一组图像对上一层图像进行降采样(又叫下采样)得到。
那么,为什么要构建这么一个尺度空间呢?
答:高斯金字塔可以模拟人眼,人眼看东西是近大远小,近处清晰 远处模糊的。上图中,从下面看这些图像,发现离得越近越清晰,越远越模糊,自然也是近处图像大,远处图像小的。那么为什么要模拟人眼呢?人眼可以发现不同姿态、处于不同光照等条件下的物体是同一个,因为这个物体具有同样的局部特征,人眼可以迅速匹配,sift正是要找图像的局部特征的。
(图片来源于网络,侵删)
接下来要思考的,便是这个高斯金字塔要设多少组,每组要设多少层,用于高斯模糊的高斯核sigma要设为多少?
一些参数:
- 组数(大层数):
其中,M、N为原图像的大小,t为塔顶图像的最小维数的对数值。如,对于512*512大小的图像,如果塔顶图像为4*4,t为2,则n=9-2=7,分为7组(大层)图像。
- 每组的层数:s+3(s 是 高斯差分金字塔 想要从多少张图像中检测极值点)
- sigma:
3.高斯差分金字塔
两层直接减:
特征点就在高斯差分金字塔里找,这就是第二步的内容了
--------------------------------------------------------------------------------------------------------------------------------
Lowe,D 的SIFT代码(初始图像高斯金字塔最底层图像)
我们想要得到的高斯金字塔最底层图像是自然图像经1.6的高斯核模糊得到的,而我们手头上的是相机捕捉到的初始图像(可看作自然图像经0.5的高斯核模糊得到)。没错,就是这个样子。所以,我们想要得到高斯金字塔最底层图像,还要经过处理。
//生成高斯金字塔最底层图像
/*
Converts an image to 8-bit grayscale and Gaussian-smooths it. The image is
optionally doubled in size prior to smoothing.
@param img input image(初始图像,经相机捕捉得到)
@param img_dbl if true, image is doubled in size prior to smoothing(控制高斯金字塔最低层图像由哪种方式得到)
img_dbl不为0的话,将初始图像放大两倍再进行高斯模糊(这里调用时,就将其设为1)
img_del为0的话,对初始图像进行高斯系数为1.52的高斯模糊
@param sigma total std of Gaussian smoothing(sigma为1.6,想要高斯金字塔最底层图像的模糊系数)
*/
static IplImage* create_init_img( IplImage* img, int img_dbl, double sigma )
{
IplImage* gray;//灰度图
IplImage* dbl;//放大灰度图
double sig_diff;//
gray = convert_to_gray32( img ); //调用函数convert_to_gray32。将原始图像转化为32位灰度图
if( img_dbl ) //初始图像处理方法1
{
sig_diff = sqrt( sigma * sigma - SIFT_INIT_SIGMA * SIFT_INIT_SIGMA * 4 ); //SIFT_INIT_SIGMA为0.5,sqrt(1.6^2+(2*0.5)^2)=1.25
dbl = cvCreateImage( cvSize( img->width*2, img->height*2 ),
IPL_DEPTH_32F, 1 ); //创建双倍img大小 单通道 32位灰度图空间
cvResize( gray, dbl, CV_INTER_CUBIC );//放大图像,输入图像gray,输出dbl,放大方法为双立方插值
cvSmooth( dbl, dbl, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff );//分离高斯模糊,输入图像为dbl,输出图像为dbl,高斯核为sig_diff
cvReleaseImage( &gray );//释放灰度图空间
return dbl; //dbl就视为高斯金字塔最底层图像
}
else //初始图像处理方法2
{
sig_diff = sqrt( sigma * sigma - SIFT_INIT_SIGMA * SIFT_INIT_SIGMA );//sqrt(1.6^2-0.5^2)
cvSmooth( gray, gray, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff );//分离高斯模糊,输入图像为dbl,输出图像为dbl,高斯核为sig_diff
return gray;//gray就视为高斯金字塔最底层图像
}
}
//转为32位灰度图
/*
Converts an image to 32-bit grayscale
@param img a 3-channel 8-bit color (BGR) or 8-bit gray image
@return Returns a 32-bit grayscale image
*/
static IplImage* convert_to_gray32( IplImage* img )
{
IplImage* gray8, * gray32;
gray32 = cvCreateImage( cvGetSize(img), IPL_DEPTH_32F, 1 );//创建img大小 单通道 32位灰度图空间
if( img->nChannels == 1 ) //如果初始图像的通道数为1,说明输入图像为8位灰度图
gray8 = cvClone( img );//图像克隆,直接复制
else //初始图像的通道数为3,说明输入图像位RGB图像
{
gray8 = cvCreateImage( cvGetSize(img), IPL_DEPTH_8U, 1 );//创建img大小 单通道 8位灰度图空间
cvCvtColor( img, gray8, CV_BGR2GRAY );//将RGB图像转为8位灰度图,输入图像为img,输入图像为gray8
}
cvConvertScale( gray8, gray32, 1.0 / 255.0, 0 ); //将8位灰度图转为32位灰度图,比例因子是1/255,偏移量为0
cvReleaseImage( &gray8 );//释放8位灰度图空间
return gray32; //返回32位灰度图
}
Lowe,D 的SIFT代码(建立高斯金字塔)
//建立高斯金子塔
/*
Builds Gaussian scale space pyramid from an image
@param base base image of the pyramid(最底层图像)
@param octvs number of octaves of scale space(高斯金字塔的组数/大层数)
@param intvls number of intervals per octave(想要从高斯差分金字塔的多少张图片中提取特征,高斯金字塔每组的层数intvls+3)
@param sigma amount of Gaussian smoothing per octave(sigma为1.6)
@return Returns a Gaussian scale space pyramid as an octvs x (intvls + 3)
array
*/
static IplImage*** build_gauss_pyr( IplImage* base, int octvs,
int intvls, double sigma )
{
IplImage*** gauss_pyr; //指向高斯金字塔
const int _intvls = intvls;
double sig[_intvls+3], sig_total, sig_prev, k;
int i, o;
gauss_pyr = calloc( octvs, sizeof( IplImage** ) );//开辟高斯金字塔空间,octvs个 IplImage** 大小的空间
for( i = 0; i < octvs; i++ )
gauss_pyr[i] = calloc( intvls + 3, sizeof( IplImage *) ); //开辟高斯金字塔每组的空间,intvls+3个 IplImage* 大小的空间
/*
precompute Gaussian sigmas using the following formula:
\sigma_{total}^2 = \sigma_{i}^2 + \sigma_{i-1}^2
sig[i] is the incremental sigma value needed to compute
the actual sigma of level i. Keeping track of incremental
sigmas vs. total sigmas keeps the gaussian kernel small.
*/
//高斯模糊的参数
k = pow( 2.0, 1.0 / intvls );
sig[0] = sigma;
sig[1] = sigma * sqrt( k*k- 1 );
for (i = 2; i < intvls + 3; i++)
sig[i] = sig[i-1] * k;
for( o = 0; o < octvs; o++ )
for( i = 0; i < intvls + 3; i++ )
{
if( o == 0 && i == 0 ) //对于高斯金字塔最底层的图像
gauss_pyr[o][i] = cvCloneImage(base); //函数的输入图像直接复制过来
/* base of new octvave is halved image from end of previous octave */
else if( i == 0 ) //其余组最底层的图像
gauss_pyr[o][i] = downsample( gauss_pyr[o-1][intvls] );//调用下采样函数downsample,直接对其上一组的倒数第三层进行下采样
/* blur the current octave's last image to create the next one */
else //剩余非各组底层的图像
{
gauss_pyr[o][i] = cvCreateImage( cvGetSize(gauss_pyr[o][i-1]),
IPL_DEPTH_32F, 1 );//创建大小同其同组上一层图像大小相同 单通道 32位灰图的图像空间
cvSmooth( gauss_pyr[o][i-1], gauss_pyr[o][i],
CV_GAUSSIAN, 0, 0, sig[i], sig[i] ); //分离高斯滤波,输入图像gauss_pyr[o][i-1],输出图像gauss_pyr[o][i]
}
}
return gauss_pyr;//返回指向高斯金字塔的指针
}
这里在构建高斯金字塔各组非底层的图像时,是在它同组下一层图像的基础上,进行的高斯模糊。关于参数,说明如下:
//下采样
/*
Downsamples an image to a quarter of its size (half in each dimension)
using nearest-neighbor interpolation
@param img an image(需要进行下采样的图像)
@return Returns an image whose dimensions are half those of img
*/
static IplImage* downsample( IplImage* img )
{
IplImage* smaller = cvCreateImage( cvSize(img->width / 2, img->height / 2),
img->depth, img->nChannels ); //创建大小为原图像的一半 位深和通道数同原图像的图像空间
cvResize( img, smaller, CV_INTER_NN );//下采样(缩小图像),输入图像img,输出图像smaller,缩减方法为最近邻居插补
return smaller;
}
Lowe,D 的SIFT代码(建立高斯差分金字塔)
//建立高斯差分金字塔
/*
Builds a difference of Gaussians scale space pyramid by subtracting adjacent
intervals of a Gaussian pyramid
@param gauss_pyr Gaussian scale-space pyramid(高斯金字塔)
@param octvs number of octaves of scale space(高斯金字塔的组数/大层数)
@param intvls number of intervals per octave(想要从高斯差分金字塔的多少张图片中提取特征,高斯金字塔每组的层数intvls+3)
@return Returns a difference of Gaussians scale space pyramid as an
octvs x (intvls + 2) array
*/
static IplImage*** build_dog_pyr( IplImage*** gauss_pyr, int octvs, int intvls )
{
IplImage*** dog_pyr;
int i, o;
dog_pyr = calloc( octvs, sizeof( IplImage** ) );//开辟高斯差分金字塔空间,octvs个 IplImage** 大小的空间
for( i = 0; i < octvs; i++ )
dog_pyr[i] = calloc( intvls + 2, sizeof(IplImage*) );//开辟高斯差分金字塔每组的空间,intvls+2个 IplImage* 大小的空间
for( o = 0; o < octvs; o++ )
for( i = 0; i < intvls + 2; i++ )
{
dog_pyr[o][i] = cvCreateImage( cvGetSize(gauss_pyr[o][i]),
IPL_DEPTH_32F, 1 );//创建同高斯金字塔对应组、层的图像大小 单通道 32位灰度图像空间
cvSub( gauss_pyr[o][i+1], gauss_pyr[o][i], dog_pyr[o][i], NULL );//产生差分图像,gauss_pyr[o][i+1]-gauss_pyr[o][i]=dog_pyr[o][i]
}
return dog_pyr;