All checks were successful
ci/woodpecker/push/check_format Pipeline was successful
143 lines
5.5 KiB
Python
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()
|