Appearance
question:okay well there are couple things to consider, my images are different sized and first image is reference so it won't have a homogprahy. Also since the initial images are different sized the morphed images might be different sized as well. The final panorama image must not lose image data and get as much as it can, what I mean is if final image cuts, disregards parts one of the warped images because it is too big we don't want that, instead the panorama image should adjust in size to fit all the warped images.
answer:I understand your requirements better now. Based on your description, I have updated the `image_merging` function to handle different-sized images, use the first image as a reference, and adjust the size of the final panorama image to fit all the warped images without losing any data. ```python 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) 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], [w - 1, 0, 1], [0, h - 1, 1], [w - 1, h - 1, 1]]) min_x = min(min_x, corners[:, 0].min()) min_y = min(min_y, corners[:, 1].min()) max_x = max(max_x, corners[:, 0].max()) max_y = max(max_y, corners[:, 1].max()) merged_height = int(np.round(max_y - min_y)) merged_width = int(np.round(max_x - min_x)) # Initialize the merged image with zeros merged_image = np.zeros((merged_height, merged_width, 3), dtype=np.uint8) # 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] return merged_image ``` This updated function computes the size of the final panorama image by considering the corners of all warped images, including the reference image. It then initializes the merged image with the computed size and overlays the warped images on it without losing any data. You can use this `image_merging` function along with your `warp_perspective` function to merge multiple images with different sizes and create a panorama image that fits all the warped images without cutting or disregarding any parts. Make sure to test the functions with your specific use case to ensure they work as expected.
question:I am having error in image_merging(images, homographies) 195 y_offset, x_offset = max(0, -min_y), max(0, -min_x) 196 merged_image_slice = merged_image[y_offset:y_offset + img.shape[0], x_offset:x_offset + img.shape[1]] --> 197 merged_image_slice[~mask] = img[~mask] 198 199 return merged_image IndexError: boolean index did not match indexed array along dimension 1; dimension is 1067 but corresponding boolean dimension is 1068
answer:I apologize for the confusion. The error occurs because the dimensions of the `mask` and the `merged_image_slice` do not match. To fix this issue, we need to ensure that the dimensions of the `mask` and the `merged_image_slice` are the same before applying the mask. Here's the updated `image_merging` function with the fix: ```python 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) 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], [w - 1, 0, 1], [0, h - 1, 1], [w - 1, h - 1, 1]]) min_x = min(min_x, corners[:, 0].min()) min_y = min(min_y, corners[:, 1].min()) max_x = max(max_x, corners[:, 0].max()) max_y = max(max_y, corners[:, 1].max()) merged_height = int(np.round(max_y - min_y)) merged_width = int(np.round(max_x - min_x)) # Initialize the merged image with zeros merged_image = np.zeros((merged_height, merged_width, 3), dtype=np.uint8) # 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]] # Ensure the dimensions of the mask and the merged_image_slice are the same mask = mask[:merged_image_slice.shape[0], :merged_image_slice.shape[1]] merged_image_slice[~mask] = img[~mask] return merged_image ``` This updated function ensures that the dimensions of the `mask` and the `merged_image_slice` are the same before applying the mask, which should resolve the IndexError. Please test the function with your specific use case to ensure it works as expected.
question:something is wrong with height and width, for some reason panorama image is vertically got bigger instead of horizontally. I am displaying using matplotlib. And I see an image what has blackness underneath
answer:I apologize for the confusion. It seems that the issue is caused by the way the merged image size is computed. Let's update the `image_merging` function to correctly compute the size of the merged image by considering the transformed corners of all warped images, including the reference image. Here's the updated `image_merging` function: ```python 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) 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, H in zip(images, [np.eye(3)] + homographies): h, w = img.shape[:2] 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)) min_x = min(min_x, transformed_corners[:, 0].min()) min_y = min(min_y, transformed_corners[:, 1].min()) max_x = max(max_x, transformed_corners[:, 0].max()) max_y = max(max_y, transformed_corners[:, 1].max()) merged_height = int(np.round(max_y - min_y)) merged_width = int(np.round(max_x - min_x)) # Initialize the merged image with zeros merged_image = np.zeros((merged_height, merged_width, 3), dtype=np.uint8) # 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]] # Ensure the dimensions of the mask and the merged_image_slice are the same mask = mask[:merged_image_slice.shape[0], :merged_image_slice.shape[1]] merged_image_slice[~mask] = img[~mask] return merged_image ``` This updated function computes the size of the final panorama image by considering the transformed corners of all images, including the reference image. It should now correctly compute the size of the merged image and create a panorama image that fits all the warped images without cutting or disregarding any parts. Please test the function with your specific use case to ensure it works as expected.
question:ok look I fixed the problem and it was irrelevant but now I have a new problem, 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. 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"
answer:I apologize for the confusion earlier. Based on your description, it seems that the issue is related to the way the homography is calculated or applied in the `warp_perspective` function. To ensure that no content is missing, we need to adjust the way the target coordinates are computed in the `warp_perspective` function. Here's the updated `warp_perspective` function: