当前位置: 首页 > article >正文

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 算法的高效实现,使其在实际应用中得到了广泛的使用。

相关文章:

SIFT 算法原理详解

SIFT 算法原理详解 SIFT&#xff08;尺度不变特征变换&#xff0c;Scale-Invariant Feature Transform&#xff09;是一种经典的局部特征检测和描述算法&#xff0c;它能够在不同的尺度、旋转和光照变化下稳定地检测图像特征。SIFT 主要包括以下几个步骤&#xff1a;尺度空间极…...

基于springboot的益智游戏系统的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…...

短剧系统开发文案:打造沉浸式互动娱乐新体验

一、项目背景 随着短视频与碎片化娱乐的兴起&#xff0c;短剧市场呈现爆发式增长。用户对剧情紧凑、节奏明快、互动性强的内容需求激增&#xff0c;传统影视平台已难以满足个性化与参与感需求。「XX短剧系统」应运而生&#xff0c;致力于打造集内容创作、分发、互动于一体的短…...

第十二节:第四部分:集合框架:List系列集合:LinkedList集合的底层原理、特有方法、栈、队列

LinkedList集合的底层原理 LinkedList集合的应用场景之一 代码&#xff1a;掌握LinkedList集合的使用 package com.itheima.day19_Collection_List;import java.util.LinkedList; import java.util.List;//掌握LinkedList集合的使用。 public class ListTest3 {public static …...

多模态大语言模型arxiv论文略读(104)

Talk Less, Interact Better: Evaluating In-context Conversational Adaptation in Multimodal LLMs ➡️ 论文标题&#xff1a;Talk Less, Interact Better: Evaluating In-context Conversational Adaptation in Multimodal LLMs ➡️ 论文作者&#xff1a;Yilun Hua, Yoav…...

【C++高级主题】多重继承下的类作用域

目录 一、类作用域与名字查找规则&#xff1a;理解二义性的根源 1.1 类作用域的基本概念 1.2 单继承的名字查找流程 1.3 多重继承的名字查找特殊性 1.4 关键规则&#xff1a;“最近” 作用域优先&#xff0c;但多重继承无 “最近” 二、多重继承二义性的典型类型与代码示…...

基于Android的一周穿搭APP的设计与实现 _springboot+vue

开发语言&#xff1a;Java框架&#xff1a;springboot AndroidJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.6 系统展示 APP登录 A…...

机器学习——使用多个决策树

使用单一决策树的弱点之一是决策树对数据中的微小变化非常敏感&#xff0c;一个使算法不那么敏感或更健壮的解决方案&#xff0c;不是建立一个决策树&#xff0c;而是要建立大量的决策树&#xff0c;我们称之为树合奏。 在这个例子中&#xff0c;我们一直在使用最好的特性来分…...

C# 中的对话框与导航:构建流畅用户交互的完整指南

在现代应用程序开发中&#xff0c;良好的用户交互体验是成功的关键因素之一。作为.NET开发者&#xff0c;熟练掌握C#中的对话框与导航技术&#xff0c;能够显著提升应用程序的易用性和专业性。本文将全面探讨Windows Forms、WPF、ASP.NET Core和MAUI等平台下的对话框与导航实现…...

DeepSeek - 尝试一下GitHub Models中的DeepSeek

1.简单介绍 当前DeepSeek使用的人很多&#xff0c;各大AI平台中也快速引入了DeekSeek&#xff0c;比如Azure AI Foundary(以前名字是Azure AI Studio)中的Model Catalog, HuggingFace, GitHub Models等。同时也出现了一些支持DeepSeek的.NET类库。微软的Semantic Kernel也支持…...

【判断酒酒花数】2022-3-31

缘由对超长正整数的处理&#xff1f; - C语言论坛 - 编程论坛 void 判断酒酒花数(_int64 n) {//缘由https://bbs.bccn.net/thread-508634-1-1.html_int64 t n; int h 0, j 0;//while (j < 3)h t % 10, t / 10, j;//整数的个位十位百位之和是其前缀while (t > 0)h t…...

