{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91498,"databundleVersionId":11655853,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom numpy.linalg import norm\nfrom scipy.spatial.transform import Rotation as R\nimport matplotlib.pyplot as plt\nfrom mpl_toolkits.mplot3d import Axes3D\nfrom pathlib import Path\nimport cv2\nimport os\nfrom PIL import Image\nfrom itertools import combinations\nimport networkx as nx\nimport glob","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:50.231186Z","iopub.execute_input":"2025-05-23T20:30:50.231579Z","iopub.status.idle":"2025-05-23T20:30:50.239254Z","shell.execute_reply.started":"2025-05-23T20:30:50.231547Z","shell.execute_reply":"2025-05-23T20:30:50.237350Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/image-matching-challenge-2025/train_labels.csv')\ndf = df.dropna()\nprint(df.info())\nprint(df.describe())\nprint(\"Null data:\\n\", df.isnull().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:50.241056Z","iopub.execute_input":"2025-05-23T20:30:50.241459Z","iopub.status.idle":"2025-05-23T20:30:50.368953Z","shell.execute_reply.started":"2025-05-23T20:30:50.241428Z","shell.execute_reply":"2025-05-23T20:30:50.368041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df['translation_vector'] = df['translation_vector'].apply(lambda x: np.fromstring(x, sep=';') if pd.notnull(x) else np.zeros(3))\n\ndf['rotation_matrix'] = df['rotation_matrix'].apply(lambda x: np.fromstring(x, sep=';').reshape(3, 3) if pd.notnull(x) else np.eye(3))\n\ndf[['x', 'y', 'z']] = pd.DataFrame(df['translation_vector'].tolist(), index=df.index)\n\nfigs = plt.figure(figsize=(8,6))\naxes = figs.add_axes(111, projection='3d')\n\naxes.scatter(df['x'], df['y'], df['z'], c='black', s=10, alpha=0.7)\naxes.set_title(\"3D Scatter of Camera Translation Vectors\")\naxes.set_xlabel(\"X\")\naxes.set_ylabel(\"Y\")\naxes.set_zlabel(\"Z\")\nplt.grid(True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:50.369911Z","iopub.execute_input":"2025-05-23T20:30:50.370209Z","iopub.status.idle":"2025-05-23T20:30:50.831311Z","shell.execute_reply.started":"2025-05-23T20:30:50.370185Z","shell.execute_reply":"2025-05-23T20:30:50.830211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scenes = df['scene'].unique()\nplt.figure(figsize=(9,7))\nfor scene in scenes:\n    group = df[df['scene'] == scene]\n    plt.scatter(group['x'], group['y'], label=scene, s=10)\nplt.title(\"2D Camera Positions by Scene\")\nplt.xlabel(\"X\")\nplt.ylabel(\"Y\")\nplt.grid(True)\nplt.legend(bbox_to_anchor=(1.5, 1))\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:50.833798Z","iopub.execute_input":"2025-05-23T20:30:50.834077Z","iopub.status.idle":"2025-05-23T20:30:52.101495Z","shell.execute_reply.started":"2025-05-23T20:30:50.834055Z","shell.execute_reply":"2025-05-23T20:30:52.100268Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scene_counts = df['scene'].value_counts().sort_values(ascending=False)\n\nplt.figure(figsize=(10, 6))\nscene_counts.plot(kind='bar', color='pink')\nplt.title(\"Scene-Based Image Counts\")\nplt.xlabel(\"Scene Type\")\nplt.ylabel(\"Image Count\")\nplt.xticks(rotation=90, ha='right')\nplt.grid(axis='y')\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:52.102465Z","iopub.execute_input":"2025-05-23T20:30:52.102722Z","iopub.status.idle":"2025-05-23T20:30:52.630480Z","shell.execute_reply.started":"2025-05-23T20:30:52.102703Z","shell.execute_reply":"2025-05-23T20:30:52.629497Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(10, 5))\nplt.hist(df['x'].dropna(), bins=30, color='skyblue', label='X', alpha=0.6)\nplt.hist(df['y'].dropna(), bins=30, color='green', label='Y', alpha=0.6)\nplt.hist(df['z'].dropna(), bins=30, color='red', label='Z', alpha=0.6)\nplt.title(\"Distribution of Camera Translation Coordinates\")\nplt.xlabel(\"Coordinate Value\")\nplt.ylabel(\"Frequency\")\nplt.legend()\nplt.grid(True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:52.631623Z","iopub.execute_input":"2025-05-23T20:30:52.631963Z","iopub.status.idle":"2025-05-23T20:30:53.147949Z","shell.execute_reply.started":"2025-05-23T20:30:52.631932Z","shell.execute_reply":"2025-05-23T20:30:53.146814Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scene_counts = df.groupby(['dataset', 'scene']).size().reset_index(name='count')\nscene_counts = scene_counts.sort_values(by='count', ascending=False)\n\nplt.figure(figsize=(14, 7))\nplt.barh(scene_counts['scene'] + \" (\" + scene_counts['dataset'] + \")\", scene_counts['count'], color='orchid')\nplt.xlabel(\"Number of Images\")\nplt.title(\"Image Count per Scene per Dataset\")\nplt.tight_layout()\nplt.grid(True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:53.149152Z","iopub.execute_input":"2025-05-23T20:30:53.149868Z","iopub.status.idle":"2025-05-23T20:30:53.879645Z","shell.execute_reply.started":"2025-05-23T20:30:53.149836Z","shell.execute_reply":"2025-05-23T20:30:53.878445Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scenes = df['scene'].unique()\nplt.figure(figsize=(10, 8))\n\nfor scene in scenes:\n    subset = df[df['scene'] == scene]\n    plt.scatter(subset['x'].mean(), subset['y'].mean(), label=scene)\n\nplt.title(\"2D Centroids of Scenes (XY Plane)\")\nplt.xlabel(\"X Mean\")\nplt.ylabel(\"Y Mean\")\nplt.legend(bbox_to_anchor=(1.5, 1))\nplt.grid(True)\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:53.880678Z","iopub.execute_input":"2025-05-23T20:30:53.880940Z","iopub.status.idle":"2025-05-23T20:30:55.148916Z","shell.execute_reply.started":"2025-05-23T20:30:53.880918Z","shell.execute_reply":"2025-05-23T20:30:55.147894Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df['img_path'] = df.apply(\n    lambda row: (Path(\"train\") / row[\"dataset\"] / row[\"image\"]).as_posix(),\n    axis=1\n)\n\norb = cv2.ORB_create(\n    nfeatures=4000,\n    scaleFactor=1.2,\n    nlevels=8\n)\nprint(\"ORB parameters:\")\nprint(\"nfeatures:\", orb.getMaxFeatures())\nprint(\"scaleFactor:\", orb.getScaleFactor())\nprint(\"nlevels:\", orb.getNLevels())\n\ndef extract_features(image_path, detector):\n    print(\"Trying to load image:\", image_path)\n    img = cv2.imread(str(image_path), cv2.IMREAD_GRAYSCALE)\n\n    if img is None:\n        print(\"❌ Warning: could not load image\", image_path)\n        return None, None\n\n    print(\"Image loaded, shape:\", img.shape)\n    keypoints, descriptors = detector.detectAndCompute(img, None)\n\n    print(\"Extracted\", len(keypoints), \"keypoints\")\n    if descriptors is not None:\n        print(\"Descriptors shape:\", descriptors.shape)\n    else:\n        print(\"No descriptors found.\")\n\n    return keypoints, descriptors\n\nBASE_PATH = Path(\"/kaggle/input/image-matching-challenge-2025/train\")\n\ndf['img_path'] = df.apply(\n    lambda row: (BASE_PATH / row[\"dataset\"] / row[\"image\"]).as_posix(),\n    axis=1\n)\n\nsample_path = df['img_path'].iloc[0]\nprint(\"File exists?\", os.path.exists(sample_path))\nprint(\"Absolute path:\", os.path.abspath(sample_path))\n\nprint(\"File exists?\", os.path.exists(sample_path))\nprint(\"Absolute path:\", os.path.abspath(sample_path))\n\ntry:\n    img = Image.open(sample_path)\n    plt.imshow(img)\n    plt.title(\"Sample Image\")\n    plt.axis(\"off\")\nexcept Exception as e:\n    print(\"PIL could not open image:\", e)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:55.150132Z","iopub.execute_input":"2025-05-23T20:30:55.150479Z","iopub.status.idle":"2025-05-23T20:30:56.076028Z","shell.execute_reply.started":"2025-05-23T20:30:55.150452Z","shell.execute_reply":"2025-05-23T20:30:56.074997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)\n\ndef match_descriptors(desc1, desc2, matcher, ratio=0.75, min_matches=15):\n    if desc1 is None or desc2 is None:\n        return 0\n    matches = matcher.knnMatch(desc1, desc2, k=2)\n    good = []\n\n    for m, n in matches:\n        if m.distance < ratio * n.distance:\n            good.append(m)\n\n    if len(good) >= min_matches:\n        return len(good)\n    else:\n        return 0\n\ndescriptor_cache = {}\n\ndef cache_features(img_path):\n    if img_path not in descriptor_cache:\n        kps, descs = extract_features(img_path, orb)\n        descriptor_cache[img_path] = descs\n    return descriptor_cache[img_path]\nmatcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)\n\nsample_df = df.head(5)\nfor path1, path2 in combinations(sample_df['img_path'], 2):\n    desc1 = cache_features(path1)\n    desc2 = cache_features(path2)\n    score = match_descriptors(desc1, desc2, matcher)\n\n    print(\"Match score between\\n\", Path(path1).name and Path(path2).name, score)\n\nG = nx.Graph()\n\nsample_df = df.head(10)\n\nfor idx in sample_df.index:\n    G.add_node(idx)\n\nmatcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)\ndescriptor_cache = {}\n\ndef cache_features(img_path):\n    if img_path not in descriptor_cache:\n        _, descs = extract_features(img_path, orb)\n        descriptor_cache[img_path] = descs\n    return descriptor_cache[img_path]\n\nfor i, j in combinations(sample_df.index, 2):\n    path_i = sample_df.at[i, 'img_path']\n    path_j = sample_df.at[j, 'img_path']\n\n    desc_i = cache_features(path_i)\n    desc_j = cache_features(path_j)\n\n    score = match_descriptors(desc_i, desc_j, matcher)\n\nif score > 20:\n    G.add_edge(i, j, weight=score)\n    print(\"Added edge: \", Path(path_i).name, \" ↔ \", Path(path_j).name,\" with score \", str(score))\n\ncomponents = list(nx.connected_components(G))\nfiltered_components = []\n\nfor c in components:\n    if len(c) >= 2:\n        filtered_components.append(c)\n\ncomponents = filtered_components\n\nprint(\"Number of grouped scenes:\", len(components))\nscene_map = {}\nfor i, comp in enumerate(components):\n    for node in comp:\n        scene_map[node] = \"scene_\" + str(i)\n\ndf['pred_scene'] = df.index.map(scene_map).fillna(\"outlier\")\nprint(\"Prediction scene:\\n\",df['pred_scene'].value_counts().head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:30:56.078799Z","iopub.execute_input":"2025-05-23T20:30:56.079103Z","iopub.status.idle":"2025-05-23T20:31:07.292494Z","shell.execute_reply.started":"2025-05-23T20:30:56.079080Z","shell.execute_reply":"2025-05-23T20:31:07.291549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scene_counts = df['pred_scene'].value_counts().sort_values(ascending=False)\n\nplt.figure(figsize=(13, 6))\nscene_counts.plot(kind='bar', color='purple')\nplt.title(\"Distribution of Images Across Predicted Scene Categories\")\nplt.xlabel(\"Predicted Scene Category\")\nplt.ylabel(\"Number of Images\")\nplt.xticks(rotation=45)\nplt.grid(True)\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:07.293379Z","iopub.execute_input":"2025-05-23T20:31:07.293629Z","iopub.status.idle":"2025-05-23T20:31:07.562683Z","shell.execute_reply.started":"2025-05-23T20:31:07.293607Z","shell.execute_reply":"2025-05-23T20:31:07.561501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scene_id = df['pred_scene'].unique()[0]\n\nscene_images = df[df['pred_scene'] == scene_id]['img_path'].tolist()[:5]\n\nplt.figure(figsize=(12, 4))\n\nfor i, img_path in enumerate(scene_images):\n    img = Image.open(img_path)\n    plt.subplot(1, len(scene_images), i + 1)\n    plt.imshow(img)\n    plt.title(Path(img_path).name)\n    plt.axis('off')\n\nplt.suptitle(\"Sample Images from \" + str(scene_id))\nplt.subplots_adjust(top=0.75)\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:07.563782Z","iopub.execute_input":"2025-05-23T20:31:07.564073Z","iopub.status.idle":"2025-05-23T20:31:11.026344Z","shell.execute_reply.started":"2025-05-23T20:31:07.564049Z","shell.execute_reply":"2025-05-23T20:31:11.025072Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def match_keypoints(img1_path, img2_path, detector, matcher):\n    img1 = cv2.imread(img1_path, cv2.IMREAD_GRAYSCALE)\n    img2 = cv2.imread(img2_path, cv2.IMREAD_GRAYSCALE)\n\n    kp1, des1 = detector.detectAndCompute(img1, None)\n    kp2, des2 = detector.detectAndCompute(img2, None)\n\n    matches = matcher.knnMatch(des1, des2, k=2)\n\n    good = []\n    for m, n in matches:\n        if m.distance < 0.75 * n.distance:\n            good.append(m)\n\n    if len(good) < 8:\n        print(\"Not enough good matches.\")\n        return None, None, None\n\n    pts1 = np.float32([kp1[m.queryIdx].pt for m in good])\n    pts2 = np.float32([kp2[m.trainIdx].pt for m in good])\n\n    return pts1, pts2, good\n\nfocal_length = 1.0\nprincipal_point = (0.0, 0.0)\n\ndef estimate_pose_from_points(pts1, pts2, focal=1.0, pp=(0.0, 0.0)):\n    E, mask = cv2.findEssentialMat(pts1, pts2, focal=focal, pp=pp, method=cv2.RANSAC, prob=0.999, threshold=1.0)\n    if E is None:\n        print(\"Failed to compute essential matrix.\")\n        return None, None\n\n    print(\"Essential matrix computed.\")\n    _, R, t, mask = cv2.recoverPose(E, pts1, pts2, focal=focal, pp=pp)\n\n    return R, t\n\nimg1_path = df['img_path'].iloc[0]\nimg2_path = df['img_path'].iloc[1]\npts1, pts2, _ = match_keypoints(img1_path, img2_path, orb, bf)\n\nif pts1 is not None:\n    print(\"Matched keypoints:\", len(pts1))\n    R, t = estimate_pose_from_points(pts1, pts2)\n    if R is not None:\n        print(\"Rotation matrix:\\n\", R)\n        print(\"Translation vector:\\n\", t)\n\nscene_id = [s for s in df['pred_scene'].unique() if s != 'outlier'][0]\nscene_df = df[df['pred_scene'] == scene_id].reset_index(drop=True)\n\npose_graph = {}\nref_img_path = scene_df['img_path'].iloc[0]\npose_graph[ref_img_path] = (np.eye(3), np.zeros((3, 1)))\n\nfor i in range(1, len(scene_df)):\n    img_path = scene_df['img_path'].iloc[i]\n    pts1, pts2, good = match_keypoints(ref_img_path, img_path, orb, bf)\n\n    if pts1 is None:\n        print(\"Could not match:\", img_path)\n        pose_graph[img_path] = (None, None)\n        continue\n\n    print(\"Matches found:\", len(good))\n    R, t = estimate_pose_from_points(pts1, pts2)\n\n    if R is not None and t is not None:\n        pose_graph[img_path] = (R, t)\n        print(\"Stored pose for:\", Path(img_path).name)\n    else:\n        pose_graph[img_path] = (None, None)\n        print(\"Pose estimation failed for:\", Path(img_path).name)\n\nplt.figure(figsize=(14, 6))\n\nfor img_path, (R, t) in pose_graph.items():\n    if t is not None:\n        x, y, z = t.flatten()\n        plt.scatter(x, z, c='red')\n        plt.text(x, z, Path(img_path).name[:10], fontsize=19)\n\nplt.title(\"Estimated Camera Translations (X vs Z)\")\nplt.xlabel(\"X (translation)\")\nplt.ylabel(\"Z (translation)\")\nplt.grid(True)\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:11.027365Z","iopub.execute_input":"2025-05-23T20:31:11.027630Z","iopub.status.idle":"2025-05-23T20:31:12.877769Z","shell.execute_reply.started":"2025-05-23T20:31:11.027610Z","shell.execute_reply":"2025-05-23T20:31:12.876864Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def format_matrix(mat):\n    if mat is None:\n        return 'nan;nan;nan;nan;nan;nan;nan;nan;nan'\n    return ';'.join([str(round(x, 6)) for x in mat.flatten()])\n\ndef format_vector(vec):\n    if vec is None:\n        return 'nan;nan;nan'\n    return ';'.join([str(round(x, 6)) for x in vec.flatten()])\n\ndf['rotation_matrix'] = df['img_path'].apply(lambda p: format_matrix(pose_graph.get(p, (None, None))[0]))\ndf['translation_vector'] = df['img_path'].apply(lambda p: format_vector(pose_graph.get(p, (None, None))[1]))\n\nsubmission = df[['dataset', 'pred_scene', 'image', 'rotation_matrix', 'translation_vector']]\nsubmission.columns = ['dataset', 'scene', 'image', 'rotation_matrix', 'translation_vector']\n\nsubmission.to_csv('submission.csv', index=False)\nprint(\"submission.csv written!\")\nprint(submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:12.878603Z","iopub.execute_input":"2025-05-23T20:31:12.878924Z","iopub.status.idle":"2025-05-23T20:31:12.910380Z","shell.execute_reply.started":"2025-05-23T20:31:12.878901Z","shell.execute_reply":"2025-05-23T20:31:12.909275Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Matching Image Section**","metadata":{}},{"cell_type":"code","source":"def draw_matches_statue(img1_path, img2_path, detector, matcher, max_matches=30):\n    img1 = cv2.imread(img1_path)\n    img2 = cv2.imread(img2_path)\n\n    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)\n    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)\n\n    kp1, des1 = detector.detectAndCompute(gray1, None)\n    kp2, des2 = detector.detectAndCompute(gray2, None)\n\n    matches = matcher.knnMatch(des1, des2, k=2)\n    good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]\n\n    if len(good_matches) > max_matches:\n        good_matches = good_matches[:max_matches]\n\n    h1, w1 = img1.shape[:2]\n    h2, w2 = img2.shape[:2]\n\n    height = max(h1, h2)\n    vis = np.zeros((height, w1 + w2, 3), dtype=np.uint8)\n    vis[:h1, :w1] = img1\n    vis[:h2, w1:] = img2\n\n    for match in good_matches:\n        pt1 = tuple(np.round(kp1[match.queryIdx].pt).astype(int))\n        pt2 = tuple(np.round(kp2[match.trainIdx].pt).astype(int))\n        pt2 = (pt2[0] + w1, pt2[1])\n\n        color = (255, 51, 255)\n        cv2.line(vis, pt1, pt2, color, thickness=5)\n\n    vis = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)\n\n    plt.figure(figsize=(14, 6))\n    plt.imshow(vis)\n    plt.title(\"Statue Image Matches\")\n    plt.axis('off')\n    plt.tight_layout()\n\norb = cv2.ORB_create(nfeatures=4000, scaleFactor=1.2, nlevels=8)\nbf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)\n\nimg1_path = df[df['pred_scene'] != 'outlier']['img_path'].iloc[0]\nimg2_path = df[df['pred_scene'] != 'outlier']['img_path'].iloc[1]\n\ndraw_matches_statue(img1_path, img2_path, orb, bf)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:12.911340Z","iopub.execute_input":"2025-05-23T20:31:12.911586Z","iopub.status.idle":"2025-05-23T20:31:14.888581Z","shell.execute_reply.started":"2025-05-23T20:31:12.911567Z","shell.execute_reply":"2025-05-23T20:31:14.887406Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def draw_matches_taj_mahal(detector, matcher, max_matches=30):\n    folder = Path(\"/kaggle/input/image-matching-challenge-2025/train/pt_sacrecoeur_trevi_tajmahal\")\n\n    image_files = sorted(list(folder.glob(\"*.png\")) + list(folder.glob(\"*.jpg\")))\n    if len(image_files) < 2:\n        print(\"Not enough images in the folder.\")\n        return\n\n    img1_path = str(image_files[0])\n    img2_path = str(image_files[1])\n\n    print(\"Matching:\", image_files[0].name, \"↔\", image_files[1].name)\n\n    img1 = cv2.imread(img1_path)\n    img2 = cv2.imread(img2_path)\n\n    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)\n    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)\n\n    kp1, des1 = detector.detectAndCompute(gray1, None)\n    kp2, des2 = detector.detectAndCompute(gray2, None)\n\n    matches = matcher.knnMatch(des1, des2, k=2)\n    good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]\n\n    if len(good_matches) > max_matches:\n        good_matches = good_matches[:max_matches]\n\n    h1, w1 = img1.shape[:2]\n    h2, w2 = img2.shape[:2]\n    height = max(h1, h2)\n    vis = np.zeros((height, w1 + w2, 3), dtype=np.uint8)\n    vis[:h1, :w1] = img1\n    vis[:h2, w1:] = img2\n\n    for match in good_matches:\n        pt1 = tuple(np.round(kp1[match.queryIdx].pt).astype(int))\n        pt2 = tuple(np.round(kp2[match.trainIdx].pt).astype(int))\n        pt2 = (pt2[0] + w1, pt2[1])\n        cv2.line(vis, pt1, pt2, (50, 50, 255), thickness=2)\n\n    vis = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)\n    plt.figure(figsize=(14, 6))\n    plt.imshow(vis)\n    plt.title(\"Taj Mahal Image Matches\")\n    plt.axis('off')\n    plt.tight_layout()\n\ndraw_matches_taj_mahal(orb, bf)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:14.889706Z","iopub.execute_input":"2025-05-23T20:31:14.889980Z","iopub.status.idle":"2025-05-23T20:31:15.863665Z","shell.execute_reply.started":"2025-05-23T20:31:14.889958Z","shell.execute_reply":"2025-05-23T20:31:15.862746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def draw_matches_church(detector, matcher, max_matches=30):\n    folder = Path(\"/kaggle/input/image-matching-challenge-2025/train/imc2023_theather_imc2024_church\")\n\n    image_files = sorted(list(folder.glob(\"*.png\")) + list(folder.glob(\"*.jpg\")))\n    if len(image_files) < 2:\n        print(\"Not enough images in the folder.\")\n        return\n\n    img1_path = str(image_files[0])\n    img2_path = str(image_files[1])\n\n    print(\"Matching:\", image_files[0].name, \"↔\", image_files[1].name)\n\n    img1 = cv2.imread(img1_path)\n    img2 = cv2.imread(img2_path)\n\n    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)\n    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)\n\n    kp1, des1 = detector.detectAndCompute(gray1, None)\n    kp2, des2 = detector.detectAndCompute(gray2, None)\n\n    matches = matcher.knnMatch(des1, des2, k=2)\n    good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]\n\n    if len(good_matches) > max_matches:\n        good_matches = good_matches[:max_matches]\n\n    h1, w1 = img1.shape[:2]\n    h2, w2 = img2.shape[:2]\n    height = max(h1, h2)\n    vis = np.zeros((height, w1 + w2, 3), dtype=np.uint8)\n    vis[:h1, :w1] = img1\n    vis[:h2, w1:] = img2\n\n    for match in good_matches:\n        pt1 = tuple(np.round(kp1[match.queryIdx].pt).astype(int))\n        pt2 = tuple(np.round(kp2[match.trainIdx].pt).astype(int))\n        pt2 = (pt2[0] + w1, pt2[1])\n        cv2.line(vis, pt1, pt2, (0, 0, 0), thickness=2)\n\n    vis = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)\n    plt.figure(figsize=(14, 6))\n    plt.imshow(vis)\n    plt.title(\"Church Image Matches\")\n    plt.axis('off')\n    plt.tight_layout()\n\ndraw_matches_church(orb, bf)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:15.864759Z","iopub.execute_input":"2025-05-23T20:31:15.865019Z","iopub.status.idle":"2025-05-23T20:31:16.782340Z","shell.execute_reply.started":"2025-05-23T20:31:15.864998Z","shell.execute_reply":"2025-05-23T20:31:16.781053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def draw_matches_ets(detector, matcher, max_matches=30):\n    folder = Path(\"/kaggle/input/image-matching-challenge-2025/train/ETs\")\n\n    image_files = sorted(list(folder.glob(\"*.png\")) + list(folder.glob(\"*.jpg\")))\n    if len(image_files) < 2:\n        print(\"Not enough images in the folder.\")\n        return\n\n    img1_path = str(image_files[0])\n    img2_path = str(image_files[1])\n\n    print(\"Matching:\", image_files[0].name, \"↔\", image_files[1].name)\n\n    img1 = cv2.imread(img1_path)\n    img2 = cv2.imread(img2_path)\n\n    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)\n    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)\n\n    kp1, des1 = detector.detectAndCompute(gray1, None)\n    kp2, des2 = detector.detectAndCompute(gray2, None)\n\n    matches = matcher.knnMatch(des1, des2, k=2)\n    good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]\n\n    if len(good_matches) > max_matches:\n        good_matches = good_matches[:max_matches]\n\n    h1, w1 = img1.shape[:2]\n    h2, w2 = img2.shape[:2]\n    height = max(h1, h2)\n    vis = np.zeros((height, w1 + w2, 3), dtype=np.uint8)\n    vis[:h1, :w1] = img1\n    vis[:h2, w1:] = img2\n\n    for match in good_matches:\n        pt1 = tuple(np.round(kp1[match.queryIdx].pt).astype(int))\n        pt2 = tuple(np.round(kp2[match.trainIdx].pt).astype(int))\n        pt2 = (pt2[0] + w1, pt2[1])\n        cv2.line(vis, pt1, pt2, (0, 255, 0), thickness=2)\n\n    vis = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)\n    plt.figure(figsize=(10, 6))\n    plt.imshow(vis)\n    plt.title(\"ETs Image Matches\")\n    plt.axis('off')\n    plt.tight_layout()\n\ndraw_matches_ets(orb, bf)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:16.783597Z","iopub.execute_input":"2025-05-23T20:31:16.783917Z","iopub.status.idle":"2025-05-23T20:31:17.375486Z","shell.execute_reply.started":"2025-05-23T20:31:16.783894Z","shell.execute_reply":"2025-05-23T20:31:17.374600Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def draw_matches_baalshamin(detector, matcher, max_matches=30):\n    folder = Path(\"/kaggle/input/image-matching-challenge-2025/train/imc2024_dioscuri_baalshamin\")\n\n    image_files = sorted(list(folder.glob(\"*.png\")) + list(folder.glob(\"*.jpg\")))\n    if len(image_files) < 2:\n        print(\"Not enough images in the folder.\")\n        return\n\n    img1_path = str(image_files[0])\n    img2_path = str(image_files[1])\n\n    print(\"Matching:\", image_files[0].name, \"↔\", image_files[1].name)\n\n    img1 = cv2.imread(img1_path)\n    img2 = cv2.imread(img2_path)\n\n    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)\n    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)\n\n    kp1, des1 = detector.detectAndCompute(gray1, None)\n    kp2, des2 = detector.detectAndCompute(gray2, None)\n\n    matches = matcher.knnMatch(des1, des2, k=2)\n    good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]\n\n    if len(good_matches) > max_matches:\n        good_matches = good_matches[:max_matches]\n\n    h1, w1 = img1.shape[:2]\n    h2, w2 = img2.shape[:2]\n    height = max(h1, h2)\n    vis = np.zeros((height, w1 + w2, 3), dtype=np.uint8)\n    vis[:h1, :w1] = img1\n    vis[:h2, w1:] = img2\n\n    for match in good_matches:\n        pt1 = tuple(np.round(kp1[match.queryIdx].pt).astype(int))\n        pt2 = tuple(np.round(kp2[match.trainIdx].pt).astype(int))\n        pt2 = (pt2[0] + w1, pt2[1])\n        cv2.line(vis, pt1, pt2, (0, 0, 0), thickness=5)\n\n    vis = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)\n    plt.figure(figsize=(14, 6))\n    plt.imshow(vis)\n    plt.title(\"Baalshamin Image Matches\")\n    plt.axis('off')\n    plt.tight_layout()\n\ndraw_matches_baalshamin(orb, bf)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T20:31:17.376484Z","iopub.execute_input":"2025-05-23T20:31:17.376818Z","iopub.status.idle":"2025-05-23T20:31:19.382564Z","shell.execute_reply.started":"2025-05-23T20:31:17.376787Z","shell.execute_reply":"2025-05-23T20:31:19.381485Z"}},"outputs":[],"execution_count":null}]}