ANON-STUDIOS-254 commited on
Commit
0748867
·
verified ·
1 Parent(s): 51f3de2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -0
app.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Step 2: Import all necessary modules
3
+ import gradio as gr
4
+ import cv2
5
+ import numpy as np
6
+
7
+ # Step 3: Define the core backend function for analysis
8
+ def run_visual_analysis(img1_rgb, img2_rgb, ratio_threshold, method_choice):
9
+ """
10
+ A single, robust function to handle feature matching. It selects the matcher
11
+ based on the user's choice from the UI.
12
+ """
13
+ # --- Input Validation ---
14
+ if img1_rgb is None or img2_rgb is None:
15
+ raise gr.Error("Please upload two images to compare.")
16
+
17
+ matcher_type = "FLANN" if "FLANN" in method_choice else "Brute-Force"
18
+
19
+ # --- Feature Detection ---
20
+ img1_gray = cv2.cvtColor(img1_rgb, cv2.COLOR_RGB2GRAY)
21
+ img2_gray = cv2.cvtColor(img2_rgb, cv2.COLOR_RGB2GRAY)
22
+ sift = cv2.SIFT_create()
23
+ kp1, des1 = sift.detectAndCompute(img1_gray, None)
24
+ kp2, des2 = sift.detectAndCompute(img2_gray, None)
25
+
26
+ if des1 is None or des2 is None or len(kp1) < 2 or len(kp2) < 2:
27
+ return None, "Analysis Failed: Could not find enough features in one or both images. They may be too blurry, too small, or lack distinct details."
28
+
29
+ # --- Feature Matching ---
30
+ if matcher_type == 'FLANN':
31
+ index_params = dict(algorithm=1, trees=5)
32
+ search_params = dict(checks=50)
33
+ matcher = cv2.FlannBasedMatcher(index_params, search_params)
34
+ else: # Brute-Force
35
+ matcher = cv2.BFMatcher()
36
+
37
+ matches = matcher.knnMatch(np.float32(des1), np.float32(des2), k=2)
38
+
39
+ # --- Filter Matches using Lowe's Ratio Test ---
40
+ good_matches = []
41
+ # Ensure matches were found and have the correct format (k=2)
42
+ if matches and len(matches[0]) == 2:
43
+ for m, n in matches:
44
+ if m.distance < ratio_threshold * n.distance:
45
+ good_matches.append(m)
46
+
47
+ if len(good_matches) < 4:
48
+ return None, f"Found only {len(good_matches)} high-quality matches. This is too few for a reliable geometric analysis. Try increasing the 'Match Sensitivity' slider."
49
+
50
+ # --- Geometric Verification using Homography ---
51
+ src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
52
+ dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
53
+
54
+ M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
55
+
56
+ if M is None or mask is None:
57
+ return None, f"Found {len(good_matches)} initial matches, but could not establish a consistent geometric relationship. The scenes may be too different."
58
+
59
+ inlier_mask = mask.ravel().tolist()
60
+ inlier_count = sum(inlier_mask)
61
+
62
+ if inlier_count < 4:
63
+ return None, f"Found {len(good_matches)} initial matches, but only {inlier_count} were geometrically consistent. The scenes might be too different or the sensitivity setting too low."
64
+
65
+ # --- Visualization ---
66
+ h1, w1 = img1_rgb.shape[:2]
67
+ h2, w2 = img2_rgb.shape[:2]
68
+ combined_img = np.zeros((max(h1, h2), w1 + w2, 3), dtype=np.uint8)
69
+ combined_img[:h1, :w1, :] = img1_rgb
70
+ combined_img[:h2, w1:w1 + w2, :] = img2_rgb
71
+
72
+ # Draw only the inlier matches (geometrically verified)
73
+ for i, match in enumerate(good_matches):
74
+ if inlier_mask[i]:
75
+ pt1 = tuple(map(int, kp1[match.queryIdx].pt))
76
+ pt2_shifted = (int(kp2[match.trainIdx].pt[0] + w1), int(kp2[match.trainIdx].pt[1]))
77
+ # Draw a green line for the match
78
+ cv2.line(combined_img, pt1, pt2_shifted, (34, 139, 34), 1, cv2.LINE_AA)
79
+ # Draw cyan circles on the keypoints
80
+ cv2.circle(combined_img, pt1, 4, (0, 255, 255), -1)
81
+ cv2.circle(combined_img, pt2_shifted, 4, (0, 255, 255), -1)
82
+
83
+ # --- Generate Summary Report ---
84
+ summary = (
85
+ f"**✅ {matcher_type} Analysis Complete**\n\n"
86
+ f"- **Total Features Found:** Image 1: `{len(kp1)}`, Image 2: `{len(kp2)}`\n"
87
+ f"- **High-Quality Matches (Ratio Test):** `{len(good_matches)}`\n"
88
+ f"- **Geometrically Consistent Matches (Inliers):** `{inlier_count}`\n\n"
89
+ f"The visualization highlights **{inlier_count}** structural elements robustly identified in both images."
90
+ )
91
+ return combined_img, summary
92
+
93
+ # Step 4: Build the Gradio Interface
94
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="slate", secondary_hue="orange")) as demo:
95
+ gr.Markdown(
96
+ "# 🖼️ Visual Feature Comparator\n"
97
+ "Upload two images to find and visualize matching features. This tool uses SIFT and geometric verification to robustly compare scenes."
98
+ )
99
+
100
+ with gr.Row(variant="panel"):
101
+ # --- Column 1: Controls ---
102
+ with gr.Column(scale=1, min_width=350):
103
+ gr.Markdown("### ⚙️ Controls")
104
+
105
+ # Set value=None to start with an empty upload box
106
+ img1_input = gr.Image(type="numpy", label="Upload Image 1")
107
+ img2_input = gr.Image(type="numpy", label="Upload Image 2")
108
+
109
+ with gr.Accordion("Analysis Settings", open=True):
110
+ method_selector = gr.Radio(
111
+ choices=['SIFT + FLANN (Fast, Recommended)', 'SIFT + Brute-Force (Classic, Slower)'],
112
+ value='SIFT + FLANN (Fast, Recommended)',
113
+ label="Analysis Method"
114
+ )
115
+ ratio_slider = gr.Slider(
116
+ minimum=0.4, maximum=0.95, value=0.75, step=0.01,
117
+ label="Match Sensitivity",
118
+ info="Lower = fewer, higher-quality matches. Higher = more, potentially incorrect matches."
119
+ )
120
+
121
+ run_button = gr.Button("🚀 Run Visual Analysis", variant="primary")
122
+
123
+ # --- Column 2: Results ---
124
+ with gr.Column(scale=3):
125
+ gr.Markdown("### 📊 Results")
126
+ result_summary = gr.Markdown("Analysis results will be displayed here.", elem_id="summary_text")
127
+ result_image = gr.Image(label="Visual Comparison", interactive=False, show_download_button=True)
128
+
129
+ # Step 5: Define the main action for the run button
130
+ run_button.click(
131
+ fn=run_visual_analysis,
132
+ inputs=[img1_input, img2_input, ratio_slider, method_selector],
133
+ outputs=[result_image, result_summary],
134
+ api_name="visual_analysis",
135
+ show_progress="full"
136
+ )
137
+
138
+ # Step 6: Launch the App
139
+ print("🚀 Launching the Visual Feature Comparator...")
140
+ demo.launch(debug=True, share=True)