对称加密-非对称加密

目录 非对称加密算法的优缺点是什么&#xff1f; ​一、非对称加密的核心特点​ ​二、非对称加密的显著优点​ 1. ​解决密钥分发难题​ 2. ​支持数字签名​ 3. ​前向安全性​ 4. ​访问控制灵活性​ ​三、非对称加密的局限性​ 1. ​性能瓶颈​ 2. ​密钥长度要…...

【OCCT+ImGUI系列】011-Poly-Poly_Triangle三角形面片

Poly_Triangle 是什么&#xff1f; Poly_Triangle 是一个非常轻量的类&#xff0c;用于表示一个三角网格中的单个三角形面片。它是构成 Poly_Triangulation&#xff08;三角网格对象&#xff09;的基本单位之一。之后会写关于碰撞检测的相关文章&#xff0c;三角面片是非常重要…...

【机器学习基础】机器学习入门核心算法:Mini-Batch K-Means算法

机器学习入门核心算法&#xff1a;Mini-Batch K-Means算法 一、算法逻辑工作流程与传统K-Means对比 二、算法原理与数学推导1. 目标函数2. Mini-Batch更新规则3. 学习率衰减机制4. 伪代码 三、模型评估1. 内部评估指标2. 收敛性判断3. 超参数调优 四、应用案例1. 图像处理 - 颜…...

机器学习实战36-基于遗传算法的水泵调度优化项目研究与代码实现

大家好,我是微学AI,今天给大家介绍一下机器学习实战36-基于遗传算法的水泵调度优化项目研究与代码实现。 文章目录 一、项目介绍二、项目背景三、数学原理与算法分析动态规划模型遗传算法设计编码方案适应度函数约束处理算法参数能量消耗模型一泵房能耗二泵房能耗效率计算模…...

计算机视觉与深度学习 | 基于Matlab的门禁指纹识别与人脸识别双系统实现

系统架构 #mermaid-svg-d8CEMhB3dNDpJu8M {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-d8CEMhB3dNDpJu8M .error-icon{fill:#552222;}#mermaid-svg-d8CEMhB3dNDpJu8M .error-text{fill:#552222;stroke:#552222;}#…...

TypeScript 定义同步方法

在TypeScript中定义同步方法是一个常见的需求&#xff0c;尤其是在处理不涉及异步操作的情况下。本文将详细介绍如何在TypeScript中定义和使用同步方法&#xff0c;包括代码示例和详细解释。 一、定义同步方法 在TypeScript中&#xff0c;定义同步方法与JavaScript类似&#…...

debian12.9或ubuntu,vagrant离线安装插件vagrant-libvirt,20250601

系统盘: https://mirror.lzu.edu.cn/debian-cd/12.9.0/amd64/iso-dvd/debian-12.9.0-amd64-DVD-1.iso 需要的依赖包,无需安装ruby( sudo apt install -y ruby-full ruby-dev rubygems,后来发现不安装会有编译警告,还是安装吧 ) ,无需安装 zlib1g-dev liblzma-dev libxml2-de…...

【仿muduo库实现并发服务器】使用正则表达式提取HTTP元素

使用正则表达式提取HTTP元素 1.正则表达式2.正则库的使用3.使用正则表达式提取HTTP请求行 1.正则表达式 正则表达式它其实是描述了一种字符串匹配的模式&#xff0c;它可以用来在一个字符串中检测一个特定格式的字串&#xff0c;以及可以将符合特定规则的字串进行替换或者提取…...

核心机制:流量控制

搭配滑动窗口使用的 窗口大小 窗口越大,传输速度就越快,但是也不能无限大,太大了,对于可靠性会有影响 比如发生方以非常快的速度,发送,接收方的处理速度跟不上,也就会导致有效数据被接受方丢弃(又得重传) 流量控制,就是根据接收方的处理能力(如何衡量?),干预到发送方的发送…...

Java中并发修改异常如何处理

