目录
相关阅读
简述
在计算机视觉中,图像查找是一项常见任务,比如:
- 在一张大图中查找 特定目标(如商标、书本封面)。
- 通过图像匹配实现 目标识别。
- 通过 特征点匹配 + 透视变换 进行目标对齐。
要实现图像查找,我们通常使用 特征匹配 + 单应性矩阵。本文将详细介绍单应性矩阵的概念,并结合 OpenCV 代码示例展示如何在一张大图中找到目标图像。
1. 什么是单应性矩阵?
1.1 单应性矩阵的定义
单应性矩阵(Homography Matrix)是一种 3×3 变换矩阵,用于描述 两个平面之间的透视变换,它可以将一张图像的 任意四边形区域 映射到另一张图像中的对应区域。数学上,单应性矩阵 H 作用于二维坐标 (x, y),变换为 (x1, y1):
其中,H 是一个 3×3 矩阵:
1.2 单应性矩阵的作用
- 图像查找:从大图中找到目标图像。
- 透视变换:校正倾斜图像(如文档矫正)。
- 全景拼接:通过特征点匹配找到两张图片的变换关系,进行无缝拼接。
2. OpenCV 图像查找流程
2.1 主要步骤
- 特征提取:使用 SIFT 提取两张图像的关键点和描述子。
- 特征匹配:使用 BFMatcher / FLANN 进行特征点匹配。
- 单应性计算:使用 cv2.findHomography() 计算单应性矩阵 H。
- 透视变换:使用 H 进行图像映射,标记目标区域。
3. 代码实现:从大图中查找目标图像
以下这段代码的主要目的是在一张较大的场景图像(box_in_sence)中检测并定位特定目标图像(box)的位置。通过特征提取、特征匹配、计算单应性矩阵以及透视变换等步骤,最终在包含目标图像和场景图像的拼接结果图上绘制出目标图像在场景中的实际位置轮廓。
import cv2
import numpy as np
# 读取图像
box = cv2.imread('D:\\resource\\filter\\logo.jpg')
box_in_sence = cv2.imread('D:\\resource\\filter\\web.png')
# 检查图像是否读取成功
if box is None or box_in_sence is None:
print("图像读取失败,请检查文件路径。")
exit(1)
cv2.imshow("box", box)
cv2.imshow("box_in_sence", box_in_sence)
# 创建特征检测器
orb = cv2.SIFT_create()
kp1, des1 = orb.detectAndCompute(box, None)
kp2, des2 = orb.detectAndCompute(box_in_sence, None)
# 暴力匹配
bf = cv2.BFMatcher(cv2.NORM_L1, crossCheck=True)
matches = bf.match(des1, des2)
goodMatches = []
# 筛选出好的描述子
matches = sorted(matches, key=lambda x: x.distance)
for i in range(len(matches)):
if matches[i].distance < 0.7 * matches[-1].distance:
goodMatches.append(matches[i])
# 检查匹配的特征点数量是否足够
if len(goodMatches) < 4:
print("匹配的特征点不足,无法计算单应性矩阵。")
exit(1)
result = cv2.drawMatches(box, kp1, box_in_sence, kp2, goodMatches, None)
obj_pts, scene_pts = [], []
# 单独保存 obj 和 scene 好的点位置
for f in goodMatches:
obj_pts.append(kp1[f.queryIdx].pt)
scene_pts.append(kp2[f.trainIdx].pt)
# 计算单应性矩阵
H, _ = cv2.findHomography(np.float32(obj_pts), np.float32(scene_pts), cv2.RANSAC)
h, w = box.shape[0:2]
pts = np.float32([[0, 0], [0, h], [w, h], [w, 0]]).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts, H).reshape(-1, 2)
# 偏移
for i in range(4):
dst[i][0] += w
cv2.polylines(result, [np.int32(dst)], True, (0, 255, 0), 3, cv2.LINE_AA)
cv2.imshow("Match", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
▶️运行结果:
小图logo:
大图box_in_sence:
特征匹配:
4. 代码解释
4.1 读取图像
# 读取图像
box = cv2.imread('D:\\resource\\filter\\logo.jpg')
box_in_sence = cv2.imread('D:\\resource\\filter\\web.png')
# 检查图像是否读取成功
if box is None or box_in_sence is None:
print("图像读取失败,请检查文件路径。")
exit(1)
cv2.imshow("box", box)
cv2.imshow("box_in_sence", box_in_sence)
使用 cv2.imread 函数读取目标图像和场景图像,并通过 cv2.imshow 显示这两张图像。
4.2 特征提取
# 创建特征检测器
orb = cv2.SIFT_create()
kp1, des1 = orb.detectAndCompute(box,None)
kp2, des2 = orb.detectAndCompute(box_in_sence,None)
使用 cv2.SIFT_create() 创建 SIFT(尺度不变特征变换)特征检测器。detectAndCompute 方法用于在目标图像和场景图像中检测特征点(kp1 和 kp2)并计算对应的描述子(des1 和 des2)。
4.3 特征匹配
# 暴力匹配
bf = cv2.BFMatcher(cv2.NORM_L1, crossCheck=True)
matches = bf.match(des1, des2)
goodMatches = []
# 筛选出好的描述子
matches = sorted(matches, key=lambda x: x.distance)
for i in range(len(matches)):
if matches[i].distance < 0.7 * matches[-1].distance:
goodMatches.append(matches[i])
# 检查匹配的特征点数量是否足够
if len(goodMatches) < 4:
print("匹配的特征点不足,无法计算单应性矩阵。")
exit(1)
result = cv2.drawMatches(box, kp1, box_in_sence, kp2, goodMatches, None)
使用 cv2.BFMatcher 创建一个暴力匹配器,使用 cv2.NORM_L1 距离度量和 crossCheck=True 确保双向匹配一致。bf.match 方法找到描述子之间的匹配。对匹配结果按距离排序,筛选出距离小于最长距离 70% 的匹配作为好的匹配。最后使用 cv2.drawMatches 绘制好的匹配结果。
4.4 提取匹配点的坐标
obj_pts, scene_pts = [], []
# 单独保存 obj 和 scene 好的点位置
for f in goodMatches:
obj_pts.append(kp1[f.queryIdx].pt)
scene_pts.append(kp2[f.trainIdx].pt)
遍历好的匹配结果,提取目标图像和场景图像中匹配点的坐标。
4.5 计算单应性矩阵
H, _= cv2.findHomography(np.float32(obj_pts), np.float32(scene_pts), cv2.RANSAC)
使用 cv2.findHomography 函数,结合 RANSAC(随机抽样一致性)算法计算单应性矩阵 H,该矩阵用于将目标图像的点映射到场景图像中。
4.6 透视变换
h, w = box.shape[0:2]
pts = np.float32([[0, 0], [0, h], [w, h], [w, 0]]).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts, H).reshape(-1, 2)
获取目标图像的高度 h 和宽度 w,定义目标图像的四个角点 pts。使用 cv2.perspectiveTransform 函数通过单应性矩阵 H 对四个角点进行透视变换,得到它们在场景图像中的对应位置 dst。
4.7 添加偏移量并绘制轮廓
# 加上偏移量
for i in range(4):
dst[i][0] += w
cv2.polylines(result, [np.int32(dst)], True, (0, 255, 0), 3, cv2.LINE_AA)
为 dst 中的 x 坐标添加目标图像的宽度 w 作为偏移量,是为了在拼接图像中正确定位。使用 cv2.polylines 函数在匹配结果图像 result 上绘制变换后四个角点构成的四边形,颜色为绿色,线宽为 3 像素。
4.8 显示结果并释放资源
cv2.imshow("Match", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
显示最终的匹配结果图像,等待用户按下任意键后关闭所有窗口。
5. 适用场景
应用场景 | 说明 |
---|---|
图像查找 | 在大图中找到小目标(如 Logo 识别) |
目标识别 | 识别特定物体,如商标、书籍封面 |
物体跟踪 | 视频中跟踪特定目标 |
全景拼接 | 通过单应性矩阵拼接多张图像 |
6. 选用不同特征检测器
特征检测方法 | 适用场景 | 计算速度 |
---|---|---|
SIFT | 高精度匹配 | 较慢 |
ORB | 实时应用 | 快 |
上述代码中的特征提取部分使用了 SIFT(尺度不变特征变换)算法,除此之外,还有很多其他的特征提取方法可供选择,以下为你详细介绍常见的方法及其代码示例:
6.1 改用ORB特征检测
ORB 是一种快速且高效的特征提取算法,结合了 FAST 特征点检测和 BRIEF 描述子,并且具有旋转不变性。与 SIFT 相比,ORB 的计算速度更快,适合对实时性要求较高的场景。
import cv2
import numpy as np
# 读取图像
box = cv2.imread('D:\\resource\\filter\\logo.jpg')
box_in_sence = cv2.imread('D:\\resource\\filter\\web.png')
# 创建 ORB 特征检测器
orb = cv2.ORB_create()
# 检测特征点并计算描述子
kp1, des1 = orb.detectAndCompute(box, None)
kp2, des2 = orb.detectAndCompute(box_in_sence, None)
# 由于 ORB 使用的是二进制描述子,匹配时使用汉明距离
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 后续的匹配筛选、单应性矩阵计算等步骤与原代码类似
# ...
7. 总结
单应性矩阵可用于透视变换,在大图中找到目标图像。SIFT/ORB + FLANN/BFMatcher 可以进行高效的特征匹配。cv2.findHomography() 计算目标的变换矩阵,找到在大图中的位置。