SIFT 算法原理详解

SIFT 算法原理详解

SIFT(尺度不变特征变换,Scale-Invariant Feature Transform)是一种经典的局部特征检测和描述算法,它能够在不同的尺度、旋转和光照变化下稳定地检测图像特征。SIFT 主要包括以下几个步骤:尺度空间极值检测关键点定位方向分配特征描述符生成,每个步骤的具体实现细节如下:

1. 尺度空间极值检测(Scale-space extrema detection)

尺度空间是指图像在不同尺度下的变化。SIFT 通过对图像应用不同的高斯模糊滤波器生成一系列不同尺度的图像(即尺度空间),并通过比较相邻尺度上的图像像素,寻找潜在的关键点。

具体步骤:
  1. 生成高斯金字塔:使用高斯模糊函数对图像进行多次模糊处理,得到不同尺度的图像。

    • 高斯核 G(x, y, σ) 公式为:

      G ( x , y , σ ) = 1 2 π σ 2 exp ⁡ ( − x 2 + y 2 2 σ 2 ) G(x, y, \sigma) = \frac{1}{2\pi\sigma^2} \exp\left(-\frac{x^2 + y^2}{2\sigma^2}\right) G(x,y,σ)=2πσ21exp(2σ2x2+y2)

      其中,σ 表示高斯模糊的标准差。

  2. 构建尺度空间
    通过对原始图像应用不同的高斯模糊核,得到多个不同模糊程度的图像,形成尺度空间。在每个尺度下,都通过计算图像的差分(DoG,Difference of Gaussian)来检测图像的极值点(即潜在的特征点)。

    DoG 图像是通过在连续的尺度下应用高斯差分来构建的:

    D o G ( x , y , σ ) = G ( x , y , k σ ) − G ( x , y , σ ) DoG(x, y, \sigma) = G(x, y, k\sigma) - G(x, y, \sigma) DoG(x,y,σ)=G(x,y,kσ)G(x,y,σ)

    其中,k 是常数,表示不同尺度间的倍数。

  3. 寻找极值点
    在尺度空间中,关键点是局部最大值或最小值,即在其邻域内(包括上、下两个尺度和当前尺度的8个邻域)具有极大或极小的响应值的点。

2. 关键点定位(Keypoint Localization)

通过尺度空间极值检测后,需要精确定位特征点。为了排除不稳定的低对比度点和边缘响应,SIFT 采用了一个细化步骤,基于泰勒展开和Hessian矩阵对关键点进行精确定位。

具体步骤:
  1. 低对比度点剔除:如果某个关键点的对比度低于一定阈值,则认为该点不稳定,应该剔除。
  2. 边缘响应抑制:对于边缘上的点,其信息量较少,容易受到噪声影响,需要进行抑制。SIFT 通过计算 Hessian 矩阵的条件数来判断是否为边缘点,如果条件数大于某个阈值,认为该点为边缘点,剔除掉。

3. 方向分配(Orientation Assignment)

为了提高特征点的旋转不变性,SIFT 为每个关键点分配一个或多个主方向。这是通过计算关键点邻域内的梯度方向分布来实现的。

具体步骤:
  1. 计算关键点邻域的梯度信息
    通过计算关键点邻域内每个像素点的梯度幅度和方向,得到一个方向直方图。

    • 梯度幅度:m = sqrt( (Ix)^2 + (Iy)^2 )
    • 梯度方向:θ = atan2(Iy, Ix)
  2. 生成方向直方图
    根据梯度方向信息生成一个方向直方图。直方图的每个柱表示某个特定方向的梯度总和。

  3. 主方向分配
    对直方图进行高斯加权,选取最大峰值所对应的方向作为主方向。若直方图中存在多个峰值,则为该点分配多个方向。

4. 特征描述符生成(Keypoint Descriptor)

SIFT 通过对关键点的邻域进行细致描述,生成特征描述符,这个描述符可以用来进行特征匹配。描述符是一个具有旋转、尺度不变性的向量,能够高效地进行匹配。