在 Java 中&#xff0c;ConcurrentModificationException&#xff08;并发修改异常&#xff09; 是遍历集合时最常见的错误之一。它发生在迭代过程中直接修改集合结构&#xff08;添加/删除元素&#xff09;时&#xff0c;与是否多线程无关。以下是详细的处理方案&#xff1a; …...

极智项目 | 基于PyQT实现的YOLOv12行人目标检测软件设计

基于YOLOv12的专业级行人目标检测软件应用 开发者: 极智视界 软件下载&#xff1a;链接 &#x1f31f; 项目特色 专业检测: 基于最新YOLOv12模型&#xff0c;专门针对行人检测优化现代界面: 采用PyQt5构建的美观、直观的图形用户界面高性能: 支持GPU加速&#xff0c;检测速…...

JavaScript 对象展开语法

文章目录 JavaScript 对象展开语法1、对象展开&#xff08;Spread&#xff09;操作&#xff1a;2、组件注册3、示例应用总结 JavaScript 对象展开语法 示例代码&#xff1a; export default {...student,components: {ConponentA: ConponentA,ConponentB: ConponentB},这段代…...

简单transformer运用

通俗易懂解读&#xff1a;hw04.py 文件内容与 Transformer 的应用 这个文件是一个 Python 脚本&#xff08;hw04.py&#xff09;&#xff0c;用于完成 NTU 2021 Spring 机器学习课程的 HW4 作业任务&#xff1a;扬声器分类&#xff08;Speaker Classification&#xff09;。它…...

vscode不满足先决条件问题的解决——vscode的老版本安装与禁止更新(附安装包)

目录 起因 vscode更新设置的关闭 安装包 结语 起因 由于主包用的系统是centos的&#xff0c;且版本有点老了&#xff0c;再加上vscode现在不支持老版本的&#xff0c;这对主包来说更是雪上加霜啊 但是主包看了网上很多教程&#xff0c;眼花缭乱&#xff0c;好多配置要改&…...

RustDesk 搭建自建服务器并设置服务自启动

目录 0. 介绍 1. 事前准备 1.1 有公网 ip 的云服务器一台 1.2 服务端部署包 1.3 客户端安装包 2. 部署 2.1 服务器环境准备 2.2 上传服务端部署包 2.3 运行 pm2 3. 客户端使用 3.1 安装 3.2 配置 3.2.1 解锁网络设置 3.2.2 ID / 中级服务器 3.3 启动效果 > …...

【数据库】数据库恢复技术

数据库恢复技术 实现恢复的核心是使用冗余&#xff0c;也就是根据冗余数据重建不正确数据。 事务 事务是一个数据库操作序列&#xff0c;是一个不可分割的工作单位&#xff0c;是恢复和并发的基本单位。 在关系数据库中&#xff0c;一个事务是一条或多条SQL语句&#xff0c…...

Qt企业级串口通信实战:高效稳定的工业级应用开发指南

目录 一、前言 二、问题代码剖析 2.1 典型缺陷示例 2.2 企业级应用必备特性对比 三、关键优化策略与代码实现 3.1 增强型串口管理类 问题1&#xff1a;explicit关键字的作用 3.2 智能错误恢复机制 3.3 数据分帧处理算法 四、性能优化实测数据 五、工业级应用场景 六…...

力扣HOT100之动态规划:32. 最长有效括号

这道题放在动态规划里属实是有点难为人了&#xff0c;感觉用动态规划来做反而更难理解了&#xff0c;这道题用索引栈来做相当好理解&#xff0c;这里先讲下索引栈的思路。 索引栈做法 我们定义一个存放整数的栈&#xff0c;定义一个全局变量result来记录最长有效子串的长度&a…...

深入理解前端DOM:现代Web开发的基石

什么是DOM&#xff1f; DOM&#xff08;Document Object Model&#xff0c;文档对象模型&#xff09;是Web开发中最重要的概念之一。它是一个跨平台、语言独立的接口&#xff0c;将HTML或XML文档表示为树形结构&#xff0c;其中每个节点都是文档的一个部分&#xff08;如元素、…...