{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":3932654,"sourceType":"datasetVersion","datasetId":2334639},{"sourceId":153412198,"sourceType":"kernelVersion"}],"dockerImageVersionId":31239,"isInternetEnabled":true,"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,"execution":{"iopub.status.busy":"2026-01-13T21:56:00.173687Z","iopub.execute_input":"2026-01-13T21:56:00.174278Z","iopub.status.idle":"2026-01-13T21:56:02.570003Z","shell.execute_reply.started":"2026-01-13T21:56:00.174248Z","shell.execute_reply":"2026-01-13T21:56:02.568814Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pwd ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T22:00:30.362159Z","iopub.execute_input":"2026-01-13T22:00:30.362508Z","iopub.status.idle":"2026-01-13T22:00:30.482714Z","shell.execute_reply.started":"2026-01-13T22:00:30.362481Z","shell.execute_reply":"2026-01-13T22:00:30.481681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom skimage import io, color\nfrom skimage.feature import hog\nimport os\nimport glob\n\n# Base path to dataset\nBASE_PATH = '/kaggle/input/caltech-101/caltech-101'\n\n# ============================================================================\n# TASK 1: HOG (Histogram of Oriented Gradients) - Shape Analysis\n# ============================================================================\n\ndef find_images(folder_name):\n    \"\"\"\n    Helper function to find images in a folder with multiple extensions\n    \"\"\"\n    folder_path = os.path.join(BASE_PATH, folder_name)\n    for ext in ['*.jpg', '*.jpeg', '*.png', '*.JPG', '*.JPEG', '*.PNG']:\n        images = glob.glob(os.path.join(folder_path, ext))\n        if images:\n            return images\n    return []\n\ndef task1_hog_visualization():\n    \"\"\"\n    Apply HOG feature extraction and visualize orientation gradients\n    \"\"\"\n    # Try multiple folder names (case variations)\n    for folder in ['Motorbikes', 'motorbikes', 'Faces', 'faces', 'Faces_easy']:\n        images = find_images(folder)\n        if images:\n            print(f\"Task 1 - Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found in Motorbikes or Faces folders\")\n        print(f\"Available folders in {BASE_PATH}:\")\n        print(os.listdir(BASE_PATH)[:20])\n        return\n    \n    img_path = images[0]\n    print(f\"Task 1 - Processing: {os.path.basename(img_path)}\")\n    \n    # Load and convert to grayscale\n    img = io.imread(img_path)\n    if len(img.shape) == 3:\n        gray = color.rgb2gray(img)\n    else:\n        gray = img\n    \n    # Compute HOG features with visualization\n    fd, hog_image = hog(\n        gray,\n        orientations=9,\n        pixels_per_cell=(8, 8),\n        cells_per_block=(2, 2),\n        visualize=True,\n        block_norm='L2-Hys'\n    )\n    \n    # Create visualization\n    fig, axes = plt.subplots(1, 2, figsize=(14, 6))\n    \n    # Original image\n    axes[0].imshow(gray, cmap='gray')\n    axes[0].set_title('Original Image', fontsize=14, fontweight='bold')\n    axes[0].axis('off')\n    \n    # HOG visualization\n    axes[1].imshow(hog_image, cmap='gray')\n    axes[1].set_title('HOG Visualization (Orientation Gradients)', \n                      fontsize=14, fontweight='bold')\n    axes[1].axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('hog_result.png', dpi=300, bbox_inches='tight')\n    print(\"✓ Saved: hog_result.png\")\n    plt.show()\n    \n    print(f\"HOG descriptor size: {len(fd)} features\")\n\n# ============================================================================\n# TASK 2: SIFT (Scale-Invariant Feature Transform) - Local Landmarks\n# ============================================================================\n\ndef task2_sift_keypoints():\n    \"\"\"\n    Detect and visualize SIFT keypoints with scale and orientation\n    \"\"\"\n    # Try multiple folder names\n    for folder in ['accordion', 'Accordion', 'sunflower', 'Sunflower']:\n        images = find_images(folder)\n        if images:\n            print(f\"\\nTask 2 - Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"\\nERROR: No images found in accordion or sunflower folders\")\n        print(f\"Available folders in {BASE_PATH}:\")\n        print(os.listdir(BASE_PATH)[:20])\n        return\n    \n    img_path = images[0]\n    print(f\"Task 2 - Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = cv2.imread(img_path)\n    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    \n    # Create SIFT detector\n    sift = cv2.SIFT_create()\n    \n    # Detect keypoints and compute descriptors\n    keypoints, descriptors = sift.detectAndCompute(gray, None)\n    \n    # Draw rich keypoints (with scale and orientation)\n    img_keypoints = cv2.drawKeypoints(\n        img,\n        keypoints,\n        None,\n        flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS\n    )\n    img_keypoints_rgb = cv2.cvtColor(img_keypoints, cv2.COLOR_BGR2RGB)\n    \n    # Create visualization\n    fig, axes = plt.subplots(1, 2, figsize=(14, 6))\n    \n    # Original image\n    axes[0].imshow(img_rgb)\n    axes[0].set_title('Original Image', fontsize=14, fontweight='bold')\n    axes[0].axis('off')\n    \n    # SIFT keypoints\n    axes[1].imshow(img_keypoints_rgb)\n    axes[1].set_title(f'SIFT Keypoints (n={len(keypoints)}) with Scale & Orientation', \n                      fontsize=14, fontweight='bold')\n    axes[1].axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('sift_result.png', dpi=300, bbox_inches='tight')\n    print(\"✓ Saved: sift_result.png\")\n    plt.show()\n    \n    print(f\"Detected {len(keypoints)} SIFT keypoints\")\n    print(f\"Descriptor shape: {descriptors.shape}\")\n\n# ============================================================================\n# TASK 3: ORB Feature Matching - Identifying Similarities\n# ============================================================================\n\ndef task3_orb_matching():\n    \"\"\"\n    Match features between two images using ORB\n    \"\"\"\n    # Try multiple folder names\n    for folder in ['airplanes', 'Airplanes', 'airplane']:\n        images = find_images(folder)\n        if len(images) >= 2:\n            print(f\"\\nTask 3 - Found {len(images)} images in {folder}\")\n            break\n    \n    if not images or len(images) < 2:\n        print(\"\\nERROR: Need at least 2 airplane images\")\n        print(f\"Available folders in {BASE_PATH}:\")\n        print(os.listdir(BASE_PATH)[:20])\n        return\n    \n    img1_path = images[0]\n    img2_path = images[1]\n    print(f\"Task 3 - Processing:\")\n    print(f\"  Image 1: {os.path.basename(img1_path)}\")\n    print(f\"  Image 2: {os.path.basename(img2_path)}\")\n    \n    # Load images\n    img1 = cv2.imread(img1_path)\n    img2 = cv2.imread(img2_path)\n    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)\n    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)\n    \n    # Create ORB detector\n    orb = cv2.ORB_create(nfeatures=1000)\n    \n    # Detect keypoints and compute descriptors\n    kp1, des1 = orb.detectAndCompute(gray1, None)\n    kp2, des2 = orb.detectAndCompute(gray2, None)\n    \n    # Create BFMatcher with Hamming distance\n    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)\n    \n    # Match descriptors\n    matches = bf.match(des1, des2)\n    \n    # Sort matches by distance (best matches first)\n    matches = sorted(matches, key=lambda x: x.distance)\n    \n    # Draw top 30 matches\n    img_matches = cv2.drawMatches(\n        img1, kp1,\n        img2, kp2,\n        matches[:30],\n        None,\n        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n    )\n    img_matches_rgb = cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB)\n    \n    # Create visualization\n    fig, ax = plt.subplots(1, 1, figsize=(16, 8))\n    ax.imshow(img_matches_rgb)\n    ax.set_title(f'ORB Feature Matching - Top 30 Matches (Total: {len(matches)} matches)', \n                 fontsize=14, fontweight='bold')\n    ax.axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('orb_match.png', dpi=300, bbox_inches='tight')\n    print(\"✓ Saved: orb_match.png\")\n    plt.show()\n    \n    print(f\"Image 1: {len(kp1)} keypoints\")\n    print(f\"Image 2: {len(kp2)} keypoints\")\n    print(f\"Total matches found: {len(matches)}\")\n    if matches:\n        print(f\"Best match distance: {matches[0].distance:.2f}\")\n        print(f\"Average match distance (top 30): {np.mean([m.distance for m in matches[:30]]):.2f}\")\n\n# ============================================================================\n# MAIN EXECUTION\n# ============================================================================\n\nif __name__ == \"__main__\":\n    print(\"=\" * 70)\n    print(\"CALTECH 101 COMPUTER VISION FEATURE DETECTION\")\n    print(\"=\" * 70)\n    \n    # Run all three tasks\n    task1_hog_visualization()\n    task2_sift_keypoints()\n    task3_orb_matching()\n    \n    print(\"\\n\" + \"=\" * 70)\n    print(\"ALL TASKS COMPLETED!\")\n    print(\"Generated files: hog_result.png, sift_result.png, orb_match.png\")\n    print(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T22:11:48.602445Z","iopub.execute_input":"2026-01-13T22:11:48.603295Z","iopub.status.idle":"2026-01-13T22:11:52.980301Z","shell.execute_reply.started":"2026-01-13T22:11:48.603265Z","shell.execute_reply":"2026-01-13T22:11:52.979325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom skimage import io, color, transform\nfrom skimage.feature import hog\nfrom scipy.ndimage import gaussian_filter\nfrom scipy.spatial.distance import euclidean\nimport os\nimport glob\n\n# Base path to dataset\nBASE_PATH = '/kaggle/input/caltech-101/caltech-101'\n\n# ============================================================================\n# HELPER FUNCTIONS\n# ============================================================================\n\ndef find_images(folder_name):\n    \"\"\"Helper function to find images in a folder with multiple extensions\"\"\"\n    folder_path = os.path.join(BASE_PATH, folder_name)\n    for ext in ['*.jpg', '*.jpeg', '*.png', '*.JPG', '*.JPEG', '*.PNG']:\n        images = glob.glob(os.path.join(folder_path, ext))\n        if images:\n            return images\n    return []\n\ndef extract_hog_features(img_path):\n    \"\"\"Extract HOG features from an image\"\"\"\n    img = io.imread(img_path)\n    if len(img.shape) == 3:\n        gray = color.rgb2gray(img)\n    else:\n        gray = img\n    \n    # Resize for consistent comparison\n    gray = transform.resize(gray, (128, 128), anti_aliasing=True)\n    \n    features = hog(\n        gray,\n        orientations=9,\n        pixels_per_cell=(8, 8),\n        cells_per_block=(2, 2),\n        visualize=False,\n        block_norm='L2-Hys'\n    )\n    return features, img\n\n# ============================================================================\n# TASK 4: Visualizing the DOG (Difference of Gaussians)\n# ============================================================================\n\ndef task4_dog_visualization():\n    \"\"\"\n    Demonstrate how SIFT uses Difference of Gaussians for blob detection\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 4: DIFFERENCE OF GAUSSIANS (DOG) - BLOB DETECTION\")\n    print(\"=\" * 70)\n    \n    # Try to find butterfly images\n    for folder in ['butterfly', 'Butterfly', 'Motorbikes', 'motorbikes']:\n        images = find_images(folder)\n        if images:\n            print(f\"Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load and convert to grayscale\n    img = io.imread(img_path)\n    if len(img.shape) == 3:\n        gray = color.rgb2gray(img)\n    else:\n        gray = img\n    \n    # Apply Gaussian blur with two different sigmas\n    sigma1 = 1.0\n    sigma2 = 1.6\n    \n    gaussian1 = gaussian_filter(gray, sigma=sigma1)\n    gaussian2 = gaussian_filter(gray, sigma=sigma2)\n    \n    # Compute Difference of Gaussians\n    dog = gaussian1 - gaussian2\n    \n    # Normalize DOG for better visualization\n    dog_normalized = np.abs(dog)\n    dog_normalized = (dog_normalized - dog_normalized.min()) / (dog_normalized.max() - dog_normalized.min())\n    \n    # Create visualization\n    fig, axes = plt.subplots(1, 3, figsize=(18, 6))\n    \n    # Gaussian Blur 1\n    axes[0].imshow(gaussian1, cmap='gray')\n    axes[0].set_title(f'Gaussian Blur (σ={sigma1})', fontsize=14, fontweight='bold')\n    axes[0].axis('off')\n    \n    # Gaussian Blur 2\n    axes[1].imshow(gaussian2, cmap='gray')\n    axes[1].set_title(f'Gaussian Blur (σ={sigma2})', fontsize=14, fontweight='bold')\n    axes[1].axis('off')\n    \n    # Difference of Gaussians\n    axes[2].imshow(dog_normalized, cmap='hot')\n    axes[2].set_title('Difference of Gaussians (DOG)\\nBlob & Edge Features', \n                      fontsize=14, fontweight='bold')\n    axes[2].axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('dog_result.png', dpi=300, bbox_inches='tight')\n    print(\"✓ Saved: dog_result.png\")\n    plt.show()\n    \n    print(f\"\\nDOG Statistics:\")\n    print(f\"  σ₁ = {sigma1}, σ₂ = {sigma2}\")\n    print(f\"  DOG range: [{dog.min():.4f}, {dog.max():.4f}]\")\n    print(f\"  DOG acts as a band-pass filter highlighting edges and blobs\")\n\n# ============================================================================\n# TASK 5: Rotation Invariance Challenge (SIFT vs. HOG)\n# ============================================================================\n\ndef task5_rotation_invariance():\n    \"\"\"\n    Demonstrate SIFT's rotation invariance vs HOG's rotation sensitivity\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 5: ROTATION INVARIANCE - SIFT vs HOG\")\n    print(\"=\" * 70)\n    \n    # Find motorbike images\n    for folder in ['Motorbikes', 'motorbikes', 'Faces', 'faces']:\n        images = find_images(folder)\n        if images:\n            print(f\"Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = cv2.imread(img_path)\n    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    \n    # Create rotated version (45 degrees)\n    img_rotated = transform.rotate(img_rgb, angle=45, resize=True, preserve_range=True).astype(np.uint8)\n    gray_rotated = color.rgb2gray(img_rotated)\n    gray_rotated = (gray_rotated * 255).astype(np.uint8)\n    \n    # === SIFT PART: Show rotation invariance ===\n    print(\"\\n--- SIFT Analysis ---\")\n    sift = cv2.SIFT_create()\n    \n    # Detect keypoints in both images\n    kp1, des1 = sift.detectAndCompute(gray, None)\n    kp2, des2 = sift.detectAndCompute(gray_rotated, None)\n    \n    # Match features using BFMatcher\n    bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)\n    matches = bf.match(des1, des2)\n    matches = sorted(matches, key=lambda x: x.distance)\n    \n    print(f\"Original image: {len(kp1)} keypoints\")\n    print(f\"Rotated image: {len(kp2)} keypoints\")\n    print(f\"Matched keypoints: {len(matches)}\")\n    \n    # Convert for cv2.drawMatches\n    img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)\n    img_rotated_bgr = cv2.cvtColor(img_rotated, cv2.COLOR_RGB2BGR)\n    \n    # Draw matches\n    img_matches = cv2.drawMatches(\n        img_bgr, kp1,\n        img_rotated_bgr, kp2,\n        matches[:30],\n        None,\n        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n    )\n    img_matches_rgb = cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB)\n    \n    # === HOG PART: Show rotation sensitivity ===\n    print(\"\\n--- HOG Analysis ---\")\n    \n    # Resize both images to same size for fair comparison\n    target_size = (128, 128)\n    gray_resized = cv2.resize(gray, target_size)\n    gray_rotated_resized = cv2.resize(gray_rotated, target_size)\n    \n    # Extract HOG features\n    hog_original = hog(\n        gray_resized,\n        orientations=9,\n        pixels_per_cell=(8, 8),\n        cells_per_block=(2, 2),\n        visualize=False,\n        block_norm='L2-Hys'\n    )\n    \n    hog_rotated = hog(\n        gray_rotated_resized,\n        orientations=9,\n        pixels_per_cell=(8, 8),\n        cells_per_block=(2, 2),\n        visualize=False,\n        block_norm='L2-Hys'\n    )\n    \n    # Calculate Euclidean distance\n    hog_distance = euclidean(hog_original, hog_rotated)\n    \n    print(f\"HOG descriptor size: {len(hog_original)} features\")\n    print(f\"HOG Euclidean Distance: {hog_distance:.4f}\")\n    print(f\"  → Large distance indicates HOG is rotation-sensitive\")\n    \n    # Create visualization\n    fig = plt.figure(figsize=(16, 10))\n    \n    # SIFT matches\n    ax1 = plt.subplot(2, 1, 1)\n    ax1.imshow(img_matches_rgb)\n    ax1.set_title(f'SIFT: Rotation Invariant (Top 30 of {len(matches)} matches)\\n'\n                  f'Features match despite 45° rotation',\n                  fontsize=14, fontweight='bold')\n    ax1.axis('off')\n    \n    # HOG comparison\n    ax2 = plt.subplot(2, 2, 3)\n    ax2.imshow(gray_resized, cmap='gray')\n    ax2.set_title('Original Image\\nHOG Feature Vector', fontsize=12, fontweight='bold')\n    ax2.axis('off')\n    \n    ax3 = plt.subplot(2, 2, 4)\n    ax3.imshow(gray_rotated_resized, cmap='gray')\n    ax3.set_title(f'Rotated 45°\\nHOG Distance: {hog_distance:.2f}\\n(High = Rotation Sensitive)', \n                  fontsize=12, fontweight='bold')\n    ax3.axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('rotation_invariance.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: rotation_invariance.png\")\n    plt.show()\n    \n    print(\"\\n📊 Conclusion:\")\n    print(\"  • SIFT: Rotation-invariant → keypoints match after rotation\")\n    print(\"  • HOG: Rotation-sensitive → feature vector changes significantly\")\n\n# ============================================================================\n# TASK 6: Simple Image Retrieval (Feature-based Search)\n# ============================================================================\n\ndef task6_image_retrieval():\n    \"\"\"\n    Demonstrate content-based image retrieval using HOG features\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 6: FEATURE-BASED IMAGE RETRIEVAL\")\n    print(\"=\" * 70)\n    \n    # Find query image from car_side\n    query_images = find_images('car_side')\n    if not query_images:\n        print(\"ERROR: No car_side images found\")\n        return\n    \n    query_path = query_images[0]\n    print(f\"Query image: {os.path.basename(query_path)}\")\n    \n    # Collect gallery images: 2 more cars + 3 from other categories\n    gallery_paths = []\n    gallery_labels = []\n    \n    # Add 2 more car_side images\n    if len(query_images) >= 3:\n        gallery_paths.extend(query_images[1:3])\n        gallery_labels.extend(['car_side', 'car_side'])\n    \n    # Add images from other categories\n    other_folders = ['airplanes', 'dalmatian', 'Motorbikes', 'Faces', 'butterfly']\n    for folder in other_folders:\n        imgs = find_images(folder)\n        if imgs and len(gallery_paths) < 5:\n            gallery_paths.append(imgs[0])\n            gallery_labels.append(folder)\n    \n    if len(gallery_paths) < 5:\n        print(f\"Warning: Only found {len(gallery_paths)} gallery images\")\n    \n    print(f\"Gallery: {len(gallery_paths)} images\")\n    \n    # Extract query features\n    query_features, query_img = extract_hog_features(query_path)\n    \n    # Extract gallery features and calculate distances\n    results = []\n    for i, (path, label) in enumerate(zip(gallery_paths, gallery_labels)):\n        features, img = extract_hog_features(path)\n        distance = euclidean(query_features, features)\n        results.append({\n            'path': path,\n            'label': label,\n            'distance': distance,\n            'image': img\n        })\n        print(f\"  {i+1}. {label}: distance = {distance:.4f}\")\n    \n    # Sort by similarity (smallest distance first)\n    results.sort(key=lambda x: x['distance'])\n    \n    # Create visualization\n    fig = plt.figure(figsize=(16, 10))\n    \n    # Query image at the top\n    ax_query = plt.subplot(2, 3, 2)\n    ax_query.imshow(query_img)\n    ax_query.set_title('QUERY IMAGE\\n(car_side)', \n                       fontsize=14, fontweight='bold', \n                       bbox=dict(boxstyle='round', facecolor='gold', alpha=0.8))\n    ax_query.axis('off')\n    \n    # Gallery images sorted by similarity\n    for i, result in enumerate(results):\n        ax = plt.subplot(2, 3, i+4)\n        ax.imshow(result['image'])\n        \n        # Color code: green for same class, red for different\n        color = 'lightgreen' if result['label'] == 'car_side' else 'lightcoral'\n        \n        ax.set_title(f\"#{i+1} Most Similar\\n{result['label']}\\n\"\n                    f\"Distance: {result['distance']:.3f}\",\n                    fontsize=11, fontweight='bold',\n                    bbox=dict(boxstyle='round', facecolor=color, alpha=0.7))\n        ax.axis('off')\n    \n    plt.suptitle('Content-Based Image Retrieval using HOG Features\\n'\n                 'Green = Same Category | Red = Different Category',\n                 fontsize=16, fontweight='bold', y=0.98)\n    \n    plt.tight_layout()\n    plt.savefig('image_retrieval.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: image_retrieval.png\")\n    plt.show()\n    \n    print(\"\\n📊 Results Summary:\")\n    print(f\"  Query: car_side\")\n    for i, result in enumerate(results):\n        match = \"✓ MATCH\" if result['label'] == 'car_side' else \"✗ DIFFERENT\"\n        print(f\"  Rank {i+1}: {result['label']:15s} | Distance: {result['distance']:.4f} | {match}\")\n\n# ============================================================================\n# MAIN EXECUTION\n# ============================================================================\n\nif __name__ == \"__main__\":\n    print(\"=\" * 70)\n    print(\"ADVANCED COMPUTER VISION TASKS\")\n    print(\"=\" * 70)\n    \n    # Run all three advanced tasks\n    task4_dog_visualization()\n    task5_rotation_invariance()\n    task6_image_retrieval()\n    \n    print(\"\\n\" + \"=\" * 70)\n    print(\"ALL ADVANCED TASKS COMPLETED!\")\n    print(\"Generated files:\")\n    print(\"  • dog_result.png\")\n    print(\"  • rotation_invariance.png\")\n    print(\"  • image_retrieval.png\")\n    print(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T22:26:05.88551Z","iopub.execute_input":"2026-01-13T22:26:05.886201Z","iopub.status.idle":"2026-01-13T22:26:10.198245Z","shell.execute_reply.started":"2026-01-13T22:26:05.886169Z","shell.execute_reply":"2026-01-13T22:26:10.196812Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom skimage import io, color\nfrom skimage.feature import hog\nimport os\nimport glob\nimport time\n\n# Base path to dataset\nBASE_PATH = '/kaggle/input/caltech-101/caltech-101'\n\n# ============================================================================\n# HELPER FUNCTIONS\n# ============================================================================\n\ndef find_images(folder_name):\n    \"\"\"Helper function to find images in a folder with multiple extensions\"\"\"\n    folder_path = os.path.join(BASE_PATH, folder_name)\n    for ext in ['*.jpg', '*.jpeg', '*.png', '*.JPG', '*.JPEG', '*.PNG']:\n        images = glob.glob(os.path.join(folder_path, ext))\n        if images:\n            return images\n    return []\n\n# ============================================================================\n# TASK 7: Hyperparameter Impact (HOG Granularity)\n# ============================================================================\n\ndef task7_hog_granularity():\n    \"\"\"\n    Demonstrate how cell size affects HOG feature extraction granularity\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 7: HOG HYPERPARAMETER IMPACT - CELL SIZE GRANULARITY\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['Motorbikes', 'motorbikes', 'Faces', 'faces']:\n        images = find_images(folder)\n        if images:\n            print(f\"Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load and convert to grayscale\n    img = io.imread(img_path)\n    if len(img.shape) == 3:\n        gray = color.rgb2gray(img)\n    else:\n        gray = img\n    \n    # Define three different cell sizes\n    cell_sizes = [(4, 4), (8, 8), (16, 16)]\n    labels = ['Fine Detail (4×4)', 'Standard (8×8)', 'Coarse Shape (16×16)']\n    \n    # Compute HOG for each cell size\n    hog_images = []\n    feature_counts = []\n    \n    for cell_size in cell_sizes:\n        fd, hog_img = hog(\n            gray,\n            orientations=9,\n            pixels_per_cell=cell_size,\n            cells_per_block=(2, 2),\n            visualize=True,\n            block_norm='L2-Hys'\n        )\n        hog_images.append(hog_img)\n        feature_counts.append(len(fd))\n        print(f\"  Cell size {cell_size}: {len(fd)} features\")\n    \n    # Create visualization\n    fig, axes = plt.subplots(1, 4, figsize=(20, 5))\n    \n    # Original image\n    axes[0].imshow(gray, cmap='gray')\n    axes[0].set_title('Original Image', fontsize=13, fontweight='bold')\n    axes[0].axis('off')\n    \n    # HOG visualizations with different cell sizes\n    for i, (hog_img, label, cell_size, feat_count) in enumerate(zip(hog_images, labels, cell_sizes, feature_counts)):\n        axes[i+1].imshow(hog_img, cmap='gray')\n        axes[i+1].set_title(f'{label}\\n{feat_count} features', \n                           fontsize=13, fontweight='bold')\n        axes[i+1].axis('off')\n    \n    plt.suptitle('HOG Cell Size Impact: Spatial Resolution vs. Feature Compression\\n'\n                 'Smaller cells = More detail | Larger cells = Coarser shape representation',\n                 fontsize=14, fontweight='bold', y=1.02)\n    \n    plt.tight_layout()\n    plt.savefig('hog_granularity.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: hog_granularity.png\")\n    plt.show()\n    \n    print(\"\\n📊 Analysis:\")\n    print(\"  • 4×4 cells: High spatial resolution, captures fine texture details\")\n    print(\"  • 8×8 cells: Balanced trade-off (standard choice)\")\n    print(\"  • 16×16 cells: Low resolution, captures only coarse shape structure\")\n    print(f\"  • Feature reduction: {feature_counts[0]} → {feature_counts[1]} → {feature_counts[2]}\")\n\n# ============================================================================\n# TASK 8: Stress Test (SIFT vs. Image Noise)\n# ============================================================================\n\ndef task8_sift_noise_robustness():\n    \"\"\"\n    Test SIFT robustness against varying levels of Gaussian noise\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 8: SIFT ROBUSTNESS STRESS TEST - NOISE RESISTANCE\")\n    print(\"=\" * 70)\n    \n    # Find images from same category\n    for folder in ['stop_sign', 'airplanes', 'Airplanes', 'Motorbikes', 'motorbikes']:\n        images = find_images(folder)\n        if len(images) >= 2:\n            print(f\"Found {len(images)} images in {folder}\")\n            break\n    \n    if not images or len(images) < 2:\n        print(\"ERROR: Need at least 2 images from the same category\")\n        return\n    \n    query_path = images[0]\n    target_path = images[1]\n    print(f\"Query: {os.path.basename(query_path)}\")\n    print(f\"Target: {os.path.basename(target_path)}\")\n    \n    # Load images\n    query_img = cv2.imread(query_path)\n    target_img = cv2.imread(target_path)\n    query_gray = cv2.cvtColor(query_img, cv2.COLOR_BGR2GRAY)\n    target_gray = cv2.cvtColor(target_img, cv2.COLOR_BGR2GRAY)\n    \n    # Create SIFT detector\n    sift = cv2.SIFT_create()\n    \n    # Detect keypoints in query image (remains constant)\n    kp_query, des_query = sift.detectAndCompute(query_gray, None)\n    \n    # Create three noise levels for target image\n    noise_levels = [\n        ('Clean', 0),\n        ('Light Noise', 25),\n        ('Heavy Noise', 50)\n    ]\n    \n    results = []\n    \n    for noise_label, noise_sigma in noise_levels:\n        # Add Gaussian noise\n        if noise_sigma > 0:\n            noise = np.random.normal(0, noise_sigma, target_gray.shape).astype(np.float32)\n            noisy_target = np.clip(target_gray.astype(np.float32) + noise, 0, 255).astype(np.uint8)\n        else:\n            noisy_target = target_gray\n        \n        # Detect keypoints in noisy target\n        kp_target, des_target = sift.detectAndCompute(noisy_target, None)\n        \n        # Match features\n        bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)\n        matches = bf.match(des_query, des_target)\n        matches = sorted(matches, key=lambda x: x.distance)\n        \n        print(f\"\\n{noise_label} (σ={noise_sigma}):\")\n        print(f\"  Target keypoints: {len(kp_target)}\")\n        print(f\"  Matches found: {len(matches)}\")\n        \n        # Convert to RGB for visualization\n        noisy_target_bgr = cv2.cvtColor(noisy_target, cv2.COLOR_GRAY2BGR)\n        \n        # Draw matches\n        img_matches = cv2.drawMatches(\n            query_img, kp_query,\n            noisy_target_bgr, kp_target,\n            matches[:30],\n            None,\n            flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n        )\n        img_matches_rgb = cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB)\n        \n        results.append({\n            'label': noise_label,\n            'sigma': noise_sigma,\n            'matches': len(matches),\n            'keypoints': len(kp_target),\n            'image': img_matches_rgb\n        })\n    \n    # Create visualization\n    fig, axes = plt.subplots(3, 1, figsize=(16, 14))\n    \n    for i, result in enumerate(results):\n        axes[i].imshow(result['image'])\n        \n        # Color code based on match quality\n        if result['matches'] > 50:\n            color_box = 'lightgreen'\n            quality = 'EXCELLENT'\n        elif result['matches'] > 20:\n            color_box = 'yellow'\n            quality = 'GOOD'\n        else:\n            color_box = 'lightcoral'\n            quality = 'DEGRADED'\n        \n        axes[i].set_title(f\"{result['label']} (σ={result['sigma']}) - \"\n                         f\"{result['matches']} matches | {result['keypoints']} keypoints | \"\n                         f\"Quality: {quality}\",\n                         fontsize=13, fontweight='bold',\n                         bbox=dict(boxstyle='round', facecolor=color_box, alpha=0.7))\n        axes[i].axis('off')\n    \n    plt.suptitle('SIFT Robustness Test: Performance Under Noise Conditions\\n'\n                 'Green = Robust | Yellow = Moderate | Red = Degraded',\n                 fontsize=15, fontweight='bold', y=0.995)\n    \n    plt.tight_layout()\n    plt.savefig('sift_noise_test.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: sift_noise_test.png\")\n    plt.show()\n    \n    print(\"\\n📊 Robustness Analysis:\")\n    for r in results:\n        degradation = (1 - r['matches'] / results[0]['matches']) * 100 if results[0]['matches'] > 0 else 0\n        print(f\"  {r['label']:15s}: {r['matches']:3d} matches ({degradation:5.1f}% degradation)\")\n\n# ============================================================================\n# TASK 9: Efficiency Benchmark (SIFT vs. ORB vs. HOG)\n# ============================================================================\n\ndef task9_performance_benchmark():\n    \"\"\"\n    Compare processing speed and feature density of SIFT, ORB, and HOG\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 9: PERFORMANCE BENCHMARK - SIFT vs ORB vs HOG\")\n    print(\"=\" * 70)\n    \n    # Find test image\n    for folder in ['Motorbikes', 'motorbikes', 'airplanes', 'Airplanes']:\n        images = find_images(folder)\n        if images:\n            print(f\"Using image from {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = cv2.imread(img_path)\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    gray_skimage = color.rgb2gray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n    \n    # Number of iterations for timing\n    n_iterations = 10\n    \n    results = {\n        'methods': [],\n        'times': [],\n        'features': []\n    }\n    \n    # === SIFT Benchmark ===\n    print(\"\\n--- Benchmarking SIFT ---\")\n    sift = cv2.SIFT_create()\n    \n    start_time = time.time()\n    for _ in range(n_iterations):\n        kp_sift, des_sift = sift.detectAndCompute(gray, None)\n    sift_time = (time.time() - start_time) / n_iterations\n    \n    results['methods'].append('SIFT')\n    results['times'].append(sift_time * 1000)  # Convert to ms\n    results['features'].append(len(kp_sift))\n    \n    print(f\"  Time: {sift_time*1000:.2f} ms\")\n    print(f\"  Keypoints: {len(kp_sift)}\")\n    print(f\"  Descriptor size: {des_sift.shape}\")\n    \n    # === ORB Benchmark ===\n    print(\"\\n--- Benchmarking ORB ---\")\n    orb = cv2.ORB_create(nfeatures=1000)\n    \n    start_time = time.time()\n    for _ in range(n_iterations):\n        kp_orb, des_orb = orb.detectAndCompute(gray, None)\n    orb_time = (time.time() - start_time) / n_iterations\n    \n    results['methods'].append('ORB')\n    results['times'].append(orb_time * 1000)\n    results['features'].append(len(kp_orb))\n    \n    print(f\"  Time: {orb_time*1000:.2f} ms\")\n    print(f\"  Keypoints: {len(kp_orb)}\")\n    print(f\"  Descriptor size: {des_orb.shape}\")\n    \n    # === HOG Benchmark ===\n    print(\"\\n--- Benchmarking HOG ---\")\n    \n    start_time = time.time()\n    for _ in range(n_iterations):\n        hog_features = hog(\n            gray_skimage,\n            orientations=9,\n            pixels_per_cell=(8, 8),\n            cells_per_block=(2, 2),\n            visualize=False,\n            block_norm='L2-Hys'\n        )\n    hog_time = (time.time() - start_time) / n_iterations\n    \n    results['methods'].append('HOG')\n    results['times'].append(hog_time * 1000)\n    results['features'].append(len(hog_features))\n    \n    print(f\"  Time: {hog_time*1000:.2f} ms\")\n    print(f\"  Feature vector size: {len(hog_features)}\")\n    \n    # Create visualization\n    fig, axes = plt.subplots(1, 2, figsize=(14, 6))\n    \n    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1']\n    \n    # Processing Time Chart\n    bars1 = axes[0].bar(results['methods'], results['times'], color=colors, alpha=0.8, edgecolor='black', linewidth=2)\n    axes[0].set_ylabel('Processing Time (milliseconds)', fontsize=12, fontweight='bold')\n    axes[0].set_title('Processing Speed Comparison\\n(Lower is Better)', fontsize=13, fontweight='bold')\n    axes[0].grid(axis='y', alpha=0.3, linestyle='--')\n    \n    # Add value labels on bars\n    for bar in bars1:\n        height = bar.get_height()\n        axes[0].text(bar.get_x() + bar.get_width()/2., height,\n                    f'{height:.1f}ms',\n                    ha='center', va='bottom', fontweight='bold', fontsize=11)\n    \n    # Feature Count Chart\n    bars2 = axes[1].bar(results['methods'], results['features'], color=colors, alpha=0.8, edgecolor='black', linewidth=2)\n    axes[1].set_ylabel('Number of Features', fontsize=12, fontweight='bold')\n    axes[1].set_title('Feature Detection Density\\n(Higher = More Keypoints)', fontsize=13, fontweight='bold')\n    axes[1].grid(axis='y', alpha=0.3, linestyle='--')\n    \n    # Add value labels on bars\n    for bar in bars2:\n        height = bar.get_height()\n        axes[1].text(bar.get_x() + bar.get_width()/2., height,\n                    f'{int(height)}',\n                    ha='center', va='bottom', fontweight='bold', fontsize=11)\n    \n    plt.suptitle(f'Feature Extraction Performance Benchmark (Average of {n_iterations} runs)\\n'\n                 'Image: ' + os.path.basename(img_path),\n                 fontsize=14, fontweight='bold', y=1.02)\n    \n    plt.tight_layout()\n    plt.savefig('performance_benchmark.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: performance_benchmark.png\")\n    plt.show()\n    \n    # Performance analysis\n    print(\"\\n📊 Performance Summary:\")\n    print(f\"  {'Method':<10} {'Time (ms)':<15} {'Features':<15} {'Speed Ratio'}\")\n    print(\"  \" + \"-\" * 60)\n    \n    baseline_time = results['times'][0]\n    for method, time_ms, features in zip(results['methods'], results['times'], results['features']):\n        speedup = baseline_time / time_ms\n        print(f\"  {method:<10} {time_ms:>8.2f} ms     {features:>8}        {speedup:.2f}×\")\n    \n    print(\"\\n  Key Insights:\")\n    print(f\"  • ORB is ~{baseline_time/results['times'][1]:.1f}× faster than SIFT\")\n    print(f\"  • HOG produces {results['features'][2]} global features vs point-based descriptors\")\n    print(f\"  • Trade-off: Speed (ORB) vs Accuracy (SIFT) vs Global shape (HOG)\")\n\n# ============================================================================\n# MAIN EXECUTION\n# ============================================================================\n\nif __name__ == \"__main__\":\n    print(\"=\" * 70)\n    print(\"FINAL COMPUTER VISION TASKS - PART 2\")\n    print(\"=\" * 70)\n    \n    # Run all three tasks\n    task7_hog_granularity()\n    task8_sift_noise_robustness()\n    task9_performance_benchmark()\n    \n    print(\"\\n\" + \"=\" * 70)\n    print(\"ALL FINAL TASKS COMPLETED!\")\n    print(\"Generated files:\")\n    print(\"  • hog_granularity.png\")\n    print(\"  • sift_noise_test.png\")\n    print(\"  • performance_benchmark.png\")\n    print(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T22:28:15.475838Z","iopub.execute_input":"2026-01-13T22:28:15.47622Z","iopub.status.idle":"2026-01-13T22:28:21.797659Z","shell.execute_reply.started":"2026-01-13T22:28:15.476195Z","shell.execute_reply":"2026-01-13T22:28:21.79666Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom skimage import io, color, transform\nfrom skimage.feature import hog\nimport os\nimport glob\n\n# Base path to dataset\nBASE_PATH = '/kaggle/input/caltech-101/caltech-101'\n\n# ============================================================================\n# HELPER FUNCTIONS\n# ============================================================================\n\ndef find_images(folder_name):\n    \"\"\"Helper function to find images in a folder with multiple extensions\"\"\"\n    folder_path = os.path.join(BASE_PATH, folder_name)\n    for ext in ['*.jpg', '*.jpeg', '*.png', '*.JPG', '*.JPEG', '*.PNG']:\n        images = glob.glob(os.path.join(folder_path, ext))\n        if images:\n            return images\n    return []\n\n# ============================================================================\n# TASK 10: Scale Invariance Proof (The \"S\" in SIFT)\n# ============================================================================\n\ndef task10_scale_invariance():\n    \"\"\"\n    Demonstrate SIFT's scale invariance by matching zoomed regions\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 10: SCALE INVARIANCE - THE 'S' IN SIFT\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['sunflower', 'Sunflower', 'watch', 'butterfly', 'Butterfly']:\n        images = find_images(folder)\n        if images:\n            print(f\"Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load original image\n    img_original = cv2.imread(img_path)\n    gray_original = cv2.cvtColor(img_original, cv2.COLOR_BGR2GRAY)\n    \n    h, w = gray_original.shape\n    \n    # Create zoomed version: crop center region and resize back\n    crop_factor = 0.4  # Crop to 40% of original (center region)\n    crop_h, crop_w = int(h * crop_factor), int(w * crop_factor)\n    \n    # Calculate crop boundaries (center crop)\n    start_h = (h - crop_h) // 2\n    start_w = (w - crop_w) // 2\n    \n    # Crop the center region\n    cropped = gray_original[start_h:start_h+crop_h, start_w:start_w+crop_w]\n    \n    # Resize back to original size (simulates zoom)\n    img_zoomed = cv2.resize(cropped, (w, h), interpolation=cv2.INTER_LINEAR)\n    \n    print(f\"\\nOriginal size: {w}×{h}\")\n    print(f\"Cropped region: {crop_w}×{crop_h} (center {crop_factor*100:.0f}%)\")\n    print(f\"Zoomed back to: {w}×{h} (scale factor: {1/crop_factor:.1f}×)\")\n    \n    # Create SIFT detector\n    sift = cv2.SIFT_create()\n    \n    # Detect keypoints\n    kp_original, des_original = sift.detectAndCompute(gray_original, None)\n    kp_zoomed, des_zoomed = sift.detectAndCompute(img_zoomed, None)\n    \n    print(f\"\\nKeypoints detected:\")\n    print(f\"  Original: {len(kp_original)}\")\n    print(f\"  Zoomed: {len(kp_zoomed)}\")\n    \n    # Match features\n    bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)\n    matches = bf.match(des_original, des_zoomed)\n    matches = sorted(matches, key=lambda x: x.distance)\n    \n    print(f\"  Matches found: {len(matches)}\")\n    \n    # Convert to BGR for visualization\n    img_original_bgr = cv2.cvtColor(gray_original, cv2.COLOR_GRAY2BGR)\n    img_zoomed_bgr = cv2.cvtColor(img_zoomed, cv2.COLOR_GRAY2BGR)\n    \n    # Draw matches\n    img_matches = cv2.drawMatches(\n        img_original_bgr, kp_original,\n        img_zoomed_bgr, kp_zoomed,\n        matches[:50],\n        None,\n        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n    )\n    img_matches_rgb = cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB)\n    \n    # Create visualization\n    fig, axes = plt.subplots(2, 1, figsize=(16, 12))\n    \n    # Top: Side-by-side comparison\n    ax_top = axes[0]\n    combined = np.hstack([\n        cv2.cvtColor(img_original_bgr, cv2.COLOR_BGR2RGB),\n        cv2.cvtColor(img_zoomed_bgr, cv2.COLOR_BGR2RGB)\n    ])\n    ax_top.imshow(combined)\n    ax_top.set_title(f'Original Image (Full View) | Zoomed {1/crop_factor:.1f}× (Center Region Enlarged)\\n'\n                     f'Same features at different scales',\n                     fontsize=13, fontweight='bold',\n                     bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.7))\n    ax_top.axis('off')\n    \n    # Bottom: Matches\n    axes[1].imshow(img_matches_rgb)\n    axes[1].set_title(f'SIFT Scale Invariance: {len(matches)} Feature Matches Across Scale Changes\\n'\n                      f'Features are recognized regardless of size',\n                      fontsize=13, fontweight='bold',\n                      bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.7))\n    axes[1].axis('off')\n    \n    plt.suptitle('Scale Invariance Demonstration: The \"S\" in SIFT\\n'\n                 'SIFT recognizes the same features whether they\\'re 500 pixels or 50 pixels wide',\n                 fontsize=15, fontweight='bold', y=0.995)\n    \n    plt.tight_layout()\n    plt.savefig('scale_invariance.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: scale_invariance.png\")\n    plt.show()\n    \n    print(\"\\n📊 Scale Invariance Analysis:\")\n    print(f\"  • Despite {1/crop_factor:.1f}× scale difference, {len(matches)} features matched\")\n    print(f\"  • Match ratio: {len(matches)/min(len(kp_original), len(kp_zoomed))*100:.1f}%\")\n    print(f\"  • Proof: SIFT identifies features independent of their size\")\n\n# ============================================================================\n# TASK 11: Occlusion Test (Robustness to Missing Data)\n# ============================================================================\n\ndef task11_occlusion_robustness():\n    \"\"\"\n    Test SIFT's ability to match features despite partial occlusion\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 11: OCCLUSION TEST - ROBUSTNESS TO MISSING DATA\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['Motorbikes', 'motorbikes', 'car_side']:\n        images = find_images(folder)\n        if images:\n            print(f\"Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img_original = cv2.imread(img_path)\n    gray_original = cv2.cvtColor(img_original, cv2.COLOR_BGR2GRAY)\n    \n    h, w = gray_original.shape\n    \n    # Create occluded version by drawing black rectangles\n    img_occluded = gray_original.copy()\n    \n    # Add multiple occlusion blocks (40% total coverage)\n    occlusion_blocks = [\n        (int(w*0.1), int(h*0.2), int(w*0.4), int(h*0.6)),  # Large block on left\n        (int(w*0.6), int(h*0.1), int(w*0.9), int(h*0.3)),  # Block on top right\n    ]\n    \n    total_occluded_pixels = 0\n    for x1, y1, x2, y2 in occlusion_blocks:\n        cv2.rectangle(img_occluded, (x1, y1), (x2, y2), (0, 0, 0), -1)\n        total_occluded_pixels += (x2 - x1) * (y2 - y1)\n    \n    occlusion_percentage = (total_occluded_pixels / (h * w)) * 100\n    print(f\"\\nOcclusion: {occlusion_percentage:.1f}% of image covered\")\n    \n    # Create SIFT detector\n    sift = cv2.SIFT_create()\n    \n    # Detect keypoints\n    kp_original, des_original = sift.detectAndCompute(gray_original, None)\n    kp_occluded, des_occluded = sift.detectAndCompute(img_occluded, None)\n    \n    print(f\"\\nKeypoints detected:\")\n    print(f\"  Original: {len(kp_original)}\")\n    print(f\"  Occluded: {len(kp_occluded)}\")\n    print(f\"  Lost: {len(kp_original) - len(kp_occluded)} ({(1-len(kp_occluded)/len(kp_original))*100:.1f}%)\")\n    \n    # Match features\n    bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)\n    matches = bf.match(des_original, des_occluded)\n    matches = sorted(matches, key=lambda x: x.distance)\n    \n    print(f\"  Matches found: {len(matches)}\")\n    print(f\"  Match rate: {len(matches)/len(kp_occluded)*100:.1f}% of visible keypoints\")\n    \n    # Convert to BGR for visualization\n    img_original_bgr = cv2.cvtColor(gray_original, cv2.COLOR_GRAY2BGR)\n    img_occluded_bgr = cv2.cvtColor(img_occluded, cv2.COLOR_GRAY2BGR)\n    \n    # Draw matches\n    img_matches = cv2.drawMatches(\n        img_original_bgr, kp_original,\n        img_occluded_bgr, kp_occluded,\n        matches[:40],\n        None,\n        matchColor=(0, 255, 0),\n        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n    )\n    img_matches_rgb = cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB)\n    \n    # Create visualization\n    fig = plt.figure(figsize=(16, 10))\n    \n    # Main matches display\n    ax_main = plt.subplot(2, 1, 1)\n    ax_main.imshow(img_matches_rgb)\n    ax_main.set_title(f'SIFT Occlusion Robustness: {len(matches)} Matches Despite {occlusion_percentage:.0f}% Occlusion\\n'\n                      f'Local features in visible regions still match correctly',\n                      fontsize=13, fontweight='bold',\n                      bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.7))\n    ax_main.axis('off')\n    \n    # Side-by-side comparison\n    ax_left = plt.subplot(2, 2, 3)\n    ax_left.imshow(cv2.cvtColor(img_original_bgr, cv2.COLOR_BGR2RGB))\n    ax_left.set_title('Original Image\\nAll Features Visible', \n                     fontsize=12, fontweight='bold')\n    ax_left.axis('off')\n    \n    ax_right = plt.subplot(2, 2, 4)\n    ax_right.imshow(cv2.cvtColor(img_occluded_bgr, cv2.COLOR_BGR2RGB))\n    ax_right.set_title(f'Occluded Image\\n{occlusion_percentage:.0f}% Blocked (Black Regions)', \n                      fontsize=12, fontweight='bold')\n    ax_right.axis('off')\n    \n    plt.suptitle('Occlusion Test: Recognition Despite Missing Data\\n'\n                 'Like recognizing a car parked behind a tree - partial view is enough',\n                 fontsize=15, fontweight='bold', y=0.995)\n    \n    plt.tight_layout()\n    plt.savefig('occlusion_test.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: occlusion_test.png\")\n    plt.show()\n    \n    print(\"\\n📊 Occlusion Robustness Analysis:\")\n    print(f\"  • {occlusion_percentage:.0f}% of image occluded (blocked)\")\n    print(f\"  • {len(matches)} features successfully matched\")\n    print(f\"  • Proof: Local features enable recognition from partial views\")\n    print(f\"  • Real-world: Recognizing objects behind obstacles, partial visibility\")\n\n# ============================================================================\n# TASK 12: Cyborg Vision (HOG + SIFT Fusion Overlay)\n# ============================================================================\n\ndef task12_cyborg_vision():\n    \"\"\"\n    Create a stunning fusion visualization combining HOG and SIFT\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 12: CYBORG VISION - HOG + SIFT FUSION\")\n    print(\"=\" * 70)\n    \n    # Find high-contrast image\n    for folder in ['electric_guitar', 'schooner', 'Motorbikes', 'motorbikes', 'watch']:\n        images = find_images(folder)\n        if images:\n            print(f\"Found {len(images)} images in {folder}\")\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = io.imread(img_path)\n    if len(img.shape) == 3:\n        gray = color.rgb2gray(img)\n    else:\n        gray = img\n        img = color.gray2rgb(gray)\n    \n    print(\"\\n--- Computing HOG Skeleton ---\")\n    # Compute HOG with visualization\n    fd, hog_image = hog(\n        gray,\n        orientations=9,\n        pixels_per_cell=(8, 8),\n        cells_per_block=(2, 2),\n        visualize=True,\n        block_norm='L2-Hys'\n    )\n    \n    print(f\"  HOG features: {len(fd)}\")\n    \n    # Normalize and convert HOG to RGB for colorful overlay\n    hog_normalized = (hog_image - hog_image.min()) / (hog_image.max() - hog_image.min())\n    \n    # Create a colorful HOG visualization (cyan-purple gradient)\n    hog_colored = np.zeros((*hog_normalized.shape, 3))\n    hog_colored[:, :, 0] = hog_normalized * 0.5  # Red channel\n    hog_colored[:, :, 1] = hog_normalized * 0.8  # Green channel\n    hog_colored[:, :, 2] = hog_normalized * 1.0  # Blue channel (dominant)\n    \n    print(\"\\n--- Detecting SIFT Landmarks ---\")\n    # Detect SIFT keypoints on original image\n    img_cv = cv2.cvtColor((img * 255).astype(np.uint8), cv2.COLOR_RGB2BGR)\n    gray_cv = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)\n    \n    sift = cv2.SIFT_create(nfeatures=200)  # Limit for cleaner visualization\n    keypoints, descriptors = sift.detectAndCompute(gray_cv, None)\n    \n    print(f\"  SIFT keypoints: {len(keypoints)}\")\n    \n    # Create the fusion image\n    print(\"\\n--- Creating Fusion Visualization ---\")\n    \n    # Resize HOG to match original image size\n    hog_resized = transform.resize(hog_colored, img.shape[:2], anti_aliasing=True)\n    \n    # Create base fusion image (HOG as skeleton)\n    fusion = (hog_resized * 0.8 + img * 0.2)  # Blend HOG with original\n    fusion = np.clip(fusion, 0, 1)\n    \n    # Convert to uint8 for OpenCV drawing\n    fusion_uint8 = (fusion * 255).astype(np.uint8)\n    fusion_cv = cv2.cvtColor(fusion_uint8, cv2.COLOR_RGB2BGR)\n    \n    # Draw SIFT keypoints with rich visualization (colorful circles)\n    for kp in keypoints:\n        x, y = int(kp.pt[0]), int(kp.pt[1])\n        size = int(kp.size)\n        angle = kp.angle\n        \n        # Draw outer circle (cyan)\n        cv2.circle(fusion_cv, (x, y), size, (255, 255, 0), 2)\n        \n        # Draw inner circle (yellow)\n        cv2.circle(fusion_cv, (x, y), size//2, (0, 255, 255), 1)\n        \n        # Draw orientation line (magenta)\n        end_x = int(x + size * np.cos(np.radians(angle)))\n        end_y = int(y + size * np.sin(np.radians(angle)))\n        cv2.line(fusion_cv, (x, y), (end_x, end_y), (255, 0, 255), 2)\n        \n        # Draw center dot (white)\n        cv2.circle(fusion_cv, (x, y), 2, (255, 255, 255), -1)\n    \n    # Convert back to RGB\n    fusion_final = cv2.cvtColor(fusion_cv, cv2.COLOR_BGR2RGB)\n    \n    # Create cinematic visualization\n    fig = plt.figure(figsize=(20, 12))\n    \n    # Main cyborg vision\n    ax_main = plt.subplot(1, 1, 1)\n    ax_main.imshow(fusion_final)\n    ax_main.set_title('CYBORG VISION: Neural Feature Fusion\\n'\n                     'Blue Skeleton = HOG Gradients (Global Shape) | '\n                     'Colorful Circles = SIFT Keypoints (Local Landmarks)',\n                     fontsize=16, fontweight='bold', pad=20,\n                     bbox=dict(boxstyle='round', facecolor='black', alpha=0.7),\n                     color='cyan')\n    ax_main.axis('off')\n    \n    # Add text annotations\n    fig.text(0.5, 0.02, \n             f'HOG: {len(fd)} shape features | SIFT: {len(keypoints)} interest points | '\n             f'Combined: Structure + Landmarks',\n             ha='center', fontsize=13, fontweight='bold',\n             bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.8))\n    \n    plt.tight_layout()\n    plt.savefig('cyborg_vision.png', dpi=300, bbox_inches='tight', facecolor='black')\n    print(\"\\n✓ Saved: cyborg_vision.png\")\n    plt.show()\n    \n    print(\"\\n📊 Fusion Analysis:\")\n    print(f\"  • HOG Skeleton: {len(fd)} gradient features (blue web structure)\")\n    print(f\"  • SIFT Landmarks: {len(keypoints)} interest points (colored circles)\")\n    print(f\"  • Visualization combines global shape (HOG) with local features (SIFT)\")\n    print(f\"  • Perfect for presentation 'Questions?' slide background\")\n    \n    # Create a cleaner version for actual slide background\n    print(\"\\n--- Creating Clean Background Version ---\")\n    \n    fig_bg = plt.figure(figsize=(20, 12))\n    ax_bg = plt.subplot(1, 1, 1)\n    ax_bg.imshow(fusion_final)\n    ax_bg.axis('off')\n    \n    plt.tight_layout(pad=0)\n    plt.savefig('cyborg_vision_background.png', dpi=300, bbox_inches='tight', \n                facecolor='black', pad_inches=0)\n    print(\"✓ Saved: cyborg_vision_background.png (clean version for slides)\")\n    plt.show()\n\n# ============================================================================\n# MAIN EXECUTION\n# ============================================================================\n\nif __name__ == \"__main__\":\n    print(\"=\" * 70)\n    print(\"ULTIMATE COMPUTER VISION TASKS - FINALE\")\n    print(\"=\" * 70)\n    \n    # Run all three ultimate tasks\n    task10_scale_invariance()\n    task11_occlusion_robustness()\n    task12_cyborg_vision()\n    \n    print(\"\\n\" + \"=\" * 70)\n    print(\"🎉 ALL ULTIMATE TASKS COMPLETED! 🎉\")\n    print(\"=\" * 70)\n    print(\"\\nGenerated files:\")\n    print(\"  • scale_invariance.png - Proves SIFT recognizes features at any size\")\n    print(\"  • occlusion_test.png - Shows recognition despite 40% obstruction\")\n    print(\"  • cyborg_vision.png - Stunning HOG+SIFT fusion visualization\")\n    print(\"  • cyborg_vision_background.png - Clean version for slide background\")\n    print(\"\\n🎓 Your computer vision presentation is now complete!\")\n    print(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T22:32:05.600931Z","iopub.execute_input":"2026-01-13T22:32:05.601294Z","iopub.status.idle":"2026-01-13T22:32:15.47251Z","shell.execute_reply.started":"2026-01-13T22:32:05.601272Z","shell.execute_reply":"2026-01-13T22:32:15.471575Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom skimage import io, color, transform, exposure\nfrom skimage.feature import hog, corner_harris, corner_peaks, local_binary_pattern\nfrom skimage.measure import ransac\nfrom skimage.transform import AffineTransform\nfrom scipy.spatial.distance import cdist\nfrom sklearn.cluster import KMeans\nfrom sklearn.decomposition import PCA\nimport os\nimport glob\nimport time\n\n# Base path to dataset\nBASE_PATH = '/kaggle/input/caltech-101/caltech-101'\n\n# ============================================================================\n# HELPER FUNCTIONS\n# ============================================================================\n\ndef find_images(folder_name):\n    \"\"\"Helper function to find images in a folder with multiple extensions\"\"\"\n    folder_path = os.path.join(BASE_PATH, folder_name)\n    for ext in ['*.jpg', '*.jpeg', '*.png', '*.JPG', '*.JPEG', '*.PNG']:\n        images = glob.glob(os.path.join(folder_path, ext))\n        if images:\n            return images\n    return []\n\n# ============================================================================\n# TASK 13: Feature Descriptor Dimensionality Comparison\n# ============================================================================\n\ndef task13_descriptor_dimensionality():\n    \"\"\"\n    Compare descriptor dimensions and efficiency across different methods\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 13: DESCRIPTOR DIMENSIONALITY ANALYSIS\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['Motorbikes', 'motorbikes', 'Faces', 'faces']:\n        images = find_images(folder)\n        if images:\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = cv2.imread(img_path)\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    \n    # Initialize detectors\n    sift = cv2.SIFT_create(nfeatures=100)\n    orb = cv2.ORB_create(nfeatures=100)\n    \n    # Extract features\n    kp_sift, des_sift = sift.detectAndCompute(gray, None)\n    kp_orb, des_orb = orb.detectAndCompute(gray, None)\n    \n    # HOG (global descriptor)\n    hog_features = hog(\n        color.rgb2gray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)),\n        orientations=9,\n        pixels_per_cell=(8, 8),\n        cells_per_block=(2, 2),\n        visualize=False\n    )\n    \n    # Calculate storage requirements\n    sift_storage = len(kp_sift) * 128 * 4  # 4 bytes per float32\n    orb_storage = len(kp_orb) * 32 * 1  # 1 byte per uint8\n    hog_storage = len(hog_features) * 4\n    \n    print(f\"\\nDescriptor Analysis:\")\n    print(f\"  SIFT: {len(kp_sift)} keypoints × 128 dims = {len(kp_sift)*128} values ({sift_storage/1024:.2f} KB)\")\n    print(f\"  ORB:  {len(kp_orb)} keypoints × 32 dims = {len(kp_orb)*32} values ({orb_storage/1024:.2f} KB)\")\n    print(f\"  HOG:  1 descriptor × {len(hog_features)} dims ({hog_storage/1024:.2f} KB)\")\n    \n    # Create visualization\n    fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n    \n    # Descriptor dimension comparison\n    methods = ['SIFT', 'ORB', 'HOG']\n    dimensions = [128, 32, len(hog_features)]\n    colors = ['#FF6B6B', '#4ECDC4', '#45B7D1']\n    \n    axes[0, 0].bar(methods, dimensions, color=colors, alpha=0.8, edgecolor='black', linewidth=2)\n    axes[0, 0].set_ylabel('Descriptor Dimensions', fontsize=12, fontweight='bold')\n    axes[0, 0].set_title('Descriptor Dimensionality', fontsize=13, fontweight='bold')\n    axes[0, 0].grid(axis='y', alpha=0.3)\n    for i, (m, d) in enumerate(zip(methods, dimensions)):\n        axes[0, 0].text(i, d, f'{d}', ha='center', va='bottom', fontweight='bold')\n    \n    # Storage requirements\n    storage = [sift_storage/1024, orb_storage/1024, hog_storage/1024]\n    axes[0, 1].bar(methods, storage, color=colors, alpha=0.8, edgecolor='black', linewidth=2)\n    axes[0, 1].set_ylabel('Storage (KB)', fontsize=12, fontweight='bold')\n    axes[0, 1].set_title('Memory Footprint', fontsize=13, fontweight='bold')\n    axes[0, 1].grid(axis='y', alpha=0.3)\n    for i, (m, s) in enumerate(zip(methods, storage)):\n        axes[0, 1].text(i, s, f'{s:.2f}', ha='center', va='bottom', fontweight='bold')\n    \n    # Keypoint count\n    keypoint_counts = [len(kp_sift), len(kp_orb), 1]\n    axes[1, 0].bar(methods, keypoint_counts, color=colors, alpha=0.8, edgecolor='black', linewidth=2)\n    axes[1, 0].set_ylabel('Number of Keypoints', fontsize=12, fontweight='bold')\n    axes[1, 0].set_title('Feature Density', fontsize=13, fontweight='bold')\n    axes[1, 0].grid(axis='y', alpha=0.3)\n    for i, (m, k) in enumerate(zip(methods, keypoint_counts)):\n        axes[1, 0].text(i, k, f'{k}', ha='center', va='bottom', fontweight='bold')\n    \n    # Total information (dimensions × keypoints)\n    total_info = [len(kp_sift)*128, len(kp_orb)*32, len(hog_features)]\n    axes[1, 1].bar(methods, total_info, color=colors, alpha=0.8, edgecolor='black', linewidth=2)\n    axes[1, 1].set_ylabel('Total Descriptor Values', fontsize=12, fontweight='bold')\n    axes[1, 1].set_title('Information Content', fontsize=13, fontweight='bold')\n    axes[1, 1].grid(axis='y', alpha=0.3)\n    for i, (m, t) in enumerate(zip(methods, total_info)):\n        axes[1, 1].text(i, t, f'{t}', ha='center', va='bottom', fontweight='bold', fontsize=9)\n    \n    plt.suptitle('Feature Descriptor Comparison: Dimensionality & Efficiency', \n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('descriptor_analysis.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: descriptor_analysis.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 14: Multi-Scale Feature Detection\n# ============================================================================\n\ndef task14_multiscale_detection():\n    \"\"\"\n    Visualize feature detection across multiple image scales\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 14: MULTI-SCALE FEATURE DETECTION\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['butterfly', 'Butterfly', 'watch']:\n        images = find_images(folder)\n        if images:\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = cv2.imread(img_path)\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    \n    # Create SIFT detector\n    sift = cv2.SIFT_create()\n    \n    # Test multiple scales\n    scales = [0.5, 1.0, 2.0]\n    results = []\n    \n    for scale in scales:\n        # Resize image\n        h, w = gray.shape\n        new_h, new_w = int(h * scale), int(w * scale)\n        scaled = cv2.resize(gray, (new_w, new_h))\n        \n        # Detect keypoints\n        kp, des = sift.detectAndCompute(scaled, None)\n        \n        # Draw keypoints\n        scaled_bgr = cv2.cvtColor(scaled, cv2.COLOR_GRAY2BGR)\n        img_kp = cv2.drawKeypoints(\n            scaled_bgr, kp, None,\n            flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS\n        )\n        \n        results.append({\n            'scale': scale,\n            'keypoints': len(kp),\n            'image': cv2.cvtColor(img_kp, cv2.COLOR_BGR2RGB),\n            'size': f'{new_w}×{new_h}'\n        })\n        \n        print(f\"  Scale {scale:.1f}× ({new_w}×{new_h}): {len(kp)} keypoints\")\n    \n    # Create visualization\n    fig, axes = plt.subplots(1, 3, figsize=(18, 6))\n    \n    for i, result in enumerate(results):\n        axes[i].imshow(result['image'])\n        axes[i].set_title(f\"Scale {result['scale']:.1f}× ({result['size']})\\n\"\n                         f\"{result['keypoints']} keypoints detected\",\n                         fontsize=12, fontweight='bold')\n        axes[i].axis('off')\n    \n    plt.suptitle('Multi-Scale Feature Detection: SIFT Across Different Resolutions',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('multiscale_detection.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: multiscale_detection.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 15: Feature Matching with RANSAC\n# ============================================================================\n\ndef task15_ransac_matching():\n    \"\"\"\n    Demonstrate robust matching using RANSAC to filter outliers\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 15: ROBUST MATCHING WITH RANSAC\")\n    print(\"=\" * 70)\n    \n    # Find images\n    for folder in ['airplanes', 'Airplanes']:\n        images = find_images(folder)\n        if len(images) >= 2:\n            break\n    \n    if len(images) < 2:\n        print(\"ERROR: Need at least 2 images\")\n        return\n    \n    img1_path = images[0]\n    img2_path = images[1]\n    print(f\"Image 1: {os.path.basename(img1_path)}\")\n    print(f\"Image 2: {os.path.basename(img2_path)}\")\n    \n    # Load images\n    img1 = cv2.imread(img1_path, cv2.IMREAD_GRAYSCALE)\n    img2 = cv2.imread(img2_path, cv2.IMREAD_GRAYSCALE)\n    \n    # Detect features\n    sift = cv2.SIFT_create()\n    kp1, des1 = sift.detectAndCompute(img1, None)\n    kp2, des2 = sift.detectAndCompute(img2, None)\n    \n    # Match features with ratio test\n    bf = cv2.BFMatcher()\n    matches = bf.knnMatch(des1, des2, k=2)\n    \n    # Apply Lowe's ratio test\n    good_matches = []\n    for m, n in matches:\n        if m.distance < 0.75 * n.distance:\n            good_matches.append(m)\n    \n    print(f\"\\nInitial matches (ratio test): {len(good_matches)}\")\n    \n    # Extract matched points\n    src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 2)\n    dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 2)\n    \n    # Find homography with RANSAC\n    if len(good_matches) >= 4:\n        H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)\n        inliers = mask.ravel().tolist()\n        inlier_matches = [m for m, inl in zip(good_matches, inliers) if inl]\n        outlier_matches = [m for m, inl in zip(good_matches, inliers) if not inl]\n        \n        print(f\"RANSAC inliers: {len(inlier_matches)}\")\n        print(f\"RANSAC outliers: {len(outlier_matches)}\")\n        print(f\"Inlier ratio: {len(inlier_matches)/len(good_matches)*100:.1f}%\")\n        \n        # Draw matches\n        img1_bgr = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)\n        img2_bgr = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)\n        \n        # Before RANSAC\n        img_before = cv2.drawMatches(\n            img1_bgr, kp1, img2_bgr, kp2,\n            good_matches[:50], None,\n            flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n        )\n        \n        # After RANSAC\n        img_after = cv2.drawMatches(\n            img1_bgr, kp1, img2_bgr, kp2,\n            inlier_matches[:50], None,\n            matchColor=(0, 255, 0),\n            flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n        )\n        \n        # Create visualization\n        fig, axes = plt.subplots(2, 1, figsize=(16, 12))\n        \n        axes[0].imshow(cv2.cvtColor(img_before, cv2.COLOR_BGR2RGB))\n        axes[0].set_title(f'Before RANSAC: {len(good_matches)} matches (includes outliers)',\n                         fontsize=13, fontweight='bold',\n                         bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.7))\n        axes[0].axis('off')\n        \n        axes[1].imshow(cv2.cvtColor(img_after, cv2.COLOR_BGR2RGB))\n        axes[1].set_title(f'After RANSAC: {len(inlier_matches)} inliers (outliers removed)\\n'\n                         f'Inlier ratio: {len(inlier_matches)/len(good_matches)*100:.1f}%',\n                         fontsize=13, fontweight='bold',\n                         bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.7))\n        axes[1].axis('off')\n        \n        plt.suptitle('RANSAC: Robust Feature Matching by Outlier Rejection',\n                     fontsize=15, fontweight='bold')\n        plt.tight_layout()\n        plt.savefig('ransac_matching.png', dpi=300, bbox_inches='tight')\n        print(\"\\n✓ Saved: ransac_matching.png\")\n        plt.show()\n\n# ============================================================================\n# TASK 16: Histogram Comparison (Color & HOG)\n# ============================================================================\n\ndef task16_histogram_comparison():\n    \"\"\"\n    Compare color histograms vs HOG histograms for similarity\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 16: HISTOGRAM-BASED SIMILARITY\")\n    print(\"=\" * 70)\n    \n    # Find images from same category\n    for folder in ['sunflower', 'Sunflower']:\n        images = find_images(folder)\n        if len(images) >= 3:\n            break\n    \n    if len(images) < 3:\n        print(\"ERROR: Need at least 3 images\")\n        return\n    \n    # Select 3 images\n    selected = images[:3]\n    print(f\"Comparing 3 images from {folder}\")\n    \n    # Extract histograms\n    color_hists = []\n    hog_hists = []\n    images_rgb = []\n    \n    for img_path in selected:\n        img = io.imread(img_path)\n        if len(img.shape) == 2:\n            img = color.gray2rgb(img)\n        \n        images_rgb.append(img)\n        \n        # Color histogram (HSV)\n        img_hsv = color.rgb2hsv(img)\n        h_hist, _ = np.histogram(img_hsv[:,:,0], bins=30, range=(0, 1))\n        color_hists.append(h_hist / h_hist.sum())  # Normalize\n        \n        # HOG features\n        gray = color.rgb2gray(img)\n        gray_resized = transform.resize(gray, (128, 128))\n        hog_feat = hog(gray_resized, orientations=9, pixels_per_cell=(8,8),\n                      cells_per_block=(2,2), visualize=False)\n        hog_hists.append(hog_feat / np.linalg.norm(hog_feat))\n    \n    # Compute similarities\n    from scipy.spatial.distance import correlation\n    \n    color_sim_12 = 1 - correlation(color_hists[0], color_hists[1])\n    color_sim_13 = 1 - correlation(color_hists[0], color_hists[2])\n    color_sim_23 = 1 - correlation(color_hists[1], color_hists[2])\n    \n    hog_sim_12 = 1 - correlation(hog_hists[0], hog_hists[1])\n    hog_sim_13 = 1 - correlation(hog_hists[0], hog_hists[2])\n    hog_sim_23 = 1 - correlation(hog_hists[1], hog_hists[2])\n    \n    print(f\"\\nColor Histogram Similarities:\")\n    print(f\"  Image 1-2: {color_sim_12:.3f}\")\n    print(f\"  Image 1-3: {color_sim_13:.3f}\")\n    print(f\"  Image 2-3: {color_sim_23:.3f}\")\n    \n    print(f\"\\nHOG Histogram Similarities:\")\n    print(f\"  Image 1-2: {hog_sim_12:.3f}\")\n    print(f\"  Image 1-3: {hog_sim_13:.3f}\")\n    print(f\"  Image 2-3: {hog_sim_23:.3f}\")\n    \n    # Visualization\n    fig = plt.figure(figsize=(16, 10))\n    \n    # Display images\n    for i, img in enumerate(images_rgb):\n        ax = plt.subplot(3, 3, i+1)\n        ax.imshow(img)\n        ax.set_title(f'Image {i+1}', fontsize=12, fontweight='bold')\n        ax.axis('off')\n    \n    # Color histogram comparison\n    ax = plt.subplot(3, 3, 4)\n    for i, hist in enumerate(color_hists):\n        ax.plot(hist, label=f'Image {i+1}', linewidth=2)\n    ax.set_title('Color Histograms (Hue)', fontsize=11, fontweight='bold')\n    ax.set_xlabel('Hue Bin')\n    ax.set_ylabel('Frequency')\n    ax.legend()\n    ax.grid(alpha=0.3)\n    \n    # Color similarity matrix\n    ax = plt.subplot(3, 3, 5)\n    color_matrix = np.array([[1, color_sim_12, color_sim_13],\n                            [color_sim_12, 1, color_sim_23],\n                            [color_sim_13, color_sim_23, 1]])\n    im = ax.imshow(color_matrix, cmap='YlGn', vmin=0, vmax=1)\n    ax.set_xticks([0, 1, 2])\n    ax.set_yticks([0, 1, 2])\n    ax.set_xticklabels(['Img 1', 'Img 2', 'Img 3'])\n    ax.set_yticklabels(['Img 1', 'Img 2', 'Img 3'])\n    ax.set_title('Color Similarity Matrix', fontsize=11, fontweight='bold')\n    for i in range(3):\n        for j in range(3):\n            ax.text(j, i, f'{color_matrix[i,j]:.2f}', ha='center', va='center', fontweight='bold')\n    \n    # HOG histograms (first 50 bins)\n    ax = plt.subplot(3, 3, 7)\n    for i, hist in enumerate(hog_hists):\n        ax.plot(hist[:50], label=f'Image {i+1}', linewidth=2)\n    ax.set_title('HOG Features (first 50 bins)', fontsize=11, fontweight='bold')\n    ax.set_xlabel('Feature Dimension')\n    ax.set_ylabel('Value')\n    ax.legend()\n    ax.grid(alpha=0.3)\n    \n    # HOG similarity matrix\n    ax = plt.subplot(3, 3, 8)\n    hog_matrix = np.array([[1, hog_sim_12, hog_sim_13],\n                          [hog_sim_12, 1, hog_sim_23],\n                          [hog_sim_13, hog_sim_23, 1]])\n    im = ax.imshow(hog_matrix, cmap='YlGn', vmin=0, vmax=1)\n    ax.set_xticks([0, 1, 2])\n    ax.set_yticks([0, 1, 2])\n    ax.set_xticklabels(['Img 1', 'Img 2', 'Img 3'])\n    ax.set_yticklabels(['Img 1', 'Img 2', 'Img 3'])\n    ax.set_title('HOG Similarity Matrix', fontsize=11, fontweight='bold')\n    for i in range(3):\n        for j in range(3):\n            ax.text(j, i, f'{hog_matrix[i,j]:.2f}', ha='center', va='center', fontweight='bold')\n    \n    plt.suptitle('Histogram-Based Similarity: Color vs Shape',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('histogram_comparison.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: histogram_comparison.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 17: Feature Clustering and Visual Vocabulary\n# ============================================================================\n\ndef task17_visual_vocabulary():\n    \"\"\"\n    Create a visual vocabulary using K-means clustering of SIFT descriptors\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 17: VISUAL VOCABULARY (BAG OF FEATURES)\")\n    print(\"=\" * 70)\n    \n    # Find multiple images\n    for folder in ['airplanes', 'Airplanes']:\n        images = find_images(folder)\n        if len(images) >= 10:\n            break\n    \n    if len(images) < 10:\n        print(\"ERROR: Need at least 10 images\")\n        return\n    \n    selected = images[:10]\n    print(f\"Building vocabulary from {len(selected)} images\")\n    \n    # Extract all SIFT descriptors\n    sift = cv2.SIFT_create(nfeatures=50)\n    all_descriptors = []\n    \n    for img_path in selected:\n        img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n        kp, des = sift.detectAndCompute(img, None)\n        if des is not None:\n            all_descriptors.append(des)\n    \n    # Concatenate all descriptors\n    all_descriptors = np.vstack(all_descriptors)\n    print(f\"Total descriptors: {len(all_descriptors)}\")\n    \n    # K-means clustering to create visual words\n    n_clusters = 50\n    print(f\"Creating visual vocabulary with {n_clusters} words...\")\n    kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)\n    kmeans.fit(all_descriptors)\n    \n    # Visualize cluster centers using PCA\n    pca = PCA(n_components=2)\n    descriptors_2d = pca.fit_transform(all_descriptors[:1000])  # Sample for visualization\n    centers_2d = pca.transform(kmeans.cluster_centers_)\n    \n    # Assign clusters to sample\n    labels = kmeans.predict(all_descriptors[:1000])\n    \n    print(f\"✓ Visual vocabulary created\")\n    print(f\"  Variance explained: {pca.explained_variance_ratio_.sum()*100:.1f}%\")\n    \n    # Create visualization\n    fig = plt.figure(figsize=(16, 10))\n    \n    # PCA visualization\n    ax1 = plt.subplot(2, 2, 1)\n    scatter = ax1.scatter(descriptors_2d[:, 0], descriptors_2d[:, 1], \n                         c=labels, cmap='tab20', alpha=0.5, s=10)\n    ax1.scatter(centers_2d[:, 0], centers_2d[:, 1], \n               c='red', marker='X', s=200, edgecolors='black', linewidths=2,\n               label='Cluster Centers')\n    ax1.set_xlabel('First Principal Component', fontweight='bold')\n    ax1.set_ylabel('Second Principal Component', fontweight='bold')\n    ax1.set_title(f'Visual Words in PCA Space ({n_clusters} clusters)', \n                 fontsize=12, fontweight='bold')\n    ax1.legend()\n    ax1.grid(alpha=0.3)\n    \n    # Cluster size distribution\n    ax2 = plt.subplot(2, 2, 2)\n    unique, counts = np.unique(kmeans.labels_, return_counts=True)\n    ax2.bar(range(n_clusters), counts, color='steelblue', alpha=0.7, edgecolor='black')\n    ax2.set_xlabel('Visual Word ID', fontweight='bold')\n    ax2.set_ylabel('Number of Descriptors', fontweight='bold')\n    ax2.set_title('Visual Vocabulary Distribution', fontsize=12, fontweight='bold')\n    ax2.grid(axis='y', alpha=0.3)\n    \n    # Example: Encode one image as bag of words\n    test_img = cv2.imread(selected[0], cv2.IMREAD_GRAYSCALE)\n    kp_test, des_test = sift.detectAndCompute(test_img, None)\n    if des_test is not None:\n        word_assignments = kmeans.predict(des_test)\n        histogram, _ = np.histogram(word_assignments, bins=n_clusters, range=(0, n_clusters))\n        \n        ax3 = plt.subplot(2, 2, 3)\n        ax3.imshow(test_img, cmap='gray')\n        ax3.set_title(f'Test Image\\n{len(kp_test)} features detected', \n                     fontsize=12, fontweight='bold')\n        ax3.axis('off')\n        \n        ax4 = plt.subplot(2, 2, 4)\n        ax4.bar(range(n_clusters), histogram, color='coral', alpha=0.7, edgecolor='black')\n        ax4.set_xlabel('Visual Word ID', fontweight='bold')\n        ax4.set_ylabel('Frequency', fontweight='bold')\n        ax4.set_title('Bag-of-Words Representation', fontsize=12, fontweight='bold')\n        ax4.grid(axis='y', alpha=0.3)\n    \n    plt.suptitle('Visual Vocabulary: Converting Images to Word Histograms',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('visual_vocabulary.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: visual_vocabulary.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 18: Illumination Invariance Test\n# ============================================================================\n\ndef task18_illumination_invariance():\n    \"\"\"\n    Test feature robustness to brightness and contrast changes\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 18: ILLUMINATION INVARIANCE TEST\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['Motorbikes', 'motorbikes']:\n        images = find_images(folder)\n        if images:\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load original\n    img_original = cv2.imread(img_path)\n    gray_original = cv2.cvtColor(img_original, cv2.COLOR_BGR2GRAY)\n    \n    # Create illumination variations\n    # Darkened (brightness -50)\n    img_dark = np.clip(gray_original.astype(np.float32) - 50, 0, 255).astype(np.uint8)\n    \n    # Brightened (brightness +50)\n    img_bright = np.clip(gray_original.astype(np.float32) + 50, 0, 255).astype(np.uint8)\n    \n    # Low contrast\n    img_low_contrast = exposure.rescale_intensity(gray_original, in_range=(50, 200))\n    \n    # Detect features in all versions\n    sift = cv2.SIFT_create()\n    \n    kp_orig, des_orig = sift.detectAndCompute(gray_original, None)\n    kp_dark, des_dark = sift.detectAndCompute(img_dark, None)\n    kp_bright, des_bright = sift.detectAndCompute(img_bright, None)\n    kp_low, des_low = sift.detectAndCompute(img_low_contrast, None)\n    \n    # Match against original\n    bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)\n    \n    matches_dark = bf.match(des_orig, des_dark)\n    matches_bright = bf.match(des_orig, des_bright)\n    matches_low = bf.match(des_orig, des_low)\n    \n    print(f\"\\nMatching Results:\")\n    print(f\"  Original keypoints: {len(kp_orig)}\")\n    print(f\"  Dark → {len(matches_dark)} matches ({len(matches_dark)/len(kp_orig)*100:.1f}%)\")\n    print(f\"  Bright → {len(matches_bright)} matches ({len(matches_bright)/len(kp_orig)*100:.1f}%)\")\n    print(f\"  Low contrast → {len(matches_low)} matches ({len(matches_low)/len(kp_orig)*100:.1f}%)\")\n    \n    # Visualization\n    fig, axes = plt.subplots(2, 4, figsize=(18, 9))\n    \n    # Original + variations\n    axes[0, 0].imshow(gray_original, cmap='gray')\n    axes[0, 0].set_title('Original', fontsize=11, fontweight='bold')\n    axes[0, 0].axis('off')\n    \n    axes[0, 1].imshow(img_dark, cmap='gray')\n    axes[0, 1].set_title('Darkened (-50)', fontsize=11, fontweight='bold')\n    axes[0, 1].axis('off')\n    \n    axes[0, 2].imshow(img_bright, cmap='gray')\n    axes[0, 2].set_title('Brightened (+50)', fontsize=11, fontweight='bold')\n    axes[0, 2].axis('off')\n    \n    axes[0, 3].imshow(img_low_contrast, cmap='gray')\n    axes[0, 3].set_title('Low Contrast', fontsize=11, fontweight='bold')\n    axes[0, 3].axis('off')\n    \n    # Match visualizations\n    conditions = ['Dark', 'Bright', 'Low Contrast']\n    matches_list = [matches_dark, matches_bright, matches_low]\n    images_list = [img_dark, img_bright, img_low_contrast]\n    \n    for i, (cond, matches, img_var) in enumerate(zip(conditions, matches_list, images_list)):\n        img_orig_bgr = cv2.cvtColor(gray_original, cv2.COLOR_GRAY2BGR)\n        img_var_bgr = cv2.cvtColor(img_var, cv2.COLOR_GRAY2BGR)\n        \n        img_matches = cv2.drawMatches(\n            img_orig_bgr, kp_orig,\n            img_var_bgr, [kp_dark, kp_bright, kp_low][i],\n            matches[:20], None,\n            matchColor=(0, 255, 0),\n            flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n        )\n        \n        axes[1, i+1].imshow(cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB))\n        axes[1, i+1].set_title(f'{cond}: {len(matches)} matches\\n'\n                              f'({len(matches)/len(kp_orig)*100:.0f}% preserved)',\n                              fontsize=10, fontweight='bold')\n        axes[1, i+1].axis('off')\n    \n    # Summary chart\n    axes[1, 0].bar(conditions, [len(m) for m in matches_list], \n                   color=['#8B4513', '#FFD700', '#A9A9A9'], alpha=0.7, edgecolor='black')\n    axes[1, 0].set_ylabel('Matches', fontweight='bold')\n    axes[1, 0].set_title('Illumination Robustness', fontsize=11, fontweight='bold')\n    axes[1, 0].grid(axis='y', alpha=0.3)\n    axes[1, 0].tick_params(axis='x', rotation=45)\n    \n    plt.suptitle('SIFT Illumination Invariance: Performance Under Lighting Changes',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('illumination_invariance.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: illumination_invariance.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 19: Corner Detection Comparison\n# ============================================================================\n\ndef task19_corner_detection():\n    \"\"\"\n    Compare different corner detection methods\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 19: CORNER DETECTION METHODS COMPARISON\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['watch', 'electric_guitar']:\n        images = find_images(folder)\n        if images:\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = io.imread(img_path)\n    if len(img.shape) == 3:\n        gray = color.rgb2gray(img)\n    else:\n        gray = img\n        img = color.gray2rgb(gray)\n    \n    # Method 1: Harris corners\n    harris_response = corner_harris(gray, sigma=2)\n    harris_coords = corner_peaks(harris_response, min_distance=10, threshold_rel=0.02)\n    \n    # Method 2: Shi-Tomasi (Good Features to Track)\n    gray_cv = (gray * 255).astype(np.uint8)\n    shi_tomasi_corners = cv2.goodFeaturesToTrack(gray_cv, maxCorners=100, \n                                                  qualityLevel=0.01, minDistance=10)\n    \n    # Method 3: FAST\n    fast = cv2.FastFeatureDetector_create(threshold=50)\n    fast_kp = fast.detect(gray_cv, None)\n    fast_coords = np.array([kp.pt for kp in fast_kp])\n    \n    print(f\"\\nCorner Detection Results:\")\n    print(f\"  Harris: {len(harris_coords)} corners\")\n    print(f\"  Shi-Tomasi: {len(shi_tomasi_corners) if shi_tomasi_corners is not None else 0} corners\")\n    print(f\"  FAST: {len(fast_coords)} corners\")\n    \n    # Visualization\n    fig, axes = plt.subplots(2, 2, figsize=(14, 12))\n    \n    # Original\n    axes[0, 0].imshow(img)\n    axes[0, 0].set_title('Original Image', fontsize=12, fontweight='bold')\n    axes[0, 0].axis('off')\n    \n    # Harris\n    axes[0, 1].imshow(img)\n    axes[0, 1].plot(harris_coords[:, 1], harris_coords[:, 0], 'r+', markersize=10, mew=2)\n    axes[0, 1].set_title(f'Harris Corner Detector\\n{len(harris_coords)} corners', \n                        fontsize=12, fontweight='bold')\n    axes[0, 1].axis('off')\n    \n    # Shi-Tomasi\n    axes[1, 0].imshow(img)\n    if shi_tomasi_corners is not None:\n        for corner in shi_tomasi_corners:\n            x, y = corner.ravel()\n            axes[1, 0].plot(x, y, 'go', markersize=8, mew=2, fillstyle='none')\n    axes[1, 0].set_title(f'Shi-Tomasi (Good Features)\\n'\n                        f'{len(shi_tomasi_corners) if shi_tomasi_corners is not None else 0} corners',\n                        fontsize=12, fontweight='bold')\n    axes[1, 0].axis('off')\n    \n    # FAST\n    axes[1, 1].imshow(img)\n    if len(fast_coords) > 0:\n        axes[1, 1].plot(fast_coords[:, 0], fast_coords[:, 1], 'b^', markersize=8, mew=2, fillstyle='none')\n    axes[1, 1].set_title(f'FAST Corner Detector\\n{len(fast_coords)} corners',\n                        fontsize=12, fontweight='bold')\n    axes[1, 1].axis('off')\n    \n    plt.suptitle('Corner Detection Methods Comparison',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('corner_detection.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: corner_detection.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 20: Local Binary Patterns (LBP)\n# ============================================================================\n\ndef task20_local_binary_patterns():\n    \"\"\"\n    Demonstrate LBP for texture classification\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 20: LOCAL BINARY PATTERNS (TEXTURE ANALYSIS)\")\n    print(\"=\" * 70)\n    \n    # Find images from different texture categories\n    folders = ['butterfly', 'dalmatian', 'sunflower']\n    images_list = []\n    labels = []\n    \n    for folder in folders:\n        imgs = find_images(folder)\n        if imgs:\n            images_list.append(imgs[0])\n            labels.append(folder)\n    \n    if len(images_list) < 3:\n        print(\"ERROR: Need images from 3 different categories\")\n        return\n    \n    print(f\"Analyzing textures from: {', '.join(labels)}\")\n    \n    # LBP parameters\n    radius = 3\n    n_points = 8 * radius\n    \n    # Process each image\n    results = []\n    for img_path, label in zip(images_list, labels):\n        img = io.imread(img_path)\n        if len(img.shape) == 3:\n            gray = color.rgb2gray(img)\n        else:\n            gray = img\n        \n        # Compute LBP\n        lbp = local_binary_pattern(gray, n_points, radius, method='uniform')\n        \n        # Compute histogram\n        n_bins = int(lbp.max() + 1)\n        hist, _ = np.histogram(lbp.ravel(), bins=n_bins, range=(0, n_bins))\n        hist = hist.astype(float)\n        hist /= hist.sum()  # Normalize\n        \n        results.append({\n            'label': label,\n            'image': img,\n            'lbp': lbp,\n            'histogram': hist\n        })\n        \n        print(f\"  {label}: {n_bins} unique patterns\")\n    \n    # Visualization\n    fig = plt.figure(figsize=(16, 10))\n    \n    for i, result in enumerate(results):\n        # Original image\n        ax1 = plt.subplot(3, 3, i*3 + 1)\n        if len(result['image'].shape) == 2:\n            ax1.imshow(result['image'], cmap='gray')\n        else:\n            ax1.imshow(result['image'])\n        ax1.set_title(f\"{result['label'].title()}\\nOriginal\", fontsize=11, fontweight='bold')\n        ax1.axis('off')\n        \n        # LBP image\n        ax2 = plt.subplot(3, 3, i*3 + 2)\n        ax2.imshow(result['lbp'], cmap='gray')\n        ax2.set_title('LBP Texture Map', fontsize=11, fontweight='bold')\n        ax2.axis('off')\n        \n        # Histogram\n        ax3 = plt.subplot(3, 3, i*3 + 3)\n        ax3.bar(range(len(result['histogram'])), result['histogram'], \n               color='steelblue', alpha=0.7, edgecolor='black')\n        ax3.set_title('LBP Histogram', fontsize=11, fontweight='bold')\n        ax3.set_xlabel('Pattern ID')\n        ax3.set_ylabel('Frequency')\n        ax3.grid(axis='y', alpha=0.3)\n    \n    plt.suptitle(f'Local Binary Patterns: Texture Signature Extraction\\n'\n                 f'Radius={radius}, Points={n_points}',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('local_binary_patterns.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: local_binary_patterns.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 21: Keypoint Density Heatmap\n# ============================================================================\n\ndef task21_keypoint_heatmap():\n    \"\"\"\n    Create spatial heatmap of keypoint distribution\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 21: KEYPOINT DENSITY HEATMAP\")\n    print(\"=\" * 70)\n    \n    # Find image\n    for folder in ['Faces', 'faces', 'Motorbikes', 'motorbikes']:\n        images = find_images(folder)\n        if images:\n            break\n    \n    if not images:\n        print(\"ERROR: No images found\")\n        return\n    \n    img_path = images[0]\n    print(f\"Processing: {os.path.basename(img_path)}\")\n    \n    # Load image\n    img = cv2.imread(img_path)\n    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    # Detect keypoints with SIFT\n    sift = cv2.SIFT_create()\n    kp, des = sift.detectAndCompute(gray, None)\n    \n    print(f\"Detected {len(kp)} keypoints\")\n    \n    # Create heatmap\n    h, w = gray.shape\n    heatmap = np.zeros((h, w), dtype=np.float32)\n    \n    # Add Gaussian blobs at each keypoint\n    for k in kp:\n        x, y = int(k.pt[0]), int(k.pt[1])\n        sigma = k.size / 2\n        \n        # Create small Gaussian kernel\n        kernel_size = int(sigma * 6)\n        if kernel_size % 2 == 0:\n            kernel_size += 1\n        kernel_size = max(kernel_size, 5)\n        \n        y1 = max(0, y - kernel_size//2)\n        y2 = min(h, y + kernel_size//2 + 1)\n        x1 = max(0, x - kernel_size//2)\n        x2 = min(w, x + kernel_size//2 + 1)\n        \n        if y2 > y1 and x2 > x1:\n            small_h = y2 - y1\n            small_w = x2 - x1\n            yy, xx = np.ogrid[-kernel_size//2:kernel_size//2+1, -kernel_size//2:kernel_size//2+1]\n            yy = yy[:small_h, :]\n            xx = xx[:, :small_w]\n            gaussian = np.exp(-(xx**2 + yy**2) / (2 * sigma**2))\n            heatmap[y1:y2, x1:x2] += gaussian[:small_h, :small_w]\n    \n    # Normalize heatmap\n    if heatmap.max() > 0:\n        heatmap = heatmap / heatmap.max()\n    \n    # Visualization\n    fig, axes = plt.subplots(2, 2, figsize=(14, 12))\n    \n    # Original image\n    axes[0, 0].imshow(img_rgb)\n    axes[0, 0].set_title('Original Image', fontsize=12, fontweight='bold')\n    axes[0, 0].axis('off')\n    \n    # Keypoints\n    img_kp = cv2.drawKeypoints(img, kp, None, \n                               flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)\n    axes[0, 1].imshow(cv2.cvtColor(img_kp, cv2.COLOR_BGR2RGB))\n    axes[0, 1].set_title(f'Detected Keypoints (n={len(kp)})', fontsize=12, fontweight='bold')\n    axes[0, 1].axis('off')\n    \n    # Heatmap\n    im = axes[1, 0].imshow(heatmap, cmap='hot', interpolation='bilinear')\n    axes[1, 0].set_title('Keypoint Density Heatmap', fontsize=12, fontweight='bold')\n    axes[1, 0].axis('off')\n    plt.colorbar(im, ax=axes[1, 0], fraction=0.046, pad=0.04)\n    \n    # Overlay\n    axes[1, 1].imshow(img_rgb)\n    axes[1, 1].imshow(heatmap, cmap='hot', alpha=0.5, interpolation='bilinear')\n    axes[1, 1].set_title('Heatmap Overlay', fontsize=12, fontweight='bold')\n    axes[1, 1].axis('off')\n    \n    plt.suptitle('Keypoint Spatial Distribution Analysis',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('keypoint_heatmap.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: keypoint_heatmap.png\")\n    plt.show()\n\n# ============================================================================\n# TASK 22: Cross-Category Feature Transferability\n# ============================================================================\n\ndef task22_cross_category_transfer():\n    \"\"\"\n    Test if features learned from one category can match another\n    \"\"\"\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TASK 22: CROSS-CATEGORY FEATURE TRANSFERABILITY\")\n    print(\"=\" * 70)\n    \n    # Select diverse categories\n    categories = [\n        ('airplanes', 'airplanes'),\n        ('car_side', 'car_side'),\n        ('Faces', 'faces'),\n        ('butterfly', 'butterfly')\n    ]\n    \n    images_by_cat = {}\n    for cat_options in categories:\n        for cat in cat_options:\n            imgs = find_images(cat)\n            if imgs:\n                images_by_cat[cat_options[0]] = imgs[0]\n                break\n    \n    if len(images_by_cat) < 4:\n        print(\"ERROR: Need 4 different categories\")\n        return\n    \n    cat_names = list(images_by_cat.keys())\n    print(f\"Categories: {', '.join(cat_names)}\")\n    \n    # Extract SIFT features from all\n    sift = cv2.SIFT_create(nfeatures=100)\n    features = {}\n    \n    for cat, img_path in images_by_cat.items():\n        img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n        kp, des = sift.detectAndCompute(img, None)\n        features[cat] = {'image': img, 'kp': kp, 'des': des}\n        print(f\"  {cat}: {len(kp)} keypoints\")\n    \n    # Create matching matrix\n    match_matrix = np.zeros((4, 4), dtype=int)\n    bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)\n    \n    for i, cat1 in enumerate(cat_names):\n        for j, cat2 in enumerate(cat_names):\n            if features[cat1]['des'] is not None and features[cat2]['des'] is not None:\n                matches = bf.match(features[cat1]['des'], features[cat2]['des'])\n                match_matrix[i, j] = len(matches)\n    \n    print(\"\\nCross-category matching matrix:\")\n    print(match_matrix)\n    \n    # Visualization\n    fig = plt.figure(figsize=(16, 12))\n    \n    # Display images\n    for i, cat in enumerate(cat_names):\n        ax = plt.subplot(3, 4, i+1)\n        ax.imshow(features[cat]['image'], cmap='gray')\n        ax.set_title(f\"{cat.replace('_', ' ').title()}\\n{len(features[cat]['kp'])} features\",\n                    fontsize=11, fontweight='bold')\n        ax.axis('off')\n    \n    # Matching matrix heatmap\n    ax = plt.subplot(3, 2, 3)\n    im = ax.imshow(match_matrix, cmap='YlOrRd', aspect='auto')\n    ax.set_xticks(range(4))\n    ax.set_yticks(range(4))\n    ax.set_xticklabels([c.replace('_', ' ') for c in cat_names], rotation=45, ha='right')\n    ax.set_yticklabels([c.replace('_', ' ') for c in cat_names])\n    ax.set_title('Cross-Category Match Matrix', fontsize=12, fontweight='bold')\n    \n    # Add text annotations\n    for i in range(4):\n        for j in range(4):\n            text_color = 'white' if match_matrix[i, j] > match_matrix.max()/2 else 'black'\n            ax.text(j, i, str(match_matrix[i, j]), ha='center', va='center',\n                   color=text_color, fontweight='bold')\n    \n    plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)\n    \n    # Analysis\n    ax = plt.subplot(3, 2, 4)\n    \n    # Within-category vs cross-category\n    within_cat = np.diagonal(match_matrix).mean()\n    cross_cat = (match_matrix.sum() - np.diagonal(match_matrix).sum()) / (16 - 4)\n    \n    ax.bar(['Within\\nCategory', 'Cross\\nCategory'], [within_cat, cross_cat],\n           color=['green', 'orange'], alpha=0.7, edgecolor='black', linewidth=2)\n    ax.set_ylabel('Average Matches', fontweight='bold')\n    ax.set_title('Transferability Analysis', fontsize=12, fontweight='bold')\n    ax.grid(axis='y', alpha=0.3)\n    \n    for i, (label, val) in enumerate(zip(['Within', 'Cross'], [within_cat, cross_cat])):\n        ax.text(i, val, f'{val:.1f}', ha='center', va='bottom', fontweight='bold')\n    \n    # Example: Best cross-category match\n    np.fill_diagonal(match_matrix, 0)  # Exclude self-matches\n    best_i, best_j = np.unravel_index(match_matrix.argmax(), match_matrix.shape)\n    \n    if match_matrix[best_i, best_j] > 0:\n        cat1, cat2 = cat_names[best_i], cat_names[best_j]\n        matches = bf.match(features[cat1]['des'], features[cat2]['des'])\n        matches = sorted(matches, key=lambda x: x.distance)[:15]\n        \n        img1_bgr = cv2.cvtColor(features[cat1]['image'], cv2.COLOR_GRAY2BGR)\n        img2_bgr = cv2.cvtColor(features[cat2]['image'], cv2.COLOR_GRAY2BGR)\n        \n        img_matches = cv2.drawMatches(\n            img1_bgr, features[cat1]['kp'],\n            img2_bgr, features[cat2]['kp'],\n            matches, None,\n            flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS\n        )\n        \n        ax = plt.subplot(3, 1, 3)\n        ax.imshow(cv2.cvtColor(img_matches, cv2.COLOR_BGR2RGB))\n        ax.set_title(f'Best Cross-Category Match: {cat1} ↔ {cat2} '\n                    f'({len(matches)} matches shown)',\n                    fontsize=12, fontweight='bold',\n                    bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.7))\n        ax.axis('off')\n    \n    plt.suptitle('Feature Transferability Across Object Categories',\n                 fontsize=15, fontweight='bold')\n    plt.tight_layout()\n    plt.savefig('cross_category_transfer.png', dpi=300, bbox_inches='tight')\n    print(\"\\n✓ Saved: cross_category_transfer.png\")\n    print(f\"\\nInsight: Within-category: {within_cat:.1f}, Cross-category: {cross_cat:.1f}\")\n    plt.show()\n\n# ============================================================================\n# MAIN EXECUTION\n# ============================================================================\n\nif __name__ == \"__main__\":\n    print(\"=\" * 70)\n    print(\"EXTENDED ADVANCED COMPUTER VISION TASKS (13-22)\")\n    print(\"=\" * 70)\n    \n    # Run all extended tasks\n    task13_descriptor_dimensionality()\n    task14_multiscale_detection()\n    task15_ransac_matching()\n    task16_histogram_comparison()\n    task17_visual_vocabulary()\n    task18_illumination_invariance()\n    task19_corner_detection()\n    task20_local_binary_patterns()\n    task21_keypoint_heatmap()\n    task22_cross_category_transfer()\n    \n    print(\"\\n\" + \"=\" * 70)\n    print(\"🎉 ALL EXTENDED TASKS COMPLETED! 🎉\")\n    print(\"=\" * 70)\n    print(\"\\nGenerated files:\")\n    print(\"  • descriptor_analysis.png\")\n    print(\"  • multiscale_detection.png\")\n    print(\"  • ransac_matching.png\")\n    print(\"  • histogram_comparison.png\")\n    print(\"  • visual_vocabulary.png\")\n    print(\"  • illumination_invariance.png\")\n    print(\"  • corner_detection.png\")\n    print(\"  • local_binary_patterns.png\")\n    print(\"  • keypoint_heatmap.png\")\n    print(\"  • cross_category_transfer.png\")\n    print(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T23:07:26.050369Z","iopub.execute_input":"2026-01-13T23:07:26.050763Z","iopub.status.idle":"2026-01-13T23:07:53.741114Z","shell.execute_reply.started":"2026-01-13T23:07:26.050716Z","shell.execute_reply":"2026-01-13T23:07:53.740116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!rm -rf /kaggle/working/*\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-13T23:07:17.519244Z","iopub.execute_input":"2026-01-13T23:07:17.519747Z","iopub.status.idle":"2026-01-13T23:07:17.654152Z","shell.execute_reply.started":"2026-01-13T23:07:17.519702Z","shell.execute_reply":"2026-01-13T23:07:17.65284Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}