OpenCV 计算机视觉完全教程 / 第 10 章 — 特征检测与匹配
第 10 章 — 特征检测与匹配
10.1 特征检测概述
特征(Feature)是图像中独特且可重复检测的点,如角点、斑点、边缘交点等。
特征检测流水线
输入图像 → ① 关键点检测 → ② 描述子计算 → ③ 特征匹配 → ④ 变换估计
(Keypoint) (Descriptor) (Matching) (Estimation)
主流特征检测器对比
| 检测器 | 类型 | 速度 | 精度 | 旋转不变 | 尺度不变 | 专利 |
|---|---|---|---|---|---|---|
| Harris | 角点 | ★★★★★ | ★★☆ | ❌ | ❌ | 无 |
| Shi-Tomasi | 角点 | ★★★★★ | ★★★ | ❌ | ❌ | 无 |
| FAST | 角点 | ★★★★★ | ★★☆ | ❌ | ❌ | 无 |
| ORB | 角点+描述 | ★★★★★ | ★★★ | ✅ | ✅ | 无 |
| SIFT | 斑点 | ★★★☆☆ | ★★★★★ | ✅ | ✅ | 无 (2020到期) |
| SURF | 斑点 | ★★★★☆ | ★★★★ | ✅ | ✅ | 有 |
| BRISK | 角点+描述 | ★★★★☆ | ★★★★ | ✅ | ✅ | 无 |
| AKAZE | 斑点+描述 | ★★★☆☆ | ★★★★ | ✅ | ✅ | 无 |
10.2 Harris 角点检测
Harris 角点检测基于自相关矩阵的特征值判断。
import cv2
import numpy as np
img = cv2.imread("building.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
# Harris 角点检测
# blockSize: 邻域大小
# ksize: Sobel 核大小
# k: Harris 参数 (0.04-0.06)
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)
# 膨胀以标记角点
dst = cv2.dilate(dst, None)
# 阈值化
result = img.copy()
result[dst > 0.01 * dst.max()] = [0, 0, 255]
Shi-Tomasi 角点(推荐)
import cv2
img = cv2.imread("building.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Shi-Tomasi 角点检测
corners = cv2.goodFeaturesToTrack(
gray,
maxCorners=100, # 最大角点数
qualityLevel=0.01, # 质量等级(0-1)
minDistance=10 # 角点间最小距离
)
result = img.copy()
if corners is not None:
for corner in corners:
x, y = corner.ravel()
cv2.circle(result, (int(x), int(y)), 4, (0, 255, 0), -1)
print(f"检测到 {len(corners)} 个角点")
10.3 FAST 角点检测
import cv2
img = cv2.imread("building.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# FAST 检测器
fast = cv2.FastFeatureDetector_create(
threshold=20, # 阈值
nonmaxSuppression=True # 非极大值抑制
)
keypoints = fast.detect(gray, None)
print(f"FAST 检测到 {len(keypoints)} 个关键点")
print(f"检测器类型: {fast.getType()}")
# 绘制关键点
result = cv2.drawKeypoints(img, keypoints, None,
color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
10.4 SIFT 特征检测
SIFT(Scale-Invariant Feature Transform)是最经典的特征检测算法。
SIFT 流程
① 构建尺度空间(DoG 金字塔)
↓
② 关键点定位(极值检测 + 亚像素精化)
↓
③ 方向赋值(梯度方向直方图)
↓
④ 描述子生成(128 维向量)
import cv2
import numpy as np
img = cv2.imread("building.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建 SIFT 检测器(OpenCV 4.4+ 已移入主模块)
sift = cv2.SIFT_create(
nfeatures=500, # 最大特征数
nOctaveLayers=3, # 每组层数
contrastThreshold=0.04, # 对比度阈值
edgeThreshold=10, # 边缘阈值
sigma=1.6 # 高斯 σ
)
# 同时检测关键点 + 计算描述子
keypoints, descriptors = sift.detectAndCompute(gray, None)
print(f"SIFT 关键点: {len(keypoints)}")
print(f"描述子形状: {descriptors.shape}") # (N, 128)
# 绘制关键点
result = cv2.drawKeypoints(img, keypoints, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
// C++ SIFT
cv::Mat img = cv::imread("building.jpg");
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
auto sift = cv::SIFT::create(500);
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
sift->detectAndCompute(gray, cv::noArray(), keypoints, descriptors);
10.5 ORB 特征检测(推荐)
ORB(Oriented FAST and Rotated BRIEF)是 SIFT 的高效替代方案。
import cv2
img = cv2.imread("building.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建 ORB 检测器
orb = cv2.ORB_create(
nfeatures=1000, # 最大特征数
scaleFactor=1.2, # 金字塔缩放因子
nLevels=8, # 金字塔层数
edgeThreshold=31, # 边缘阈值
firstLevel=0,
WTA_K=2, # 每个描述子元素用几个点
scoreType=cv2.ORB_HARRIS_SCORE, # 评分类型
patchSize=31, # 描述子补丁大小
fastThreshold=20 # FAST 阈值
)
# 检测 + 计算
keypoints, descriptors = orb.detectAndCompute(gray, None)
print(f"ORB 关键点: {len(keypoints)}")
print(f"描述子形状: {descriptors.shape}") # (N, 32)
# 绘制
result = cv2.drawKeypoints(img, keypoints, None,
color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
ORB vs SIFT 选择
| 场景 | 推荐 | 原因 |
|---|---|---|
| 实时应用 | ORB | 10-100 倍更快 |
| 高精度匹配 | SIFT | 128 维描述子更精确 |
| 移动端/嵌入式 | ORB | 计算量低 |
| 拼接/全景 | SIFT | 尺度不变性更好 |
| 视频追踪 | ORB | 速度关键 |
10.6 特征匹配
10.6.1 BFMatcher(暴力匹配)
import cv2
import numpy as np
# 读取两幅图像
img1 = cv2.imread("object.jpg")
img2 = cv2.imread("scene.jpg")
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 检测特征
orb = cv2.ORB_create(1000)
kp1, des1 = orb.detectAndCompute(gray1, None)
kp2, des2 = orb.detectAndCompute(gray2, None)
# 暴力匹配
# NORM_HAMMING: 用于 ORB(二进制描述子)
# NORM_L2: 用于 SIFT(浮点描述子)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
# KNN 匹配(返回最近的 k 个匹配)
matches = bf.knnMatch(des1, des2, k=2)
# Lowe 比率测试 — 过滤不可靠匹配
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance: # 距离比 < 0.75
good_matches.append(m)
print(f"总匹配: {len(matches)}, 优质匹配: {len(good_matches)}")
# 绘制匹配结果
result = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
10.6.2 FLANN 匹配(推荐)
FLANN(Fast Library for Approximate Nearest Neighbors)速度更快。
import cv2
import numpy as np
# FLANN 参数
FLANN_INDEX_KDTREE = 1
FLANN_INDEX_LSH = 6
# SIFT/SURF 使用 KD 树
index_params_kdtree = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
# ORB/BRISK 使用 LSH
index_params_lsh = dict(algorithm=FLANN_INDEX_LSH,
table_number=6,
key_size=12,
multi_probe_level=1)
search_params = dict(checks=50)
# 创建 FLANN 匹配器
flann = cv2.FlannBasedMatcher(index_params_kdtree, search_params)
# 使用 SIFT
sift = cv2.SIFT_create(1000)
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)
# KNN 匹配
matches = flann.knnMatch(des1, des2, k=2)
# Lowe 比率测试
good_matches = []
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)
匹配器选择
| 特征类型 | 描述子 | 匹配器 | 距离度量 |
|---|---|---|---|
| SIFT/SURF | 浮点 128 维 | BFMatcher/L2 或 FLANN/KD树 | NORM_L2 |
| ORB/BRISK | 二进制 32 维 | BFMatcher/HAMMING 或 FLANN/LSH | NORM_HAMMING |
| AKAZE | 二进制 | BFMatcher/HAMMING | NORM_HAMMING |
10.7 RANSAC 鲁棒估计
RANSAC 用于从匹配点中剔除误匹配,估计变换矩阵。
import cv2
import numpy as np
# 假设已获得 good_matches, kp1, kp2
if len(good_matches) >= 4:
# 提取匹配点坐标
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches])
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches])
# RANSAC 估计单应性矩阵
H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# H: 3×3 变换矩阵
# mask: 内点掩码 (1=内点, 0=外点)
matches_mask = mask.ravel().tolist()
inliers = sum(matches_mask)
print(f"内点: {inliers}/{len(good_matches)} "
f"({inliers/len(good_matches)*100:.1f}%)")
# 只绘制内点
draw_params = dict(
matchColor=(0, 255, 0),
singlePointColor=None,
matchesMask=matches_mask,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
result = cv2.drawMatches(img1, kp1, img2, kp2,
good_matches, None, **draw_params)
findHomography 方法
| 方法 | 常量 | 说明 |
|---|---|---|
| 最小二乘 | 0 | 所有点参与计算 |
| RANSAC | cv2.RANSAC | 随机采样,鲁棒 |
| LMEDS | cv2.LMEDS | 最小中值,无阈值 |
| RHO | cv2.RHO | 基于 PROSAC |
10.8 实战:图像拼接预览
"""
image_stitching.py — 简易图像拼接
"""
import cv2
import numpy as np
def stitch_images(img1, img2):
"""将两幅图像拼接"""
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 特征检测与匹配
sift = cv2.SIFT_create(2000)
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)
flann = cv2.FlannBasedMatcher(dict(algorithm=1, trees=5),
dict(checks=50))
matches = flann.knnMatch(des1, des2, k=2)
good = [m for m, n in matches if m.distance < 0.7 * n.distance]
if len(good) < 4:
print("匹配不足")
return None
# 计算单应性矩阵
src_pts = np.float32([kp1[m.queryIdx].pt for m in good])
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good])
H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# 透视变换
h, w = img1.shape[:2]
result = cv2.warpPerspective(img1, H, (w * 2, h))
result[0:img2.shape[0], 0:img2.shape[1]] = img2
return result
# 使用
# img1 = cv2.imread("left.jpg")
# img2 = cv2.imread("right.jpg")
# stitched = stitch_images(img1, img2)
10.9 特征检测器性能对比
| 指标 | Harris | FAST | ORB | SIFT | AKAZE |
|---|---|---|---|---|---|
| 检测速度 | 5ms | 2ms | 15ms | 150ms | 80ms |
| 特征数量(1080p) | ~200 | ~1000 | ~1000 | ~2000 | ~1500 |
| 匹配精度 | 低 | 中 | 高 | 最高 | 高 |
| 描述子维度 | — | — | 32 | 128 | 486 |
| 内存占用 | 低 | 低 | 低 | 中 | 高 |
10.10 扩展阅读
| 资源 | 链接 | 说明 |
|---|---|---|
| SIFT 论文 | Lowe, “Distinctive Image Features…” (2004) | 原始论文 |
| ORB 论文 | Rublee et al., “ORB: An efficient alternative to SIFT/SURF” (2011) | ORB 算法 |
| OpenCV 特征教程 | docs.opencv.org/4.x/d5/d6f/tutorial_feature_flann_matcher | FLANN 匹配 |
| 下一章 | 第 11 章 — 目标检测 | 模板匹配/HOG/YOLO |
本章小结: 掌握了 Harris、SIFT、ORB 等特征检测器,学会了暴力匹配和 FLANN 匹配,以及 RANSAC 鲁棒估计和单应性矩阵计算。