特征检测与匹配
特征检测是计算机视觉的核心技术之一,用于在图像中找到具有代表性的关键点,并提取这些点的特征描述符。特征匹配则是在不同图像之间找到对应的特征点。
特征点概述
特征点是图像中具有显著性质的点,如角点、边缘点等。一个好的特征点应该具有以下性质:
可重复性:在不同图像中能够被重复检测到。
独特性:具有独特的特征,便于区分。
高效性:检测和描述的计算效率高。
局部性:特征只依赖于局部区域,对遮挡和噪声具有鲁棒性。
角点检测
角点是图像中两个边缘相交的点,是最基本的特征点类型。
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 级联分类器和深度学习模型进行目标检测。