Motion Estimation and Compensation
모션 추정과 보상 — 시간적 중복의 핵심
프레임 사이의 시간적 중복을 활용하는 핵심 기술. 모션 벡터를 얼마나 정확하고 빠르게 찾느냐가 인터 예측의 품질을 결정한다.
Overview
비디오의 압축률이 폭증하는 결정적 이유는 모션 보상이다. I-프레임만으로는 1/10도 어렵지만 P/B-프레임이 들어가면 1/100~1/300이 가능. 그 마법의 중심에 모션 추정이 있다.
이번 장은 모션 벡터가 어떻게 만들어지고, 왜 인코더의 연산 비용 80%를 차지하며, 코덱의 진화가 어떻게 이 단계를 더 정교하게 만들어 왔는지 본다.
- 모션 추정과 모션 보상의 정확한 의미와 순서를 안다
- Block matching 알고리즘이 무엇인지 설명할 수 있다
- Sub-pixel 정확도가 왜 필요하고 어떻게 구현되는지 안다
- 인코더의 가장 큰 연산 비용이 이 단계임을 이해한다
- Reference frame 개수가 압축률·연산 비용에 미치는 영향을 안다
Sections
5.1 모션 추정과 모션 보상의 구분
둘은 짝이지만 다른 일이다.
모션 추정 (Motion Estimation) — 인코더만 하는 일. 현재 프레임의 블록 하나를 잡고, 참조 프레임에서 가장 비슷한 블록을 찾는다. 그 위치 차이가 모션 벡터.
모션 보상 (Motion Compensation) — 인코더·디코더 둘 다 하는 일. 모션 벡터를 받아 참조 프레임에서 그 위치의 블록을 가져와 "예측"으로 사용. 잔차 = 현재 - 예측을 계산하고 그것만 압축.
왜 이 구분이 중요한가? 인코더는 모션 추정에서 압도적 시간을 쓰지만, 디코더는 그 결과(모션 벡터)만 받아 모션 보상하면 끝. 그래서 인코딩은 느리고 디코딩은 빠르다. 이게 "라이브 방송엔 H.264, 저장엔 H.265/AV1"의 근거 중 하나다.
코덱 진화는 사실상 "모션 추정을 더 영리하게, 모션 보상을 더 정교하게"의 누적이다.
5.2 Block Matching — 모션 벡터 찾기
가장 단순한 모션 추정 알고리즘이 block matching. 현재 프레임의 한 블록(보통 16×16)을 참조 프레임의 주변 영역(예: ±32 픽셀)에서 모든 위치와 비교, 가장 비슷한 위치를 찾는다.
비슷한 정도를 측정하는 함수: - SAD (Sum of Absolute Differences): 차이 절댓값 합. 빠름. - SATD (Sum of Absolute Transformed Differences): SAD를 Hadamard 변환 후. 더 정확. - SSE (Sum of Squared Errors): 차이 제곱 합.
Full search (모든 위치 다 비교)는 정확하지만 느리다. 32×32 범위면 한 블록당 1024번 비교. 1080p 30fps이면 초당 수십억 번. 비현실적.
그래서 실제 인코더는 빠른 검색 알고리즘을 쓴다: - Diamond search: 다이아몬드 패턴으로 검색 → 빠르지만 local minimum 위험 - Hexagon search: 육각형 패턴 → 균형 - EPZS (Enhanced Predictive Zonal Search): 인접 블록의 모션 벡터로 시작점 예측 → x264·x265 기본
Preset(-preset slow vs -preset ultrafast)이 결정하는 게 바로 이 검색의 철저함이다.
5.3 Sub-pixel 정확도 — 1/4·1/8 픽셀
정수 픽셀 단위 모션 벡터로는 부족하다. 카메라가 0.5픽셀 움직였다면 정수로 표현 못 함. 그래서 sub-pixel 정확도.
- H.264: 1/4 픽셀 정확도 - H.265: 1/4 픽셀 정확도 - AV1: 1/8 픽셀 정확도
0.25 픽셀 위치의 "픽셀 값"이 뭔가? 보간(interpolation)으로 만든다. H.264는 6-tap FIR 필터, H.265는 8-tap, AV1은 8-tap + warping까지.
왜 중요한가: sub-pixel 정확도가 높을수록 더 정확한 예측 → 더 작은 잔차 → 더 좋은 압축. H.264 → AV1로 가면서 압축률 향상의 한 축이 여기다.
비용: 보간 필터를 모든 모션 추정 후보 위치에 적용해야 하니 연산이 폭증. 인코더 비용의 큰 부분.
5.4 Reference Frames — 여러 참조 프레임
초창기 코덱(MPEG-2)은 바로 직전 프레임 1개만 참조했다. H.264부터는 여러 참조 프레임 지원. 최대 16장까지(메모리·연산 비용 한계 내에서).
왜 도움이 되나? 어떤 객체가 잠깐 가려졌다가 다시 나타나는 경우 — "직전 프레임에선 안 보이지만 5프레임 전엔 보인다". 그 5프레임 전을 참조하면 예측이 잘 맞는다.
H.265의 CRA(Clean Random Access)·IDR·BLA 같은 키프레임 정책이 reference frame 관리의 일부.
B-프레임의 경우 양방향 참조 — 이전 프레임 + 이후 프레임 둘 다 사용. "이 블록은 직전 프레임의 (x+3, y-1)이거나 직후 프레임의 (x-2, y+1)의 평균"이라고 예측. 더 정확.
참조 프레임 개수의 트레이드오프: - 많으면 → 더 좋은 예측 → 더 작은 파일 - 많으면 → 더 많은 메모리 → 디코더가 더 강력해야 함 - 많으면 → 인코딩이 더 느림 (각 참조 프레임마다 검색)
5.5 AV1의 새 무기 — Warped Motion과 Global Motion
H.264·H.265는 "평행이동(translation)"만 다룬다. 즉 블록이 옆으로·위아래로 움직였다는 가정. 하지만 실제로는 회전·줌·perspective 변화도 일어난다.
AV1의 Warped Motion: 블록 단위 회전·확대·shear까지 표현. "이 블록은 참조 프레임에서 (x+5, y-2) 위치에서 가져오되, 10도 회전 + 1.05배 확대"라고 표현 가능.
Global Motion: 카메라 패닝·줌처럼 화면 전체가 같은 방향으로 움직이는 경우, 프레임 단위로 전역 변환을 정의. 모든 블록의 모션 벡터 대신 한 번에 처리.
이 두 기법이 AV1이 H.265 대비 30% 더 좋은 압축률을 달성하는 주요 이유 중 하나. 자연 영상에서 회전·줌·패닝은 매우 흔하다.
단점: 모션 추정의 검색 공간이 폭증해서 인코딩이 H.265보다 5~10배 느려진다. AV1 도입의 가장 큰 장벽.
한 사람이 가게에서 물건을 훔쳐 도망갔다. CCTV 영상을 보고 추적할 때, 1번 카메라(밤 9시)의 그 사람을 찾아 그 사람의 모습을 기억한다. 그 다음 2번 카메라(밤 9시 5분) 영상에서 "같은 사람이 어디 있을까?" 찾는다. 옷·키·걸음걸이가 비슷한 사람을 발견 — 가게에서 동쪽으로 100m 떨어진 위치에서 발견했다고 치자.
이 "100m 동쪽"이 모션 벡터다. 그리고 이 정보를 가지고 "5분 뒤엔 같은 사람이 동쪽으로 100m 더 갔을 거다"라고 예측하는 게 모션 보상이다.
비디오 코덱이 정확히 이걸 한다. 한 블록의 "같은 모습"을 다음 프레임에서 찾아 위치 차이만 기록(모션 벡터), 그 위치에서 픽셀을 가져와 다음 프레임을 만든다(모션 보상). 차이는: CCTV 추적은 한 사람 한 명, 비디오 코덱은 한 프레임에 수천 블록 동시에. 그래서 빠른 검색 알고리즘이 핵심.
두 프레임 사이의 모션 벡터를 직접 계산해 보자. 단순한 SAD 기반 block matching.
import cv2
import numpy as np
cap = cv2.VideoCapture('sample.mp4')
_, frame_prev = cap.read()
_, frame_curr = cap.read()
cap.release()
BLOCK = 16
SEARCH = 16 # ± 16 pixels around the original position
def sad(a, b):
return np.abs(a.astype(int) - b.astype(int)).sum()
def find_mv(curr_block, prev_frame, cx, cy):
"""Return the best (dx, dy) and its SAD."""
best, best_mv = float('inf'), (0, 0)
h, w = prev_frame.shape[:2]
for dy in range(-SEARCH, SEARCH + 1):
for dx in range(-SEARCH, SEARCH + 1):
x, y = cx + dx, cy + dy
if x < 0 or y < 0 or x + BLOCK > w or y + BLOCK > h:
continue
candidate = prev_frame[y:y+BLOCK, x:x+BLOCK]
cost = sad(curr_block, candidate)
if cost < best:
best, best_mv = cost, (dx, dy)
return best_mv, best
# Compute MVs for the whole current frame
h, w = frame_curr.shape[:2]
mvs = []
for cy in range(0, h - BLOCK, BLOCK):
for cx in range(0, w - BLOCK, BLOCK):
curr_block = frame_curr[cy:cy+BLOCK, cx:cx+BLOCK]
mv, cost = find_mv(curr_block, frame_prev, cx, cy)
mvs.append((cx, cy, mv[0], mv[1], cost))
# Stats: how much motion?
mv_array = np.array([(m[2], m[3]) for m in mvs])
print(f'모션 벡터 평균: dx={mv_array[:,0].mean():.2f}, dy={mv_array[:,1].mean():.2f}')
print(f' → 화면 전체가 평균 ({mv_array[:,0].mean():.1f}, {mv_array[:,1].mean():.1f}) 픽셀 움직임 = 카메라 패닝')
print(f'\n0 모션 블록 비율: {(np.abs(mv_array).sum(axis=1) == 0).mean()*100:.1f}%')
print(f' → 정적 영역의 비율. 모션 보상이 없어도 되는 블록')
이 단순 구현으로 모션 벡터 분포가 보인다. 일반 비디오에서 카메라 패닝이 있으면 모든 블록의 MV가 같은 방향, 정적 장면이면 거의 모든 MV가 0. 실제 x264/x265는 이보다 100배 빠른 EPZS·Hex 검색을 쓰지만 핵심 아이디어는 동일. 특히 "모션 벡터의 평균이 카메라 움직임"이라는 점이 AV1의 Global Motion 발상의 근거.
✅ 시니어가 보는 것
- 모션 추정·보상의 구분을 명확히 안다
- Block matching의 검색 알고리즘 종류와 trade-off를 안다
- Sub-pixel 정확도가 왜 필요한지 안다
- x264·x265의 preset이 무엇을 바꾸는지 설명 가능
⚠️ 레드 플래그
- 모션 추정과 모션 보상을 혼동
- Full search가 실용적이라고 생각
- Sub-pixel 보간이 손실이라고 오해
- Reference frame 개수가 디코더 비용에 미치는 영향 모름
🎤 예상 인터뷰 질문
- 모션 추정과 모션 보상의 차이는?
- 왜 sub-pixel 모션 벡터가 필요한가요?
- x264의
-preset slow와-preset ultrafast의 가장 큰 차이는?
Key Takeaways
추정 vs 보상
인코더가 MV 찾기 vs 둘 다 MV로 예측.
Block matching
참조 프레임에서 비슷한 블록 찾기.
비용의 80%
인코더 시간의 대부분이 모션 추정.
Sub-pixel
1/4·1/8 픽셀 보간. 더 정확한 예측.
Multi-reference
여러 프레임 참조. 가려졌다 나타난 객체.
B-frame 양방향
이전+이후 둘 다 참조. 가장 효율적.
Warped/Global motion
AV1의 신무기. 회전·줌·패닝 직접 표현.
Preset = 검색 철저함
ultrafast~slow가 검색 깊이를 조절.