{"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":31012,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom sklearn.cluster import DBSCAN\nfrom scipy.spatial.distance import cdist\nfrom tqdm.notebook import tqdm\nimport networkx as nx\n\n# Set random seed for reproducibility\nnp.random.seed(42)\n\n# Function to load images from a dataset\ndef load_images(dataset_path):\n    images = {}\n    for img_file in os.listdir(dataset_path):\n        if img_file.endswith(\".png\"):\n            img_id = img_file.split(\".\")[0]\n            img_path = os.path.join(dataset_path, img_file)\n            img = cv2.imread(img_path)\n            images[img_id] = img\n    return images\n\n# Function to extract SIFT features from images\ndef extract_features(images):\n    sift = cv2.SIFT_create()\n    features = {}\n    for img_id, img in tqdm(images.items(), desc=\"Extracting features\"):\n        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n        keypoints, descriptors = sift.detectAndCompute(gray, None)\n        features[img_id] = (keypoints, descriptors)\n    return features\n\n# Function to match features between pairs of images\ndef match_features(features, threshold=0.75):\n    matches_dict = {}\n    bf = cv2.BFMatcher(cv2.NORM_L2)\n\n    img_ids = list(features.keys())\n    for i in range(len(img_ids)):\n        for j in range(i + 1, len(img_ids)):\n            img1_id = img_ids[i]\n            img2_id = img_ids[j]\n            kp1, des1 = features[img1_id]\n            kp2, des2 = features[img2_id]\n\n            if des1 is None or des2 is None:\n                continue\n\n            matches = bf.knnMatch(des1, des2, k=2)\n            good_matches = []\n            for m, n in matches:\n                if m.distance < threshold * n.distance:\n                    good_matches.append(m)\n\n            if len(good_matches) > 10:  # Filter out weak matches\n                matches_dict[(img1_id, img2_id)] = good_matches\n\n    return matches_dict\n\n# Function to build a graph from feature matches\ndef build_graph(matches):\n    G = nx.Graph()\n    for (img1, img2), match in matches.items():\n        if len(match) > 0:\n            G.add_edge(img1, img2, weight=len(match))\n    return G\n\n# Function to cluster scenes using connected components and DBSCAN\ndef cluster_scenes(G, features):\n    scenes = []\n    for component in nx.connected_components(G):\n        scenes.append(list(component))\n\n    # Optional: Refine clustering using feature similarity\n    feature_vectors = []\n    ids = []\n\n    for img_id, (kp, des) in features.items():\n        if des is not None:\n            avg_desc = np.mean(des, axis=0)\n            feature_vectors.append(avg_desc)\n            ids.append(img_id)\n\n    feature_matrix = np.array(feature_vectors)\n    db = DBSCAN(metric='cosine', eps=0.2, min_samples=2).fit(feature_matrix)\n    labels = db.labels_\n\n    refined_scenes = {}\n    for idx, label in enumerate(labels):\n        if label not in refined_scenes:\n            refined_scenes[label] = []\n        refined_scenes[label].append(ids[idx])\n\n    outlier_scene = [img_id for img_id in features if img_id not in ids]\n    if len(outlier_scene) > 0:\n        refined_scenes[-1] = outlier_scene\n\n    return list(refined_scenes.values())\n\n# Function to estimate camera pose between two matched images\ndef estimate_relative_pose(kp1, kp2, matches, K):\n    pts1 = np.float32([kp1[m.queryIdx].pt for m in matches])\n    pts2 = np.float32([kp2[m.trainIdx].pt for m in matches])\n\n    E, mask = cv2.findEssentialMat(pts1, pts2, K, method=cv2.RANSAC, prob=0.999, threshold=1.0)\n    if E is None or mask.sum() < 5:\n        return None\n\n    _, R, t, _ = cv2.recoverPose(E, pts1, pts2, K)\n    return R, t\n\n# Main pipeline\ndef main():\n    # Load test data\n    test_dir = \"/kaggle/input/image-matching-challenge-2025/test\"\n    test_datasets = os.listdir(test_dir)\n\n    all_rows = []\n\n    for dataset_name in tqdm(test_datasets, desc=\"Processing datasets\"):\n        dataset_path = os.path.join(test_dir, dataset_name)\n        images = load_images(dataset_path)\n\n        if len(images) == 0:\n            print(f\"No images found in {dataset_name}\")\n            continue\n\n        features = extract_features(images)\n        matches = match_features(features)\n        G = build_graph(matches)\n        scenes = cluster_scenes(G, features)\n\n        # Dummy intrinsic matrix (assume same for all images)\n        h, w = list(images.values())[0].shape[:2]\n        K = np.array([[w, 0, w / 2],\n                      [0, w, h / 2],\n                      [0, 0, 1]])\n\n        camera_poses = {}\n\n        for scene in scenes:\n            for i in range(len(scene)):\n                for j in range(i + 1, len(scene)):\n                    img1_id = scene[i]\n                    img2_id = scene[j]\n                    if (img1_id, img2_id) in matches:\n                        kp1, des1 = features[img1_id]\n                        kp2, des2 = features[img2_id]\n                        match_pair = matches[(img1_id, img2_id)]\n                        pose = estimate_relative_pose(kp1, kp2, match_pair, K)\n                        if pose:\n                            R, t = pose\n                            camera_poses[img1_id] = (R, t)\n                            camera_poses[img2_id] = (R @ R.T, -R @ t)  # Simplified\n\n        # Generate submission rows\n        for img_id in images:\n            row = {\n                \"image\": f\"{img_id}.png\",\n                \"dataset\": dataset_name,\n                \"scene\": \"outliers\",\n                \"rotation_matrix\": \";\".join([\"nan\"] * 9),\n                \"translation_vector\": \";\".join([\"nan\"] * 3),\n            }\n\n            for scene_idx, scene in enumerate(scenes):\n                if img_id in scene:\n                    row[\"scene\"] = f\"cluster_{scene_idx}\"\n                    if img_id in camera_poses:\n                        R, t = camera_poses[img_id]\n                        row[\"rotation_matrix\"] = \";\".join(map(str, R.flatten()))\n                        row[\"translation_vector\"] = \";\".join(map(str, t.flatten()))\n                    break\n\n            all_rows.append(row)\n\n    submission_df = pd.DataFrame(all_rows)\n    submission_df.to_csv(\"submission.csv\", index=False)\n    print(\"✅ Submission generated successfully.\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-13T05:57:07.078137Z","iopub.execute_input":"2025-05-13T05:57:07.078478Z","iopub.status.idle":"2025-05-13T05:58:44.771692Z","shell.execute_reply.started":"2025-05-13T05:57:07.078432Z","shell.execute_reply":"2025-05-13T05:58:44.770861Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}