ytcollector 1.0.8__py3-none-any.whl → 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ytcollector/__init__.py +36 -11
- ytcollector/analyzer.py +239 -0
- ytcollector/cli.py +186 -218
- ytcollector/config.py +79 -61
- ytcollector/dataset_builder.py +71 -0
- ytcollector/downloader.py +315 -470
- ytcollector/utils.py +116 -134
- ytcollector-1.1.1.dist-info/METADATA +207 -0
- ytcollector-1.1.1.dist-info/RECORD +12 -0
- ytcollector-1.1.1.dist-info/entry_points.txt +4 -0
- {ytcollector-1.0.8.dist-info → ytcollector-1.1.1.dist-info}/top_level.txt +0 -1
- config/settings.py +0 -39
- ytcollector/verifier.py +0 -187
- ytcollector-1.0.8.dist-info/METADATA +0 -105
- ytcollector-1.0.8.dist-info/RECORD +0 -12
- ytcollector-1.0.8.dist-info/entry_points.txt +0 -2
- {ytcollector-1.0.8.dist-info → ytcollector-1.1.1.dist-info}/WHEEL +0 -0
ytcollector/verifier.py
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
YOLO-World Verifier Module
|
|
3
|
-
YOLO-World 기반 객체 탐지 및 클래스 검증
|
|
4
|
-
"""
|
|
5
|
-
import json
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import List, Dict, Optional
|
|
8
|
-
import logging
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
|
|
11
|
-
import cv2
|
|
12
|
-
from tqdm import tqdm
|
|
13
|
-
|
|
14
|
-
# Updated imports for new package structure
|
|
15
|
-
from .config import (
|
|
16
|
-
YOLO_MODEL,
|
|
17
|
-
CONFIDENCE_THRESHOLD,
|
|
18
|
-
FRAME_SAMPLE_RATE,
|
|
19
|
-
TASK_CLASSES,
|
|
20
|
-
)
|
|
21
|
-
from .utils import get_report_path
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger(__name__)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class YOLOWorldVerifier:
|
|
27
|
-
"""YOLO-World 기반 영상 검증 클래스"""
|
|
28
|
-
|
|
29
|
-
def __init__(self, task_type: str, base_dir: Path = None, model_name: str = YOLO_MODEL):
|
|
30
|
-
self.task_type = task_type
|
|
31
|
-
self.base_dir = base_dir or Path.cwd()
|
|
32
|
-
self.model_name = model_name
|
|
33
|
-
self.model = None
|
|
34
|
-
self.classes = TASK_CLASSES.get(task_type, [])
|
|
35
|
-
|
|
36
|
-
if not self.classes:
|
|
37
|
-
raise ValueError(f"Unknown task type: {task_type}")
|
|
38
|
-
|
|
39
|
-
def load_model(self):
|
|
40
|
-
"""YOLO-World 모델 로드"""
|
|
41
|
-
if self.model is None:
|
|
42
|
-
from ultralytics import YOLOWorld
|
|
43
|
-
|
|
44
|
-
logger.info(f"Loading YOLO-World model: {self.model_name}")
|
|
45
|
-
self.model = YOLOWorld(self.model_name)
|
|
46
|
-
|
|
47
|
-
logger.info(f"Setting classes for {self.task_type}: {self.classes}")
|
|
48
|
-
self.model.set_classes(self.classes)
|
|
49
|
-
|
|
50
|
-
return self.model
|
|
51
|
-
|
|
52
|
-
def verify_frame(self, frame) -> List[Dict]:
|
|
53
|
-
"""단일 프레임에서 객체 탐지"""
|
|
54
|
-
model = self.load_model()
|
|
55
|
-
results = model.predict(frame, conf=CONFIDENCE_THRESHOLD, verbose=False)
|
|
56
|
-
|
|
57
|
-
detections = []
|
|
58
|
-
for result in results:
|
|
59
|
-
boxes = result.boxes
|
|
60
|
-
for box in boxes:
|
|
61
|
-
detection = {
|
|
62
|
-
'class_id': int(box.cls[0]),
|
|
63
|
-
'class_name': self.classes[int(box.cls[0])] if int(box.cls[0]) < len(self.classes) else 'unknown',
|
|
64
|
-
'confidence': float(box.conf[0]),
|
|
65
|
-
'bbox': box.xyxy[0].tolist(),
|
|
66
|
-
}
|
|
67
|
-
detections.append(detection)
|
|
68
|
-
|
|
69
|
-
return detections
|
|
70
|
-
|
|
71
|
-
def verify_video(
|
|
72
|
-
self,
|
|
73
|
-
video_path: Path,
|
|
74
|
-
sample_rate: int = FRAME_SAMPLE_RATE
|
|
75
|
-
) -> Dict:
|
|
76
|
-
"""영상 전체 검증"""
|
|
77
|
-
logger.info(f"Verifying video: {video_path}")
|
|
78
|
-
|
|
79
|
-
cap = cv2.VideoCapture(str(video_path))
|
|
80
|
-
|
|
81
|
-
if not cap.isOpened():
|
|
82
|
-
raise ValueError(f"Cannot open video: {video_path}")
|
|
83
|
-
|
|
84
|
-
fps = cap.get(cv2.CAP_PROP_FPS)
|
|
85
|
-
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
86
|
-
duration = total_frames / fps if fps > 0 else 0
|
|
87
|
-
|
|
88
|
-
frame_results = []
|
|
89
|
-
detection_count = 0
|
|
90
|
-
frames_with_detection = 0
|
|
91
|
-
frame_idx = 0
|
|
92
|
-
|
|
93
|
-
pbar = tqdm(total=total_frames // sample_rate, desc="Verifying")
|
|
94
|
-
|
|
95
|
-
while True:
|
|
96
|
-
ret, frame = cap.read()
|
|
97
|
-
if not ret:
|
|
98
|
-
break
|
|
99
|
-
|
|
100
|
-
if frame_idx % sample_rate == 0:
|
|
101
|
-
detections = self.verify_frame(frame)
|
|
102
|
-
|
|
103
|
-
if detections:
|
|
104
|
-
frames_with_detection += 1
|
|
105
|
-
detection_count += len(detections)
|
|
106
|
-
|
|
107
|
-
frame_results.append({
|
|
108
|
-
'frame_idx': frame_idx,
|
|
109
|
-
'timestamp_sec': frame_idx / fps if fps > 0 else 0,
|
|
110
|
-
'detections': detections,
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
pbar.update(1)
|
|
114
|
-
|
|
115
|
-
frame_idx += 1
|
|
116
|
-
|
|
117
|
-
cap.release()
|
|
118
|
-
pbar.close()
|
|
119
|
-
|
|
120
|
-
sampled_frames = max(1, total_frames // sample_rate)
|
|
121
|
-
detection_rate = frames_with_detection / sampled_frames
|
|
122
|
-
|
|
123
|
-
result = {
|
|
124
|
-
'video_path': str(video_path),
|
|
125
|
-
'task_type': self.task_type,
|
|
126
|
-
'classes': self.classes,
|
|
127
|
-
'summary': {
|
|
128
|
-
'total_frames': total_frames,
|
|
129
|
-
'sampled_frames': sampled_frames,
|
|
130
|
-
'fps': fps,
|
|
131
|
-
'duration_sec': duration,
|
|
132
|
-
'frames_with_detection': frames_with_detection,
|
|
133
|
-
'total_detections': detection_count,
|
|
134
|
-
'detection_rate': detection_rate,
|
|
135
|
-
},
|
|
136
|
-
'frame_results': frame_results,
|
|
137
|
-
'verified_at': datetime.now().isoformat(),
|
|
138
|
-
'model': self.model_name,
|
|
139
|
-
'is_valid': detection_rate > 0.01, # 1% 이상 탐지되면 유효한 것으로 간주 (기존 10%에서 하향)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
logger.info(
|
|
143
|
-
f"Verification complete: {frames_with_detection}/{sampled_frames} frames "
|
|
144
|
-
f"({detection_rate:.1%}) with {self.task_type} detected"
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
return result
|
|
148
|
-
|
|
149
|
-
def save_report(self, result: Dict, output_path: Optional[Path] = None) -> Path:
|
|
150
|
-
"""검증 결과 JSON 저장"""
|
|
151
|
-
if output_path is None:
|
|
152
|
-
video_name = Path(result['video_path']).stem
|
|
153
|
-
output_path = get_report_path(self.base_dir, self.task_type, video_name)
|
|
154
|
-
|
|
155
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
|
156
|
-
json.dump(result, f, ensure_ascii=False, indent=2)
|
|
157
|
-
|
|
158
|
-
logger.info(f"Report saved to: {output_path}")
|
|
159
|
-
return output_path
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def verify_clip(video_path: Path, task_type: str, base_dir: Path = None) -> Dict:
|
|
163
|
-
"""클립 검증 헬퍼 함수"""
|
|
164
|
-
verifier = YOLOWorldVerifier(task_type, base_dir)
|
|
165
|
-
result = verifier.verify_video(video_path)
|
|
166
|
-
verifier.save_report(result)
|
|
167
|
-
return result
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def batch_verify(video_dir: Path, task_type: str, base_dir: Path = None) -> List[Dict]:
|
|
171
|
-
"""디렉토리 내 모든 영상 일괄 검증"""
|
|
172
|
-
verifier = YOLOWorldVerifier(task_type, base_dir)
|
|
173
|
-
results = []
|
|
174
|
-
|
|
175
|
-
video_files = list(video_dir.glob("*.mp4"))
|
|
176
|
-
logger.info(f"Found {len(video_files)} videos to verify")
|
|
177
|
-
|
|
178
|
-
for video_path in video_files:
|
|
179
|
-
try:
|
|
180
|
-
result = verifier.verify_video(video_path)
|
|
181
|
-
verifier.save_report(result)
|
|
182
|
-
results.append(result)
|
|
183
|
-
except Exception as e:
|
|
184
|
-
logger.error(f"Failed to verify {video_path}: {e}")
|
|
185
|
-
results.append({'video_path': str(video_path), 'error': str(e), 'is_valid': False})
|
|
186
|
-
|
|
187
|
-
return results
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ytcollector
|
|
3
|
-
Version: 1.0.8
|
|
4
|
-
Summary: SBS 데이터셋 수집기
|
|
5
|
-
Requires-Python: >=3.9
|
|
6
|
-
Description-Content-Type: text/markdown
|
|
7
|
-
Requires-Dist: yt-dlp>=2024.1.0
|
|
8
|
-
Requires-Dist: ultralytics>=8.1.0
|
|
9
|
-
Requires-Dist: opencv-python>=4.9.0
|
|
10
|
-
Requires-Dist: tqdm>=4.66.0
|
|
11
|
-
Requires-Dist: imageio-ffmpeg>=0.4.9
|
|
12
|
-
|
|
13
|
-
# Downloader: SBS 데이터셋 수집기
|
|
14
|
-
|
|
15
|
-
YouTube 영상에서 얼굴, 자동차 번호판, 타투, 텍스트 자막을 수집하고 YOLO-World로 검증하는 자동화 파이프라인입니다.
|
|
16
|
-
|
|
17
|
-
## 1. 설치 및 시작
|
|
18
|
-
|
|
19
|
-
**필수 요구사항:**
|
|
20
|
-
- Python 3.8 이상
|
|
21
|
-
- FFmpeg (pip 설치 시 `imageio-ffmpeg`를 통해 자동으로 구성되나, 실패 시 Mac: `brew install ffmpeg` 설치 권장)
|
|
22
|
-
|
|
23
|
-
**설치:**
|
|
24
|
-
```bash
|
|
25
|
-
cd 260203_sbs_dataset
|
|
26
|
-
pip install -e .
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
**프로젝트 초기화 (최초 1회):**
|
|
30
|
-
필요한 폴더(`urls/`, `video/`)를 생성합니다.
|
|
31
|
-
```bash
|
|
32
|
-
ytcollector init
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 2. URL 관리
|
|
38
|
-
|
|
39
|
-
다운로드할 YouTube 영상 리스트는 텍스트 파일로 관리합니다.
|
|
40
|
-
**파일 위치:** `urls/<태스크이름>/youtube_url.txt` (예: `urls/face/youtube_url.txt`)
|
|
41
|
-
|
|
42
|
-
**파일 형식 (CSV 스타일):**
|
|
43
|
-
```text
|
|
44
|
-
task_type,url,timestamp_min,timestamp_sec,description
|
|
45
|
-
face,https://www.youtube.com/watch?v=VIDEO_ID,2,30,설명
|
|
46
|
-
```
|
|
47
|
-
* `init` 명령 실행 시 샘플 내용이 포함된 파일이 자동 생성됩니다.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 3. 사용법 (다운로드 & 검증)
|
|
52
|
-
|
|
53
|
-
이 프로그램은 **다운로드 → YOLO 검증 → (성공 시) 저장** 순서로 작동합니다. 타겟 객체가 없으면 자동으로 삭제됩니다.
|
|
54
|
-
|
|
55
|
-
### 주요 명령어 예시
|
|
56
|
-
안정적으로 하나씩 다운로드하거나, 여러 태스크를 동시에 처리하고 목표 수량을 설정할 수 있습니다.
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
# 기본 다운로드 (태스크 하나)
|
|
60
|
-
ytcollector download --task face
|
|
61
|
-
|
|
62
|
-
# 여러 태스크 동시에 실행 및 목표 수량(-n) 설정
|
|
63
|
-
ytcollector download --task face tattoo text -n 100
|
|
64
|
-
|
|
65
|
-
# 🚀 Fast 모드 (병렬 다운로드)
|
|
66
|
-
ytcollector download --task face --fast
|
|
67
|
-
```
|
|
68
|
-
* **방화벽 우회**: 랜덤 딜레이(1~3초)가 적용되어 차단을 방지합니다.
|
|
69
|
-
* **에러 무시**: 중간에 에러가 나도 멈추지 않고 다음 영상으로 넘어갑니다.
|
|
70
|
-
|
|
71
|
-
### 저장 파일 규칙
|
|
72
|
-
* **파일명**: `face_0001.mp4`, `face_0002.mp4` ... (순차 번호)
|
|
73
|
-
* **중복 방지**: `download_history.json`에 기록하여, 이미 받은 URL은 중복해서 받지 않습니다.
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## 4. NAS / 네트워크 저장 설정
|
|
78
|
-
|
|
79
|
-
영상을 로컬이 아닌 NAS에 저장하려면 설정을 수정하세요.
|
|
80
|
-
OS(Windows/Mac)를 자동 감지하여 적절한 경로를 사용합니다.
|
|
81
|
-
|
|
82
|
-
**설정 파일 수정:** `ytcollector/config.py`
|
|
83
|
-
```python
|
|
84
|
-
# Windows 예시
|
|
85
|
-
NAS_PATH_WINDOWS = r"\\NAS_SERVER_IP\Data\Private Dataset\..."
|
|
86
|
-
|
|
87
|
-
# Mac 예시 (/Volumes로 마운트된 경로 확인 필요)
|
|
88
|
-
NAS_PATH_MAC = "/Volumes/Data/Private Dataset/..."
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## 5. 전체 명령어 목록
|
|
94
|
-
|
|
95
|
-
| 명령어 | 설명 | 예시 |
|
|
96
|
-
|--------|------|------|
|
|
97
|
-
| `init` | 프로젝트 초기화 | `ytcollector init` |
|
|
98
|
-
| `download` | 대량 다운로드 (여러 태스크, 개수 제한 가능) | `ytcollector download --task face tattoo -n 50` |
|
|
99
|
-
| `download-single` | URL 1개만 테스트 다운로드 | `ytcollector download-single --task face -u ...` |
|
|
100
|
-
| `verify` | 수동 YOLO 검증 (기존 파일) | `ytcollector verify --task face` |
|
|
101
|
-
| `list-tasks` | 지원하는 태스크 목록 확인 | `ytcollector list-tasks` |
|
|
102
|
-
|
|
103
|
-
## 6. 문제 해결
|
|
104
|
-
* **검증 실패가 너무 많음**: `config.py`에서 `CONFIDENCE_THRESHOLD` (기본 0.25) 값을 낮춰보세요.
|
|
105
|
-
* **IP 차단**: Fast 모드 사용 중 YouTube 접근이 막히면 잠시 기다린 후 다시 시도하거나 딜레이 시간을 늘리세요.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
config/settings.py,sha256=RcK41kaUC0zam5SsdXfb7u_qjM_TlJDa0a8hC_MGacQ,1197
|
|
2
|
-
ytcollector/__init__.py,sha256=Alq_JsvEi2RZusFB7-AAhzNnMFyBbHNU6NJR_AxDgs4,365
|
|
3
|
-
ytcollector/cli.py,sha256=Fg8GA7gQu3s26XgUg1NBEZuNKQpZLRrnp2s5bqhed8M,8636
|
|
4
|
-
ytcollector/config.py,sha256=ez9flxTbjmdiJB7_IYivWd9xaRfJ8CLBhPYRedLi8Mk,2323
|
|
5
|
-
ytcollector/downloader.py,sha256=QrqwG7PpoJZymKnOOEj1aqZudE6qY3AV4fiuo1anoGk,19901
|
|
6
|
-
ytcollector/utils.py,sha256=gInDx6adV-SfQ2SH5_i8w1gvYL-Nsmz1e1W__gCdVH8,4654
|
|
7
|
-
ytcollector/verifier.py,sha256=OHkAyUF3J6wFqKSa7RYT9Z6-W_lwTXarBEF9xqxNthA,6366
|
|
8
|
-
ytcollector-1.0.8.dist-info/METADATA,sha256=a0t8IXj-xlqbsy_D5EBNemVRi-Z52gJAmu2VJlep0Cc,3727
|
|
9
|
-
ytcollector-1.0.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
-
ytcollector-1.0.8.dist-info/entry_points.txt,sha256=PoanZbxogGnV4tLcZZkla0Yh7OvPtqcukDYr563w5RA,53
|
|
11
|
-
ytcollector-1.0.8.dist-info/top_level.txt,sha256=TVfBZHJgYRfSSTgLJELvOoMA55qR8kWuxtiIaItwzIQ,19
|
|
12
|
-
ytcollector-1.0.8.dist-info/RECORD,,
|
|
File without changes
|