I have a problem with my code, in my warp perspective function when I warp the image, the image gets aligned to left side from the interest point, what I mean is from the middle of the image however there is content on the left, so content is missing, it gets cut because it is on the negative part of the image. Can you make sure there is no content missing ? Also this might have something to do with how homography is calculated, since warp_perspective only warps perspective according to already calculated homography. Here is my full code: “import numpy as np
import cv2
import os
import glob
import matplotlib.pyplot as plt
import time
from kayla_tools import

def compute_homography_matrix(src_pts, dst_pts):
def normalize_points(pts):
pts_homogeneous = np.hstack((pts, np.ones((pts.shape[0], 1))))
centroid = np.mean(pts, axis=0)
scale = np.sqrt(2) / np.mean(np.linalg.norm(pts - centroid, axis=1))
T = np.array([[scale, 0, -scale * centroid[0]], [0, scale, -scale * centroid[1]], [0, 0, 1]])
normalized_pts = (T @ pts_homogeneous.T).T
return normalized_pts[:, :2], T

src_pts_normalized, T1 = normalize_points(src_pts)
dst_pts_normalized, T2 = normalize_points(dst_pts)

A = []
for p1, p2 in zip(src_pts_normalized, dst_pts_normalized):
x1, y1 = p1
x2, y2 = p2
A.append([0, 0, 0, -x1, -y1, -1, y2 * x1, y2 * y1, y2])
A.append([x1, y1, 1, 0, 0, 0, -x2 * x1, -x2 * y1, -x2])

A = np.array(A)
try:
_, , VT = np.linalg.svd(A)
except np.linalg.LinAlgError:
return None

h = VT[-1]

H_normalized = h.reshape(3, 3)
H = np.linalg.inv(T2) @ H_normalized @ T1

if np.abs(H[-1, -1]) > 1e-6:
H = H / H[-1, -1]
else:
return None

return H

def filter_matches(matches, ratio_thres=0.7):
filtered_matches = []
for match in matches:
good_match = []
for m, n in match:
if m.distance < ratio_thres * n.distance:
good_match.append(m)
filtered_matches.append(good_match)

return filtered_matches

def find_homography(keypoints, filtered_matches):
homographies = []
skipped_indices = [] # Keep track of skipped images and their indices

