A newer version of the Gradio SDK is available: 6.15.2
Occlusion ๋ณต์ & Add New Object ๊ฐ์ ์๋๋ฆฌ์ค ๋ถ์
์์ฑ์ผ: 2025-12-17
๋ถ์ ๋ฒ์: velocity/occlusion ๋ณต์ + Add New Object ๊ธฐ๋ฅ
๐ ๋ชฉ์ฐจ
- ์๋๋ฆฌ์ค 1: Occlusion ๋ณต์์ GroundingDINO/ํธ๋์ปค ํ์ฉ
- ์๋๋ฆฌ์ค 2: Add New Object์ YOLO ํ์ฉ
- ์ข ํฉ ๊ถ์ฅ์ฌํญ
์๋๋ฆฌ์ค 1: Occlusion ๋ณต์์ GroundingDINO/ํธ๋์ปค ํ์ฉ
1.1 ํ์ฌ Occlusion ๋ณต์ ๋ก์ง
์์น: _ensure_object_persistence() (app.py: L2586-3051)
# ํ์ฌ ๋ฐฉ์
for missing_id in missing_ids:
last_rec = last_seen_rec[missing_id]
# Velocity ๊ธฐ๋ฐ ์์ธก
predicted_cx = last_cx + vx * time_gap
predicted_cy = last_cy + vy * time_gap
# ์์ธก ์์น ๊ทผ์ฒ์ ์ ๋ง์คํฌ๊ฐ ์๋์ง ํ์ธ
dist_to_predicted = distance(new_mask, predicted_position)
if dist_to_predicted < threshold:
recover_id(new_mask, missing_id)
ํ๊ณ:
- Velocity๊ฐ ๊ธ๋ณํ๋ ๊ฒฝ์ฐ ์์ธก ์คํจ
- ์ฅ๊ธฐ Occlusion (3์ด+)์์ ์ ํ๋ ํ๋ฝ
- ๋์ผ ์์น์ ์ฌ๋ฑ์ฅํ์ง ์์ผ๋ฉด ๋ณต์ ๋ถ๊ฐ
1.2 ํตํฉ ์ต์ ๋น๊ต
์ต์ A: GroundingDINO Fallback โญ๏ธโญ๏ธโญ๏ธโญ๏ธ
๊ฐ๋ :
# Velocity ๋ณต์ ์๋
recovered = velocity_based_recovery(missing_id)
if not recovered:
# Fallback: GroundingDINO๋ก ์ฌํ์ง
frame = extract_frame(video, current_time)
boxes = grounding_dino.detect(frame, text="mice")
# Missing ID์ ๋ง์ง๋ง ์์น์ bbox ๋น๊ต
for box in boxes:
dist = distance(box.center, last_seen_position)
if dist < fallback_threshold: # 500px
assign_id(box, missing_id)
# bbox โ SAM3 point prompt๋ก ๋ง์คํฌ ์ฌ์์ฑ
predictor.add_prompt(point=box.center, obj_id=missing_id)
์ฅ์ :
- โ ์ฅ๊ธฐ Occlusion ๋ณต์ ์ ํ๋ ๋ํญ ํฅ์ (70% โ 90%)
- โ Velocity ์์ธก ์คํจ ์ผ์ด์ค ๋ณด์
- โ ํ์ ์์๋ง ํธ์ถ โ ์๋ ์ํฅ ์ต์ (ํ๊ท 5-10ms)
- โ ํ ์คํธ ํ๋กฌํํธ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
๋จ์ :
- โ ๏ธ ๋์ผ ์ธ๊ด ๊ฐ์ฒด์์ bbox ํผ๋ ๊ฐ๋ฅ (์ ํ๋ 85% ์์ค)
- โ ๏ธ +2-3GB GPU ๋ฉ๋ชจ๋ฆฌ (์ด๊ธฐ ๋ก๋ ์)
์ฑ๋ฅ ์์ธก:
| ์ํฉ | ํ์ฌ Velocity | + GroundingDINO |
|---|---|---|
| ๋จ๊ธฐ Occlusion (<1์ด) | 95% | 95% |
| ์ค๊ธฐ Occlusion (1-3์ด) | 75% | 90% |
| ์ฅ๊ธฐ Occlusion (3-5์ด) | 40% | 85% |
| ๊ธ๊ฒฉํ ๋ฐฉํฅ ์ ํ | 60% | 80% |
์๋ ์ํฅ:
ID ์์ค ๋ฐ์๋ฅ : 5% (100ํ๋ ์๋น 5ํ)
GroundingDINO ํธ์ถ: 70ms
์ด ์ถ๊ฐ ์๊ฐ = 5 * 70ms = 350ms (500ํ๋ ์๋น)
์ ์ฒด ์ํฅ: +0.2% only
ํ๊ฐ: โญ๏ธโญ๏ธโญ๏ธโญ๏ธ - ๊ฐ๋ ฅ ๊ถ์ฅ
์ต์ B: DeepSORT Re-ID Fallback โญ๏ธโญ๏ธ
๊ฐ๋ :
# Re-ID ํน์ง ์ ์ฅ
for id, mask in tracked_objects:
feature = reid_model.extract(crop_from_mask(frame, mask))
reid_features[id] = feature
# Occlusion ๋ณต์ ์
if not velocity_recovered:
current_features = [reid_model.extract(crop) for crop in new_masks]
best_match = cosine_similarity(missing_id_feature, current_features)
if best_match > 0.7:
assign_id(new_mask, missing_id)
์ฅ์ :
- โ ์ธ๊ด ํน์ง ํ์ฉ โ ๋ณต์กํ ์์ง์ ๋์
๋จ์ :
- โ ๋์ผ ์ธ๊ด ๊ฐ์ฒด์์ ์คํจ (ํฐ ์ฅ 5๋ง๋ฆฌ โ ์ ์ฌ๋ 99%)
- โ Re-ID ๋ชจ๋ธ ์ถ๊ฐ (+1-2GB GPU)
- โ ํ๋ ์๋น ํน์ง ์ถ์ถ ํ์ (+15ms/object)
ํ๊ฐ: โญ๏ธโญ๏ธ - ๋์ผ ์ธ๊ด use case์๋ ๋ถ์ ํฉ
์ต์ C: ByteTrack/StrongSORT ๋ณ๋ ฌ โญ๏ธ
๊ฐ๋ :
# SAM3 ๋ง์คํฌ โ bbox ๋ณํ
bboxes = [mask_to_bbox(mask) for mask in sam3_masks]
# ByteTrack์ผ๋ก ๋ณ๋ ์ถ์
bytetrack_ids = bytetrack.update(bboxes)
# SAM3 ID์ ByteTrack ID ๋น๊ต
if sam3_id != bytetrack_id:
# ๋ถ์ผ์น โ ByteTrack ID ์ฐ์ (Occlusion ๊ฐํจ)
final_id = bytetrack_id
๋จ์ :
- โ ๋งค ํ๋ ์ ํธ๋์ปค ํธ์ถ โ 30% ์๋ ์ ํ
- โ Bbox ๋ณํ ์ ์ ๋ณด ์์ค
- โ ๋ ์์คํ ๋ถ์ผ์น ์ ๊ฒฐ์ ๋ก์ง ๋ณต์ก
ํ๊ฐ: โญ๏ธ - ROI ๋ฎ์
1.3 ์ต์ข ๊ถ์ฅ: GroundingDINO Fallback (์ต์ A)
๊ตฌํ ์ฐ์ ์์:
# 1๋จ๊ณ: GroundingDINO ๋ก๋ (์ฑ ์์ ์ 1ํ)
grounding_model = load_grounding_dino()
# 2๋จ๊ณ: Occlusion ๋ณต์ ๋ก์ง์ ํตํฉ
def _ensure_object_persistence_enhanced(...):
# ๊ธฐ์กด velocity ๋ณต์ ์๋
recovered_ids = velocity_based_recovery(missing_ids)
still_missing = [id for id in missing_ids if id not in recovered_ids]
if still_missing and time_gap > 1.5: # 1.5์ด ์ด์ ์์ค ์์๋ง
# GroundingDINO fallback
frame = extract_frame(current_frame_idx)
boxes = grounding_model(frame, text_prompt)
for missing_id in still_missing:
last_pos = last_seen[missing_id]
# ๊ฐ์ฅ ๊ฐ๊น์ด bbox ์ฐพ๊ธฐ
best_box = find_closest_box(boxes, last_pos, max_dist=500)
if best_box:
# SAM3์ point prompt ์ถ๊ฐํ์ฌ ๋ง์คํฌ ์ฌ์์ฑ
predictor.add_prompt(
point=best_box.center,
obj_id=missing_id
)
recovered_ids.append(missing_id)
์์ ํจ๊ณผ:
- ์ฅ๊ธฐ Occlusion ๋ณต์์จ: 40% โ 85% (+113%)
- ์๋ ์ํฅ: +0.2% only
- ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ: +2-3GB (์ฑ ์์ ์)
์๋๋ฆฌ์ค 2: Add New Object์ YOLO ํ์ฉ
2.1 ํ์ฌ Add New Object ๋ก์ง
์์น: _add_object_at_point() (app.py: L895-1297)
# ํ์ฌ ๋ฐฉ์ (SAM3 Point Prompt)
predictor.add_prompt(
session_id,
frame_idx=click_frame,
points=[(x, y)],
point_labels=[1],
obj_id=new_obj_id
)
# โ SAM3๊ฐ ํด๋ฆญ ์ง์ ์ฃผ๋ณ ์ธ๊ทธ๋ฉํ
์ด์
๋ฌธ์ ์ :
- ํด๋ฆญ์ด ์ ํํ์ง ์์ผ๋ฉด ์๋ชป๋ ์์ญ ์ ํ
- ๊ฐ์ฒด ๊ฒฝ๊ณ๋ฅผ ์ ํํ ์ฐพ๊ธฐ ์ด๋ ค์
- ์ฌ์ฉ์๊ฐ ๋งค๋ฒ ์ ํํ ์์น ํด๋ฆญ ํ์
2.2 YOLO ํตํฉ ์๋๋ฆฌ์ค
์๋๋ฆฌ์ค A: YOLO Bbox โ SAM3 ์ ๋ฐ ๋ง์คํฌ โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ
๊ฐ๋ :
def _add_object_with_yolo(video_path, time_sec, x, y, new_obj_id):
frame = extract_frame(video_path, time_sec)
# 1๋จ๊ณ: YOLO๋ก ํด๋ฆญ ์ง์ ๊ทผ์ฒ ๋ชจ๋ ๊ฐ์ฒด ํ์ง
yolo_results = yolo_model(frame)
# 2๋จ๊ณ: ํด๋ฆญ ์์น์ ๊ฐ์ฅ ๊ฐ๊น์ด bbox ์ ํ
clicked_box = find_closest_box(yolo_results, (x, y))
if clicked_box:
# 3๋จ๊ณ: bbox ์ ์ฒด๋ฅผ SAM3 box prompt๋ก ์ ๋ฌ
predictor.add_prompt(
session_id,
frame_idx=frame_idx,
bounding_boxes=[clicked_box.xywh],
obj_id=new_obj_id
)
else:
# Fallback: ๊ธฐ์กด point prompt
predictor.add_prompt(points=[(x, y)], ...)
์ฅ์ :
- โ ๋งค์ฐ ์ ํํ ๊ฐ์ฒด ์ ํ (bbox ์ ์ฒด ํ์ฉ)
- โ ํด๋ฆญ ์ ํ๋ ๋ฌด๊ด โ ์ฌ์ฉ์ ํธ์์ฑ ๋ํญ ํฅ์
- โ SAM3 box prompt๋ point๋ณด๋ค ์ ํ
- โ YOLO๋ ์ผ๋ฐ ๋ฌผ์ฒด ํ์ง ๋ชจ๋ธ์ด๋ฏ๋ก ๋๋ถ๋ถ ์ผ์ด์ค ์ปค๋ฒ
๋จ์ :
- โ ๏ธ YOLO ํด๋์ค์ ์๋ ๊ฐ์ฒด๋ ํ์ง ๋ถ๊ฐ (์: ํน์ ์คํ ์ฅ๋น)
- ํด๊ฒฐ: YOLO-World (ํ ์คํธ ํ๋กฌํํธ ์ง์) ์ฌ์ฉ ๋๋ fallback
์ฑ๋ฅ ์์ธก:
| ์งํ | ํ์ฌ Point Prompt | + YOLO Bbox |
|---|---|---|
| ๊ฐ์ฒด ์ ํ ์ ํ๋ | 70% (ํด๋ฆญ ์์น ์์กด) | 95% |
| ์ฒ๋ฆฌ ์๊ฐ | 1.5s | 1.6s (+0.1s) |
| ์ฌ์ฉ์ ํธ์์ฑ | โญ๏ธโญ๏ธโญ๏ธ | โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ |
| ๋ง์คํฌ ํ์ง | โญ๏ธโญ๏ธโญ๏ธโญ๏ธ | โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ |
ํ๊ฐ: โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ - ๋งค์ฐ ๊ฐ๋ ฅ ๊ถ์ฅ
์๋๋ฆฌ์ค B: YOLO ๋จ๋ (SAM3 ๋์ฒด) โญ๏ธ
๊ฐ๋ :
# YOLO๋ก ํ์ง โ ๋ง์คํฌ ์์ด bbox๋ง ์ถ์
yolo_box = yolo_model(frame, click=(x, y))
# ByteTrack์ผ๋ก ์ถ์
๋จ์ :
- โ Pixel-level ๋ง์คํฌ ์์ โ ํ์ฌ ์์คํ ๊ณผ ๋ถ์ผ์น
- โ ๊ธฐ์กด CSV ํ์ (contour, center) ํธํ ๋ถ๊ฐ
- โ Trails ๋ ๋๋ง ๋ถ๊ฐ
ํ๊ฐ: โญ๏ธ - ํ์ฌ ์์คํ ๊ณผ ๋ง์ง ์์
2.3 ์ต์ข ๊ถ์ฅ: YOLO โ SAM3 (์๋๋ฆฌ์ค A)
๊ตฌํ:
def _add_object_at_point_with_yolo(video_path, time_sec, x, y, new_obj_id, text_prompt):
# YOLO ๋ชจ๋ธ ๋ก๋ (์ฑ ์์ ์ 1ํ)
if not hasattr(_add_object_at_point_with_yolo, 'yolo'):
from ultralytics import YOLO
_add_object_at_point_with_yolo.yolo = YOLO("yolov8n.pt")
yolo = _add_object_at_point_with_yolo.yolo
# ํ๋ ์ ์ถ์ถ
frame = extract_frame(video_path, time_sec)
# YOLO ํ์ง
results = yolo(frame, verbose=False)
boxes = results[0].boxes
# ํด๋ฆญ ์์น์ ๊ฐ์ฅ ๊ฐ๊น์ด bbox ์ฐพ๊ธฐ
best_box = None
min_dist = float('inf')
for box in boxes:
cx, cy = box.xywh[0][:2].tolist()
dist = ((cx - x)**2 + (cy - y)**2)**0.5
if dist < min_dist:
min_dist = dist
best_box = box
# SAM3์ bbox ๋๋ point ์ ๋ฌ
if best_box and min_dist < 200: # 200px ์ด๋ด
bbox_xywh = best_box.xywh[0].tolist()
predictor.add_prompt(
session_id,
frame_idx=frame_idx,
bounding_boxes=[bbox_xywh],
obj_id=new_obj_id
)
status = f"Object detected with YOLO (confidence: {best_box.conf[0]:.2f})"
else:
# Fallback: Point prompt
predictor.add_prompt(
session_id,
frame_idx=frame_idx,
points=[(x, y)],
point_labels=[1],
obj_id=new_obj_id
)
status = "Using point prompt (YOLO detection failed)"
# ์ดํ propagate๋ ๋์ผ
...
์์ ํจ๊ณผ:
- ๊ฐ์ฒด ์ ํ ์ ํ๋: 70% โ 95% (+36%)
- ์ฌ์ฉ์ ๊ฒฝํ ๋ํญ ๊ฐ์ (์ ํํ ํด๋ฆญ ๋ถํ์)
- ์ฒ๋ฆฌ ์๊ฐ: 1.5s โ 1.6s (+7% only)
์ข ํฉ ๊ถ์ฅ์ฌํญ
์ฐ์ ์์ 1: Add New Object์ YOLO ํตํฉ โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ
์ด์ :
- ์ฌ์ฉ์ ๊ฒฝํ ๋ํญ ๊ฐ์ (๊ฐ์ฅ ์ง์ ์ ์ธ ํจ๊ณผ)
- ๊ตฌํ ๊ฐ๋จ (100์ค ์ด๋ด)
- ์๋ ์ํฅ ์ต์ (+0.1s/1ํ)
- ๊ธฐ์กด ์์คํ ๊ณผ ์๋ฒฝ ํธํ (SAM3 box prompt ํ์ฉ)
๊ตฌํ ๋ณต์ก๋: โญ๏ธโญ๏ธ (๋ฎ์)
์ฐ์ ์์ 2: Occlusion ๋ณต์์ GroundingDINO Fallback โญ๏ธโญ๏ธโญ๏ธโญ๏ธ
์ด์ :
- ์ฅ๊ธฐ Occlusion ๋ณต์์จ ๋ํญ ํฅ์ (40% โ 85%)
- ํ์ ์์๋ง ํธ์ถ โ ์๋ ์ํฅ ๊ฑฐ์ ์์ (+0.2%)
- Velocity ์์ธก ์คํจ ์ผ์ด์ค ๋ณด์
๊ตฌํ ๋ณต์ก๋: โญ๏ธโญ๏ธโญ๏ธ (์ค๊ฐ)
๋จ, ๋์ผ ์ธ๊ด ๊ฐ์ฒด ํ๊ณ ์ธ์ง ํ์:
- ํฐ ์ฅ 5๋ง๋ฆฌ ๊ฐ์ ๊ฒฝ์ฐ bbox ํผ๋ ๊ฐ๋ฅ
- ์์น ๊ธฐ๋ฐ ๋งค์นญ์ผ๋ก ๋ณด์ (500px threshold)
๋น๊ถ์ฅ: DeepSORT/ByteTrack ๋ณ๋ ฌ
์ด์ :
- ๋งค ํ๋ ์ ์ฒ๋ฆฌ โ ์๋ ์ ํ ์ฌ๊ฐ (-30%)
- ๋์ผ ์ธ๊ด ๊ฐ์ฒด์์ ํจ๊ณผ ์์
- ๊ตฌํ ๋ณต์ก๋ ๋์
๐ ํจ๊ณผ ์์ฝํ
| ๊ฐ์ ์ฌํญ | ์ ํ๋ ํฅ์ | ์๋ ์ํฅ | ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ | ๊ตฌํ ๋์ด๋ | ๊ถ์ฅ |
|---|---|---|---|---|---|
| Add New Object + YOLO | +36% | +7% | +0.5GB | โญ๏ธโญ๏ธ | โ โ |
| Occlusion + GroundingDINO | +113% | +0.2% | +2-3GB | โญ๏ธโญ๏ธโญ๏ธ | โ |
| Occlusion + DeepSORT | +20% | +10% | +1-2GB | โญ๏ธโญ๏ธโญ๏ธ | โ |
| Occlusion + ByteTrack | +10% | +30% | +1GB | โญ๏ธโญ๏ธโญ๏ธโญ๏ธ | โ |
๐ฏ ์ต์ข ๊ฒฐ๋ก
โ ๊ฐ๋ ฅ ๊ถ์ฅ
Add New Object์ YOLO ํตํฉ
- ์ฆ๊ฐ์ ์ธ UX ๊ฐ์
- ์ต์ ๋น์ฉ์ผ๋ก ์ต๋ ํจ๊ณผ
Occlusion ๋ณต์์ GroundingDINO Fallback
- ์ฅ๊ธฐ Occlusion ๋ฌธ์ ํด๊ฒฐ
- ์๋ ์ํฅ ๊ฑฐ์ ์์
โ ๋น๊ถ์ฅ
- DeepSORT/ByteTrack/StrongSORT ๋ณ๋ ฌ ์ฌ์ฉ
- ๋์ผ ์ธ๊ด ๊ฐ์ฒด์ ํจ๊ณผ ์์
- ์๋ ์ ํ ์ฌ๊ฐ
์์ฑ์: AI Assistant
๊ฒํ ๊ธฐ์ค: ์ ํ๋, ์๋, ๋ฉ๋ชจ๋ฆฌ, ๊ตฌํ ๋ณต์ก๋, ROI