## 비디오 어노테이터 전용 스키마
### 지원 도구(Tools)
| Tool Code | 설명 | 특이사항 |
| -------------- | -------------------- | ------------------------------------------------- |
| `segmentation` | 비디오 구간 분리 | - |
| `bounding_box` | 프레임별 바운딩 박스 | 키프레임 보간 지원, `bakeInterpolatedFrames` 처리 |
>
### 비디오 전용 extra (메타데이터)
| 키 | 타입 | 설명 |
| --- | --- | --- |
| frameCount | number | 전체 프레임 수 |
| frameRate | number | FPS(프레임/초) |
| durationSeconds | number | 영상 길이(초) |
| width / height | number | 원본 해상도(px) |
> 필요 시 카메라 정보, 코덱 등 프로젝트별 속성을 자유롭게 추가합니다.
>
#### annotationsData 구조
**Segmentation (구간):**
```json
{
"id": "L3xevRklfm",
"section": {
"startFrame": 17,
"endFrame": 29
}
}
```
**Bounding Box (프레임별 좌표 + 구간):**
```json
{
"id": "bbox_001",
"section": {
"startFrame": 10,
"endFrame": 50
},
"frames": {
"10": { "bbox": { "x": 100, "y": 100, "width": 50, "height": 50 } },
"30": { "bbox": { "x": 150, "y": 120, "width": 55, "height": 52 } },
"50": { "bbox": { "x": 200, "y": 140, "width": 60, "height": 55 } }
},
"segments": [{ "start": 10, "end": 50 }]
}
```
### beforeAction Hook
```javascript
// 키프레임 보간 데이터를 실제 프레임 데이터로 변환
beforeAction: (annotatorData, variables) => {
for (const annotation of annotationData) {
if (annotationData.tool === 'bounding_box') {
bakeInterpolatedFrames(annotation)
}
}
return dataGrooming(annotatorData, variables)
}
```
### 샘플
프레임 구간 기반의 세그멘테이션과 바운딩박스(키프레임 보간 포함) 예제:
```json
{
"extra": {
"video_1": {
"totalFrames": 300,
"fps": 30,
"currentFrame": 50
}
},
"relations": {
"video_1": []
},
"annotations": {
"video_1": [
{
"id": "L3xevRklfm",
"tool": "segmentation",
"isLocked": false,
"isVisible": true,
"isValid": true,
"classification": {
"class": "장면_전환",
"scene_type": "outdoor"
},
"label": ["장면_전환"]
},
{
"id": "Yf3yNlPxfj",
"tool": "segmentation",
"isLocked": false,
"isVisible": true,
"isValid": true,
"classification": {
"class": "액션",
"action_type": "walking"
},
"label": ["액션"]
},
{
"id": "bbox_video_001",
"tool": "bounding_box",
"isLocked": false,
"isVisible": true,
"isValid": true,
"classification": {
"class": "사람",
"person_id": "person_1"
},
"label": ["사람"]
},
{
"id": "bbox_video_002",
"tool": "bounding_box",
"isLocked": false,
"isVisible": true,
"isValid": true,
"classification": {
"class": "자동차",
"vehicle_type": "sedan"
},
"label": ["자동차"]
}
]
},
"annotationsData": {
"video_1": [
{
"id": "L3xevRklfm",
"section": {
"startFrame": 17,
"endFrame": 89
}
},
{
"id": "Yf3yNlPxfj",
"section": {
"startFrame": 90,
"endFrame": 150
}
},
{
"id": "bbox_video_001",
"section": {
"startFrame": 10,
"endFrame": 100
},
"frames": {
"10": {
"bbox": { "x": 100, "y": 150, "width": 50, "height": 120 },
"isKeyframe": true
},
"30": {
"bbox": { "x": 150, "y": 145, "width": 52, "height": 122 },
"isKeyframe": true
},
"60": {
"bbox": { "x": 220, "y": 140, "width": 55, "height": 125 },
"isKeyframe": true
},
"100": {
"bbox": { "x": 350, "y": 130, "width": 58, "height": 128 },
"isKeyframe": true
}
},
"segments": [{ "start": 10, "end": 100 }]
},
{
"id": "bbox_video_002",
"section": {
"startFrame": 50,
"endFrame": 200
},
"frames": {
"50": {
"bbox": { "x": 400, "y": 200, "width": 80, "height": 60 },
"isKeyframe": true
},
"100": {
"bbox": { "x": 300, "y": 195, "width": 85, "height": 62 },
"isKeyframe": true
},
"150": {
"bbox": { "x": 180, "y": 190, "width": 90, "height": 65 },
"isKeyframe": true
},
"200": {
"bbox": { "x": 50, "y": 185, "width": 95, "height": 68 },
"isKeyframe": true
}
},
"segments": [{ "start": 50, "end": 200 }]
}
]
},
"annotationGroups": {
"video_1": [
{
"id": "RDdQzKr5B3",
"tool": "annotationGroup",
"isLocked": false,
"isValid": true,
"annotationList": [
{ "annotationId": "L3xevRklfm", "children": [] },
{ "annotationId": "Yf3yNlPxfj", "children": [] }
],
"classification": {
"class": "장면_그룹"
}
},
{
"id": "q5VHfub1YF",
"tool": "annotationGroup",
"isLocked": false,
"isValid": true,
"annotationList": [
{ "annotationId": "bbox_video_001", "children": [] },
{ "annotationId": "bbox_video_002", "children": [] }
],
"classification": {
"class": "객체_그룹",
"interaction": "approaching"
}
}
]
},
"assignmentId": 3087
}
```