mower-ng/水平拼接orb flann 自动识别重叠区域 无接缝修复.py
Elaina 7c284cc063
All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
卡死重启时间重置
2025-02-01 11:47:30 +08:00

143 lines
5.5 KiB
Python

from glob import glob
import cv2
import numpy as np
class ImageStitcher:
def __init__(self, folder_path=None):
self.images = [] # 存储图像的列表
self.offsets = [] # 存储每对图像的偏移量
if folder_path:
self.load_images(folder_path) # 如果提供了文件夹路径,则加载图像
def load_images(self, folder_path):
"""加载指定文件夹中的所有PNG图像,并裁剪掉顶部300像素。"""
self.images = []
for item in glob(f"{folder_path}/*.png"):
img = cv2.imread(item)[300:]
if img is not None:
self.images.append(img) # 裁剪掉顶部300像素
else:
print(f"Warning: Unable to load image {item}")
def calculate_offsets(self):
"""计算每对相邻图像的偏移量。"""
if len(self.images) < 2:
raise ValueError("At least two images are required to calculate offsets.")
# 初始化 ORB 检测器
orb = cv2.ORB_create()
# 初始化 FLANN 匹配器(针对二进制描述符)
FLANN_INDEX_LSH = 6 # LSH 算法适用于二进制描述符
index_params = dict(
algorithm=FLANN_INDEX_LSH,
table_number=6, # 哈希表数量
key_size=12, # 哈希键大小
multi_probe_level=1, # 多探针级别
)
search_params = dict(checks=20) # 控制搜索精度,值越大越精确但越慢
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 预计算所有图像的关键点和描述符
keypoints_and_descriptors = []
for img in self.images:
keypoints, descriptors = orb.detectAndCompute(img, None)
keypoints_and_descriptors.append((keypoints, descriptors))
self.offsets = [] # 清空偏移量
for i in range(len(self.images) - 1):
keypoints1, descriptors1 = keypoints_and_descriptors[i]
keypoints2, descriptors2 = keypoints_and_descriptors[i + 1]
# 使用 FLANN 进行特征匹配
matches = flann.knnMatch(descriptors1, descriptors2, k=2)
# 过滤匹配点,使用 Lowe's ratio test
good_matches = []
for match_pair in matches:
if len(match_pair) == 2: # 确保每个匹配对有两个点
m, n = match_pair
if m.distance < 0.8 * n.distance: # Lowe's ratio test
good_matches.append(m)
# 至少需要 4 个匹配点来计算单应性矩阵
if len(good_matches) >= 4:
src_pts = np.float32(
[keypoints1[m.queryIdx].pt for m in good_matches]
).reshape(-1, 1, 2)
dst_pts = np.float32(
[keypoints2[m.trainIdx].pt for m in good_matches]
).reshape(-1, 1, 2)
# 计算单应性矩阵
H, _ = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC, 5.0)
# 计算 curr_img 的四个角在 prev_img 坐标系中的位置
h, w = self.images[i + 1].shape[:2]
corners = np.float32(
[[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]
).reshape(-1, 1, 2)
warped_corners = cv2.perspectiveTransform(corners, H)
# 计算偏移量
offset_x = int(warped_corners[0][0][0]) # 左上角的 x 坐标
offset_y = int(warped_corners[0][0][1]) # 左上角的 y 坐标
self.offsets.append((offset_x, offset_y))
else:
raise ValueError(
f"Not enough matches found between image {i} and image {i + 1}."
)
def stitch_images(self):
"""根据偏移量统一拼接所有图像。"""
if not self.images:
raise ValueError("No images provided for stitching.")
if not self.offsets:
self.calculate_offsets() # 如果没有计算偏移量,则先计算
# 计算每张图像在最终拼接图像中的绝对位置
absolute_positions = [(0, 0)] # 第一张图像的绝对位置为 (0, 0)
for i in range(1, len(self.images)):
prev_x, prev_y = absolute_positions[i - 1]
offset_x, offset_y = self.offsets[i - 1]
absolute_positions.append((prev_x + offset_x, prev_y + offset_y))
# 计算最终拼接图像的大小
min_x = min(pos[0] for pos in absolute_positions)
min_y = min(pos[1] for pos in absolute_positions)
max_x = max(
pos[0] + img.shape[1] for pos, img in zip(absolute_positions, self.images)
)
max_y = max(
pos[1] + img.shape[0] for pos, img in zip(absolute_positions, self.images)
)
# 创建空白图像用于存储拼接结果
stitched_image = np.zeros((max_y - min_y, max_x - min_x, 3), dtype=np.uint8)
# 将每张图像放置到正确的位置
for img, (x, y) in zip(self.images, absolute_positions):
x -= min_x
y -= min_y
stitched_image[y : y + img.shape[0], x : x + img.shape[1]] = img
return stitched_image
def main():
folder_path = "./img" # 图像文件夹路径
# 创建 ImageStitcher 实例并加载图像
stitcher = ImageStitcher(folder_path)
# 拼接图像
stitched_image = stitcher.stitch_images()
# 保存结果
cv2.imwrite("result.png", stitched_image)
if __name__ == "__main__":
main()