#记录本人学习过程。如果有幸对你有帮助,倍感荣幸
本系列将分四个篇章由易到难对项目进行拆解,分为(一)设计界面,(二)图像拼接,(三)图像检测,(四)硬件测试
一、相关简介
图像拼接是指将同一场景中具有重叠区域的多幅图像进行缝合,最后构成一幅宽视野大幅图像的图像处理过程,它在无人机连续图像拍摄监控、自动化生产线进行自动检测和分类、机器人感知周围环境进行判断等都有重要应用。
二、主要原理介绍
尺度不变特征变换(Scale Invariant Feature Transform
简称
SIFT
)是机器视觉领域对目标图像进行特征点提取的一种方法,SIFT 特征点具有旋转不变性、尺度不变性和亮度变化保持不变性的性质,是一种非常稳定的局部特征。所以
被广泛应用于图像拼接领域,作为两幅或者多幅图像进行拼接的依据。
SIFT 方法需要对图像进行多次高斯模糊和 DoG 差分计算,然后在每个尺度上寻找局部极值点,并使用 Hessian 矩阵对这些极值点进行精确定位和尺度估计。之后对每个极值点的周围区域进行方向分配,使用梯度直方图对该区域的特征进行描述,得到 SIFT 特征点。最终,根据特征点的相似度进行特征匹配。
SIFTF方法
计算量比较大,特别是在大规模图像匹配时,速度较慢。所以本项目采用Canny+SIFT的方法。 Canny边缘检测处理输入图像,剔除图像较为强烈的边缘特征,然后将剔除边缘特征后的图像使用 SIFT 方法处理,得到特征点进行匹配,完成图像拼接。本方法能在不影响图像拼接质量的情况下,提升了特征点提取的速度并降低了对硬件设备的要求。
三、基本应用
图像拼接程序由单应性矩阵计算和图像变换拼接两段程序实现,首先计算单应性矩阵,根据计算出的矩阵对图像进行变换,然后拼接变换之后的图像,最终程序返回拼接得到的宽幅图像。
3.1单应性矩阵计算
单应性矩阵计算程序分为图像特征点提取匹配和特征点过滤两个环节。单应性矩阵计算程序流程图如图所示。首先经过
Canny
算子进行边缘响应滤除,减少检测出边缘特征点的概率,然后进行特征点提取和匹配,得到优质特征点,再根据这些优质特征点计算单应性矩阵,将计算效果最好的矩阵保留下来作为变换矩阵。
具体实现见以下代码:
首先对两幅图进行
Canny
算子滤波,因对输入图像要求为灰度图,所以要先转换为灰度图,然后将边缘信息剔除
img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)#转换为灰度图
edges = cv2.Canny(img1_gray, 200, 200)# Canny 滤波得到边缘二值图像
img1_ed = cv2.subtract(img1_gray, edges)# 剔除边缘信息
建立
SIFT
特征检测器,对剔除边缘信息的灰度图像进行特征点检测,得到特征点坐标和对应的描述子
sift = cv2.SIFT_create()# 建立 SIFT 特征检测器
k1, d1 = sift.detectAndCompute(img1_ed, None)# 获取特征点和描述子
在对两图像处理后,建立暴力匹配检测器匹配两组特征点,每个特征点保留 2 个最佳匹配结果,然后将这两个结果作为滤除不良特征点的依据,如果两个结果与当前被匹配特征点距离相近,则认为次特征点为不良特征点,需要剔除,反之保留在数组 verify_matches 中
# 创建特征匹配器
bf = cv2.BFMatcher()
# 使用描述子进行一对多的描述子匹配
maches = bf.knnMatch(d1, d2, k=2)
# 筛选有效的特征描述子存入数组中
verify_matches = []
for m1, m2 in maches:
if m1.distance < 0.7 * m2.distance:
verify_matches.append(m1)
最后对合格的特征点进行单应性矩阵计算
# 单应性矩阵需要最低四个特征描述子坐标点进行计算
if len(verify_matches) > 6:
# 存放求取单应性矩阵所需的img1和img2的特征描述子坐标点
img1_pts = []
img2_pts = []
for m in verify_matches:
# 通过使用循环获取img1和img2图像优化后描述子所对应的特征点
img1_pts.append(k1[m.queryIdx].pt)
img2_pts.append(k2[m.trainIdx].pt)
# 得到的坐标是[(x1,y1),(x2,y2),....]
# 计算需要的坐标格式:[[x1,y1],[x2,y2],....]所以需要转化
img1_pts = np.float32(img1_pts).reshape(-1, 1, 2)
img2_pts = np.float32(img2_pts).reshape(-1, 1, 2)
# 计算单应性矩阵用来优化特征点
# print('匹配特征点个数:', len(verify_matches))
H, mask = cv2.findHomography(img1_pts, img2_pts, cv2.RANSAC, 5.0)
return H
3.2图像变换拼接
在得到单应性矩阵之后,执行图像变换拼接程序。程序流程图如图所示。首先推算出拼接之后的图像大小,根据大小建立空白矩阵变量,保存最后拼接结果。然后将其中一幅图像的所有点进行仿射变换,得到新的图像。由于每张图像都是从原点开始计算每个像素点坐标的,所以为了实现拼接保存还需要对图像进行平移,确保两图像叠加之后所有像素点坐标没有负值。
具体实现见以下代码:
用
perspectiveTransform
函数计算四个角点的坐标经过仿射变换之后的坐标,变换之后的坐标与变换之前的坐标做差取绝对值推算出拼接之后的图像大小,根据大小建立空白矩阵变量
# 获取原始图的高、宽
h1, w1 = img1.shape[:2]
# 获取四个点的坐标,变换数据类型便于计算
img1_dims = np.float32([[0, 0], [0, h1], [h1, w1], [w1, 0]]).reshape(-1, 1, 2)
# 获取根据单应性矩阵透视变换后的图像四点坐标
img1_transform = cv2.perspectiveTransform(img1_dims, H)
# 合并矩阵 获取最大x和最小x,最大y和最小y 根据最大最小计算合并后图像的大小;
result_dims = np.concatenate((img2_dims, img1_transform), axis=0)
# 使用min获取横向最小坐标值,ravel将二维转换为一维,强转为int型,
# 最小-0.5,最大+0.5防止四舍五入造成影响
[x_min, y_min] = np.int32(result_dims.min(axis=0).ravel() - 0.5)
[x_max, y_max] = np.int32(result_dims.max(axis=0).ravel() + 0.5)
齐次变换矩阵与单应性矩阵相乘得到新的组合矩阵,图像经过这样一个矩阵处理可以实现平移和透射两步操作。warpPerspective 函数将 img1 按照矩阵 transform_arary.dot(H)进行了透视变换,输出图像大小尺寸为(ww, hh)。最后将经过仿射变换和平移的图像与经过平移的另一副图像叠加,保存在之前建立的空白变量中,得到拼接结果
# 平移距离
transform_dist = [-x_min, -y_min]
if transform_dist[0] > 3000 or transform_dist[1] > 3000:
return img1
# 齐次变换矩阵
transform_arary = np.array([[1, 0, transform_dist[0]],
[0, 1, transform_dist[1]],
[0, 0, 1]])
# 输出图像的尺寸
ww = x_max - x_min
hh = y_max - y_min
# 透视变换实现平移
result_img = cv2.warpPerspective(img1, transform_arary.dot(H), (ww, hh))
# 将img2添加进平移后的图像
result_img[transform_dist[1]:transform_dist[1] + h2,
transform_dist[0]:transform_dist[0] + w2] = img2
return result_img
四、总结
最后附效果图