{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":91498,"databundleVersionId":11655853,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Intoduction","metadata":{}},{"cell_type":"markdown","source":"### Key Aspects of Feature Matching\n#### 1. Feature Detection\nThe process begins by detecting key features in each image. These features are typically points of interest that are easy to distinguish, such as corners or edges.\nCommon feature detectors include Harris Corner Detector, Laplacian of Gaussian for blob detection, and Canny Edge Detector.\n#### 2. Feature Description\nOnce features are detected, they are described using feature descriptors, which provide a numerical representation of the feature's characteristics.\nPopular feature descriptors include SIFT (Scale-Invariant Feature Transform), SURF (Speeded Up Robust Features), ORB (Oriented FAST and Rotated BRIEF), and BRIEF (Binary Robust Independent Elementary Features).\n#### 3. Feature Matching\nThe core step involves comparing the feature descriptors from different images to find matches.\nTechniques like brute-force matching, where each feature is compared with all features in the other image, and K-Nearest Neighbors (KNN) matching, where the closest matches are identified, are commonly used.\nRobust matching methods like RANSAC (Random Sample Consensus) help in dealing with noise and outliers to improve accuracy.","metadata":{}},{"cell_type":"code","source":"import cv2\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport numpy as np\nimport os\nfrom PIL import Image\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:57:03.497554Z","iopub.execute_input":"2025-08-25T13:57:03.497827Z","iopub.status.idle":"2025-08-25T13:57:03.501699Z","shell.execute_reply.started":"2025-08-25T13:57:03.497806Z","shell.execute_reply":"2025-08-25T13:57:03.501040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_labels = pd.read_csv(\"/kaggle/input/image-matching-challenge-2025/train_labels.csv\")\ntrain_labels.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:57:03.502867Z","iopub.execute_input":"2025-08-25T13:57:03.503124Z","iopub.status.idle":"2025-08-25T13:57:03.526268Z","shell.execute_reply.started":"2025-08-25T13:57:03.503107Z","shell.execute_reply":"2025-08-25T13:57:03.525672Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_labels.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:57:03.527159Z","iopub.execute_input":"2025-08-25T13:57:03.527440Z","iopub.status.idle":"2025-08-25T13:57:03.535325Z","shell.execute_reply.started":"2025-08-25T13:57:03.527424Z","shell.execute_reply":"2025-08-25T13:57:03.534662Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scene_number =train_labels[\"scene\"].unique()\nscecne_count=train_labels[\"scene\"].value_counts()\n\nscene_number,scecne_count","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:57:03.536638Z","iopub.execute_input":"2025-08-25T13:57:03.536870Z","iopub.status.idle":"2025-08-25T13:57:03.547413Z","shell.execute_reply.started":"2025-08-25T13:57:03.536854Z","shell.execute_reply":"2025-08-25T13:57:03.546752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset_counts = train_labels[\"dataset\"].value_counts()\nnum_datasets = train_labels[\"dataset\"].nunique()\nnum_datasets,dataset_counts","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:57:03.555438Z","iopub.execute_input":"2025-08-25T13:57:03.555834Z","iopub.status.idle":"2025-08-25T13:57:03.561297Z","shell.execute_reply.started":"2025-08-25T13:57:03.555818Z","shell.execute_reply":"2025-08-25T13:57:03.560674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_thresholds_path = \"../input/image-matching-challenge-2025/train_thresholds.csv\"\ntrain_thresholds = pd.read_csv(train_thresholds_path)\ntrain_thresholds.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:57:03.579261Z","iopub.execute_input":"2025-08-25T13:57:03.579446Z","iopub.status.idle":"2025-08-25T13:57:03.587906Z","shell.execute_reply.started":"2025-08-25T13:57:03.579431Z","shell.execute_reply":"2025-08-25T13:57:03.587217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_thresholds.describe()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:58:26.015339Z","iopub.execute_input":"2025-08-25T13:58:26.015947Z","iopub.status.idle":"2025-08-25T13:58:26.026746Z","shell.execute_reply.started":"2025-08-25T13:58:26.015923Z","shell.execute_reply":"2025-08-25T13:58:26.025947Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" convert the threshold into a list of list, then flatten it ","metadata":{}},{"cell_type":"code","source":"threshold_lists = train_thresholds[\"thresholds\"].apply(lambda x: list(map(float, x.split(\";\"))))\nall_thresholds = np.concatenate(threshold_lists.values)\nall_thresholds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:58:26.192922Z","iopub.execute_input":"2025-08-25T13:58:26.193376Z","iopub.status.idle":"2025-08-25T13:58:26.199709Z","shell.execute_reply.started":"2025-08-25T13:58:26.193357Z","shell.execute_reply":"2025-08-25T13:58:26.199132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 5))\nplt.hist(all_thresholds, bins=30, edgecolor=\"black\")\nplt.xlabel(\"Score Threshold\")\nplt.ylabel(\"Count\")\nplt.title(\"Distribution of Similarity Thresholds\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:58:26.812452Z","iopub.execute_input":"2025-08-25T13:58:26.813152Z","iopub.status.idle":"2025-08-25T13:58:27.066789Z","shell.execute_reply.started":"2025-08-25T13:58:26.813036Z","shell.execute_reply":"2025-08-25T13:58:27.066187Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img1 = cv2.imread(\"/kaggle/input/image-matching-challenge-2025/train/imc2024_dioscuri_baalshamin/baalshamin_19577300988_4e4ff423a7_o.png\", cv2.IMREAD_GRAYSCALE)\nimg2 = cv2.imread(\"/kaggle/input/image-matching-challenge-2025/train/imc2024_dioscuri_baalshamin/baalshamin_194d.png\", cv2.IMREAD_GRAYSCALE)\n\nplt.subplot(121), plt.imshow(img1, cmap='gray'), plt.title(\"Image 1\")\nplt.subplot(122), plt.imshow(img2, cmap='gray'), plt.title(\"Image 2\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:58:28.439597Z","iopub.execute_input":"2025-08-25T13:58:28.440123Z","iopub.status.idle":"2025-08-25T13:58:29.073833Z","shell.execute_reply.started":"2025-08-25T13:58:28.440100Z","shell.execute_reply":"2025-08-25T13:58:29.073043Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature Detection ","metadata":{}},{"cell_type":"markdown","source":"Like we said before @ the introduction First step is to detect we can use \n\n1-ORB (fast, binary).\n\n2-SIFT (scale/rotation robust).\n\n3-SURF .","metadata":{}},{"cell_type":"markdown","source":"## 1- ORB","metadata":{}},{"cell_type":"markdown","source":"ORB uses FAST (Features from Accelerated Segment Test) as its detector.\n\n- How FAST works:\n\nFor a candidate pixel, look at a circle of 16 pixels around it.\n\nCompare the center pixel intensity with these 16 neighbors.\n\nIf enough neighbors are all brighter or all darker → that pixel is a corner (keypoint).\n\n- ORB improves FAST by:\n\nUsing Harris Corner measure to keep only the strongest points.\n\nAdding an orientation (by computing intensity centroid) → makes features rotation-invariant.\n\n- ORB detector = FAST corners + orientation + Harris ranking.","metadata":{}},{"cell_type":"code","source":"orb = cv2.ORB_create()\n\n# Detect keypoints\nkp1_ORB = orb.detect(img1, None)\nkp2_ORB = orb.detect(img2, None)\n\nimg_kp1 = cv2.drawKeypoints(img1, kp1_ORB, None, color=(0,255,0))\nplt.imshow(img_kp1)\nplt.title(\"Keypoints Scene 1\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:58:39.881887Z","iopub.execute_input":"2025-08-25T13:58:39.882624Z","iopub.status.idle":"2025-08-25T13:58:40.420043Z","shell.execute_reply.started":"2025-08-25T13:58:39.882599Z","shell.execute_reply":"2025-08-25T13:58:40.419213Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2- SIFT","metadata":{}},{"cell_type":"markdown","source":"SIFT finds blobs/extrema in scale space.\n\n- How it works:\n\nBuild a Gaussian pyramid (blur images with different σ).\n\nCompute Difference of Gaussians (DoG) between pyramid levels.\n\nLook for local extrema in 3D (x, y, scale).\n\nKeypoints = maxima/minima that are stable across scales.\n\nAfter detecting, SIFT assigns a dominant orientation to each keypoint (so it’s rotation invariant).\n\nSIFT detector = DoG extrema in scale space.\n### Descriptor:\n\nAround each keypoint, SIFT builds a histogram of gradient orientations in local patches (usually 16×16).\n\nDivided into 4×4 cells → each with 8-bin orientation histograms → results in 128-dimensional float descriptor.\n\nThese descriptors are robust to scale, rotation, and illumination changes.","metadata":{}},{"cell_type":"code","source":"# SIFT Detector\nsift = cv2.SIFT_create()\n\n# Detect keypoints + descriptors directly\nkp1, des1 = sift.detectAndCompute(img1, None)\nkp2, des2 = sift.detectAndCompute(img2, None)\n\n# Draw keypoints\nimg_kp1 = cv2.drawKeypoints(img1, kp1, None, color=(255,0,0))\nplt.imshow(img_kp1)\nplt.title(\"SIFT Keypoints - Image 1\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:58:55.995829Z","iopub.execute_input":"2025-08-25T13:58:55.996395Z","iopub.status.idle":"2025-08-25T13:58:58.121204Z","shell.execute_reply.started":"2025-08-25T13:58:55.996370Z","shell.execute_reply":"2025-08-25T13:58:58.120540Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature Description","metadata":{}},{"cell_type":"markdown","source":"## 1- ORB","metadata":{}},{"cell_type":"markdown","source":"ORB uses BRIEF (Binary Robust Independent Elementary Features) as its descriptor.\n\nAfter finding a keypoint (position + scale + orientation), ORB:\n\nRotates the patch around the keypoint to a canonical orientation (so it’s rotation-invariant).\n\nRuns BRIEF: it takes random pairs of pixels in the patch and checks which one is brighter.\n\nEach comparison outputs a single bit (0 or 1).\n\nConcatenating many such comparisons gives a binary string (e.g., 256 bits).","metadata":{}},{"cell_type":"code","source":"# calculate key points like (size and rotation) and Descriptors, which is numpy array, each row describes 1 kp\nkp1_ORB, des1_ORB = orb.compute(img1, kp1_ORB)\nkp2_ORB, des2_ORB = orb.compute(img2, kp2_ORB)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:59:14.119228Z","iopub.execute_input":"2025-08-25T13:59:14.119772Z","iopub.status.idle":"2025-08-25T13:59:14.164269Z","shell.execute_reply.started":"2025-08-25T13:59:14.119749Z","shell.execute_reply":"2025-08-25T13:59:14.163665Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature Matching","metadata":{}},{"cell_type":"markdown","source":"## 1- Brute force ","metadata":{}},{"cell_type":"markdown","source":"### ORB","metadata":{}},{"cell_type":"code","source":"# Brute Force Matcher\nbf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)\n\n# Match descriptors\nmatches = bf.match(des1_ORB, des2_ORB)\n\n# Sort by distance (good → bad)\nmatches = sorted(matches, key=lambda x:x.distance)\n\n# Draw top matches\nimg_matches = cv2.drawMatches(img1, kp1_ORB, img2, kp2_ORB, matches[:20], None, flags=2)\nplt.imshow(img_matches)\nplt.title(\"Feature Matching\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:59:31.965080Z","iopub.execute_input":"2025-08-25T13:59:31.965573Z","iopub.status.idle":"2025-08-25T13:59:32.584726Z","shell.execute_reply.started":"2025-08-25T13:59:31.965552Z","shell.execute_reply":"2025-08-25T13:59:32.583747Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### SIFT","metadata":{}},{"cell_type":"code","source":"# Brute Force Matcher for SIFT\nbf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)\n\n# Match descriptors\nmatches = bf.match(des1, des2)\n\n# Sort by distance (good → bad)\nmatches = sorted(matches, key=lambda x: x.distance)\n\n# Draw top matches\nimg_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:20], None, flags=2)\nplt.imshow(img_matches)\nplt.title(\"SIFT Feature Matching\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:59:48.868121Z","iopub.execute_input":"2025-08-25T13:59:48.868395Z","iopub.status.idle":"2025-08-25T13:59:54.689354Z","shell.execute_reply.started":"2025-08-25T13:59:48.868376Z","shell.execute_reply":"2025-08-25T13:59:54.688679Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2- FLANN","metadata":{}},{"cell_type":"markdown","source":"### SIFT","metadata":{}},{"cell_type":"code","source":"# FLANN parameters\nFLANN_INDEX_KDTREE = 1  \nindex_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)\nsearch_params = dict(checks=50)  # higher = more accurate but slower\n\nflann = cv2.FlannBasedMatcher(index_params, search_params)\n\n# KNN Match (k=2 → best 2 matches for each descriptor)\nmatches = flann.knnMatch(des1, des2, k=2)\n\n# Lowe’s ratio test to filter good matches\ngood_matches = []\nfor m, n in matches:\n    if m.distance < 0.7 * n.distance:  # 0.7 is the common threshold\n        good_matches.append(m)\n\n# Draw matches\nimg_matches = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, flags=2)\nplt.imshow(img_matches)\nplt.title(\"SIFT + FLANN Matching with Ratio Test\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T14:00:14.206173Z","iopub.execute_input":"2025-08-25T14:00:14.206824Z","iopub.status.idle":"2025-08-25T14:00:15.246690Z","shell.execute_reply.started":"2025-08-25T14:00:14.206800Z","shell.execute_reply":"2025-08-25T14:00:15.245924Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### ORB","metadata":{}},{"cell_type":"code","source":"des1_ORB = np.float32(des1_ORB)\ndes2_ORB = np.float32(des2_ORB)\nflann = cv2.FlannBasedMatcher(index_params, search_params)\n\n# KNN Match (k=2 → best 2 matches for each descriptor)\nmatches = flann.knnMatch(des1_ORB, des2_ORB, k=2)\n\n# Lowe’s ratio test to filter good matches\ngood_matches = []\nfor m, n in matches:\n    if m.distance < 0.7 * n.distance:  # 0.7 is the common threshold\n        good_matches.append(m)\n\n# Draw matches\nimg_matches = cv2.drawMatches(img1, kp1_ORB, img2, kp2_ORB, good_matches, None, flags=2)\nplt.imshow(img_matches)\nplt.title(\"ORB + FLANN Matching with Ratio Test\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-25T14:03:46.246403Z","iopub.execute_input":"2025-08-25T14:03:46.246922Z","iopub.status.idle":"2025-08-25T14:03:46.873503Z","shell.execute_reply.started":"2025-08-25T14:03:46.246896Z","shell.execute_reply":"2025-08-25T14:03:46.872776Z"}},"outputs":[],"execution_count":null}]}