具体步骤:
  1. 邻域区域划分
    通过将关键点邻域区域分成若干个子区域(通常为 4x4 网格),然后计算每个子区域的梯度信息。

  2. 计算子区域的梯度
    对每个子区域内的像素计算梯度信息(幅度和方向),并对梯度进行量化。每个子区域的方向直方图具有 8 个方向,因此每个子区域可以表示为一个 8 维向量。

  3. 构建描述符
    最终将所有子区域的方向直方图拼接起来,得到一个 128 维的描述符(4x4 网格,每个网格 8 个方向)。

  4. 归一化描述符
    对描述符进行归一化,确保其具有较强的鲁棒性,并进行剪切(防止过大的值影响稳定性)。

SIFT 在OpenCV中C++ 代码实现

以下是使用 OpenCV 实现 SIFT 特征检测与描述符提取的 C++ 代码示例:

#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <vector>int main() {// 读取图像cv::Mat img = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);if (img.empty()) {std::cerr << "Image not loaded!" << std::endl;return -1;}// 创建 SIFT 特征检测器cv::Ptr<cv::SIFT> sift = cv::SIFT::create();// 关键点和描述符std::vector<cv::KeyPoint> keypoints;cv::Mat descriptors;// 检测 SIFT 特征点并计算描述符sift->detectAndCompute(img, cv::noArray(), keypoints, descriptors);// 在图像中绘制关键点cv::Mat img_keypoints;cv::drawKeypoints(img, keypoints, img_keypoints);// 显示带有关键点的图像cv::imshow("SIFT Keypoints", img_keypoints);cv::waitKey(0);return 0;
}
C++ 代码解释
  1. 加载图像:使用 cv::imread() 读取图像,图像必须是灰度图像(SIFT 更适合在灰度图上工作)。
  2. SIFT 创建器:通过 cv::SIFT::create() 创建一个 SIFT 对象。
  3. 检测和计算关键点与描述符:使用 detectAndCompute() 方法来检测关键点并计算它们的描述符。
  4. 绘制关键点:使用 cv::drawKeypoints() 在图像上绘制检测到的关键点。
  5. 显示图像:通过 cv::imshow() 显示带有关键点的图像。

简化版的 SIFT 算法C++实现,包含以下步骤

  1. 高斯金字塔生成
  2. DoG (差分高斯) 图像生成
  3. 关键点检测
  4. 方向分配
  5. 描述符生成
1. 高斯金字塔生成

首先,我们需要生成一个高斯金字塔。高斯金字塔是将图像通过不同的标准差(σ)进行高斯模糊后得到的图像序列。

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>// 高斯函数
float gaussian(float x, float y, float sigma) {return exp(-(x*x + y*y) / (2 * sigma * sigma)) / (2 * M_PI * sigma * sigma);
}// 高斯模糊
void gaussianBlur(const std::vector<std::vector<float>>& input, std::vector<std::vector<float>>& output, int radius, float sigma) {int width = input.size();int height = input[0].size();int kernelSize = 2 * radius + 1;// 计算高斯核std::vector<std::vector<float>> kernel(kernelSize, std::vector<float>(kernelSize));float sum = 0;for (int i = -radius; i <= radius; ++i) {for (int j = -radius; j <= radius; ++j) {kernel[i + radius][j + radius] = gaussian(i, j, sigma);sum += kernel[i + radius][j + radius];}}// 归一化高斯核for (int i = 0; i < kernelSize; ++i) {for (int j = 0; j < kernelSize; ++j) {kernel[i][j] /= sum;}}// 应用高斯核进行模糊for (int i = radius; i < width - radius; ++i) {for (int j = radius; j < height - radius; ++j) {float value = 0;for (int ki = -radius; ki <= radius; ++ki) {for (int kj = -radius; kj <= radius; ++kj) {value += input[i + ki][j + kj] * kernel[ki + radius][kj + radius];}}output[i][j] = value;}}
}
2. 差分高斯 (DoG) 图像生成

差分高斯 (DoG) 是通过减去相邻的两个高斯图像生成的,帮助检测图像的边缘和关键点。

void computeDoG(const std::vector<std::vector<float>>& image1, const std::vector<std::vector<float>>& image2, std::vector<std::vector<float>>& DoG) {int width = image1.size();int height = image1[0].size();for (int i = 0; i < width; ++i) {for (int j = 0; j < height; ++j) {DoG[i][j] = image1[i][j] - image2[i][j];}}
}
3. 关键点检测

