Created
June 2, 2023 08:20
-
-
Save ChronoMonochrome/96e9cc519ffa6cc0cfa9dd8c7d786756 to your computer and use it in GitHub Desktop.
Image stitching with OpenCV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cv2 | |
from PIL import Image | |
import numpy as np | |
def cv2_estimate_dx_dy(prev_filename, curr_filename): | |
""" | |
Calculates dx and dy using OpenCV script for two consecutive frames | |
""" | |
# Load the previous and current frames | |
prev_frame = cv2.imread(prev_filename, cv2.IMREAD_GRAYSCALE) | |
curr_frame = cv2.imread(curr_filename, cv2.IMREAD_GRAYSCALE) | |
# Initialize SIFT feature detector and compute keypoints and descriptors for both frames | |
sift = cv2.SIFT_create() | |
kp1, des1 = sift.detectAndCompute(prev_frame, None) | |
kp2, des2 = sift.detectAndCompute(curr_frame, None) | |
# Initialize brute-force matcher | |
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True) | |
# Match features between the two frames | |
matches = bf.match(des1, des2) | |
# Sort matches by distance | |
matches = sorted(matches, key=lambda x: x.distance) | |
# Select the top n matches | |
n = min(50, len(matches)) | |
matched_kp1 = [kp1[match.queryIdx] for match in matches[:n]] | |
matched_kp2 = [kp2[match.trainIdx] for match in matches[:n]] | |
# Estimate the affine transformation matrix between matched keypoints | |
M, _ = cv2.estimateAffinePartial2D( | |
np.float32([kp.pt for kp in matched_kp1]).reshape(-1, 2), | |
np.float32([kp.pt for kp in matched_kp2]).reshape(-1, 2) | |
) | |
# Extract the translation vector from the affine matrix | |
dx, dy = M[:2, 2] | |
return dx, dy | |
# Set the width and height of the final merged image | |
width = 5000 | |
height = 5000 | |
N=125 | |
# Find the maximum width and height of any of the frames | |
max_width = 0 | |
max_height = 0 | |
for i in range(1, N+1): | |
# Open the next frame image | |
filename = f"1/output_frames_{i:03d}.jpg" | |
next_frame = Image.open(filename) | |
# Update the maximum width and height | |
max_width = max(max_width, next_frame.width) | |
max_height = max(max_height, next_frame.height) | |
# Set the starting position of the first frame | |
x_offset = max_width - 1 | |
y_offset = height - max_height # Updated to bottom left corner | |
# Create a new blank image to merge the frames onto | |
merged_image = Image.new('RGB', (width, height)) | |
# Initialize dx and dy | |
dx = 0 | |
dy = 0 | |
for i in range(1, N+1): | |
# Open the next frame image | |
filename = f"1/output_frames_{i:03d}.jpg" | |
next_frame = Image.open(filename) | |
print(filename) | |
if i == 1: | |
# For the first frame, set dx and dy according to the OpenCV script | |
dx, dy = cv2_estimate_dx_dy(filename, f"1/output_frames_{i+1:03d}.jpg") | |
elif i == 2: | |
# For the second frame, set dx and dy according to the OpenCV script | |
dx, dy = cv2_estimate_dx_dy(f"1/output_frames_{i-1:03d}.jpg", filename) | |
else: | |
# Calculate dx and dy based on the previous frame's position and the calculated values from the OpenCV script | |
x_offset += abs(dx) | |
y_offset -= abs(dy) | |
dx, dy = cv2_estimate_dx_dy(f"1/output_frames_{i-1:03d}.jpg", filename) | |
# Merge the next frame onto the blank image | |
merged_image.paste(next_frame, (round(x_offset), round(y_offset))) | |
# Save the final merged image | |
merged_image.save("merged_image.jpg") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment