跳到主要内容

特征检测与匹配

特征检测是计算机视觉的核心技术之一,用于在图像中找到具有代表性的关键点,并提取这些点的特征描述符。特征匹配则是在不同图像之间找到对应的特征点。

特征点概述

特征点是图像中具有显著性质的点,如角点、边缘点等。一个好的特征点应该具有以下性质:

可重复性:在不同图像中能够被重复检测到。

独特性:具有独特的特征,便于区分。

高效性:检测和描述的计算效率高。

局部性:特征只依赖于局部区域,对遮挡和噪声具有鲁棒性。

角点检测

角点是图像中两个边缘相交的点,是最基本的特征点类型。

Harris 角点检测

Harris 角点检测是最经典的角点检测算法:

import cv2
import numpy as np

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)

# Harris 角点检测
# blockSize: 邻域大小
# ksize: Sobel 算子大小
# k: Harris 参数
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)

# 结果膨胀,便于标记
dst = cv2.dilate(dst, None)

# 标记角点(阈值化)
image[dst > 0.01 * dst.max()] = [0, 0, 255]

cv2.imshow('Harris 角点', image)
cv2.waitKey(0)

Harris 角点检测的原理是计算图像在每个像素点的自相关函数,当自相关函数在两个方向都有较大变化时,该点就是角点。

Shi-Tomasi 角点检测

Shi-Tomasi 是 Harris 算法的改进版本,通常效果更好:

import cv2
import numpy as np

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Shi-Tomasi 角点检测
# maxCorners: 最大角点数
# qualityLevel: 角点质量阈值
# minDistance: 角点之间的最小距离
corners = cv2.goodFeaturesToTrack(
gray,
maxCorners=100,
qualityLevel=0.01,
minDistance=10
)

# 绘制角点
corners = np.int0(corners)
for corner in corners:
x, y = corner.ravel()
cv2.circle(image, (x, y), 3, (0, 0, 255), -1)

cv2.imshow('Shi-Tomasi 角点', image)
cv2.waitKey(0)

Shi-Tomasi 算法在选择角点时使用更稳定的最小特征值准则,能够检测到更稳定的角点。

SIFT 特征检测

SIFT(Scale-Invariant Feature Transform)是一种尺度不变的特征检测算法,对旋转、缩放、亮度变化具有不变性。

基本使用

SIFT 算法受专利保护,在 OpenCV 4.x 中需要安装 opencv-contrib-python 才能使用:

import cv2

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 创建 SIFT 检测器
sift = cv2.SIFT_create()

# 检测关键点和描述符
keypoints, descriptors = sift.detectAndCompute(gray, None)

# 绘制关键点
image_with_keypoints = cv2.drawKeypoints(
image,
keypoints,
None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)

cv2.imshow('SIFT 关键点', image_with_keypoints)
cv2.waitKey(0)

detectAndCompute 方法返回两个值:

  • keypoints:关键点列表,包含位置、尺度、方向等信息
  • descriptors:描述符矩阵,每行是一个关键点的 128 维描述符

SIFT 参数

# 调整 SIFT 参数
sift = cv2.SIFT_create(
nfeatures=0, # 保留的最佳特征数量,0 表示全部
nOctaveLayers=3, # 每组中的层数
contrastThreshold=0.04, # 对比度阈值
edgeThreshold=10, # 边缘阈值
sigma=1.6 # 高斯核标准差
)

ORB 特征检测

ORB(Oriented FAST and Rotated BRIEF)是一种快速的特征检测算法,是 SIFT 的免费替代方案。

基本使用

import cv2

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 创建 ORB 检测器
orb = cv2.ORB_create()

# 检测关键点和描述符
keypoints, descriptors = orb.detectAndCompute(gray, None)

# 绘制关键点
image_with_keypoints = cv2.drawKeypoints(
image,
keypoints,
None,
color=(0, 255, 0),
flags=0
)

cv2.imshow('ORB 关键点', image_with_keypoints)
cv2.waitKey(0)

ORB 参数

orb = cv2.ORB_create(
nfeatures=500, # 最大特征点数
scaleFactor=1.2, # 金字塔缩放因子
nlevels=8, # 金字塔层数
edgeThreshold=31, # 边缘阈值
firstLevel=0, # 起始层级
WTA_K=2, # BRIEF 描述符的 WTA_K
scoreType=cv2.ORB_FAST_SCORE, # 评分类型
patchSize=31, # 特征块大小
fastThreshold=20 # FAST 阈值
)

ORB 的优势:

  • 计算速度快,适合实时应用
  • 不受专利限制
  • 对旋转和缩放具有一定的不变性

特征匹配

特征匹配用于在不同图像之间找到对应的特征点。

暴力匹配

暴力匹配(Brute-Force Matcher)将一幅图像的每个描述符与另一幅图像的所有描述符进行比较:

import cv2

# 读取两幅图像
img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)

# 创建 ORB 检测器
orb = cv2.ORB_create()