关键点检测步骤使用 DoG 图像的极值来寻找可能的关键点。我们需要检查每个像素在其邻域内的极值。

struct KeyPoint {int x, y, octave, scale;
};bool isExtrema(const std::vector<std::vector<float>>& DoG, int x, int y, int width, int height) {float value = DoG[x][y];bool isMax = true;bool isMin = true;for (int dx = -1; dx <= 1; ++dx) {for (int dy = -1; dy <= 1; ++dy) {if (x + dx >= 0 && x + dx < width && y + dy >= 0 && y + dy < height) {if (DoG[x + dx][y + dy] > value) isMin = false;if (DoG[x + dx][y + dy] < value) isMax = false;}}}return isMax || isMin;
}std::vector<KeyPoint> detectKeyPoints(const std::vector<std::vector<std::vector<float>>>& DoGs, int width, int height) {std::vector<KeyPoint> keypoints;for (int octave = 0; octave < DoGs.size(); ++octave) {for (int scale = 1; scale < DoGs[octave].size() - 1; ++scale) {for (int x = 1; x < width - 1; ++x) {for (int y = 1; y < height - 1; ++y) {if (isExtrema(DoGs[octave][scale], x, y, width, height)) {keypoints.push_back(KeyPoint{x, y, octave, scale});}}}}}return keypoints;
}
4. 方向分配

每个关键点通过其邻域的梯度方向来分配一个或多个方向,使得描述符具有旋转不变性。

// 梯度计算
void computeGradients(const std::vector<std::vector<float>>& image, std::vector<std::vector<float>>& magnitude, std::vector<std::vector<float>>& direction) {int width = image.size();int height = image[0].size();for (int x = 1; x < width - 1; ++x) {for (int y = 1; y < height - 1; ++y) {float dx = image[x + 1][y] - image[x - 1][y];float dy = image[x][y + 1] - image[x][y - 1];magnitude[x][y] = sqrt(dx * dx + dy * dy);direction[x][y] = atan2(dy, dx);}}
}// 计算关键点的方向
void computeKeyPointOrientation(const std::vector<std::vector<float>>& image, int x, int y, int radius, float& orientation) {std::vector<std::vector<float>> magnitude(image.size(), std::vector<float>(image[0].size()));std::vector<std::vector<float>> direction(image.size(), std::vector<float>(image[0].size()));computeGradients(image, magnitude, direction);float sum = 0;for (int i = -radius; i <= radius; ++i) {for (int j = -radius; j <= radius; ++j) {int ix = x + i, iy = y + j;if (ix >= 0 && ix < image.size() && iy >= 0 && iy < image[0].size()) {sum += magnitude[ix][iy] * direction[ix][iy];}}}orientation = sum;
}
5. 描述符生成

描述符通过计算每个关键点的邻域内的梯度方向来生成,并将其存储在一个128维的向量中。

std::vector<float> computeDescriptor(const std::vector<std::vector<float>>& image, int x, int y, int radius) {std::vector<float> descriptor(128, 0.0f);std::vector<std::vector<float>> magnitude(image.size(), std::vector<float>(image[0].size()));std::vector<std::vector<float>> direction(image.size(), std::vector<float>(image[0].size()));computeGradients(image, magnitude, direction);int histSize = 8;for (int i = -radius; i <= radius; ++i) {for (int j = -radius; j <= radius; ++j) {int ix = x + i, iy = y + j;if (ix >= 0 && ix < image.size() && iy >= 0 && iy < image[0].size()) {int histIdx = (int)(direction[ix][iy] / (2 * M_PI) * histSize) % histSize;descriptor[histIdx] += magnitude[ix][iy];}}}return descriptor;
}
6. 总结

这是一个不依赖 OpenCV 的 SIFT 算法的简化实现。完整的 SIFT 算法实现通常会涉及更多细节,特别是在关键点定位、方向分配和描述符的构建方面。在实际使用中,OpenCV 提供了高度优化的实现,具有更高的性能和稳定性。如果需要更多细节或完整的实现,可以逐步改进上述代码,增加更多的步骤,例如边缘检测、Hessian 矩阵计算等。

