Skip to content

Instantly share code, notes, and snippets.

@ChronoMonochrome
Created June 2, 2023 08:20
Show Gist options
  • Save ChronoMonochrome/96e9cc519ffa6cc0cfa9dd8c7d786756 to your computer and use it in GitHub Desktop.
Save ChronoMonochrome/96e9cc519ffa6cc0cfa9dd8c7d786756 to your computer and use it in GitHub Desktop.
Image stitching with OpenCV
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