【OpenCV】SIFT原理与源码分析:DoG尺度空间构造
原文地址:http://blog.csdn.net/xiaowei_cqu/article/details/8067881
尺度空间理论
尺度越大图像越模糊。
为什么要讨论尺度空间?
图像的尺度空间表达就是图像在所有尺度下的描述。
尺度空间表达与金字塔多分辨率表达
高斯模糊
高斯核是唯一可以产生多尺度空间的核(《Scale-space theory: A basic tool for analysing structures at different scales》)。一个图像的尺度空间L(x,y,σ) ,定义为原始图像I(x,y)与一个可变尺度的2维高斯函数G(x,y,σ)卷积运算。
二维空间高斯函数:

尺度空间:

尺度是自然客观存在的,不是主观创造的。高斯卷积只是表现尺度空间的一种形式。
二维空间高斯函数是等高线从中心成正太分布的同心圆:

分布不为零的点组成卷积阵与原始图像做变换,即每个像素值是周围相邻像素值的高斯平均。一个5*5的高斯模版如下所示:

高斯模版是圆对称的,且卷积的结果使原始像素值有最大的权重,距离中心越远的相邻像素值权重也越小。
在实际应用中,在计算高斯函数的离散近似时,在大概3σ距离之外的像素都可以看作不起作用,这些像素的计算也就可以忽略。所以,通常程序只计算(6σ+1)*(6σ+1)就可以保证相关像素影响。
高斯模糊另一个很厉害的性质就是线性可分:使用二维矩阵变换的高斯模糊可以通过在水平和竖直方向各进行一维高斯矩阵变换相加得到。

O(N^2*m*n)次乘法就缩减成了O(N*m*n)+O(N*m*n)次乘法。(N为高斯核大小,m,n为二维图像高和宽)
其实高斯这一部分只需要简单了解就可以了,在OpenCV也只需要一句代码:
- GaussianBlur(dbl, dbl, Size(), sig_diff, sig_diff);
GaussianBlur(dbl, dbl, Size(), sig_diff, sig_diff);
我这里详写了一下是因为这块儿对分析算法效率比较有用,而且高斯模糊的算法真的很漂亮~
金字塔多分辨率
金字塔是早期图像多尺度的表示形式。图像金字塔化一般包括两个步骤:使用低通滤波器平滑图像;对平滑图像进行降采样(通常是水平,竖直方向1/2),从而得到一系列尺寸缩小的图像。

上图中(a)是对原始信号进行低通滤波,(b)是降采样得到的信号。
而对于二维图像,一个传统的金字塔中,每一层图像由上一层分辨率的长、宽各一半,也就是四分之一的像素组成:

多尺度和多分辨率
尺度空间表达和金字塔多分辨率表达之间最大的不同是:
- 尺度空间表达是由不同高斯核平滑卷积得到,在所有尺度上有相同的分辨率;
- 而金字塔多分辨率表达每层分辨率减少固定比率。
DoG(Difference of Gaussian)
高斯拉普拉斯LoG金字塔

高斯差分DoG金字塔
的近似。SIFT算法建议,在某一尺度上的特征检测可以通过对两个相邻高斯尺度空间的图像相减,得到DoG的响应值图像D(x,y,σ)。然后仿照LoG方法,通过对响应值图像D(x,y,σ)进行局部最大值搜索,在空间位置和尺度空间定位局部特征点。其中:

金字塔构建
构建高斯金字塔

- // 构建nOctaves组(每组nOctaves+3层)高斯金字塔
- void SIFT::buildGaussianPyramid( const Mat& base, vector<Mat>& pyr, int nOctaves ) const
- {
- vector<double> sig(nOctaveLayers + 3);
- pyr.resize(nOctaves*(nOctaveLayers + 3));
- // precompute Gaussian sigmas using the following formula:
- // \sigma_{total}^2 = \sigma_{i}^2 + \sigma_{i-1}^2、
- // 计算对图像做不同尺度高斯模糊的尺度因子
- sig[0] = sigma;
- double k = pow( 2., 1. / nOctaveLayers );
- for( int i = 1; i < nOctaveLayers + 3; i++ )
- {
- double sig_prev = pow(k, (double)(i-1))*sigma;
- double sig_total = sig_prev*k;
- sig[i] = std::sqrt(sig_total*sig_total - sig_prev*sig_prev);
- }
- for( int o = 0; o < nOctaves; o++ )
- {
- // DoG金子塔需要nOctaveLayers+2层图像来检测nOctaves层尺度
- // 所以高斯金字塔需要nOctaveLayers+3层图像得到nOctaveLayers+2层DoG金字塔
- for( int i = 0; i < nOctaveLayers + 3; i++ )
- {
- // dst为第o组(Octave)金字塔
- Mat& dst = pyr[o*(nOctaveLayers + 3) + i];
- // 第0组第0层为原始图像
- if( o == 0 && i == 0 )
- dst = base;
- // base of new octave is halved image from end of previous octave
- // 每一组第0副图像时上一组倒数第三幅图像隔点采样得到
- else if( i == 0 )
- {
- const Mat& src = pyr[(o-1)*(nOctaveLayers + 3) + nOctaveLayers];
- resize(src, dst, Size(src.cols/2, src.rows/2),
- 0, 0, INTER_NEAREST);
- }
- // 每一组第i副图像是由第i-1副图像进行sig[i]的高斯模糊得到
- // 也就是本组图像在sig[i]的尺度空间下的图像
- else
- {
- const Mat& src = pyr[o*(nOctaveLayers + 3) + i-1];
- GaussianBlur(src, dst, Size(), sig[i], sig[i]);
- }
- }
- }
- }
// 构建nOctaves组(每组nOctaves+3层)高斯金字塔
void SIFT::buildGaussianPyramid( const Mat& base, vector<Mat>& pyr, int nOctaves ) const
{
vector<double> sig(nOctaveLayers + 3);
pyr.resize(nOctaves*(nOctaveLayers + 3)); // precompute Gaussian sigmas using the following formula:
// \sigma_{total}^2 = \sigma_{i}^2 + \sigma_{i-1}^2、
// 计算对图像做不同尺度高斯模糊的尺度因子
sig[0] = sigma;
double k = pow( 2., 1. / nOctaveLayers );
for( int i = 1; i < nOctaveLayers + 3; i++ )
{
double sig_prev = pow(k, (double)(i-1))*sigma;
double sig_total = sig_prev*k;
sig[i] = std::sqrt(sig_total*sig_total - sig_prev*sig_prev);
} for( int o = 0; o < nOctaves; o++ )
{
// DoG金子塔需要nOctaveLayers+2层图像来检测nOctaves层尺度
// 所以高斯金字塔需要nOctaveLayers+3层图像得到nOctaveLayers+2层DoG金字塔
for( int i = 0; i < nOctaveLayers + 3; i++ )
{
// dst为第o组(Octave)金字塔
Mat& dst = pyr[o*(nOctaveLayers + 3) + i];
// 第0组第0层为原始图像
if( o == 0 && i == 0 )
dst = base; // base of new octave is halved image from end of previous octave
// 每一组第0副图像时上一组倒数第三幅图像隔点采样得到
else if( i == 0 )
{
const Mat& src = pyr[(o-1)*(nOctaveLayers + 3) + nOctaveLayers];
resize(src, dst, Size(src.cols/2, src.rows/2),
0, 0, INTER_NEAREST);
}
// 每一组第i副图像是由第i-1副图像进行sig[i]的高斯模糊得到
// 也就是本组图像在sig[i]的尺度空间下的图像
else
{
const Mat& src = pyr[o*(nOctaveLayers + 3) + i-1];
GaussianBlur(src, dst, Size(), sig[i], sig[i]);
}
}
}
}





构建DoG金字塔

- // 构建nOctaves组(每组nOctaves+2层)高斯差分金字塔
- void SIFT::buildDoGPyramid( const vector<Mat>& gpyr, vector<Mat>& dogpyr ) const
- {
- int nOctaves = (int)gpyr.size()/(nOctaveLayers + 3);
- dogpyr.resize( nOctaves*(nOctaveLayers + 2) );
- for( int o = 0; o < nOctaves; o++ )
- {
- for( int i = 0; i < nOctaveLayers + 2; i++ )
- {
- // 第o组第i副图像为高斯金字塔中第o组第i+1和i组图像相减得到
- const Mat& src1 = gpyr[o*(nOctaveLayers + 3) + i];
- const Mat& src2 = gpyr[o*(nOctaveLayers + 3) + i + 1];
- Mat& dst = dogpyr[o*(nOctaveLayers + 2) + i];
- subtract(src2, src1, dst, noArray(), CV_16S);
- }
- }
- }
// 构建nOctaves组(每组nOctaves+2层)高斯差分金字塔
void SIFT::buildDoGPyramid( const vector<Mat>& gpyr, vector<Mat>& dogpyr ) const
{
int nOctaves = (int)gpyr.size()/(nOctaveLayers + 3);
dogpyr.resize( nOctaves*(nOctaveLayers + 2) ); for( int o = 0; o < nOctaves; o++ )
{
for( int i = 0; i < nOctaveLayers + 2; i++ )
{
// 第o组第i副图像为高斯金字塔中第o组第i+1和i组图像相减得到
const Mat& src1 = gpyr[o*(nOctaveLayers + 3) + i];
const Mat& src2 = gpyr[o*(nOctaveLayers + 3) + i + 1];
Mat& dst = dogpyr[o*(nOctaveLayers + 2) + i];
subtract(src2, src1, dst, noArray(), CV_16S);
}
}
}
这个比较简单,就是一个subtract()函数。
至此,SIFT第一步就完成了。参见《SIFT原理与源码分析》
【OpenCV】SIFT原理与源码分析:DoG尺度空间构造的更多相关文章
- OpenCV SIFT原理与源码分析
http://blog.csdn.net/xiaowei_cqu/article/details/8069548 SIFT简介 Scale Invariant Feature Transform,尺度 ...
- 【OpenCV】SIFT原理与源码分析
SIFT简介 Scale Invariant Feature Transform,尺度不变特征变换匹配算法,是由David G. Lowe在1999年(<Object Recognition f ...
- 【OpenCV】SIFT原理与源码分析:关键点搜索与定位
<SIFT原理与源码分析>系列文章索引:http://www.cnblogs.com/tianyalu/p/5467813.html 由前一步<DoG尺度空间构造>,我们得到了 ...
- 【OpenCV】SIFT原理与源码分析:关键点描述
<SIFT原理与源码分析>系列文章索引:http://www.cnblogs.com/tianyalu/p/5467813.html 由前一篇<方向赋值>,为找到的关键点即SI ...
- 【OpenCV】SIFT原理与源码分析:方向赋值
<SIFT原理与源码分析>系列文章索引:http://www.cnblogs.com/tianyalu/p/5467813.html 由前一篇<关键点搜索与定位>,我们已经找到 ...
- OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波
http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...
- ConcurrentHashMap实现原理及源码分析
ConcurrentHashMap实现原理 ConcurrentHashMap源码分析 总结 ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对Ha ...
- HashMap和ConcurrentHashMap实现原理及源码分析
HashMap实现原理及源码分析 哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表, ...
- (转)ReentrantLock实现原理及源码分析
背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...
随机推荐
- UDP协议
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512 更多请看专栏, ...
- HDU - 1542 Atlantis(线段树求面积并)
https://cn.vjudge.net/problem/HDU-1542 题意 求矩形的面积并 分析 点为浮点数,需要离散化处理. 给定一个矩形的左下角坐标和右上角坐标分别为:(x1,y1).(x ...
- python -- conda pytorch
Linux上用anaconda安装pytorch Pytorch是一个非常优雅的深度学习框架.使用anaconda可以非常方便地安装pytorch.下面我介绍一下用anaconda安装pytorch的 ...
- vue-router 编程式路由
$route -> 使用它的属性 $router-> 使用它的方法 编程式的导航,即js控制跳转 //声明式:<router-link :to="..."> ...
- javascript在计算浮点数(小数)不准确,解决方案
方案来自网络,实现简单,便于做加减乘除使用,由于项目临时要用记录下 如需要更加复杂的计算类库,可以考虑 math.js等知名类库 /** * floatTool 包含加减乘除四个方法,能确保浮点数运算 ...
- Word设置多级标题
选中标题1的内容,点击编号图标,选中一个经典的编号模板,如下图 之后,再次点击编号图标,然后选中“定义新的多级列表”,将打开一个对话框 确保标题1的标号正确: 接着,确保标题2的编号正确: 依次类推, ...
- TCP时间获取程序
一.服务器程序 #include <stdio.h> #include <stdlib.h> #include <time.h> #include <sys/ ...
- 二十三、Linux 进程与信号---进程链和进程扇、守护进程和孤儿进程以及僵尸进程
23.1 进程链和进程扇 23.1.1 概念 进程链:一个父进程构建出一个子进程,子进程再构建出子子进程,子子进程构建出子子子进程.... 这种就为进程链 进程扇:一个父进程构建出多个子进程,子进程都 ...
- 机器学习等知识--- map/reduce, python 读json数据。。。
map/ reduce 了解: 简单介绍map/reduce 模式: http://www.csdn.net/article/2013-01-07/2813477-confused-about-map ...
- [C++]油田(Oil Deposits)-用DFS求连通块
[本博文非博主原创,均摘自:刘汝佳<算法竞赛入门经典>(第2版) 6.4 图] [程序代码根据书中思路,非独立实现] 例题6-12 油田(Oil Deposits,UVa572) 输入一个 ...