总结

SIFT 算法通过多尺度图像处理、极值检测、关键点定位、旋转不变性和描述符生成等步骤,在图像特征检测和匹配中取得了重要进展。虽然它是一个计算量较大的算法,但它的鲁棒性和效果非常好,尤其适用于物体识别、图像拼接等任务。OpenCV 提供了对 SIFT 算法的高效实现,使其在实际应用中得到了广泛的使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/908012.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年认证杯SPSSPRO杯数学建模D题(第二阶段)AI绘画带来的挑战解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 D题 AI绘画带来的挑战 原题再现&#xff1a; 2023 年开年&#xff0c;ChatGPT 作为一款聊天型AI工具&#xff0c;成为了超越疫情的热门词条&#xff1b;而在AI的另一个分支——绘图领域&#xff0c;一款名为Midjourney&#xff08;MJ&#xff…

电子电路:全面深入了解晶振的定义、作用及应用

本次了解重点: 1.压电效应的数学描述 2.生产工艺以及关键工序 3.电路设计部分如负阻原理和匹配电容计算 4.失效案例比如冷启动问题 5.新形态晶振技术引入5G和量子计算 6.温补晶振的补偿机制 7故障案例讲解-更换负载电池或增加预热电路 蓝牙音频断续-频偏导致 工控机死机-起振电…

【Java实用工具类】手撸SqlBuilder工具类,优雅拼接动态SQL,MyBatisPlus同款风格!

&#x1f4cc; 正文&#xff1a; 有时候我们项目底层是 JdbcTemplate 查询&#xff0c;没法像 MyBatisPlus 一样用 Wrapper 拼接条件&#xff0c;但我们又不想手撸字符串。那怎么办&#xff1f;我今天就给你整了个 SqlBuilder 工具类&#xff0c;支持 eq、ne、like、in、gt、l…

WEB3——开发者怎么查看自己的合约日志记录

在区块链中查看合约的日志信息&#xff08;也叫事件 logs&#xff09;&#xff0c;主要有以下几种方式&#xff0c;具体方法依赖于你使用的区块链平台&#xff08;如 Ethereum、BSC、Polygon 等&#xff09;和工具&#xff08;如 Etherscan、web3.js、ethers.js、Hardhat 等&am…

Maven-生命周期

目录 1.项目对象模型 2.依赖管理模型 3.仓库&#xff1a;用于存储资源&#xff0c;管理各种jar包 4.本地仓库路径 1.项目对象模型 2.依赖管理模型 3.仓库&#xff1a;用于存储资源&#xff0c;管理各种jar包 4.本地仓库路径

redis数据过期策略

redis数据过期策略有两种方案 1.惰性删除 2.定期删除 首先说惰性删除&#xff0c;对于已经过期的数据&#xff0c;访问这个key的时候判断key是否过期&#xff0c;如果过期则删除&#xff0c;这种方式对cpu友好&#xff0c;只有使用key的时候才会进行过期检查&#xff0c;用不到…

P1040 [NOIP 2003 提高组] 加分二叉树

目录 题目算法标签: 区间 d p dp dp, 动态规划, d f s dfs dfs思路代码 题目 P1040 [NOIP 2003 提高组] 加分二叉树 算法标签: 区间 d p dp dp, 动态规划, d f s dfs dfs 思路 给出的是一颗子树的中序遍历, s c o r e l r r o o t score l \times r root scorelrro…

uni-app学习笔记十七-css和scss的使用

SCSS 和 CSS的异同点 我们可以使用css和scss来设置样式。其中SCSS&#xff08;Sassy CSS&#xff09;是 CSS 预处理器 Sass&#xff08;Syntactically Awesome Stylesheets&#xff09;的一种语法格式&#xff0c;而 CSS&#xff08;Cascading Style Sheets&#xff09;是标准…

Spring Boot中Excel处理完全指南:从基础到高级实践

Excel处理基础知识 1.1 为什么需要在应用中处理Excel文件&#xff1f; 在企业应用开发中&#xff0c;Excel文件处理是一个非常常见的需求&#xff0c;主要用于以下场景&#xff1a; 数据导入&#xff1a;允许用户通过Excel上传批量数据到系统 数据导出&#xff1a;将系统数据…

