Upload 5 files
Browse files- app.py +142 -0
- requirements.txt +2 -0
- sample/shape.png +0 -0
- sample/tetris_blocks.png +0 -0
- sample/tictactoe.png +0 -0
app.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
import gradio as gr
|
4 |
+
|
5 |
+
def image_inference(image_path, mode, epsilon_thresh):
|
6 |
+
# Read the image
|
7 |
+
img = cv2.cvtColor(image_path, cv2.COLOR_RGB2BGR)
|
8 |
+
if img is None:
|
9 |
+
raise FileNotFoundError(f"Image at path '{image_path}' not found.")
|
10 |
+
|
11 |
+
# Create a copy of the original image
|
12 |
+
img_copy = img.copy()
|
13 |
+
|
14 |
+
# Convert the image to grayscale
|
15 |
+
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
16 |
+
|
17 |
+
# Apply thresholding
|
18 |
+
_, img_thresh = cv2.threshold(img_gray, 200, 255, cv2.THRESH_BINARY_INV)
|
19 |
+
|
20 |
+
# Find the contours
|
21 |
+
contours, _ = cv2.findContours(img_thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
22 |
+
contour_info = f"Number of contours found: {len(contours)}" # Text output for Gradio
|
23 |
+
# print(f"Number of contours found = {len(contours)}")
|
24 |
+
|
25 |
+
def convert_color(hsv):
|
26 |
+
# Utility to convert a single HSV color tuple into BGR
|
27 |
+
pixel_img = np.uint8([[hsv]])
|
28 |
+
return tuple(int(i) for i in cv2.cvtColor(pixel_img, cv2.COLOR_HSV2BGR).flatten())
|
29 |
+
|
30 |
+
if mode == "contour":
|
31 |
+
if len(contours) > 0:
|
32 |
+
for i, single_contour in enumerate(contours):
|
33 |
+
hsv = (int(i/len(contours) * 180), 255, 255)
|
34 |
+
color = convert_color(hsv)
|
35 |
+
# Draw the contour
|
36 |
+
img_final = cv2.drawContours(img_copy, contours, i, color, 4)
|
37 |
+
# Calculate and display the area
|
38 |
+
# area = cv2.contourArea(single_contour)
|
39 |
+
# x, y, w, h = cv2.boundingRect(single_contour)
|
40 |
+
# img_final = cv2.putText(img_final, f"Area: {area}", (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
|
41 |
+
else:
|
42 |
+
img_final = img_copy # No contours found, return the original image
|
43 |
+
elif mode == "rotated rectangle":
|
44 |
+
for cnt in contours:
|
45 |
+
# Rotated bounding box
|
46 |
+
box = cv2.minAreaRect(cnt)
|
47 |
+
box_pts = np.intp(cv2.boxPoints(box))
|
48 |
+
img_final = cv2.drawContours(img_copy, [box_pts], -1, (0, 255, 0), 4)
|
49 |
+
|
50 |
+
# Calculate and display the area
|
51 |
+
area = cv2.contourArea(cnt)
|
52 |
+
x, y, w, h = cv2.boundingRect(cnt)
|
53 |
+
img_final = cv2.putText(img_final, f"Area: {area}", (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
|
54 |
+
|
55 |
+
elif mode == "rectangle":
|
56 |
+
for cnt in contours:
|
57 |
+
# Bounding rectangle
|
58 |
+
x, y, w, h = cv2.boundingRect(cnt)
|
59 |
+
img_final = cv2.rectangle(img_copy, (x, y), (x + w, y + h), (0, 255, 0), 4)
|
60 |
+
|
61 |
+
# Calculate and display the area
|
62 |
+
area = cv2.contourArea(cnt)
|
63 |
+
img_final = cv2.putText(img_final, f"Area: {area}", (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
|
64 |
+
|
65 |
+
elif mode == "circle":
|
66 |
+
for cnt in contours:
|
67 |
+
# Enclosing circle
|
68 |
+
((x, y), radius) = cv2.minEnclosingCircle(cnt)
|
69 |
+
img_final = cv2.circle(img_copy, (int(x), int(y)), int(round(radius)), (0, 255, 0), 4)
|
70 |
+
|
71 |
+
# Calculate and display the perimeter
|
72 |
+
perimeter = cv2.arcLength(cnt, True)
|
73 |
+
x, y, w, h = cv2.boundingRect(cnt)
|
74 |
+
img_final = cv2.putText(img_final, f"Perimeter: {perimeter:.2f}", (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
|
75 |
+
|
76 |
+
elif mode == "ellipse":
|
77 |
+
for cnt in contours:
|
78 |
+
if len(cnt) >= 5: # fitEllipse requires at least 5 points
|
79 |
+
ellipse = cv2.fitEllipse(cnt)
|
80 |
+
img_final = cv2.ellipse(img_copy, ellipse, (0, 255, 0), 4)
|
81 |
+
|
82 |
+
# Calculate and display the perimeter
|
83 |
+
perimeter = cv2.arcLength(cnt, True)
|
84 |
+
x, y, w, h = cv2.boundingRect(cnt)
|
85 |
+
img_final = cv2.putText(img_final, f"Perimeter: {perimeter:.2f}", (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1)
|
86 |
+
else:
|
87 |
+
img_final = img_copy
|
88 |
+
elif mode == "centroid":
|
89 |
+
for cnt in contours:
|
90 |
+
M = cv2.moments(cnt)
|
91 |
+
if M["m00"] != 0:
|
92 |
+
x = int(round(M["m10"] / M["m00"]))
|
93 |
+
y = int(round(M["m01"] / M["m00"]))
|
94 |
+
img_final = cv2.circle(img_copy, (x, y), 10, (255, 0, 0), -1)
|
95 |
+
else:
|
96 |
+
img_final = img_copy
|
97 |
+
elif mode == "contour approx":
|
98 |
+
if len(contours) > 0:
|
99 |
+
c = max(contours, key=cv2.contourArea)
|
100 |
+
peri = cv2.arcLength(c, True)
|
101 |
+
approx = cv2.approxPolyDP(c, epsilon_thresh * peri, True)
|
102 |
+
img_final = cv2.drawContours(img_copy, [approx], -1, (0, 255, 0), 3)
|
103 |
+
x, y, w, h = cv2.boundingRect(c)
|
104 |
+
text = f"eps={epsilon_thresh:.4f}, num_pts={len(approx)}"
|
105 |
+
img_final = cv2.putText(img_copy, text, (x, y - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
|
106 |
+
else:
|
107 |
+
img_final = img_copy
|
108 |
+
else:
|
109 |
+
img_final = img_copy
|
110 |
+
print(f"Mode '{mode}' not recognized. Please choose a valid mode.")
|
111 |
+
|
112 |
+
return contour_info, cv2.cvtColor(img_final, cv2.COLOR_BGR2RGB)
|
113 |
+
|
114 |
+
# Gradio interface
|
115 |
+
input_image = gr.Image(type="numpy", label="Input Image")
|
116 |
+
epsilon_thresh = gr.Slider(
|
117 |
+
0.01,
|
118 |
+
0.1,
|
119 |
+
step=0.01,
|
120 |
+
value=0.05,
|
121 |
+
label="Epsilon Threshold",
|
122 |
+
info="Adjust the Contour Threshold according to the object size that you want to detect. Only Applicable for Contour Approx",
|
123 |
+
)
|
124 |
+
mode = gr.Radio(
|
125 |
+
["contour", "rectangle", "rotated rectangle", "circle", "ellipse", "centroid", "contour approx"],
|
126 |
+
label="Contour Type",
|
127 |
+
info="Choose the MODE",
|
128 |
+
)
|
129 |
+
output_text = gr.Textbox(label="Contour Information")
|
130 |
+
output_image = gr.Image(label="Output Image")
|
131 |
+
|
132 |
+
app = gr.Interface(
|
133 |
+
fn=image_inference,
|
134 |
+
inputs=[input_image, mode, epsilon_thresh],
|
135 |
+
outputs=[output_text, output_image],
|
136 |
+
title="Contour Detection using OpenCV",
|
137 |
+
description="A Gradio app for dynamic image analysis using Contour detection techniques.",
|
138 |
+
allow_flagging="never",
|
139 |
+
examples=[["./sample/tictactoe.png", "contour", float(0.01)], ["./sample/tetris_blocks.png", "rectangle", float(0.00)], ["./sample/shape.png", "contour approx", float(0.05)]],
|
140 |
+
cache_examples=False,
|
141 |
+
)
|
142 |
+
app.queue().launch()
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
gradio==4.42.0
|
2 |
+
opencv-python==4.10.0.84
|
sample/shape.png
ADDED
sample/tetris_blocks.png
ADDED
sample/tictactoe.png
ADDED