프레임 예산 32장으로 2시간짜리 영상을 “이해/생성”하기: Video AI 파이프라인 설계 가이드
2026년 8월 시점의 video AI는 “모델이 곧 비디오를 통째로 본다”기보다, 긴 비디오를 제한된 token/window에 맞게 ‘증거(evidence)’로 압축해 넣는 파이프라인 싸움에 가깝습니다.
들어가며
2026년 8월 시점의 video AI는 “모델이 곧 비디오를 통째로 본다”기보다, 긴 비디오를 제한된 token/window에 맞게 ‘증거(evidence)’로 압축해 넣는 파이프라인 싸움에 가깝습니다. 특히 Long Video Understanding(LVU)에서는 중요한 정보가 드문드문 등장하고, 나머지는 중복 프레임인 경우가 많아 uniform sampling(예: 매 N초 1장) 으로는 비용만 쓰고 정답 근거를 놓치기 쉽습니다. 이 문제의식은 단계적 추론/도구 호출로 증거를 모으는 VideoMind 류의 “training-free orchestrator” 접근에서 명확히 드러납니다.1
언제 쓰면 좋은가
- 고객센터/보안/제조 등: “긴 영상에서 특정 사건이 언제/어디서 일어났는지”를 찾아 리포팅(temporal grounding 포함)해야 할 때
- 미디어/교육: 챕터링, 하이라이트 추출, 자막+장면 요약처럼 검색·요약·근거 제시가 요구될 때
- 생성(video generation): 텍스트만으로 만들기보다 레퍼런스 프레임/스타일/샷 리스트를 만들어 모델에 주는 워크플로우가 필요한 때(생성 자체보다 “생성 입력을 만드는 이해 파이프라인”이 핵심)
언제 쓰면 안 되는가
- 프레임 단위 정밀 판독(예: 의료/미세 결함): MLLM 기반 요약 파이프라인은 결정적 근거 누락 리스크가 큼 → 전통 CV/전용 detector 우선
- 실시간 초저지연 제어: 1fps 저장/처리 같은 제약이 있는 API는 latency·정밀도 모두 애매할 수 있음(아래 Gemini File API 특성 참고)2
- “정답 100%”가 필요한 컴플라이언스: 샘플링/요약은 본질적으로 손실 압축이며, 후처리 검증 루프가 없으면 위험
🔧 핵심 개념
1) “비디오 이해”는 결국 프레임 토큰 예산 관리
대부분의 MLLM은 비디오를 그대로 ingest하기보다, 내부적으로 프레임→비전 인코더→visual tokens→LLM 컨텍스트로 들어갑니다. 여기서 병목은:
- 긴 영상 = 프레임 수 폭증 = 토큰 폭증
- LLM 컨텍스트는 제한적
- 그래서 “무엇을 넣고 무엇을 버릴지”가 성능/비용을 좌우
최근 연구들은 공통적으로 keyframe/segment selection을 ‘질의(query) 조건부’로 바꾸거나, 멀티턴으로 필요한 부분만 추가로 보는 방향으로 갑니다. 예를 들어 Video-MTR은 “매 턴 어떤 구간을 더 볼지”를 강화학습적 의사결정으로 다루고,3 VideoMind는 쿼리를 하위 쿼리로 쪼개 도구/모드를 바꿔가며 증거를 수집합니다.1
2) Uniform sampling vs Query-aware sampling
- Uniform sampling: 구현 쉽지만, 이벤트가 희소하면 실패 확률↑
- Query-aware keyframe selection: “지금 질문에 필요한 장면”을 우선 선택
3) 운영 관점: “4단계 파이프라인”이 사실상 표준형
현업 문서/레퍼런스에서도 비슷한 형태가 반복됩니다. 1) Asset ingestion(다운로드/트랜스코드/메타 추출) 2) Frame extraction(규칙 기반 + 장면 전환 기반 + 질의 기반) 3) Frame analysis(VLM/MLLM으로 캡션/객체/행동/텍스트(OCR) 추출) 4) Report/QA generation + Evaluation(근거 링크/타임코드 포함, self-check)
예를 들어 OCI 솔루션 문서도 “Knowledge Retrieval / Frame Analysis / Report Generation / Evaluation”처럼 도구를 분리한 agentic pipeline을 제시하고, 프레임 추출을 OpenCV로 configurable interval 처리하는 식으로 설명합니다.6
Gemini Video understanding 문서도 비디오를 시각+오디오 스트림으로 처리하며, File API로 처리 시 “1fps로 저장” 같은 특성을 명시합니다.2
→ 결론: 모델 선택보다 파이프라인 설계가 먼저입니다.
💻 실전 코드
아래는 “긴 MP4(예: 60~120분)에서 사건 조사 리포트 생성”을 목표로 한 현실적인 예제입니다.
- 입력:
incident.mp4, 조사 질의(예: “사람이 출입금지 구역에 들어간 시점과 근거 프레임”) - 출력: JSON 리포트(타임코드/근거 프레임 경로/요약/신뢰도)
- 핵심: (1) 장면 전환 기반 후보 생성 → (2) query-aware 재랭킹으로 예산 K장 확정 → (3) VLM 분석 → (4) 최종 리포트 + 간단 검증
모델 호출은 환경마다 다르니, 여기서는
analyze_frame()을 “Gemini Video understanding 또는 내부 VLM 호출”로 교체 가능한 형태로 작성했습니다. Gemini는 비디오 이해 API 가이드를 공식 제공 중입니다.2
1) 셋업
1
2
3
4
5
6
python -m venv .venv
source .venv/bin/activate
pip install opencv-python numpy ffmpeg-python pydantic tqdm
# (선택) 장면 전환 검출을 더 정교하게 하려면 PySceneDetect를 쓰는 경우가 많지만,
# 여기서는 외부 의존을 줄이고 OpenCV 히스토그램 차이로 구현합니다.
2) 파이프라인 코드 (Python)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import os, json, math
from dataclasses import dataclass
from typing import List, Dict, Any, Tuple
import cv2
import numpy as np
from tqdm import tqdm
@dataclass
class FrameCandidate:
t_sec: float
frame_path: str
score: float = 0.0
reason: str = ""
def ensure_dir(p: str):
os.makedirs(p, exist_ok=True)
def extract_candidates_by_scene_change(
video_path: str,
out_dir: str,
min_gap_sec: float = 2.0,
hist_diff_threshold: float = 0.45,
max_candidates: int = 800
) -> List[FrameCandidate]:
"""
1fps 고정이 아니라, 장면 전환(근사) 지점 위주로 후보 프레임을 뽑는다.
- 긴 비디오에서 uniform sampling 대비 "정보 밀도"가 좋아지는 경우가 많음.
"""
ensure_dir(out_dir)
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"Cannot open video: {video_path}")
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
prev_hist = None
last_pick_t = -1e9
candidates: List[FrameCandidate] = []
pbar = tqdm(total=total, desc="scan for scene-ish changes", unit="frame")
idx = 0
while True:
ok, frame = cap.read()
if not ok:
break
t_sec = idx / fps
# 다운샘플: 모든 프레임을 다 보면 비용이 큼 → 3fps 정도로 스캔
if idx % int(max(1, round(fps / 3.0))) != 0:
idx += 1
pbar.update(1)
continue
small = cv2.resize(frame, (224, 224))
hsv = cv2.cvtColor(small, cv2.COLOR_BGR2HSV)
hist = cv2.calcHist([hsv], [0, 1], None, [32, 32], [0, 180, 0, 256])
cv2.normalize(hist, hist)
if prev_hist is not None:
diff = cv2.compareHist(prev_hist, hist, cv2.HISTCMP_BHATTACHARYYA)
if diff >= hist_diff_threshold and (t_sec - last_pick_t) >= min_gap_sec:
path = os.path.join(out_dir, f"cand_{t_sec:.2f}.jpg")
cv2.imwrite(path, frame, [int(cv2.IMWRITE_JPEG_QUALITY), 90])
candidates.append(FrameCandidate(t_sec=t_sec, frame_path=path, reason=f"scene_diff={diff:.3f}"))
last_pick_t = t_sec
if len(candidates) >= max_candidates:
break
prev_hist = hist
idx += 1
pbar.update(1)
cap.release()
pbar.close()
return candidates
def query_aware_rerank(
candidates: List[FrameCandidate],
query: str,
budget_k: int = 32
) -> List[FrameCandidate]:
"""
실제로는 CLIP/text-image embedding 또는 작은 VLM으로
'query 관련성'을 빠르게 점수화해서 상위 K개를 선택한다.
여기서는 데모로:
- scene change reason이 있는 후보를 기본 점수로
- query 키워드(예: "no entry", "restricted", "person")에 대한
OCR/객체검출 결과를 쓰는 게 정석이지만, 예제는 stub.
"""
keywords = [w.lower() for w in query.replace(",", " ").split()]
for c in candidates:
base = 1.0
# 장면 전환 diff가 클수록(텍스트에 포함돼 있음) 점수 조금 가산
if "scene_diff=" in c.reason:
try:
diff = float(c.reason.split("scene_diff=")[1])
base += min(1.0, diff)
except:
pass
# "시간 다양성"을 위해 너무 근접한 후보는 약간 패널티(간단히 t_sec 기반)
diversity = 0.2 * math.sin(c.t_sec / 13.0)
c.score = base + diversity
c.reason = c.reason + f" | kw={len(keywords)}"
top = sorted(candidates, key=lambda x: x.score, reverse=True)[:budget_k]
return sorted(top, key=lambda x: x.t_sec)
def analyze_frame(frame_path: str, query: str) -> Dict[str, Any]:
"""
여기를 실제 호출로 교체:
- Gemini Video understanding/vision 모델에 이미지 + query를 넣고
(1) 프레임 설명 (2) query 관련 증거 (3) 객체/행동 (4) 텍스트(OCR) 등을 구조화 출력(JSON)
Gemini는 비디오를 시각+오디오로 이해하는 API 문서를 제공하며,
파일 처리 시 1fps 저장 같은 제약/특성이 있음.[^2]
"""
# --- stub result ---
return {
"frame_path": frame_path,
"caption": "A hallway view with a door and a person-like silhouette (stub).",
"evidence": "Potential human presence near doorway (stub).",
"confidence": 0.42
}
def build_report(video_path: str, query: str) -> Dict[str, Any]:
cand_dir = "./_candidates"
candidates = extract_candidates_by_scene_change(video_path, cand_dir)
selected = query_aware_rerank(candidates, query, budget_k=32)
analyses = []
for c in tqdm(selected, desc="analyze selected frames"):
a = analyze_frame(c.frame_path, query)
a["t_sec"] = c.t_sec
a["selection_reason"] = c.reason
analyses.append(a)
# 최종 요약: 실제로는 LLM에 analyses(구조화 JSON) + query를 넣고
# 타임라인 형태로 합치며, 근거 프레임을 반드시 링크하게 한다.
incidents = [x for x in analyses if x["confidence"] >= 0.6]
return {
"video": video_path,
"query": query,
"num_candidates": len(candidates),
"num_selected": len(selected),
"high_conf_evidence": incidents,
"all_analyses": analyses
}
if __name__ == "__main__":
report = build_report(
video_path="incident.mp4",
query="Find when a person enters a restricted area and provide evidence frames."
)
with open("report.json", "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print("Wrote report.json")
예상 출력(요약)
./_candidates/에 장면 전환 후보 프레임 JPEG 수백 장report.json에:num_selected: 32- 각 프레임별
t_sec,caption,evidence,confidence - (확장 시) “사건 타임라인” 요약 + 근거 프레임 리스트
⚡ 실전 팁 & 함정
Best Practice
1) Two-stage(cheap→expensive)로 설계
- 1차: 저렴한 특징(히스토그램/모션/자막 타임라인)으로 후보 축소
- 2차: VLM/MLLM로 고비용 분석
이 구조가 LVU에서 반복적으로 등장하는 이유는 “토큰/프레임 예산”이 병목이기 때문입니다.1
2) Query-aware selection은 ‘정확도’보다 ‘근거 회수율’ 개선에 집중 정답 자체보다 “정답에 필요한 장면을 모델 컨텍스트에 넣었는가”가 더 1차적인 실패 원인입니다. 키프레임 선택을 연구하는 흐름(FOCUS 등)도 바로 이 지점을 겨냥합니다.4
3) 오디오/자막을 프레임 선택에 섞어라 Gemini의 비디오 이해는 오디오+비주얼을 같이 처리하는 문서 흐름을 명시합니다.2
실무에서는 ASR 타임라인에서 키워드 구간을 찾고, 그 주변 N초만 dense frame으로 뽑는 게 비용 대비 효과가 큽니다.
흔한 함정/안티패턴
- “1fps로 다 넣자”: File API가 1fps로 저장되는 특성이 있더라도2, 2시간이면 7200프레임입니다. 결국 또 줄여야 하며, 줄이는 로직이 없으면 성능/비용이 폭발합니다.
- 프레임만 보고 결론 내기: 사건성 판단은 오디오/텍스트(표지판, 자막, OCR)에 크게 의존합니다. 프레임 캡션만으로는 오탐이 많습니다.
- “요약 결과만 저장”: 재현/감사(audit) 요구가 오면 망합니다. 최소한 “근거 프레임 경로 + 타임코드 + 모델 입력 프롬프트/버전”은 저장하세요.
비용/성능/안정성 트레이드오프
- 프레임 수(K)↑: recall↑, 비용↑, 컨텍스트 산만해져 precision↓ 가능
- 선택 모듈을 training-free로(FOCUS류): 도입 쉽지만, 도메인 특화 사건(제조 결함 등)에서는 전용 detector가 더 낫습니다.4
- 멀티턴 에이전트형(VideoMind/Video-MTR류): 어려운 질의에 강하지만, 시스템 복잡도/관측가능성(observability)/디버깅 비용이 올라갑니다.1
🚀 마무리
핵심은 “비디오를 이해하는 모델”보다 비디오를 ‘증거 묶음’으로 변환하는 프레임 분석 파이프라인입니다. 2026년 8월의 최신 흐름은 (1) query-aware keyframe/segment selection, (2) 멀티턴/에이전트식 증거 수집, (3) 오디오·자막·OCR을 결합한 멀티모달 근거화로 수렴하고 있습니다.1
도입 판단 기준
- “긴 영상에서 근거 기반 QA/리포트”가 목표라면: 위 4단계 파이프라인 + 2-stage 샘플링을 먼저 구축
- “정밀 판독/안전”이 목표라면: MLLM은 보조(설명/검색)로 두고, detector/트래커를 주력으로
- “생성(video generation)”이 목표라면: 생성 모델 선택 이전에 샷 리스트/키프레임/스타일 보드를 만드는 이해 파이프라인이 ROI가 큼(입력 품질이 출력 품질을 결정)
다음 학습 추천