oilbread commited on
Commit
987dfd6
ยท
1 Parent(s): 75d34a5
Files changed (1) hide show
  1. app.py +328 -264
app.py CHANGED
@@ -3,32 +3,36 @@ import cv2
3
  from ultralytics import YOLO
4
  import numpy as np
5
  import time
6
- import os
7
- from pathlib import Path
8
  import threading
9
- from queue import Queue
10
  import torch
 
 
 
11
 
12
  # GPU ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ
13
  device = 'cuda' if torch.cuda.is_available() else 'cpu'
14
  print(f"Using device: {device}")
15
 
16
- # ๊ธฐ๋ณธ YOLOv8 ๋ชจ๋ธ ๋กœ๋“œ (GPU ์‚ฌ์šฉ)
17
  default_model = YOLO("yolov8n.pt")
18
  if device == 'cuda':
19
  default_model.to(device)
20
 
21
- # ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ์ „์—ญ ๋ณ€์ˆ˜
22
- frame_skip = 2 # ํ”„๋ ˆ์ž„ ์Šคํ‚ต (2ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์ฒ˜๋ฆฌ)
23
- current_frame_count = 0
24
- last_result = None
25
- processing_lock = threading.Lock()
 
 
 
26
 
27
  # ๋ชจ๋ธ ์บ์‹œ
28
  model_cache = {}
29
 
30
- def load_model_optimized(model_source, uploaded_file=None):
31
- """์ตœ์ ํ™”๋œ ๋ชจ๋ธ ๋กœ๋“œ"""
32
  global model_cache
33
 
34
  try:
@@ -40,243 +44,205 @@ def load_model_optimized(model_source, uploaded_file=None):
40
  cache_key = model_source
41
 
42
  if cache_key not in model_cache:
43
- print(f"Loading model: {model_path}")
44
  model = YOLO(model_path)
45
  if device == 'cuda':
46
  model.to(device)
 
 
47
  model_cache[cache_key] = model
48
 
49
  return model_cache[cache_key], None
50
  except Exception as e:
51
  return default_model, f"๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ: {str(e)}"
52
 
53
- def resize_image(image, target_size=640):
54
- """์ด๋ฏธ์ง€ ํฌ๊ธฐ ์กฐ์ •์œผ๋กœ ์ฒ˜๋ฆฌ ์†๋„ ํ–ฅ์ƒ"""
55
- h, w = image.shape[:2]
56
- if max(h, w) > target_size:
57
- scale = target_size / max(h, w)
58
- new_w, new_h = int(w * scale), int(h * scale)
59
- return cv2.resize(image, (new_w, new_h)), scale
60
- return image, 1.0
61
-
62
- def fast_object_detection(image, model_source, uploaded_file, conf_threshold=0.25, iou_threshold=0.45):
63
- """๊ณ ์† ๊ฐ์ฒด ํƒ์ง€ ํ•จ์ˆ˜"""
64
- global current_frame_count, last_result, frame_skip
65
-
66
- if image is None:
67
- return last_result if last_result is not None else None, "์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
68
 
69
- # ํ”„๋ ˆ์ž„ ์Šคํ‚ต์œผ๋กœ ์ฒ˜๋ฆฌ ์†๋„ ํ–ฅ์ƒ
70
- current_frame_count += 1
71
- if current_frame_count % frame_skip != 0 and last_result is not None:
72
- return last_result, "ํ”„๋ ˆ์ž„ ์Šคํ‚ต ์ค‘..."
73
-
74
- try:
75
- # ๋…ผ๋ธ”๋กœํ‚น ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ
76
- with processing_lock:
77
- # ๋ชจ๋ธ ๋กœ๋“œ
78
- model, error_msg = load_model_optimized(model_source, uploaded_file)
79
 
80
- # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
81
- img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
 
 
 
 
 
82
 
83
- # ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์กฐ์ •์œผ๋กœ ์†๋„ ํ–ฅ์ƒ
84
- resized_img, scale = resize_image(img_bgr, target_size=480)
 
85
 
86
- # YOLO ์ถ”๋ก  ์‹คํ–‰ (์ตœ์ ํ™”๋œ ์„ค์ •)
87
  results = model(
88
- resized_img,
89
- conf=conf_threshold,
90
  iou=iou_threshold,
91
- verbose=False, # ๋กœ๊ทธ ์ถœ๋ ฅ ๋น„ํ™œ์„ฑํ™”
92
  device=device
93
  )
94
 
95
  # ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”
96
  annotated = results[0].plot()
97
 
98
- # ์›๋ณธ ํฌ๊ธฐ๋กœ ๋ณต์›
99
- if scale != 1.0:
100
- original_h, original_w = img_bgr.shape[:2]
101
- annotated = cv2.resize(annotated, (original_w, original_h))
102
-
103
- # ์„ฑ๋Šฅ ์ •๋ณด ํ‘œ์‹œ
104
  num_detections = len(results[0].boxes) if results[0].boxes is not None else 0
105
-
106
- # ์ •๋ณด ํ…์ŠคํŠธ ์ถ”๊ฐ€
107
  info_text = [
108
- f'FPS: ~{30//frame_skip}',
109
  f'Objects: {num_detections}',
110
  f'Device: {device.upper()}',
111
- f'Model: {model_source if model_source != "์—…๋กœ๋“œ๋œ ํŒŒ์ผ" else "Custom"}'
112
  ]
113
 
114
- y_offset = 30
115
  for i, text in enumerate(info_text):
116
  color = (0, 255, 0) if i == 0 else (255, 255, 0) if i == 1 else (0, 255, 255)
117
- cv2.putText(annotated, text, (10, y_offset + i * 35),
118
- cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
119
-
120
- # BGR์„ RGB๋กœ ๋ณ€ํ™˜
121
- annotated_rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
122
-
123
- # ๊ฒฐ๊ณผ ์บ์‹œ
124
- last_result = annotated_rgb
125
-
126
- status_msg = error_msg if error_msg else f"ํƒ์ง€ ์™„๋ฃŒ - {num_detections}๊ฐœ ๊ฐ์ฒด"
127
-
128
- return annotated_rgb, status_msg
129
 
130
- except Exception as e:
131
- return last_result if last_result is not None else image, f"์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}"
 
 
 
 
 
 
 
 
 
132
 
133
- def ultra_fast_detection(image):
134
- """์ดˆ๊ณ ์† ๊ฐ„๋‹จ ํƒ์ง€"""
135
- global current_frame_count, last_result
136
 
137
- if image is None:
138
- return last_result if last_result is not None else None
139
 
140
- # ๋” ๋งŽ์€ ํ”„๋ ˆ์ž„ ์Šคํ‚ต
141
- current_frame_count += 1
142
- if current_frame_count % 3 != 0 and last_result is not None:
143
- return last_result
144
 
145
- try:
146
- # ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋” ์ž‘๊ฒŒ ์กฐ์ •
147
- img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
148
- resized_img, scale = resize_image(img_bgr, target_size=320)
149
-
150
- # ์ตœ์†Œ ์„ค์ •์œผ๋กœ ์ถ”๋ก 
151
- results = default_model(
152
- resized_img,
153
- conf=0.3, # ๋†’์€ ์‹ ๋ขฐ๋„๋กœ ๋น ๋ฅธ ์ฒ˜๋ฆฌ
154
- verbose=False,
155
- device=device
156
- )
157
-
158
- annotated = results[0].plot()
159
-
160
- # ์›๋ณธ ํฌ๊ธฐ๋กœ ๋ณต์›
161
- if scale != 1.0:
162
- original_h, original_w = img_bgr.shape[:2]
163
- annotated = cv2.resize(annotated, (original_w, original_h))
164
-
165
- # ๊ฐ„๋‹จํ•œ ์ •๋ณด๋งŒ ํ‘œ์‹œ
166
- num_detections = len(results[0].boxes) if results[0].boxes is not None else 0
167
- cv2.putText(annotated, f'Objects: {num_detections} | Ultra Fast Mode',
168
- (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
169
-
170
- annotated_rgb = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
171
- last_result = annotated_rgb
172
-
173
- return annotated_rgb
174
-
175
- except Exception as e:
176
- return last_result if last_result is not None else image
177
 
178
- # ์ดˆ๊ณ ์† ๋ชจ๋“œ
179
- with gr.Blocks(title="์ดˆ๊ณ ์† YOLO ํƒ์ง€") as ultra_fast_demo:
180
- gr.Markdown("""
181
- # โšก ์ดˆ๊ณ ์† YOLO ๊ฐ์ฒด ํƒ์ง€
182
- ์ตœ์ ํ™”๋œ ์„ค์ •์œผ๋กœ ์ตœ๊ณ  ์„ฑ๋Šฅ์˜ ์‹ค์‹œ๊ฐ„ ํƒ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
183
-
184
- **์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ธฐ๋ฒ•:**
185
- - ํ”„๋ ˆ์ž„ ์Šคํ‚ต (3ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์ฒ˜๋ฆฌ)
186
- - ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ถ•์†Œ (320px)
187
- - GPU ๊ฐ€์† (์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ)
188
- - ๋…ผ๋ธ”๋กœํ‚น ์ฒ˜๋ฆฌ
189
- """)
190
 
191
- with gr.Row():
192
- ultra_webcam_input = gr.Image(
193
- label='โšก ์ดˆ๊ณ ์† ์›น์บ ',
194
- sources="webcam",
195
- streaming=True,
196
- width=640,
197
- height=480
198
- )
199
- ultra_webcam_output = gr.Image(
200
- label='๐ŸŽฏ ํƒ์ง€ ๊ฒฐ๊ณผ',
201
- width=640,
202
- height=480
203
- )
204
 
205
- ultra_webcam_input.stream(
206
- fn=ultra_fast_detection,
207
- inputs=[ultra_webcam_input],
208
- outputs=[ultra_webcam_output],
209
- stream_every=0.05 # 50ms๋งˆ๋‹ค - ๋งค์šฐ ๋น ๋ฅธ ์—…๋ฐ์ดํŠธ
210
- )
 
211
 
212
- # ์„ฑ๋Šฅ ์กฐ์ • ๊ฐ€๋Šฅํ•œ ๊ณ ๊ธ‰ ๋ฒ„์ „
213
- with gr.Blocks(title="์„ฑ๋Šฅ ์กฐ์ • ๊ฐ€๋Šฅ ํƒ์ง€") as performance_demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  gr.Markdown("""
215
- # ๐ŸŽ›๏ธ ์„ฑ๋Šฅ ์กฐ์ • ๊ฐ€๋Šฅ YOLO ํƒ์ง€
216
- ์†๋„์™€ ์ •ํ™•๋„๋ฅผ ๊ท ํ˜•์žˆ๊ฒŒ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 
 
 
 
 
 
217
  """)
218
 
219
  with gr.Row():
220
  with gr.Column():
221
- perf_webcam_input = gr.Image(
222
- label='๐Ÿ“น ์›น์บ  ์ž…๋ ฅ',
223
- sources="webcam",
224
- streaming=True,
225
- width=640,
 
226
  height=480
227
  )
228
 
229
- # ์„ฑ๋Šฅ ์„ค์ •
230
- with gr.Accordion("โš™๏ธ ์„ฑ๋Šฅ ์„ค์ •", open=True):
231
- frame_skip_slider = gr.Slider(
232
- 1, 5, 2, 1,
233
- label="ํ”„๋ ˆ์ž„ ์Šคํ‚ต",
234
- info="๋†’์„์ˆ˜๋ก ๋น ๋ฆ„ (๋‚ฎ์€ FPS)"
235
- )
236
-
237
- image_size_slider = gr.Slider(
238
- 320, 640, 480, 80,
239
- label="์ฒ˜๋ฆฌ ์ด๋ฏธ์ง€ ํฌ๊ธฐ",
240
- info="์ž‘์„์ˆ˜๋ก ๋น ๋ฆ„"
241
- )
242
 
243
- # ๋ชจ๋ธ ๋ฐ ํƒ์ง€ ์„ค์ •
244
- with gr.Accordion("๐Ÿค– ๋ชจ๋ธ ์„ค์ •", open=False):
245
- model_source = gr.Dropdown(
246
- choices=[
247
- "yolov8n.pt", # ๊ฐ€์žฅ ๋น ๋ฆ„
248
- "yolov8s.pt",
249
- "yolov8m.pt",
250
- "์—…๋กœ๋“œ๋œ ํŒŒ์ผ"
251
- ],
252
  value="yolov8n.pt",
253
  label="๋ชจ๋ธ ์„ ํƒ"
254
  )
255
 
256
- weight_file = gr.File(
257
- label="๊ฐ€์ค‘์น˜ ํŒŒ์ผ ์—…๋กœ๋“œ",
258
  file_types=[".pt", ".onnx", ".engine"],
259
  visible=False
260
  )
261
 
262
- conf_slider = gr.Slider(
263
  0.1, 1.0, 0.25, 0.05,
264
  label="์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’"
265
  )
266
 
267
- iou_slider = gr.Slider(
268
  0.1, 1.0, 0.45, 0.05,
269
  label="IoU ์ž„๊ณ„๊ฐ’"
270
  )
271
-
272
  with gr.Column():
273
- perf_webcam_output = gr.Image(
274
- label='๐ŸŽฏ ํƒ์ง€ ๊ฒฐ๊ณผ',
 
275
  width=640,
276
  height=480
277
  )
278
 
279
- status_text = gr.Textbox(
 
280
  label="๐Ÿ“Š ์ƒํƒœ",
281
  value="์ค€๋น„ ์™„๋ฃŒ",
282
  interactive=False
@@ -285,122 +251,220 @@ with gr.Blocks(title="์„ฑ๋Šฅ ์กฐ์ • ๊ฐ€๋Šฅ ํƒ์ง€") as performance_demo:
285
  # ์„ฑ๋Šฅ ์ •๏ฟฝ๏ฟฝ๏ฟฝ
286
  with gr.Accordion("๐Ÿ“ˆ ์„ฑ๋Šฅ ์ •๋ณด", open=True):
287
  gr.Markdown(f"""
288
- **์‹œ์Šคํ…œ ์ •๋ณด:**
289
- - ๋””๋ฐ”์ด์Šค: {device.upper()}
290
- - PyTorch ๋ฒ„์ „: {torch.__version__}
291
- - CUDA ์‚ฌ์šฉ ๊ฐ€๋Šฅ: {torch.cuda.is_available()}
 
 
 
 
 
292
  """)
293
 
294
- # ํ”„๋ ˆ์ž„ ์Šคํ‚ต ์—…๋ฐ์ดํŠธ
295
- def update_frame_skip(skip_value):
296
- global frame_skip
297
- frame_skip = int(skip_value)
298
- return f"ํ”„๋ ˆ์ž„ ์Šคํ‚ต ์„ค์ •: {skip_value}"
299
 
300
- frame_skip_slider.change(
301
- fn=update_frame_skip,
302
- inputs=[frame_skip_slider],
303
- outputs=[status_text]
304
  )
305
 
306
- # ํŒŒ์ผ ์—…๋กœ๋“œ ํ† ๊ธ€
307
- def toggle_file_upload(choice):
308
- return gr.update(visible=(choice == "์—…๋กœ๋“œ๋œ ํŒŒ์ผ"))
 
 
 
309
 
310
- model_source.change(
311
- fn=toggle_file_upload,
312
- inputs=[model_source],
313
- outputs=[weight_file]
314
  )
315
 
316
- # ์„ฑ๋Šฅ ์ตœ์ ํ™”๋œ ํƒ์ง€ ํ•จ์ˆ˜
317
- def optimized_detection(image, skip_val, img_size, model_src, weight_f, conf, iou):
318
- global frame_skip
319
- frame_skip = int(skip_val)
320
-
321
- # ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์กฐ์ •
322
- if image is not None:
323
- img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
324
- resized_img, scale = resize_image(img_bgr, target_size=int(img_size))
325
- resized_rgb = cv2.cvtColor(resized_img, cv2.COLOR_BGR2RGB)
326
-
327
- return fast_object_detection(resized_rgb, model_src, weight_f, conf, iou)
328
- return None, "์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
329
-
330
- perf_webcam_input.stream(
331
- fn=optimized_detection,
332
- inputs=[perf_webcam_input, frame_skip_slider, image_size_slider,
333
- model_source, weight_file, conf_slider, iou_slider],
334
- outputs=[perf_webcam_output, status_text],
335
- stream_every=0.05
336
  )
337
 
338
- # ๊ฐ„๋‹จํ•œ ๊ณ ์† ๋ฒ„์ „
339
- with gr.Blocks(title="๊ฐ„๋‹จํ•œ ๊ณ ์† ํƒ์ง€") as simple_fast_demo:
340
- gr.Markdown("# ๐Ÿš€ ๊ฐ„๋‹จํ•œ ๊ณ ์† YOLO ํƒ์ง€")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
  with gr.Row():
343
- simple_webcam_input = gr.Image(
344
- label='์นด๋ฉ”๋ผ',
345
- sources="webcam",
346
- streaming=True,
347
- width=480,
348
  height=360
349
  )
350
- simple_webcam_output = gr.Image(
351
- label='ํƒ์ง€ ๊ฒฐ๊ณผ',
 
352
  width=480,
353
  height=360
354
  )
355
 
356
- simple_webcam_input.stream(
357
- fn=ultra_fast_detection,
358
- inputs=[simple_webcam_input],
359
- outputs=[simple_webcam_output],
360
- stream_every=0.05
361
  )
362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  # ์ตœ์ข… ์•ฑ
364
- with gr.Blocks(title="๊ณ ์† YOLO ๊ฐ์ฒด ํƒ์ง€") as final_app:
365
  gr.Markdown("""
366
- # โšก ๊ณ ์† YOLO ๊ฐ์ฒด ํƒ์ง€
367
- ๋‹ค์–‘ํ•œ ์„ฑ๋Šฅ ์˜ต์…˜์œผ๋กœ ์ตœ์ ํ™”๋œ ์‹ค์‹œ๊ฐ„ ๊ฐ์ฒด ํƒ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
368
  """)
369
 
370
  with gr.Tabs():
371
- with gr.TabItem("โšก ์ดˆ๊ณ ์† ๋ชจ๋“œ"):
372
- ultra_fast_demo.render()
373
 
374
- with gr.TabItem("๐ŸŽ›๏ธ ์„ฑ๋Šฅ ์กฐ์ •"):
375
- performance_demo.render()
376
 
377
- with gr.TabItem("๐Ÿš€ ๊ฐ„๋‹จํ•œ ๊ณ ์†"):
378
- simple_fast_demo.render()
379
 
380
  gr.Markdown("""
381
  ---
382
- ## ๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ
383
-
384
- ### โšก ์†๋„ ํ–ฅ์ƒ ๋ฐฉ๋ฒ•:
385
- 1. **ํ”„๋ ˆ์ž„ ์Šคํ‚ต ์ฆ๊ฐ€**: 2-3 ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์ฒ˜๋ฆฌ
386
- 2. **์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ถ•์†Œ**: 320-480px๋กœ ์„ค์ •
387
- 3. **๊ฐ€๋ฒผ์šด ๋ชจ๋ธ ์‚ฌ์šฉ**: yolov8n.pt ๊ถŒ์žฅ
388
- 4. **๋†’์€ ์‹ ๋ขฐ๋„ ์„ค์ •**: 0.3-0.5๋กœ ์„ค์ •
389
-
390
- ### ๐ŸŽฏ ์ •ํ™•๋„ ํ–ฅ์ƒ ๋ฐฉ๋ฒ•:
391
- 1. **ํ”„๋ ˆ์ž„ ์Šคํ‚ต ๊ฐ์†Œ**: ๋ชจ๋“  ํ”„๋ ˆ์ž„ ์ฒ˜๋ฆฌ
392
- 2. **์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ฆ๊ฐ€**: 640px ์ด์ƒ
393
- 3. **์ •ํ™•ํ•œ ๋ชจ๋ธ ์‚ฌ์šฉ**: yolov8s.pt ์ด์ƒ
394
- 4. **๋‚ฎ์€ ์‹ ๋ขฐ๋„ ์„ค์ •**: 0.1-0.3์œผ๋กœ ์„ค์ •
395
-
396
- ### ๐Ÿ”ง ์‹œ์Šคํ…œ ์‚ฌ์–‘:
397
- - **CPU**: ๊ธฐ๋ณธ ์ฒ˜๋ฆฌ (~10-15 FPS)
398
- - **GPU**: CUDA ๊ฐ€์† (~20-30 FPS)
399
- - **๋ฉ”๋ชจ๋ฆฌ**: ์ตœ์†Œ 4GB RAM ๊ถŒ์žฅ
400
  """)
401
 
402
  if __name__ == "__main__":
403
- # ์ดˆ๊ณ ์† ๋ชจ๋“œ๋กœ ์‹คํ–‰
404
  final_app.launch(
405
  share=True,
406
  server_name="0.0.0.0",
 
3
  from ultralytics import YOLO
4
  import numpy as np
5
  import time
 
 
6
  import threading
7
+ from queue import Queue, Empty
8
  import torch
9
+ import base64
10
+ from io import BytesIO
11
+ from PIL import Image
12
 
13
  # GPU ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ
14
  device = 'cuda' if torch.cuda.is_available() else 'cpu'
15
  print(f"Using device: {device}")
16
 
17
+ # ๊ธฐ๋ณธ ๋ชจ๋ธ ๋กœ๋“œ
18
  default_model = YOLO("yolov8n.pt")
19
  if device == 'cuda':
20
  default_model.to(device)
21
 
22
+ # ์ŠคํŠธ๋ฆฌ๋ฐ ์ „์—ญ ๋ณ€์ˆ˜
23
+ streaming_active = False
24
+ frame_queue = Queue(maxsize=3) # ์ž‘์€ ํ๋กœ ์ง€์—ฐ ์ตœ์†Œํ™”
25
+ result_queue = Queue(maxsize=3)
26
+ processing_thread = None
27
+ fps_counter = 0
28
+ fps_start_time = time.time()
29
+ current_fps = 0
30
 
31
  # ๋ชจ๋ธ ์บ์‹œ
32
  model_cache = {}
33
 
34
+ def load_model_fast(model_source, uploaded_file=None):
35
+ """๋น ๋ฅธ ๋ชจ๋ธ ๋กœ๋“œ"""
36
  global model_cache
37
 
38
  try:
 
44
  cache_key = model_source
45
 
46
  if cache_key not in model_cache:
 
47
  model = YOLO(model_path)
48
  if device == 'cuda':
49
  model.to(device)
50
+ # ๋ชจ๋ธ ์›Œ๋ฐ์—…
51
+ dummy_input = torch.randn(1, 3, 640, 640).to(device)
52
  model_cache[cache_key] = model
53
 
54
  return model_cache[cache_key], None
55
  except Exception as e:
56
  return default_model, f"๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ: {str(e)}"
57
 
58
+ def streaming_processor(model, conf_threshold, iou_threshold, target_size=416):
59
+ """์ŠคํŠธ๋ฆฌ๋ฐ ์ฒ˜๋ฆฌ ์›Œ์ปค"""
60
+ global streaming_active, fps_counter, fps_start_time, current_fps
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ while streaming_active:
63
+ try:
64
+ # ํ”„๋ ˆ์ž„ ๊ฐ€์ ธ์˜ค๊ธฐ (๋…ผ๋ธ”๋กœํ‚น)
65
+ frame = frame_queue.get(timeout=0.1)
 
 
 
 
 
 
66
 
67
+ # FPS ๊ณ„์‚ฐ
68
+ fps_counter += 1
69
+ current_time = time.time()
70
+ if current_time - fps_start_time >= 1.0:
71
+ current_fps = fps_counter / (current_time - fps_start_time)
72
+ fps_counter = 0
73
+ fps_start_time = current_time
74
 
75
+ # ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ
76
+ if frame.shape[0] != target_size or frame.shape[1] != target_size:
77
+ frame = cv2.resize(frame, (target_size, target_size))
78
 
79
+ # YOLO ์ถ”๋ก 
80
  results = model(
81
+ frame,
82
+ conf=conf_threshold,
83
  iou=iou_threshold,
84
+ verbose=False,
85
  device=device
86
  )
87
 
88
  # ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”
89
  annotated = results[0].plot()
90
 
91
+ # ์ •๋ณด ํ‘œ์‹œ
 
 
 
 
 
92
  num_detections = len(results[0].boxes) if results[0].boxes is not None else 0
 
 
93
  info_text = [
94
+ f'FPS: {current_fps:.1f}',
95
  f'Objects: {num_detections}',
96
  f'Device: {device.upper()}',
97
+ f'Stream: ACTIVE'
98
  ]
99
 
 
100
  for i, text in enumerate(info_text):
101
  color = (0, 255, 0) if i == 0 else (255, 255, 0) if i == 1 else (0, 255, 255)
102
+ cv2.putText(annotated, text, (10, 30 + i * 25),
103
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
 
 
 
 
 
 
 
 
 
 
104
 
105
+ # ๊ฒฐ๊ณผ ํ์— ์ถ”๊ฐ€ (๋…ผ๋ธ”๋กœํ‚น)
106
+ try:
107
+ result_queue.put(annotated, block=False)
108
+ except:
109
+ pass # ํ๊ฐ€ ๊ฝ‰ ์ฐจ๋ฉด ๊ฑด๋„ˆ๋›ฐ๊ธฐ
110
+
111
+ except Empty:
112
+ continue
113
+ except Exception as e:
114
+ print(f"Processing error: {e}")
115
+ continue
116
 
117
+ def start_streaming(model_source, uploaded_file, conf_threshold, iou_threshold):
118
+ """์ŠคํŠธ๋ฆฌ๋ฐ ์‹œ์ž‘"""
119
+ global streaming_active, processing_thread
120
 
121
+ if streaming_active:
122
+ return "์ด๋ฏธ ์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘์ž…๋‹ˆ๋‹ค."
123
 
124
+ # ๋ชจ๋ธ ๋กœ๋“œ
125
+ model, error = load_model_fast(model_source, uploaded_file)
126
+ if error:
127
+ return f"๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ: {error}"
128
 
129
+ streaming_active = True
130
+ processing_thread = threading.Thread(
131
+ target=streaming_processor,
132
+ args=(model, conf_threshold, iou_threshold),
133
+ daemon=True
134
+ )
135
+ processing_thread.start()
136
+
137
+ return "์ŠคํŠธ๋ฆฌ๋ฐ ์‹œ์ž‘๋จ"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ def stop_streaming():
140
+ """์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘์ง€"""
141
+ global streaming_active
142
+ streaming_active = False
 
 
 
 
 
 
 
 
143
 
144
+ # ํ ๋น„์šฐ๊ธฐ
145
+ while not frame_queue.empty():
146
+ try:
147
+ frame_queue.get_nowait()
148
+ except:
149
+ break
 
 
 
 
 
 
 
150
 
151
+ while not result_queue.empty():
152
+ try:
153
+ result_queue.get_nowait()
154
+ except:
155
+ break
156
+
157
+ return "์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘์ง€๋จ"
158
 
159
+ def process_stream_frame(frame):
160
+ """์ŠคํŠธ๋ฆผ ํ”„๋ ˆ์ž„ ์ฒ˜๋ฆฌ"""
161
+ global streaming_active
162
+
163
+ if frame is None or not streaming_active:
164
+ return frame
165
+
166
+ # ํ”„๋ ˆ์ž„์„ BGR๋กœ ๋ณ€ํ™˜
167
+ frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
168
+
169
+ # ํ”„๋ ˆ์ž„ ํ์— ์ถ”๊ฐ€ (๋…ผ๋ธ”๋กœํ‚น)
170
+ try:
171
+ frame_queue.put(frame_bgr, block=False)
172
+ except:
173
+ pass # ํ๊ฐ€ ๊ฝ‰ ์ฐจ๋ฉด ๊ฑด๋„ˆ๋›ฐ๊ธฐ
174
+
175
+ # ๊ฒฐ๊ณผ ๊ฐ€์ ธ์˜ค๊ธฐ (๋…ผ๋ธ”๋กœํ‚น)
176
+ try:
177
+ result_bgr = result_queue.get_nowait()
178
+ result_rgb = cv2.cvtColor(result_bgr, cv2.COLOR_BGR2RGB)
179
+ return result_rgb
180
+ except Empty:
181
+ return frame # ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด ์›๋ณธ ํ”„๋ ˆ์ž„ ๋ฐ˜ํ™˜
182
+
183
+ # ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ ํƒ์ง€ ์•ฑ
184
+ with gr.Blocks(title="์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ YOLO ํƒ์ง€") as streaming_app:
185
  gr.Markdown("""
186
+ # ๐ŸŽฅ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ YOLO ๊ฐ์ฒด ํƒ์ง€
187
+ ์ง„์งœ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์œผ๋กœ ๊ณ ์† ๊ฐ์ฒด ํƒ์ง€๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
188
+
189
+ **ํŠน์ง•:**
190
+ - ์—ฐ์†์ ์ธ ๋น„๋””์˜ค ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ
191
+ - ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์œผ๋กœ ๋…ผ๋ธ”๋กœํ‚น ์ฒ˜๋ฆฌ
192
+ - ์‹ค์‹œ๊ฐ„ FPS ํ‘œ์‹œ
193
+ - ํ ๊ธฐ๋ฐ˜ ํ”„๋ ˆ์ž„ ๋ฒ„ํผ๋ง
194
  """)
195
 
196
  with gr.Row():
197
  with gr.Column():
198
+ # ์›น์บ  ์ž…๋ ฅ
199
+ webcam_stream = gr.Image(
200
+ label="๐Ÿ“น ์›น์บ  ์ŠคํŠธ๋ฆผ",
201
+ sources=["webcam"],
202
+ streaming=True,
203
+ width=640,
204
  height=480
205
  )
206
 
207
+ # ์ปจํŠธ๋กค ๋ฒ„ํŠผ
208
+ with gr.Row():
209
+ start_btn = gr.Button("๐Ÿš€ ์ŠคํŠธ๋ฆฌ๋ฐ ์‹œ์ž‘", variant="primary")
210
+ stop_btn = gr.Button("โน๏ธ ์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘์ง€", variant="secondary")
 
 
 
 
 
 
 
 
 
211
 
212
+ # ์„ค์ • ํŒจ๋„
213
+ with gr.Accordion("โš™๏ธ ์„ค์ •", open=True):
214
+ model_select = gr.Dropdown(
215
+ choices=["yolov8n.pt", "yolov8s.pt", "yolov8m.pt", "์—…๋กœ๋“œ๋œ ํŒŒ์ผ"],
 
 
 
 
 
216
  value="yolov8n.pt",
217
  label="๋ชจ๋ธ ์„ ํƒ"
218
  )
219
 
220
+ weight_upload = gr.File(
221
+ label="๊ฐ€์ค‘์น˜ ์—…๋กœ๋“œ",
222
  file_types=[".pt", ".onnx", ".engine"],
223
  visible=False
224
  )
225
 
226
+ conf_threshold = gr.Slider(
227
  0.1, 1.0, 0.25, 0.05,
228
  label="์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’"
229
  )
230
 
231
+ iou_threshold = gr.Slider(
232
  0.1, 1.0, 0.45, 0.05,
233
  label="IoU ์ž„๊ณ„๊ฐ’"
234
  )
235
+
236
  with gr.Column():
237
+ # ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ
238
+ output_stream = gr.Image(
239
+ label="๐ŸŽฏ ํƒ์ง€ ๊ฒฐ๊ณผ ์ŠคํŠธ๋ฆผ",
240
  width=640,
241
  height=480
242
  )
243
 
244
+ # ์ƒํƒœ ํ‘œ์‹œ
245
+ status_display = gr.Textbox(
246
  label="๐Ÿ“Š ์ƒํƒœ",
247
  value="์ค€๋น„ ์™„๋ฃŒ",
248
  interactive=False
 
251
  # ์„ฑ๋Šฅ ์ •๏ฟฝ๏ฟฝ๏ฟฝ
252
  with gr.Accordion("๐Ÿ“ˆ ์„ฑ๋Šฅ ์ •๋ณด", open=True):
253
  gr.Markdown(f"""
254
+ **์‹œ์Šคํ…œ ์‚ฌ์–‘:**
255
+ - ์ฒ˜๋ฆฌ ์žฅ์น˜: {device.upper()}
256
+ - PyTorch: {torch.__version__}
257
+ - CUDA: {torch.cuda.is_available()}
258
+
259
+ **์ŠคํŠธ๋ฆฌ๋ฐ ์„ค์ •:**
260
+ - ํ”„๋ ˆ์ž„ ๋ฒ„ํผ: 3 ํ”„๋ ˆ์ž„
261
+ - ์ฒ˜๋ฆฌ ํ•ด์ƒ๋„: 416x416
262
+ - ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ: ํ™œ์„ฑํ™”
263
  """)
264
 
265
+ # ํŒŒ์ผ ์—…๋กœ๋“œ ํ† ๊ธ€
266
+ def toggle_upload(choice):
267
+ return gr.update(visible=(choice == "์—…๋กœ๋“œ๋œ ํŒŒ์ผ"))
 
 
268
 
269
+ model_select.change(
270
+ fn=toggle_upload,
271
+ inputs=[model_select],
272
+ outputs=[weight_upload]
273
  )
274
 
275
+ # ์ŠคํŠธ๋ฆฌ๋ฐ ์ œ์–ด
276
+ start_btn.click(
277
+ fn=start_streaming,
278
+ inputs=[model_select, weight_upload, conf_threshold, iou_threshold],
279
+ outputs=[status_display]
280
+ )
281
 
282
+ stop_btn.click(
283
+ fn=stop_streaming,
284
+ outputs=[status_display]
 
285
  )
286
 
287
+ # ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ
288
+ webcam_stream.stream(
289
+ fn=process_stream_frame,
290
+ inputs=[webcam_stream],
291
+ outputs=[output_stream],
292
+ stream_every=0.033 # ~30 FPS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  )
294
 
295
+ # ์ดˆ๊ฒฝ๋Ÿ‰ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฒ„์ „
296
+ with gr.Blocks(title="์ดˆ๊ฒฝ๋Ÿ‰ ์ŠคํŠธ๋ฆฌ๋ฐ") as lightweight_app:
297
+ gr.Markdown("""
298
+ # โšก ์ดˆ๊ฒฝ๋Ÿ‰ ์ŠคํŠธ๋ฆฌ๋ฐ ํƒ์ง€
299
+ ์ตœ์†Œํ•œ์˜ ์„ค์ •์œผ๋กœ ์ตœ๊ณ  ์†๋„๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
300
+ """)
301
+
302
+ # ์ดˆ๊ฒฝ๋Ÿ‰ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
303
+ def ultra_light_stream(frame):
304
+ if frame is None:
305
+ return frame
306
+
307
+ try:
308
+ # ๋งค์šฐ ์ž‘์€ ํฌ๊ธฐ๋กœ ์ฒ˜๋ฆฌ
309
+ small_frame = cv2.resize(frame, (320, 240))
310
+ frame_bgr = cv2.cvtColor(small_frame, cv2.COLOR_RGB2BGR)
311
+
312
+ # ๋น ๋ฅธ ์ถ”๋ก 
313
+ results = default_model(
314
+ frame_bgr,
315
+ conf=0.3,
316
+ verbose=False,
317
+ device=device
318
+ )
319
+
320
+ annotated = results[0].plot()
321
+
322
+ # ์›๋ณธ ํฌ๊ธฐ๋กœ ๋ณต์›
323
+ annotated = cv2.resize(annotated, (frame.shape[1], frame.shape[0]))
324
+
325
+ # ๊ฐ„๋‹จํ•œ ์ •๋ณด ํ‘œ์‹œ
326
+ num_objects = len(results[0].boxes) if results[0].boxes is not None else 0
327
+ cv2.putText(annotated, f'Objects: {num_objects} | Ultra Light',
328
+ (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
329
+
330
+ return cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
331
+ except:
332
+ return frame
333
 
334
  with gr.Row():
335
+ ultra_input = gr.Image(
336
+ label="๐Ÿ“น ์ดˆ๊ฒฝ๋Ÿ‰ ์ž…๋ ฅ",
337
+ sources=["webcam"],
338
+ streaming=True,
339
+ width=480,
340
  height=360
341
  )
342
+
343
+ ultra_output = gr.Image(
344
+ label="โšก ์ดˆ๊ฒฝ๋Ÿ‰ ์ถœ๋ ฅ",
345
  width=480,
346
  height=360
347
  )
348
 
349
+ ultra_input.stream(
350
+ fn=ultra_light_stream,
351
+ inputs=[ultra_input],
352
+ outputs=[ultra_output],
353
+ stream_every=0.02 # 50 FPS ๋ชฉํ‘œ
354
  )
355
 
356
+ # OpenCV ์ง์ ‘ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฒ„์ „
357
+ with gr.Blocks(title="OpenCV ์ง์ ‘ ์ŠคํŠธ๋ฆฌ๋ฐ") as opencv_app:
358
+ gr.Markdown("""
359
+ # ๐Ÿ“น OpenCV ์ง์ ‘ ์ŠคํŠธ๋ฆฌ๋ฐ
360
+ OpenCV VideoCapture๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•œ ์ตœ๊ณ  ์„ฑ๋Šฅ ์ŠคํŠธ๋ฆฌ๋ฐ
361
+ """)
362
+
363
+ # OpenCV ์ŠคํŠธ๋ฆฌ๋ฐ ์ƒํƒœ
364
+ cv_streaming = False
365
+ cv_cap = None
366
+
367
+ def start_opencv_stream():
368
+ global cv_streaming, cv_cap
369
+ try:
370
+ cv_cap = cv2.VideoCapture(0)
371
+ cv_cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
372
+ cv_cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
373
+ cv_cap.set(cv2.CAP_PROP_FPS, 30)
374
+ cv_streaming = True
375
+ return "OpenCV ์ŠคํŠธ๋ฆฌ๋ฐ ์‹œ์ž‘"
376
+ except Exception as e:
377
+ return f"์˜ค๋ฅ˜: {str(e)}"
378
+
379
+ def stop_opencv_stream():
380
+ global cv_streaming, cv_cap
381
+ cv_streaming = False
382
+ if cv_cap:
383
+ cv_cap.release()
384
+ return "OpenCV ์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘์ง€"
385
+
386
+ def opencv_stream_process():
387
+ global cv_streaming, cv_cap
388
+
389
+ if not cv_streaming or cv_cap is None:
390
+ return None
391
+
392
+ ret, frame = cv_cap.read()
393
+ if not ret:
394
+ return None
395
+
396
+ # YOLO ์ฒ˜๋ฆฌ
397
+ results = default_model(frame, conf=0.25, verbose=False, device=device)
398
+ annotated = results[0].plot()
399
+
400
+ # OpenCV BGR์„ RGB๋กœ ๋ณ€ํ™˜
401
+ return cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
402
+
403
+ with gr.Row():
404
+ with gr.Column():
405
+ cv_start_btn = gr.Button("๐ŸŽฌ OpenCV ์ŠคํŠธ๋ฆผ ์‹œ์ž‘", variant="primary")
406
+ cv_stop_btn = gr.Button("โน๏ธ OpenCV ์ŠคํŠธ๋ฆผ ์ค‘์ง€", variant="secondary")
407
+ cv_status = gr.Textbox(label="์ƒํƒœ", value="์ค€๋น„")
408
+
409
+ with gr.Column():
410
+ cv_output = gr.Image(label="OpenCV ์ŠคํŠธ๋ฆผ ์ถœ๋ ฅ", width=640, height=480)
411
+
412
+ cv_start_btn.click(
413
+ fn=start_opencv_stream,
414
+ outputs=[cv_status]
415
+ )
416
+
417
+ cv_stop_btn.click(
418
+ fn=stop_opencv_stream,
419
+ outputs=[cv_status]
420
+ )
421
+
422
+ # ํƒ€์ด๋จธ ๊ธฐ๋ฐ˜ ์—…๋ฐ์ดํŠธ
423
+ def update_opencv_stream():
424
+ return opencv_stream_process()
425
+
426
+ # ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ํƒ€์ด๋จธ (JavaScript๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ)
427
+ gr.Markdown("""
428
+ **์ฃผ์˜:** OpenCV ์ง์ ‘ ์ŠคํŠธ๋ฆฌ๋ฐ์€ ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ ์ œํ•œ์ ์ž…๋‹ˆ๋‹ค.
429
+ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•  ๋•Œ ์ตœ์ ์˜ ์„ฑ๋Šฅ์„ ๋ณด์ž…๋‹ˆ๋‹ค.
430
+ """)
431
+
432
  # ์ตœ์ข… ์•ฑ
433
+ with gr.Blocks(title="๊ณ ์† ์ŠคํŠธ๋ฆฌ๋ฐ YOLO ํƒ์ง€") as final_app:
434
  gr.Markdown("""
435
+ # ๐ŸŽฅ ๊ณ ์† ์ŠคํŠธ๋ฆฌ๋ฐ YOLO ๊ฐ์ฒด ํƒ์ง€
436
+ ์ง„์ •ํ•œ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋œ ๊ณ ์† ๊ฐ์ฒด ํƒ์ง€ ์‹œ์Šคํ…œ
437
  """)
438
 
439
  with gr.Tabs():
440
+ with gr.TabItem("๐ŸŽฅ ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ"):
441
+ streaming_app.render()
442
 
443
+ with gr.TabItem("โšก ์ดˆ๊ฒฝ๋Ÿ‰ ์ŠคํŠธ๋ฆฌ๋ฐ"):
444
+ lightweight_app.render()
445
 
446
+ with gr.TabItem("๐Ÿ“น OpenCV ์ง์ ‘"):
447
+ opencv_app.render()
448
 
449
  gr.Markdown("""
450
  ---
451
+ ## ๐Ÿš€ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ชจ๋“œ ๋น„๊ต
452
+
453
+ | ๋ชจ๋“œ | ํŠน์ง• | ์†๋„ | ํ’ˆ์งˆ | ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค |
454
+ |------|------|------|------|-------------|
455
+ | ๐ŸŽฅ ์‹ค์‹œ๊ฐ„ | ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ, ํ ๋ฒ„ํผ๋ง | ๋น ๋ฆ„ | ๋†’์Œ | ์ •ํ™•ํ•œ ํƒ์ง€ ํ•„์š” |
456
+ | โšก ์ดˆ๊ฒฝ๋Ÿ‰ | ์ตœ์†Œ ์ฒ˜๋ฆฌ, 320p | ๋งค์šฐ ๋น ๋ฆ„ | ๋ณดํ†ต | ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง |
457
+ | ๐Ÿ“น OpenCV | ์ง์ ‘ ์ŠคํŠธ๋ฆผ ์ œ์–ด | ๊ฐ€์žฅ ๋น ๋ฆ„ | ๋†’์Œ | ๋กœ์ปฌ ํ™˜๊ฒฝ ์ตœ์ ํ™” |
458
+
459
+ ## ๐ŸŽฏ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ธฐ๋ฒ•
460
+ - **์Šค๋ ˆ๋“œ ๋ถ„๋ฆฌ**: UI์™€ ์ฒ˜๋ฆฌ ๋กœ์ง ๋ถ„๋ฆฌ
461
+ - **ํ ๋ฒ„ํผ๋ง**: ํ”„๋ ˆ์ž„ ๋“œ๋กญ ๋ฐฉ์ง€
462
+ - **๋…ผ๋ธ”๋กœํ‚น**: ์ง€์—ฐ ์ตœ์†Œํ™”
463
+ - **GPU ๊ฐ€์†**: CUDA ํ™œ์šฉ
464
+ - **ํ•ด์ƒ๋„ ์กฐ์ •**: ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ ์ตœ์ ํ™”
 
 
 
 
465
  """)
466
 
467
  if __name__ == "__main__":
 
468
  final_app.launch(
469
  share=True,
470
  server_name="0.0.0.0",