I am getting error: in merge_images(sub_images, keypoints, filtered_matches, homographies)
     84     for i in range(1, len(sub_images)):
     85         img_i = sub_images[i]
---> 86         H_i = homographies[i-1]
     87 
     88         h1, w1 = ref_img.shape[:2]

IndexError: list index out of range  In this code: import numpy as np
import cv2
import os
import glob
import matplotlib.pyplot as plt
import time


def feature_extraction(sub_images, method="SIFT"):
    if method == "SIFT":
        keypoint_extractor = cv2.xfeatures2d.SIFT_create()
    elif method == "SURF":
        keypoint_extractor = cv2.xfeatures2d.SURF_create()
    elif method == "ORB":
        keypoint_extractor = cv2.ORB_create()

    keypoints = []
    descriptors = []

    for sub_image in sub_images:
        keypoint, descriptor = keypoint_extractor.detectAndCompute(sub_image, None)
        keypoints.append(keypoint)
        descriptors.append(descriptor)

    return keypoints, descriptors


def feature_matching(descriptors, matcher_type="BF"):
    if matcher_type == "BF":
        matcher = cv2.BFMatcher()

    matches = []
    for i in range(1, len(descriptors)):
        match = matcher.knnMatch(descriptors[0], descriptors[i], k=2)
        matches.append(match)
        
    return matches


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 = []
    
    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, _ = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC)
        
        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.")
            continue
        
    return homographies

def read_ground_truth_homographies(dataset_path):
    H_files = sorted(glob.glob(os.path.join(dataset_path, "H_.txt")))
    ground_truth_homographies = []
    
    for filename in H_files:
        H = np.loadtxt(filename)
        ground_truth_homographies.append(H)
    
    return ground_truth_homographies


def merge_images(sub_images, keypoints, filtered_matches, homographies):
    ref_img = sub_images[0]

    for i in range(1, len(sub_images)):
        img_i = sub_images[i]
        H_i = homographies[i-1]

        h1, w1 = ref_img.shape[:2]
        h2, w2 = img_i.shape[:2]

        pts1 = np.float32([[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2)
        pts2 = np.float32([[0, 0], [0, h2], [w2, h2], [w2, 0]]).reshape(-1, 1, 2)
        pts2_trans = cv2.perspectiveTransform(pts2, H_i)

        new_pts = np.concatenate((pts1, pts2_trans), axis=0)
        min_x, min_y = np.int32(new_pts.min(axis=0).ravel())
        max_x, max_y = np.int32(new_pts.max(axis=0).ravel())
        trans_dst = [-min_x, -min_y]
        H_trans = np.array([[1.0, 0, trans_dst[0]], [0, 1.0, trans_dst[1]], [0, 0, 1.0]])

        result = cv2.warpPerspective(ref_img, H_trans, (max_x - min_x, max_y - min_y))
        img_warped = cv2.warpPerspective(img_i, H_i, (result.shape[1], result.shape[0]))

        mask = (result == 0)
        result[mask] = img_warped[mask]

        ref_img = result

    return ref_img


def main(dataset_path):
    filenames = sorted(glob.glob(os.path.join(dataset_path, "*.png")))
    sub_images = []

    for filename in filenames:
        img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
        sub_images.append(img)
        
    ground_truth_homographies = read_ground_truth_homographies(dataset_path)

    methods = ["SIFT", "SURF", "ORB"]
    for method in methods:
        start_time = time.time()
        
        keypoints, descriptors = feature_extraction(sub_images, method=method)
        matches = feature_matching(descriptors)
        filtered_matches = filter_matches(matches)
        homographies = find_homography(keypoints, filtered_matches)
        panorama = merge_images(sub_images, keypoints, filtered_matches, homographies)
        
        end_time = time.time()
        runtime = end_time - start_time

        print(f"Method: {method} - Runtime: {runtime:.2f} seconds")

        for idx, (image, kp) in enumerate(zip(sub_images, keypoints)):
            feature_plot = cv2.drawKeypoints(image, kp, None)
            plt.figure()
            plt.imshow(feature_plot, cmap="gray")
            plt.title(f"Feature Points - {method} - Image {idx}")

        for i, match in enumerate(filtered_matches):
            matching_plot = cv2.drawMatches(sub_images[0], keypoints[0], sub_images[i + 1], keypoints[i + 1], match, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
            plt.figure()
            plt.imshow(matching_plot, cmap="gray")
            plt.title(f"Feature Point Matching - {method} - Image 0 - Image {i + 1}")

        plt.figure()
        plt.imshow(panorama, cmap="gray")
        plt.title(f"Panorama - {method}")

        print("\nGround truth homographies")
        for i, H_gt in enumerate(ground_truth_homographies):
            print(f"Image 0 to {i+1}:")
            print(H_gt)

        print("\nComputed homographies")
        for i, H_est in enumerate(homographies):
            print(f"Image 0 to {i+1}:")
            print(H_est)

    plt.show()


if __name__ == "__main__":
    dataset_path = "dataset/v_bird"
    main(dataset_path)