强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 选择

场景推荐原因
实时应用ORB10-100 倍更快
高精度匹配SIFT128 维描述子更精确
移动端/嵌入式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/LSHNORM_HAMMING
AKAZE二进制BFMatcher/HAMMINGNORM_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所有点参与计算
RANSACcv2.RANSAC随机采样,鲁棒
LMEDScv2.LMEDS最小中值,无阈值
RHOcv2.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 特征检测器性能对比

指标HarrisFASTORBSIFTAKAZE
检测速度5ms2ms15ms150ms80ms
特征数量(1080p)~200~1000~1000~2000~1500
匹配精度最高
描述子维度32128486
内存占用

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_matcherFLANN 匹配
下一章第 11 章 — 目标检测模板匹配/HOG/YOLO

本章小结: 掌握了 Harris、SIFT、ORB 等特征检测器,学会了暴力匹配和 FLANN 匹配,以及 RANSAC 鲁棒估计和单应性矩阵计算。