第3章 特征提取与描述¶
📚 章节概述¶
本章介绍计算机视觉中的特征提取与描述技术,包括边缘检测、角点检测、特征描述符(SIFT、SURF、ORB等)以及特征匹配方法。这些技术是传统计算机视觉的核心,也是理解深度学习特征的基础。
学习时间:5-7天 难度等级:⭐⭐⭐⭐ 前置知识:第1-2章、线性代数
🎯 学习目标¶
完成本章后,你将能够: - 理解特征提取的重要性和基本概念 - 掌握边缘检测和角点检测算法 - 熟练使用SIFT、SURF、ORB等特征描述符 - 理解特征匹配的原理和方法 - 能够实现图像配准和拼接 - 完成图像拼接项目
3.1 特征提取基础¶
3.1.1 什么是特征?¶
定义:特征是图像中具有辨识性的局部区域,能够在不同视角、光照下保持稳定。
好的特征应该具备: - 可重复性:同一物体在不同图像中能检测到 - 独特性:能够区分不同的物体 - 不变性:对旋转、缩放、光照变化鲁棒 - 高效性:计算速度快
特征层次:
3.1.2 边缘检测¶
Canny边缘检测回顾:
import cv2
import numpy as np
image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Canny边缘检测
edges = cv2.Canny(gray, 100, 200)
# 查找轮廓
contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
result = image.copy()
cv2.drawContours(result, contours, -1, (0, 255, 0), 2)
Laplacian of Gaussian (LoG):
# LoG边缘检测
gray_float = np.float32(gray)
log = cv2.Laplacian(gray_float, cv2.CV_32F, ksize=5)
log = cv2.convertScaleAbs(log)
3.1.3 角点检测¶
Harris角点检测:
# Harris角点检测
gray = np.float32(gray)
harris = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)
# 阈值处理
harris = cv2.dilate(harris, None)
threshold = 0.01 * harris.max()
corner_img = image.copy()
corner_img[harris > threshold] = [0, 0, 255]
# Shi-Tomasi角点检测(更好的方法)
corners = cv2.goodFeaturesToTrack(gray, maxCorners=100, qualityLevel=0.01, minDistance=10)
corners = np.int32(corners)
for corner in corners:
x, y = corner.ravel()
cv2.circle(corner_img, (x, y), 3, (0, 255, 0), -1)
3.2 特征描述符¶
3.2.1 SIFT (Scale-Invariant Feature Transform)¶
SIFT特点: - 尺度不变性 - 旋转不变性 - 光照不变性 - 部分视角不变性
代码实现:
import cv2
import numpy as np
# 读取图像
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')
# 转换为灰度图
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 创建SIFT检测器
sift = cv2.SIFT_create()
# 检测关键点和计算描述符
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)
# 绘制关键点
img1_kp = cv2.drawKeypoints(gray1, kp1, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
img2_kp = cv2.drawKeypoints(gray2, kp2, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# 特征匹配
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
# Lowe's ratio test
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
# 绘制匹配结果
result = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
print(f"检测到 {len(kp1)} 和 {len(kp2)} 个关键点")
print(f"匹配到 {len(good_matches)} 对匹配点")
SIFT算法步骤: 1. 尺度空间极值检测:使用DoG(Difference of Gaussian) 2. 关键点定位:精确定位关键点位置 3. 方向分配:计算主方向 4. 描述符生成:生成128维描述符
3.2.2 SURF (Speeded-Up Robust Features)¶
SURF特点: - 比SIFT快 - 使用积分图像加速 - 类似SIFT的不变性
代码实现:
# 创建SURF检测器
surf = cv2.xfeatures2d.SURF_create(hessianThreshold=400)
# 检测关键点和计算描述符
kp1, des1 = surf.detectAndCompute(gray1, None)
kp2, des2 = surf.detectAndCompute(gray2, None)
# 特征匹配(与SIFT相同)
3.2.3 ORB (Oriented FAST and Rotated BRIEF)¶
ORB特点: - 速度快(实时) - 免费专利 - 旋转不变性 - 尺度不变性(金字塔)
代码实现:
# 创建ORB检测器
orb = cv2.ORB_create(nfeatures=1000)
# 检测关键点和计算描述符
kp1, des1 = orb.detectAndCompute(gray1, None)
kp2, des2 = orb.detectAndCompute(gray2, None)
# 使用汉明距离匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 按距离排序
matches = sorted(matches, key=lambda x: x.distance) # lambda匿名函数
# 绘制前50个匹配
result = cv2.drawMatches(img1, kp1, img2, kp2, matches[:50], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS) # 切片操作,取前n个元素
ORB算法组成: - FAST:快速角点检测 - BRIEF:二进制描述符 - 方向:计算主方向 - 金字塔:多尺度检测
3.2.4 其他特征描述符¶
HOG (Histogram of Oriented Gradients):
BRISK:
AKAZE:
3.3 特征匹配¶
3.3.1 暴力匹配¶
# 暴力匹配
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
matches = bf.match(des1, des2)
# 按距离排序
matches = sorted(matches, key=lambda x: x.distance)
3.3.2 FLANN匹配¶
# FLANN匹配(更快)
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# Lowe's ratio test
good_matches = []
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)
3.3.3 RANSAC(随机抽样一致)¶
# 提取匹配点坐标
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2) # 重塑张量形状
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
# 使用RANSAC计算单应性矩阵
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# 应用透视变换
h, w = img1.shape[:2]
result = cv2.warpPerspective(img1, M, (w, h))
3.4 实战案例:图像拼接¶
项目概述¶
实现全景图像拼接功能,使用特征检测和匹配技术。
完整代码¶
import cv2
import numpy as np
class ImageStitcher:
"""图像拼接类"""
def __init__(self, feature_type='sift'):
"""
初始化拼接器
feature_type: 'sift', 'surf', 'orb'
"""
self.feature_type = feature_type
self.detector = self._create_detector()
def _create_detector(self):
"""创建特征检测器"""
if self.feature_type == 'sift':
return cv2.SIFT_create()
elif self.feature_type == 'surf':
return cv2.xfeatures2d.SURF_create()
elif self.feature_type == 'orb':
return cv2.ORB_create(nfeatures=2000)
else:
raise ValueError(f"不支持的特征类型: {self.feature_type}")
def detect_and_match(self, img1, img2):
"""检测特征并匹配"""
# 转换为灰度图
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 检测关键点和计算描述符
kp1, des1 = self.detector.detectAndCompute(gray1, None)
kp2, des2 = self.detector.detectAndCompute(gray2, None)
# 特征匹配
if self.feature_type == 'orb':
# ORB使用汉明距离
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
matches = bf.knnMatch(des1, des2, k=2)
else:
# SIFT/SURF使用L2距离
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
matches = bf.knnMatch(des1, des2, k=2)
# Lowe's ratio test
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)
print(f"检测到 {len(kp1)} 和 {len(kp2)} 个关键点")
print(f"匹配到 {len(good_matches)} 对匹配点")
return kp1, kp2, good_matches
def find_homography(self, kp1, kp2, matches):
"""计算单应性矩阵"""
# 提取匹配点坐标
src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)
# 使用RANSAC计算单应性矩阵
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# 计算内点数量
inliers = sum(mask)
print(f"内点数量: {inliers}/{len(matches)}")
return M, mask
def stitch_images(self, img1, img2):
"""拼接两张图像"""
# 检测特征并匹配
kp1, kp2, matches = self.detect_and_match(img1, img2)
# 计算单应性矩阵
M, mask = self.find_homography(kp1, kp2, matches)
# 获取图像尺寸
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
# 计算拼接后的画布大小
corners1 = np.float32([[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2)
corners2 = np.float32([[0, 0], [0, h2], [w2, h2], [w2, 0]]).reshape(-1, 1, 2)
corners1_transformed = cv2.perspectiveTransform(corners1, M)
all_corners = np.concatenate((corners2, corners1_transformed), axis=0)
[x_min, y_min] = np.int32(all_corners.min(axis=0).ravel() - 0.5)
[x_max, y_max] = np.int32(all_corners.max(axis=0).ravel() + 0.5)
# 平移矩阵
translation = np.array([[1, 0, -x_min], [0, 1, -y_min], [0, 0, 1]]) # np.array创建NumPy数组
# 应用变换
result = cv2.warpPerspective(img1, translation.dot(M), (x_max - x_min, y_max - y_min))
result[-y_min:h2-y_min, -x_min:w2-x_min] = img2
return result
def draw_matches(self, img1, img2, kp1, kp2, matches):
"""绘制匹配结果"""
result = cv2.drawMatches(img1, kp1, img2, kp2, matches, None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
return result
# 使用示例
if __name__ == '__main__':
# 创建测试图像(实际使用时替换为真实图像)
img1 = np.random.randint(0, 256, (480, 640, 3), dtype=np.uint8)
img2 = np.random.randint(0, 256, (480, 640, 3), dtype=np.uint8)
# 保存测试图像
cv2.imwrite('image1.jpg', img1)
cv2.imwrite('image2.jpg', img2)
# 创建拼接器
stitcher = ImageStitcher(feature_type='orb')
# 读取图像
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')
# 拼接图像
result = stitcher.stitch_images(img1, img2)
# 保存结果
cv2.imwrite('stitched_result.jpg', result)
print("拼接完成!结果已保存为 stitched_result.jpg")
3.5 练习题¶
基础题¶
- 简答题:
- 什么是好的特征?应该具备哪些特性?
好的特征应具备:①可重复性(同一物体在不同视角/光照下能被检测到);②独特性(能区分不同对象);③不变性(对旋转、缩放、光照变化鲁棒);④高效性(计算和匹配速度快)。
-
SIFT和ORB有什么区别?
SIFT生成128维浮点描述符,精度高但计算慢且有专利限制;ORB基于FAST关键点+BRIEF二进制描述符,速度约为SIFT的100倍、免费开源,但大尺度变化下精度略低。
-
编程题:
- 使用Harris角点检测检测图像中的角点。
- 实现一个简单的特征匹配程序。
进阶题¶
- 编程题:
- 实现从零开始计算Harris角点响应函数。
-
比较不同特征描述符的性能。
-
思考题:
- 为什么需要特征描述符?
仅有关键点位置不足以进行匹配,需要描述符编码关键点周围的局部图像信息,使同一物体上的对应点描述符相似、不同物体上的点差异大,从而实现可靠的特征匹配。
- RANSAC的作用是什么?
RANSAC(随机抽样一致)用于从含有大量外点(误匹配)的数据中鲁棒地估计模型参数。通过随机采样最小点集拟合模型、统计内点数并迭代多次,选择内点最多的模型,有效剔除误匹配。
挑战题¶
- 项目题:
- 实现一个完整的图像拼接系统,支持多张图像拼接。
3.6 面试准备¶
大厂面试题¶
Q1: 什么是特征?好的特征应该具备哪些特性?
参考答案: - 定义:图像中具有辨识性的局部区域 - 特性: - 可重复性:同一物体在不同图像中能检测到 - 独特性:能够区分不同的物体 - 不变性:对旋转、缩放、光照变化鲁棒 - 高效性:计算速度快
Q2: SIFT算法的步骤是什么?
参考答案: 1. 尺度空间极值检测(DoG) 2. 关键点定位 3. 方向分配 4. 描述符生成(128维)
Q3: ORB相比SIFT有什么优势?
参考答案: - 速度:ORB比SIFT快很多 - 专利:ORB免费,SIFT有专利 - 描述符:ORB使用二进制描述符,匹配更快 - 缺点:ORB的精度略低于SIFT
Q4: 什么是RANSAC?它的作用是什么?
参考答案: - 定义:随机抽样一致算法 - 作用:从包含噪声的数据中估计模型参数 - 步骤: 1. 随机选择最小样本集 2. 估计模型参数 3. 计算内点数量 4. 重复多次,选择内点最多的模型 - 应用:特征匹配、图像拼接、3D重建
3.7 本章小结¶
核心知识点¶
- 特征提取:边缘、角点、斑点
- 特征描述符:SIFT、SURF、ORB
- 特征匹配:暴力匹配、FLANN
- RANSAC:去除误匹配
- 图像拼接:特征检测、匹配、变换
下一步¶
完成本章后,你应该: - [ ] 理解特征提取的重要性 - [ ] 掌握常用的特征描述符 - [ ] 能够实现特征匹配 - [ ] 完成图像拼接项目
下一章:04-传统计算机视觉算法.md - 学习传统CV算法
恭喜完成第3章! 🎉