Upload 5 files
Browse files- app.py +143 -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,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
theme="gstaff/xkcd",
|
| 135 |
+
inputs=[input_image, mode, epsilon_thresh],
|
| 136 |
+
outputs=[output_text, output_image],
|
| 137 |
+
title="Contour Detection using OpenCV",
|
| 138 |
+
description="A Gradio app for dynamic image analysis using Contour detection techniques.",
|
| 139 |
+
flagging_mode="never",
|
| 140 |
+
examples=[["./sample/tictactoe.png", "contour", float(0.01)], ["./sample/tetris_blocks.png", "rectangle", float(0.00)], ["./sample/shape.png", "contour approx", float(0.05)]],
|
| 141 |
+
cache_examples=False,
|
| 142 |
+
)
|
| 143 |
+
app.queue().launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==5.24.0
|
| 2 |
+
opencv-python==4.10.0.84
|
sample/shape.png
ADDED
|
sample/tetris_blocks.png
ADDED
|
sample/tictactoe.png
ADDED
|