Spaces:
Sleeping
Sleeping
File size: 8,794 Bytes
356099f 78ff8a8 356099f eb5438b 7656c57 356099f ca23df0 78ff8a8 ca23df0 78ff8a8 7656c57 78ff8a8 7656c57 78ff8a8 7656c57 356099f 7656c57 78ff8a8 7656c57 78ff8a8 7656c57 78ff8a8 7656c57 78ff8a8 7656c57 78ff8a8 7656c57 78ff8a8 7656c57 78ff8a8 356099f 7656c57 eb5438b 356099f eb5438b 7656c57 eb5438b 78ff8a8 eb5438b 78ff8a8 eb5438b 78ff8a8 7656c57 78ff8a8 356099f eb5438b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
import gradio as gr
from ultralytics import YOLO
from PIL import Image, ImageDraw, ImageFont
import numpy as np
model = YOLO("model.pt")
from PIL import Image, ImageDraw, ImageFont
import numpy as np
def predict(img, target_class, dot_color, text_color_choice, dot_size, font_size):
path = img.split("\\")[-1].split(".")[0]
print("path", path)
results = model.predict(source=img, save=False, show_labels=False, show_conf=False)
if not isinstance(results, (list, tuple)):
results = [results]
# 顏色設定
if dot_color == "紅色":
dot_rgb = (255, 0, 0)
else:
dot_rgb = (57, 255, 20) # 螢光綠
if text_color_choice == "黑色":
text_rgb = (0, 0, 0)
else:
text_rgb = (255, 255, 255)
# 大小設定
try:
r = int(dot_size)
except:
r = 10
if r < 2: r = 2
try:
fsize = int(font_size)
except:
fsize = 36
if fsize < 8: fsize = 8
count = 0
annot_img = None
annot_img_numbered = None
# 轉換成 YOLO 類別索引
if target_class == "膠囊":
class_idx = 0
else:
class_idx = 1
for i in results:
# 過濾出該類別的框
selected_boxes = []
if hasattr(i.boxes, "cls"):
for b, c in zip(i.boxes.xyxy, i.boxes.cls):
if int(c) == class_idx:
selected_boxes.append(b)
count = len(selected_boxes)
img_pil = Image.fromarray(i.orig_img[..., ::-1])
img_pil_numbered = img_pil.copy()
draw = ImageDraw.Draw(img_pil)
draw_num = ImageDraw.Draw(img_pil_numbered)
try:
font = ImageFont.truetype("DejaVuSans.ttf", fsize)
except:
try:
font = ImageFont.truetype("arial.ttf", fsize)
except:
font = ImageFont.load_default()
for idx, box in enumerate(selected_boxes, start=1):
x1, y1, x2, y2 = box[:4].tolist()
cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
# 第一張:只有圓點
draw.ellipse((cx - r, cy - r, cx + r, cy + r), fill=dot_rgb, outline=None)
# 第二張:圓點 + 編號
draw_num.ellipse((cx - r, cy - r, cx + r, cy + r), fill=dot_rgb, outline=None)
# 最後一顆改紅字
num_color = (255, 0, 0) if idx == count else text_rgb
draw_num.text((cx + r + 4, cy - r), str(idx), fill=num_color, font=font)
annot_img = np.array(img_pil)
annot_img_numbered = np.array(img_pil_numbered)
return str(count), annot_img, annot_img_numbered
# 自定義 CSS 樣式(背景改為白色、簡潔風格)
custom_css = """
.gradio-container {
background: #ffffff;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* 主標題 */
.main-header {
text-align: center;
color: #333333;
font-size: 2.2em;
font-weight: bold;
margin-bottom: 5px;
}
/* 副標題 */
.sub-header {
text-align: center;
color: #555555;
font-size: 1.1em;
margin-bottom: 20px;
font-weight: 300;
}
/* 卡片容器 */
.card-container {
background: #f9f9f9;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
margin: 15px;
}
/* 輸入區 */
.input-section {
background: #ffffff;
border-radius: 10px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid #e0e0e0;
}
/* 輸出區 */
.output-section {
background: #ffffff;
border-radius: 10px;
padding: 20px;
border: 1px solid #e0e0e0;
}
/* 按鈕 */
.predict-btn {
background: #4285f4 !important;
border: none !important;
border-radius: 20px !important;
padding: 12px 32px !important;
color: white !important;
font-size: 16px !important;
font-weight: 600 !important;
transition: background 0.2s ease !important;
cursor: pointer !important;
}
.predict-btn:hover {
background: #3367d6 !important;
}
/* 圖片上傳 */
.image-upload {
border: 2px dashed #cccccc !important;
border-radius: 10px !important;
background: #fcfcfc !important;
padding: 15px !important;
}
.image-upload:hover {
border-color: #bbbbbb !important;
background: #f5f5f5 !important;
}
/* 數量文字 */
.count-output {
font-size: 22px !important;
font-weight: 600 !important;
color: #222222 !important;
text-align: center !important;
background: #ffffff !important;
border: 1px solid #e0e0e0 !important;
border-radius: 8px !important;
padding: 12px !important;
}
/* 結果圖 */
.result-image {
border-radius: 8px !important;
box-shadow: 0 6px 18px rgba(0,0,0,0.04) !important;
border: 1px solid #e0e0e0 !important;
}
/* 響應式 */
@media (max-width: 768px) {
.main-header { font-size: 1.8em; }
.predict-btn { width: 100% !important; margin-top: 15px !important; }
}
"""
with gr.Blocks(
title="Pill Counter",
theme=gr.themes.Soft(
primary_hue="blue",
secondary_hue="green",
neutral_hue="gray",
font=gr.themes.GoogleFont("Noto Sans TC")
),
css=custom_css
) as demo:
gr.HTML("""
<div class="main-header">Pill Counter</div>
""")
with gr.Row():
with gr.Column(scale=1):
gr.HTML('<h3 style="color: #4285f4; text-align: center; margin-bottom: 15px;">📤 上傳藥物圖片</h3>')
img = gr.Image(
type="filepath",
format=["jpg", "png"],
height=450,
width=800,
label="請選擇或拖放圖片",
elem_classes=["image-upload"]
)
target_class = gr.Radio(
choices=["膠囊", "錠劑"],
value="膠囊",
label="要計算的類別",
info="選擇只計算錠劑或膠囊的總數"
)
gr.HTML('<div style="text-align: center; margin-top: 15px;">')
button = gr.Button(
"開始計算",
variant="primary",
elem_classes=["predict-btn"]
)
gr.HTML('</div><div style="text-align:center; margin-top:8px; color:#888;">支援 JPG/PNG,建議 640×640 以上</div>')
gr.HTML('</div>')
dot_color = gr.Radio(
choices=["紅色", "螢光綠"],
value="紅色",
label="圓點顏色",
info="選擇藥錠中心圓點顏色"
)
text_color_choice = gr.Radio(
choices=["黑色", "白色"],
value="黑色",
label="文字顏色",
info="選擇編號文字顏色"
)
dot_size = gr.Textbox(
label="圓點大小",
value="10",
info="設定圓點半徑大小(建議範圍 5~20)"
)
font_size = gr.Textbox(
label="字體大小",
value="36",
info="設定編號文字大小(建議範圍 20~60)"
)
with gr.Column(scale=1):
gr.HTML('<h3 style="color: #34a853; text-align: center; margin-bottom: 15px;">📊 檢測結果</h3>')
data_output = gr.Textbox(
interactive=False,
elem_classes=["count-output"]
)
gr.HTML('<div style="text-align:center; margin:15px 0;"><label style="font-size:14px;color:#555;">標註結果</label></div>')
img_output = gr.Image(
type="numpy",
elem_classes=["result-image"]
)
gr.HTML('<div style="text-align:center; margin:20px 0;"><label style="font-size:14px;color:#555;">標註 + 編號結果</label></div>')
img_output_numbered = gr.Image(
type="numpy",
elem_classes=["result-image"]
)
gr.HTML('</div>')
gr.HTML("""
<div style="background:#ffffff; border-left:4px solid #4285f4; padding:15px; margin:15px; color:#666;">
<strong>使用步驟:</strong>
<ol style="margin-top:8px;">
<li>上傳清晰的圖片,保持純色背景(白色或黑色),勿有反光</li>
<li>點擊「開始計算」按鈕</li>
<li>查看標註後圖片及藥錠總數</li>
</ol>
</div>
""")
button.click(
fn=predict,
inputs=[img, target_class, dot_color, text_color_choice, dot_size, font_size],
outputs=[data_output, img_output, img_output_numbered]
)
if __name__ == "__main__":
demo.launch()
|