# 检测关键点和描述符
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)

# 创建暴力匹配器
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# 进行匹配
matches = bf.match(des1, des2)

# 按距离排序
matches = sorted(matches, key=lambda x: x.distance)

# 绘制匹配结果
result = cv2.drawMatches(
img1, kp1, img2, kp2,
matches[:20], # 只绘制前 20 个匹配
None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)

cv2.imshow('匹配结果', result)
cv2.waitKey(0)

对于 SIFT 和 SURF 等浮点描述符,使用 NORM_L2 范数:

bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)

对于 ORB 等二进制描述符,使用 NORM_HAMMING 范数。

KNN 匹配

KNN 匹配返回每个描述符的 k 个最佳匹配:

import cv2

img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)

sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

bf = cv2.BFMatcher()

# KNN 匹配,k=2
matches = bf.knnMatch(des1, des2, k=2)

# 应用比率测试,筛选好的匹配
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append([m])

# 绘制匹配结果
result = cv2.drawMatchesKnn(
img1, kp1, img2, kp2,
good_matches,
None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)

cv2.imshow('KNN 匹配', result)
cv2.waitKey(0)

比率测试是 D. Lowe 提出的方法,只保留距离比值小于阈值的匹配,可以有效去除错误匹配。

FLANN 匹配

FLANN(Fast Library for Approximate Nearest Neighbors)是一种快速近似最近邻搜索算法:

import cv2
import numpy as np

img1 = cv2.imread('image1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('image2.jpg', cv2.IMREAD_GRAYSCALE)

sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# 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)

# 比率测试
good_matches = []
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)

# 绘制匹配结果
result = cv2.drawMatches(
img1, kp1, img2, kp2,
good_matches,
None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)

cv2.imshow('FLANN 匹配', result)
cv2.waitKey(0)

对于 ORB 等二进制描述符,需要使用不同的 FLANN 参数:

FLANN_INDEX_LSH = 6
index_params = dict(
algorithm=FLANN_INDEX_LSH,
table_number=6,
key_size=12,
multi_probe_level=1
)

特征匹配应用

图像拼接

使用特征匹配实现图像拼接:

import cv2
import numpy as np

img1 = cv2.imread('left.jpg')
img2 = cv2.imread('right.jpg')

gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# 检测特征点
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)

# 特征匹配
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)

# 筛选好的匹配
good_matches = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good_matches.append(m)

# 提取匹配点坐标
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)

# 计算单应性矩阵
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

# 图像变换
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]

# 获取变换后的图像边界
pts = np.float32([[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts, M)

# 计算拼接后图像的大小
min_x = min(0, dst[:, 0, 0].min())
max_x = max(w2, dst[:, 0, 0].max())
min_y = min(0, dst[:, 0, 1].min())
max_y = max(h2, dst[:, 0, 1].max())

# 平移矩阵
translation = np.array([
[1, 0, -min_x],
[0, 1, -min_y],
[0, 0, 1]
])

# 拼接图像
result = cv2.warpPerspective(img1, translation.dot(M), (int(max_x - min_x), int(max_y - min_y)))
result[-min_y:h2 - min_y, -min_x:w2 - min_x] = img2

cv2.imshow('拼接结果', result)
cv2.waitKey(0)

目标定位

使用特征匹配在场景中定位目标:

import cv2
import numpy as np

target = cv2.imread('target.jpg', cv2.IMREAD_GRAYSCALE)
scene = cv2.imread('scene.jpg', cv2.IMREAD_GRAYSCALE)

# 检测特征点
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(target, None)
kp2, des2 = sift.detectAndCompute(scene, None)

# 特征匹配
flann = cv2.FlannBasedMatcher(dict(algorithm=1, trees=5), dict(checks=50))
matches = flann.knnMatch(des1, des2, k=2)

# 筛选好的匹配
good_matches = []
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)

if len(good_matches) > 10:
# 提取匹配点坐标
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)

# 计算单应性矩阵
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

# 获取目标在场景中的位置
h, w = target.shape
pts = np.float32([[0, 0], [0, h], [w, h], [w, 0]]).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts, M)

# 绘制边界框
result = cv2.cvtColor(scene, cv2.COLOR_GRAY2BGR)
result = cv2.polylines(result, [np.int32(dst)], True, (0, 255, 0), 3)

cv2.imshow('目标定位', result)
cv2.waitKey(0)
else:
print("匹配点不足,无法定位目标")

特征检测算法比较

算法速度准确性尺度不变旋转不变专利
Harris中等
Shi-Tomasi中等
SIFT有(已过期)
SURF中等
ORB很快中等

选择建议:

  • 实时应用:使用 ORB
  • 高精度要求:使用 SIFT
  • 一般应用:使用 ORB 或 SIFT

下一步

掌握了特征检测与匹配后,下一章节我们将学习目标检测,了解如何使用 Haar 级联分类器和深度学习模型进行目标检测。