for i, matches in enumerate(filtered_matches):
src_pts = np.float32([keypoints[0][m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
dst_pts = np.float32([keypoints[i + 1][m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)

H = ransac_homography(src_pts, dst_pts)

if H is not None:
H = H.astype(np.float32)
homographies.append(H)
else:
print(f"Warning: Homography computation failed for image pair (0, {i + 1}). Skipping.")
skipped_indices.append(i + 1) # Add indices of skipped images to the list
continue

return homographies, skipped_indices

def ransac_homography(src_pts, dst_pts, iterations=2000, threshold=3):
best_inlier_count = 0
best_homography = None

if len(src_pts) != len(dst_pts) or len(src_pts) < 4:
raise ValueError(“The number of source and destination points must be equal and at least 4.”)

src_pts = np.array(src_pts)
dst_pts = np.array(dst_pts)

for _ in range(iterations):
indices = np.random.choice(len(src_pts), 4, replace=False)
src_subset = src_pts[indices].reshape(-1, 2)
dst_subset = dst_pts[indices].reshape(-1, 2)

homography = compute_homography_matrix(src_subset, dst_subset)
if homography is None:
continue

inliers = 0
for i in range(len(src_pts)):
projected_point = np.dot(homography, np.append(src_pts[i], 1))
if np.abs(projected_point[-1]) > 1e-6:
projected_point = projected_point / projected_point[-1]
else:
continue

distance = np.linalg.norm(projected_point[:2] - dst_pts[i])

if distance < threshold:
inliers += 1

if inliers > best_inlier_count:
best_inlier_count = inliers
best_homography = homography

if best_homography is None:
raise RuntimeError(“Failed to find a valid homography matrix.”)

return best_homography

def read_ground_truth_homographies(dataset_path):
H_files = sorted(glob.glob(os.path.join(dataset_path, “H”)))
ground_truth_homographies = []

for filename in H_files:
H = np.loadtxt(filename)
ground_truth_homographies.append(H)

return ground_truth_homographies

def warp_perspective(img, H, reverse=False):
# Apply homography matrix to the source image corners
print(img.shape)
display_image(img)
if reverse:
H = np.linalg.inv(H)
h, w = img.shape[0], img.shape[1]
corners = np.array([[0, 0, 1], [w-1, 0, 1], [0, h-1, 1], [w-1, h-1, 1]])
transformed_corners = np.dot(H, corners.T).T
transformed_corners /= transformed_corners[:, 2].reshape((-1, 1))

# Compute the bounding box of the transformed corners and
# define the target_shape as the (height, width) of the bounding box
min_x, min_y = transformed_corners.min(axis=0)[:2]
max_x, max_y = transformed_corners.max(axis=0)[:2]
target_shape = (int(np.round(max_y - min_y)), int(np.round(max_x - min_x)))
h, w = target_shape

target_y, target_x = np.meshgrid(np.arange(h), np.arange(w))
target_coordinates = np.stack([target_x.ravel(), target_y.ravel(), np.ones(target_x.size)])



source_coordinates = np.dot(np.linalg.inv(H), target_coordinates)
source_coordinates /= source_coordinates[2, :]

valid = np.logical_and(np.logical_and(0 <= source_coordinates[0, :], source_coordinates[0, :] < img.shape[1] - 1),
np.logical_and(0 <= source_coordinates[1, :], source_coordinates[1, :] < img.shape[0] - 1))

# Change the dtype to int instead of float64 to save memory
valid_source_coordinates = np.round(source_coordinates[:, valid].astype(np.float32)[:2]).astype(int)
valid_target_coordinates = target_coordinates[:, valid].astype(int)[:2]

valid_source_coordinates[0] = np.clip(valid_source_coordinates[0], 0, img.shape[1] - 1)
valid_source_coordinates[1] = np.clip(valid_source_coordinates[1], 0, img.shape[0] - 1)
valid_target_coordinates[0] = np.clip(valid_target_coordinates[0], 0, w - 1)
valid_target_coordinates[1] = np.clip(valid_target_coordinates[1], 0, h - 1)

warped_image = np.zeros((h, w, 3), dtype=np.uint8)
for i in range(3):
warped_image[…, i][valid_target_coordinates[1], valid_target_coordinates[0]] = img[…, i][valid_source_coordinates[1], valid_source_coordinates[0]]

print(warped_image.shape)
display_image(warped_image)
return warped_image

def image_merging(images, homographies):
if len(images) - 1 != len(homographies):
raise ValueError(“The number of homography matrices must be one less than the number of images.”)

# Warp all images except the first one using their corresponding homography matrices
warped_images = [images[0]] + [warp_perspective(img, H, True) for img, H in zip(images[1:], homographies)]

# Compute the size of the merged image
min_x, min_y, max_x, max_y = 0, 0, 0, 0
for img in warped_images:
h, w = img.shape[:2]
corners = np.array([[0, 0, 1], [h, 0, 1], [0, w, 1], [h, w, 1]])
min_x = min(min_x, corners[:, 1].min())
min_y = min(min_y, corners[:, 0].min())
max_x = max(max_x, corners[:, 1].max())
max_y = max(max_y, corners[:, 0].max())
print(corners, min_x, min_y, max_x, max_y)

merged_height = int(np.ceil(max_y - min_y))
merged_width = int(np.ceil(max_x - min_x))
# Initialize the merged image with zeros
merged_image = np.zeros((merged_height, merged_width, 3), dtype=np.uint8)
print(merged_image.shape)
# Merge the warped images by overlaying them on the merged image
for img in warped_images:
mask = np.all(img == 0, axis=-1)
y_offset, x_offset = max(0, -min_y), max(0, -min_x)
merged_image_slice = merged_image[y_offset:y_offset + img.shape[0], x_offset:x_offset + img.shape[1]]
merged_image_slice[~mask] = img[~mask]
print(merged_image.shape)
return merged_image”