| # Occlusion ๋ณต์ & Add New Object ๊ฐ์ ์๋๋ฆฌ์ค ๋ถ์ |
|
|
| **์์ฑ์ผ**: 2025-12-17 |
| **๋ถ์ ๋ฒ์**: velocity/occlusion ๋ณต์ + Add New Object ๊ธฐ๋ฅ |
|
|
| --- |
|
|
| ## ๐ ๋ชฉ์ฐจ |
|
|
| 1. [์๋๋ฆฌ์ค 1: Occlusion ๋ณต์์ GroundingDINO/ํธ๋์ปค ํ์ฉ](#์๋๋ฆฌ์ค-1-occlusion-๋ณต์์-groundingdinoํธ๋์ปค-ํ์ฉ) |
| 2. [์๋๋ฆฌ์ค 2: Add New Object์ YOLO ํ์ฉ](#์๋๋ฆฌ์ค-2-add-new-object์-yolo-ํ์ฉ) |
| 3. [์ข
ํฉ ๊ถ์ฅ์ฌํญ](#์ข
ํฉ-๊ถ์ฅ์ฌํญ) |
|
|
| --- |
|
|
| ## ์๋๋ฆฌ์ค 1: Occlusion ๋ณต์์ GroundingDINO/ํธ๋์ปค ํ์ฉ |
|
|
| ### 1.1 ํ์ฌ Occlusion ๋ณต์ ๋ก์ง |
|
|
| **์์น**: `_ensure_object_persistence()` (app.py: L2586-3051) |
|
|
| ```python |
| # ํ์ฌ ๋ฐฉ์ |
| 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** โญ๏ธโญ๏ธโญ๏ธโญ๏ธ |
|
|
| **๊ฐ๋
:** |
| ```python |
| # 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** โญ๏ธโญ๏ธ |
|
|
| **๊ฐ๋
:** |
| ```python |
| # 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 ๋ณ๋ ฌ** โญ๏ธ |
|
|
| **๊ฐ๋
:** |
| ```python |
| # 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) |
|
|
| **๊ตฌํ ์ฐ์ ์์:** |
|
|
| ```python |
| # 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) |
|
|
| ```python |
| # ํ์ฌ ๋ฐฉ์ (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 ์ ๋ฐ ๋ง์คํฌ** โญ๏ธโญ๏ธโญ๏ธโญ๏ธโญ๏ธ |
|
|
| **๊ฐ๋
:** |
| ```python |
| 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 ๋์ฒด)** โญ๏ธ |
|
|
| **๊ฐ๋
:** |
| ```python |
| # YOLO๋ก ํ์ง โ ๋ง์คํฌ ์์ด bbox๋ง ์ถ์ |
| yolo_box = yolo_model(frame, click=(x, y)) |
| # ByteTrack์ผ๋ก ์ถ์ |
| ``` |
|
|
| **๋จ์ :** |
| - โ Pixel-level ๋ง์คํฌ ์์ โ ํ์ฌ ์์คํ
๊ณผ ๋ถ์ผ์น |
| - โ ๊ธฐ์กด CSV ํ์ (contour, center) ํธํ ๋ถ๊ฐ |
| - โ Trails ๋ ๋๋ง ๋ถ๊ฐ |
|
|
| **ํ๊ฐ**: โญ๏ธ - ํ์ฌ ์์คํ
๊ณผ ๋ง์ง ์์ |
|
|
| --- |
|
|
| ### 2.3 ์ต์ข
๊ถ์ฅ: YOLO โ SAM3 (์๋๋ฆฌ์ค A) |
|
|
| **๊ตฌํ:** |
|
|
| ```python |
| 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 | โญ๏ธโญ๏ธโญ๏ธโญ๏ธ | โ | |
|
|
| --- |
|
|
| ## ๐ฏ ์ต์ข
๊ฒฐ๋ก |
|
|
| ### โ
๊ฐ๋ ฅ ๊ถ์ฅ |
|
|
| 1. **Add New Object์ YOLO ํตํฉ** |
| - ์ฆ๊ฐ์ ์ธ UX ๊ฐ์ |
| - ์ต์ ๋น์ฉ์ผ๋ก ์ต๋ ํจ๊ณผ |
|
|
| 2. **Occlusion ๋ณต์์ GroundingDINO Fallback** |
| - ์ฅ๊ธฐ Occlusion ๋ฌธ์ ํด๊ฒฐ |
| - ์๋ ์ํฅ ๊ฑฐ์ ์์ |
|
|
| ### โ ๋น๊ถ์ฅ |
|
|
| - DeepSORT/ByteTrack/StrongSORT ๋ณ๋ ฌ ์ฌ์ฉ |
| - ๋์ผ ์ธ๊ด ๊ฐ์ฒด์ ํจ๊ณผ ์์ |
| - ์๋ ์ ํ ์ฌ๊ฐ |
|
|
| --- |
|
|
| **์์ฑ์**: AI Assistant |
| **๊ฒํ ๊ธฐ์ค**: ์ ํ๋, ์๋, ๋ฉ๋ชจ๋ฆฌ, ๊ตฌํ ๋ณต์ก๋, ROI |
|
|