功能介绍:
本代码利用图像特征检测,实现图像拼接,为了进一步提高图像拼接适用范围和鲁棒性,对代码一再改造,逐步实现上下左右拼接、无需自定义图像顺序(自左向右、自下向上)拼接。目前需要相邻图像是待拼接的图像,后面将进一步优化代码,实现乱序图像的拼接,想法是基于特征检测和匹配进行图像筛选,再进行图像拼接。拼接后的图像黑边可以通过腐蚀变换去掉,如果有时间完成再更新同步。
本代码粗制烂造,可能存在些许问题,伙伴们可以参考,欢迎评论批评指正。
直接上代码:
import cv2
import numpy as np
from imutils import paths
import threading
# 定义加权融合中权重计算
def calWeight(d, k):
'''
:param d: 融合重叠部分直径
:param k: 融合计算权重参数
:return:
'''
x = np.arange(-d / 2, d / 2)
y = 1 / (1 + np.exp(-k * x))
return y
# 用透视矩阵(Homography)对图像进行变换,返回拼接图像
def Fusion(image_right, image_left, Homography):
row1, col1 = image_right.shape[:2] # 右图,下图——h, w
row2, col2 = image_left.shape[:2] # 左图,上图——h, w
if abs(Homography[0, 2]) > abs(Homography[1, 2]): # 左右拼接
# cv.warpPerspective():透视变换函数,用于解决cv2.warpAffine()不能处理视场和图像不平行的问题
# 作用:就是对图像进行透视变换,可保持直线不变形,但是平行线可能不再平行
result = cv2.warpPerspective(image_right, Homography, (col1 + col2, row1))
"""
cv.imwrite("./result.jpg", result)
gray_image = cv.cvtColor(result, cv.COLOR_BGR2GRAY)
# 找到所有零像素/非零像素的坐标
zero_coords = np.argwhere(gray_image == 0)
non_zero_coords = np.argwhere(gray_image > 0)
# 找到左侧零像素中 X 轴的最大值
left_zero_coords = zero_coords[zero_coords[:, 1] < gray_image.shape[1] // 2] # 左侧坐标筛选
max_x_coord = np.max(left_zero_coords[:, 1])
# 找到 x 轴最小坐标
min_x_coord = np.min(non_zero_coords[:, 1])
print("图像左侧零像素坐标的 X 轴最大值为:", max_x_coord)
print("图像中非零像素的 x 轴最小坐标为:", min_x_coord)
d = max_x_coord - min_x_coord
"""
cv2.imshow("picture_4", result) # 扭曲变换后的右图
cv2.waitKey(0)
cv2.destroyAllWindows()
# 将左图加入到变换后的右图像的左端即获得最终图像
d = int(col1 / 5) # 可以自定义加权融合区域
w = calWeight(d, 0.05)
w_expand = np.tile(w, (row2, 1))
w_expand = np.repeat(w_expand[:, :, np.newaxis], 3, axis=2)
result[:, 0:col2 - d] = image_left[:, 0:col2 - d]
result[:, col2 - d:col2] = (1 - w_expand) * image_left[:, col2 - d:col2] + w_expand * result[:, col2 - d:col2]
else: # 上下拼接
result = cv2.warpPerspective(image_right, Homography, (col1, row1 + row2))
"""
gray_image = cv.cvtColor(result, cv.COLOR_BGR2GRAY)
# 找到所有零像素/非零像素的坐标
zero_coords = np.argwhere(gray_image == 0)
non_zero_coords = np.argwhere(gray_image > 0)
# 找到上侧零像素中 X 轴的最大值
top_zero_coords = zero_coords[zero_coords[:, 0] < gray_image.shape[1] // 2] # 左侧坐标筛选
max_y_coord = np.max(top_zero_coords[:, 0])
# 找到 y 轴最小坐标
min_y_coord = np.min(non_zero_coords[:, 0])
print("图像上侧零像素坐标的 Y 轴最大值为:", max_y_coord)
print("图像中非零像素的 y 轴最小坐标为:", min_y_coord)
d = max_y_coord - min_y_coord
"""
cv2.imshow("picture_4", result) # 扭曲变换后的右图
cv2.waitKey(0)
cv2.destroyAllWindows()
# 将左图加入到变换后的右图像的左端即获得最终图像
d = int(row1 / 5) # 可以自定义加权融合区域
w = calWeight(d, 0.05)
w_expand = np.tile(w, (col2, 1))
w_expand = np.repeat(w_expand[:, :, np.newaxis], 3, axis=2)
w_expand = np.transpose(w_expand, (1, 0, 2))
result[0:row1 - d, :] = image_left[0:row1 - d, 0:]
result[row1 - d:row1, :] = (1 - w_expand) * image_left[row1 - d:row1, :] + w_expand * result[row1 - d:row1, :]
return result
# 想写成回调函数用于图像拼接,未使用
def process_item(i, result):
src_pts = np.float32([kp_list[i][m.queryIdx].pt for m in good_matches_list[i]]).reshape(-1, 1, 2)
dst_pts = np.float32([kp_list[i + 1][m.trainIdx].pt for m in good_matches_list[i]]).reshape(-1, 1, 2)
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
result = cv2.warpPerspective(result, M, (images[i + 1].shape[1] + result.shape[1], result.shape[0]))
result[:, images[i].shape[1]:] = images[i + 1]
return result
if __name__ == '__main__':
# imagePaths = sorted(list(paths.list_images("images/newimages"))) # 图像顺序依次从右向左
imagePaths = sorted(list(paths.list_images("images/newimages")), reverse=True) # 验证图像顺序不影响拼接
print(imagePaths)
num_images = len(imagePaths)
images = []
for imagePath in imagePaths:
image = cv2.imread(imagePath)
images.append(image)
# 特征检测
kp_list = []
des_list = []
sift = cv2.SIFT_create()
for image in images:
kp, des = sift.detectAndCompute(image, None)
kp_list.append(kp)
des_list.append(des)
# 特征匹配
matcher = cv2.DescriptorMatcher_create(cv2.DescriptorMatcher_FLANNBASED)
matches_list = []
for i in range(num_images - 1):
matches = matcher.knnMatch(des_list[i], des_list[i+1], k=2)
matches_list.append(matches)
# 特征筛选
good_matches_list = []
for matches in matches_list:
good_matches = []
for m, n in matches:
if m.distance < 0.7 * n.distance:
good_matches.append(m)
good_matches_list.append(good_matches)
result = images[0]
for i in range(num_images - 1):
src_pts = np.float32([kp_list[i][m.queryIdx].pt for m in good_matches_list[i]]).reshape(-1, 1, 2)
dst_pts = np.float32([kp_list[i + 1][m.trainIdx].pt for m in good_matches_list[i]]).reshape(-1, 1, 2)
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
# print(M)
if M[0, 2] < 0 or M[1, 2] < 0: # 根据平移元素的正负值,判断图像的顺序位置
M, mask = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC, 5.0)
# print(M)
result = Fusion(images[i + 1], result, M)
else:
result = Fusion(result, images[i + 1], M)
cv2.imshow("Stitched", result)
cv2.waitKey(0)
"""
result = cv2.warpPerspective(result, M, (images[i + 1].shape[1] + result.shape[1], result.shape[0]))
cv2.imshow("result", result)
cv2.waitKey(0)
# result[:, result.shape[1] - images[i + 1].shape[1]:] = images[i + 1]
result[:, :images[i + 1].shape[1]] = images[i + 1]
print(result.shape)
cv2.imshow("Stitched", result)
cv2.waitKey(0)
"""
cv2.imwrite('result.jpg', result)