Python编程基础(四) | if语句

引言&#xff1a;很久没有写 Python 了&#xff0c;有一点生疏。这是学习《Python 编程&#xff1a;从入门到实践&#xff08;第3版&#xff09;》的课后练习记录&#xff0c;主要目的是快速回顾基础知识。 练习1&#xff1a;条件测试 编写一系列条件测试&#xff0c;将每个条…

使用pandas实现合并具有共同列的两个EXCEL表

表1&#xff1a; 表2&#xff1a; 表1和表2&#xff0c;有共同的列“名称”&#xff0c;而且&#xff0c;表1的内容&#xff08;行数&#xff09;<表2的行数。 目的&#xff0c;根据“名称”列的对应内容&#xff0c;将表2列中的“所处行业”填写到表1相应的位置。 实现代…

ERP学习-AP

业务需要。持续更新学习进度 借助网上零搭建平台上手实操 这个是简道云平台页面链接&#xff0c;登录的化去手机号登录 目前开始对应付模块进行学习

Dify知识库下载小程序

一、Dify配置 1.查看或创建知识库的API 二、下载程序配置 1. 安装依赖resquirements.txt ######requirements.txt##### flask2.3.3 psycopg2-binary2.9.9 requests2.31.0 python-dotenv1.0.0#####安装依赖 pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.…

【PbstarAdmin】微前端架构下的高效后台管理系统解决方案

如果你正在寻找一个高效、稳定、易于使用、易于扩展的管理后台解决方案&#xff0c;PbstarAdmin 绝对值得一试。以下是它的在线演示和官方文档地址&#xff0c;你可以先睹为快&#xff1a; 在线演示&#xff1a;http://pbstar-admin.pbstar.cn/官方文档&#xff1a;http://pbs…

Java基础之数组(附带Comparator)

文章目录 基础概念可变参数组数组与ListComparator类1,基本概念2,使用Comparator的静态方法&#xff08;Java 8&#xff09;3,常用Comparator方法4,例子 排序与查找数组复制其他 基础概念 int[] anArray new int[10];只有创建对象时才会使用new关键字&#xff0c;所以数组是个…

Apache Doris 在数据仓库中的作用与应用实践

在当今数字化时代&#xff0c;企业数据呈爆炸式增长&#xff0c;数据仓库作为企业数据管理和分析的核心基础设施&#xff0c;其重要性不言而喻。而 Apache Doris&#xff0c;作为一款基于 MPP&#xff08;Massively Parallel Processing&#xff0c;大规模并行处理&#xff09;…

P1438 无聊的数列/P1253 扶苏的问题

因为这两天在写线性代数的作业&#xff0c;没怎么写题…… P1438 无聊的数列 题目背景 无聊的 YYB 总喜欢搞出一些正常人无法搞出的东西。有一天&#xff0c;无聊的 YYB 想出了一道无聊的题&#xff1a;无聊的数列。。。 题目描述 维护一个数列 ai​&#xff0c;支持两种操…

SpringBoot 自定义注解实现限流

SpringBoot 自定义注解实现限流 限流是为了防止服务器资源的过度消耗&#xff0c;通过一定的策略来控制访问频率&#xff0c;确保服务的高可用性和稳定性。其核心意义在于防止流量高峰时期接口过载&#xff0c;从而引起服务崩溃或响应延迟增加。本文将简述如何通过AOP和自定义…

Unity——QFramework框架 内置工具

QFramework 除了提供了一套架构之外&#xff0c;QFramework 还提供了可以脱离架构使用的工具 TypeEventSystem、EasyEvent、BindableProperty、IOCContainer。 这些工具并不是有意提供&#xff0c;而是 QFramework 的架构在设计之初是通过这几个工具组合使用而成的。 内置工具…

Vue3.5 企业级管理系统实战(二十二):动态菜单

在前几篇内容中已完成菜单、角色及菜单权限等相关开发&#xff0c;若要在左侧菜单根据用户角色动态展示菜单&#xff0c;需对 Sidebar 中的相关数据进行修改。鉴于其他相关方法及类型已在前文实现&#xff0c;本文不再重复阐述。 1 修改 Sidebar 组件 在 src/layout/componen…