영상 신호의 색 인코딩
카메라가 만든 R′G′B′ 신호는 그대로 전송되지 않는다. 밝기에 가까운 신호 하나와 색차 신호 둘로 바꾸고(Y′CbCr), 색차의 해상도를 줄이고(크로마 서브샘플링), 정해진 정수 코드 범위에 양자화한 뒤에야 코덱에 들어간다. 이 장은 그 변환을 행렬과 정수 코드 수준에서 정확히 다룬다. 그리고 디코더가 행렬·레인지·사이팅 가운데 하나라도 잘못 고르면 색이 얼마나 틀어지는지를 ΔE00로 계산한다.
- 두 계수 \(K_R, K_B\)에서 Y′CbCr 행렬과 역행렬을 유도하고, BT.601·709·2020의 값을 비교한다.
- luma \(Y'\)와 휘도 \(Y\)의 차이를 설명하고, 비일정 휘도(NCL)에서 크로마 서브샘플링이 휘도를 바꾸는 이유를 계산으로 보인다.
- 리미티드/풀 레인지 양자화 식을 쓰고, 레인지를 잘못 해석했을 때의 증상(블랙 띄움, 클리핑)을 수치로 예측한다.
- 4:4:4/4:2:2/4:2:0 표기, 크로마 사이팅, 업샘플링 필터가 화질에 주는 영향을 비교한다.
- H.273 코드 포인트로 색 메타데이터를 읽고, 행렬 불일치와 잘못된 태깅을 진단한다.
왜 RGB를 그대로 보내지 않는가
카메라의 신호 처리는 선형 RGB에 OETF를 적용한 감마 인코딩된 R′G′B′(gamma-encoded R′G′B′)로 끝난다(6장). 세 채널을 모두 같은 해상도·같은 비트로 보내면 구현은 단순하다. 하지만 영상 시스템은 거의 예외 없이 이 신호를 밝기 성분 1개와 색차 성분 2개로 바꿔서 보낸다. 이유는 두 가지다.
첫째, 시각의 공간 해상도가 성분마다 다르다. 밝기가 같고 색만 바뀌는 등휘도 격자(isoluminant grating)의 대비 감도는 휘도 격자보다 훨씬 낮은 공간 주파수에서 떨어지며, 해상 한계는 몇 배 낮다(Mullen, 1985; 반대색 채널은 2장). 색차 신호의 해상도를 절반으로 줄여도 정상 시청 거리에서는 차이가 거의 보이지 않는다. 대역폭을 아끼려면 버릴 수 있는 정보부터 버려야 하고, 그것이 색차의 고주파 성분이다.
둘째, R′G′B′ 세 채널은 서로 강하게 상관되어 있다. 자연 영상에서 에지와 질감은 대부분 세 채널에 함께 나타난다. 가중합 하나(밝기)와 차이 둘(색차)로 바꾸면 신호 에너지가 밝기 성분에 몰리고, 색차 성분은 0 근처의 작은 값이 된다. 고정된 3×3 행렬로 하는 값싼 비상관화(decorrelation)이며, 코덱의 엔트로피 부호화에 유리하다.
역사적으로는 흑백 호환성이 먼저였다. 1953년 미국 NTSC 컬러 방식은 흑백 수상기가 받던 휘도 신호를 그대로 두고, 두 색차 신호(I, Q)를 부반송파에 실어 추가했다. 흑백 수상기는 색 정보를 무시하고 밝기만 표시했고, 컬러 수상기는 색차를 복조해 RGB를 복원했다. 색차는 처음부터 좁은 대역(휘도 약 4.2 MHz에 비해 I 약 1.3 MHz, Q 약 0.4 MHz)으로 보냈다. 디지털 시대에도 같은 원리가 이어졌다. ITU-R BT.601(1982)은 휘도를 13.5 MHz, 두 색차를 각각 6.75 MHz로 샘플링하는 4:2:2를 정했고, 오늘날의 배포용 영상은 거의 모두 4:2:0이다.
이 장에서 프라임이 붙은 \(R', G', B', Y'\)은 전달 함수로 인코딩된 비선형 값이고, 프라임이 없는 \(R, G, B, Y\)는 빛에 비례하는 선형 값이다. 엄밀히는 색차도 \(C'_B, C'_R\)로 써야 하지만 관례적으로 프라임을 생략한다. 이 구분은 표기상의 꼼꼼함에 그치지 않는다. 6절의 휘도 오차가 바로 이 프라임 하나에서 생긴다.
Y′CbCr 행렬의 유도
모든 Y′CbCr 변형은 두 계수 \(K_R, K_B\)만으로 결정된다. 먼저 R′G′B′의 가중합으로 luma(luma) \(Y'\)를 정의한다. 가중치의 합을 1로 두어 백색 \((1,1,1)\)이 \(Y'=1\)이 되게 한다.
색차는 \(B'-Y'\)와 \(R'-Y'\)이다. 무채색(\(R'=G'=B'\))이면 둘 다 0이 된다. \(B'-Y'\)의 최댓값은 순수 파랑 \((0,0,1)\)에서 \(1-K_B\), 최솟값은 노랑 \((1,1,0)\)에서 \(-(1-K_B)\)이다. 이 범위를 \([-\tfrac12, +\tfrac12]\)에 맞추도록 정규화한 것이 \(C_B, C_R\)이다.
\(Y'\)를 대입해 정리하면 R′G′B′ → Y′CbCr는 3×3 행렬 하나다.
역변환도 닫힌 꼴로 쓸 수 있다. \(R'\)과 \(B'\)은 정의식을 뒤집으면 바로 나오고, \(G'\)은 \(Y'\)의 정의식 \(G' = (Y' - K_R R' - K_B B')/K_G\)에 이 둘을 대입해 얻는다.
BT.709 값(\(K_R = 0.2126,\ K_B = 0.0722\))을 넣으면 다음 수치 행렬이 된다. 역행렬의 첫 열이 모두 1이라는 점에 주목하자. \(C_B = C_R = 0\)이면 세 채널이 모두 \(Y'\)가 되므로, 무채색은 행렬과 관계없이 정확히 복원된다.
| 표준 | \(K_R\) | \(K_G\) | \(K_B\) | \(2(1-K_B)\) | \(2(1-K_R)\) | G′ ← Cb | G′ ← Cr | 계수의 출처 |
|---|---|---|---|---|---|---|---|---|
| BT.601 | 0.299 | 0.587 | 0.114 | 1.772 | 1.402 | −0.3441 | −0.7141 | NTSC 1953 원색 + C 광원 |
| BT.709 | 0.2126 | 0.7152 | 0.0722 | 1.8556 | 1.5748 | −0.1873 | −0.4681 | BT.709 원색 + D65 |
| BT.2020 / 2100 | 0.2627 | 0.6780 | 0.0593 | 1.8814 | 1.4746 | −0.1646 | −0.5714 | BT.2020 원색 + D65 |
표의 계수는 원색 좌표에서 직접 계산해 확인할 수 있다. CB.rgb2xyzMatrix로 BT.709 원색과 D65의 Y 행을 구하면 (0.2126, 0.7152, 0.0722), BT.2020은 (0.2627, 0.6780, 0.0593)이 나온다. BT.601의 0.299/0.587/0.114는 NTSC 1953 원색과 C 광원에서 나온 값이다(계산값 0.2990/0.5864/0.1146). 실제 SD 방송은 이후 SMPTE C(525줄)와 EBU(625줄) 원색으로 바뀌었지만 행렬 계수는 바꾸지 않았다. 그래서 BT.601의 \(K\)는 실제 원색과 맞지 않는 관례값이다.
\(Y'\)는 감마 인코딩된 값의 가중합이고, 진짜 휘도 \(Y\)를 인코딩한 값 \(\mathrm{OETF}(Y)\)와 다르다. 무채색에서만 두 값이 같다. BT.2020 순수 빨강 \(R'G'B'=(1,0,0)\)의 \(Y'\)는 0.2627이지만, 실제 휘도 \(Y=0.2627\)을 BT.709 OETF로 인코딩하면 0.503이다. luma를 휘도처럼 다루면(예: \(Y'\)로 밝기 평균, 노출 측정, 히스토그램 정규화) 채도 높은 색의 밝기를 체계적으로 낮게 본다. 6장의 "감마 인코딩된 값으로 평균 내기" 실수와 뿌리가 같다.
R′G′B′ ↔ Y′CbCr 계산기
행렬 불일치: 가장 흔한 색 버그
Y′CbCr 샘플 값 자체에는 어떤 행렬로 만들었는지가 기록되어 있지 않다. 디코더는 비트스트림의 matrix_coefficients(8절)를 따르고, 이 값이 없거나 "미지정"이면 추측한다. 많은 플레이어와 라이브러리는 해상도로 추측한다. SD 해상도면 601, HD 이상이면 709를 고르는 식이다. 그래서 SD로 다운스케일한 HD 소스, 태그 없이 저장된 중간 파일, 601을 하드코딩한 셰이더나 캡처 도구에서 같은 버그가 반복된다.
709로 인코딩한 신호를 601로 디코딩하면, 출력 R′G′B′는 입력에 고정 행렬 하나를 곱한 값이 된다.
이 오차를 지각 단위로 바꾸려면 원래 색과 잘못 디코딩된 색을 각각 sRGB 신호로 보고 CIELAB로 변환한 뒤 ΔE00를 계산하면 된다(7장). 아래 시뮬레이터의 24색 견본에서 709→601의 결과는 회색 6개 ΔE00 = 0, 유채색 18개 평균 3.0, 최대 8.7(75% 시안)이다. 피부색은 약 2.2로, 나란히 놓고 비교하면 보이는 수준이다. 반대 방향(601→709)은 유채색 평균 3.9로 조금 더 크다.
행렬 불일치 시뮬레이터 (인코드 행렬 ≠ 디코드 행렬)
(1) 75% 컬러 바를 인코딩해 벡터스코프로 보면, 행렬이 맞을 때 여섯 점이 목표 상자에 들어가고 틀리면 회전·확대되어 벗어난다. (2) 회색 램프는 행렬 오류에 무감각하므로 검증에 쓸 수 없다. (3) H.273의 matrix_coefficients 5(BT.470 System B/G)와 6(SMPTE 170M)은 같은 601 계수를 쓰므로 서로 바꿔도 결과가 같다. (4) 디코더, GPU 셰이더, 스크린샷·캡처 도구, 썸네일 생성기처럼 렌더링 경로마다 따로 확인해야 한다. 한 경로만 맞고 나머지가 틀린 경우가 흔하다.
풀 레인지와 리미티드 레인지
실수 \(Y'\in[0,1]\), \(C\in[-\tfrac12,\tfrac12]\)를 \(n\)비트 정수로 바꾸는 규칙은 두 가지다. 방송·제작 표준(BT.601/709/2020/2100)은 리미티드 레인지(limited / narrow / studio range)를 쓴다. 공칭 블랙과 화이트를 코드 범위 안쪽에 두고, 바깥에 여유 구간을 남긴다.
video_full_range_flag = 1에 해당한다. 8비트에서 Y′ 0–255, C 중앙 128. 컴퓨터 그래픽, JPEG, 많은 스크린 캡처가 이 방식이다.| 비트 깊이 | 레인지 | Y′ 블랙 | Y′ 화이트 | C 최소 | C 중앙 | C 최대 | Y′ 공칭 코드 수 |
|---|---|---|---|---|---|---|---|
| 8 | 리미티드 | 16 | 235 | 16 | 128 | 240 | 220 |
| 8 | 풀 | 0 | 255 | 0 (1) | 128 | 255 | 256 |
| 10 | 리미티드 | 64 | 940 | 64 | 512 | 960 | 877 |
| 10 | 풀 | 0 | 1023 | 0 (1) | 512 | 1023 | 1024 |
| 12 | 리미티드 | 256 | 3760 | 256 | 2048 | 3840 | 3505 |
풀 레인지 색차는 \(\pm0.5\)가 \(\pm127.5\)가 되어 정확한 정수가 없다. 그래서 위 식으로 반올림한 뒤 클리핑하면 8비트에서 −0.5는 0 또는 1, +0.5는 255가 된다. 실무에서 문제가 되는 일은 드물지만, 무손실 왕복 테스트에서는 이 끝점을 따로 확인해야 한다.
레인지를 잘못 해석하면
레인지 정보도 video_full_range_flag라는 메타데이터 1비트로만 전달된다. 이 1비트가 틀리면 두 가지 증상이 나타난다.
- 리미티드 신호를 풀로 해석: 블랙 코드 16이 16/255 = 6.3% 신호로 표시된다. 감마 2.4 디스플레이(BT.1886, \(L_b=0\))에서 피크의 0.13%에 해당하는 회색 블랙이 되고, 화이트 235는 피크의 82%에 그친다. 화면 전체가 뿌옇고 대비가 낮아 보이는 "블랙 띄움"(raised black / washed out)이다. 계조는 잃지 않는다.
- 풀 신호를 리미티드로 해석: \(V = (D-16)/219\)이므로 코드 0–16의 17단계가 모두 블랙, 235–255의 21단계가 모두 화이트로 잘린다. 대비가 지나치게 강해지고 어두운 부분과 밝은 하이라이트의 디테일이 사라진다(crushed blacks, clipped whites). 이 경우 정보가 실제로 손실된다.
레인지 오해석 데모: 그레이 램프와 근흑색 패치
JFIF 규격은 JPEG의 Y′CbCr를 풀 레인지 + BT.601 계수로 고정한다: \(Y = 0.299R + 0.587G + 0.114B\), \(C_b = -0.1687R - 0.3313G + 0.5B + 128\), \(C_r = 0.5R - 0.4187G - 0.0813B + 128\) (8비트 0–255). 이미지가 sRGB든 Display P3든 Y′CbCr 행렬은 항상 이 601 계수다. 원색은 ICC 프로파일이나 Exif 색공간 태그가 따로 정한다. 그래서 JPEG 디코더에 709 행렬이나 리미티드 레인지를 적용하면 3절과 이 절의 버그가 그대로 재현된다. 예: 순수 빨강 (255, 0, 0)은 JFIF에서 (Y, Cb, Cr) = (76, 85, 255)다.
HDMI·DisplayPort의 RGB 전송에도 리미티드(16–235)/풀(0–255) 구분이 있다. CTA-861 계열 규격은 TV 해상도(CE 포맷)에서 리미티드, PC 해상도(IT 포맷)에서 풀 레인지를 기본으로 하고, 실제 값은 InfoFrame으로 알린다. 그래픽 드라이버의 "RGB 출력 범위: 제한/전체" 설정과 TV의 "HDMI 블랙 레벨" 설정이 서로 어긋나면 이 시뮬레이터와 똑같은 블랙 띄움이나 클리핑이 생긴다. 확인할 때는 코드 16 근처 근흑색 패치와 235 근처 근백색 패치가 구분되는지를 본다.
크로마 서브샘플링
Y′CbCr로 바꾼 뒤에는 색차 평면의 해상도를 줄인다. 이것이 크로마 서브샘플링(chroma subsampling)이며, 표기는 \(J{:}a{:}b\) 꼴이다. 가로 \(J\)(관례적으로 4)화소, 세로 2줄짜리 기준 영역에서 \(a\)는 첫 줄의 색차 샘플 수, \(b\)는 둘째 줄에서 새로 나타나는 색차 샘플 수다. 4:4:4는 감축 없음, 4:2:2는 가로 1/2, 4:2:0은 가로·세로 각각 1/2이다(둘째 줄에 새 샘플이 0개). 4:1:1은 가로 1/4이다.
| 포맷 | 가로 × 세로 감축 | 8비트 bit/px | 대표 용도 |
|---|---|---|---|
| 4:4:4 | 1 × 1 | 24 | 색 보정·합성 중간 파일, 화면 공유·텍스트, 게임 스트리밍의 고품질 모드 |
| 4:2:2 | 1/2 × 1 | 16 | 방송 제작(BT.601/709 SDI), 고급 카메라 내부 기록, 편집용 코덱 |
| 4:2:0 | 1/2 × 1/2 | 12 | 배포: DVD·Blu-ray·디지털 방송·스트리밍, 대부분의 H.264/HEVC/AV1, 일반적인 JPEG |
| 4:1:1 | 1/4 × 1 | 12 | DV(525줄) 등 일부 구형 포맷 |
크로마 사이팅: 색차 샘플은 어디에 있는가
색차 샘플 하나가 여러 화소를 대표할 때, 그 샘플이 어느 위치의 값인지도 정해야 한다. 이를 크로마 사이팅(chroma siting)이라 한다. 4:2:2의 BT.601/709 방식은 색차를 짝수 열의 휘도와 같은 위치에 두는 co-sited다. 4:2:0은 세 방식이 쓰인다. JPEG/JFIF와 MPEG-1은 2×2 블록 중앙(가로·세로 모두 중간), MPEG-2와 H.264/HEVC의 기본값(chroma_sample_loc_type = 0)은 가로로는 왼쪽 열과 co-sited이고 세로로는 두 줄 사이다. 값 2는 왼쪽 위 휘도와 같은 위치(가로·세로 모두 co-sited)다.
다운샘플링은 저역통과 필터 후 추림, 업샘플링은 보간이다. 이 장의 시뮬레이터는 반경 \(f\)(감축 비율)의 삼각형(텐트) 필터로 프리필터링한다. 샘플 위치 \(p\)에 대해 가중치는 \(w(x) = \max(0,\ 1 - |x-p|/f)\)다. 2:1 감축이면 co-sited에서 \([1,2,1]/4\), 중앙 사이팅에서 \([1,3,3,1]/8\)이 된다. 업샘플링은 최근접(nearest)과 쌍선형(bilinear)을 비교한다. 실제 코덱 표준은 업샘플링 필터를 규정하지 않으며, 디코더·디스플레이 제조사마다 다르다(쌍선형부터 다탭 필터까지).
크로마 서브샘플링 시뮬레이터 (96×40 테스트 영상, BT.709)
1차원으로 간단히 계산해 보자. 흰 바탕(1, 1, 1)의 1화소 빨간 선(1, 0, 0)을 BT.709로 가로 2:1 감축(중앙 사이팅, \([1,3,3,1]/8\))하고 최근접으로 복원하면, 선 화소는 R′G′B′ ≈ (0.51, 0.13, 0.13)인 어두운 갈색이 된다(ΔE00 ≈ 26). 이웃 흰 화소는 분홍빛(ΔE00 ≈ 9)이 된다. 선의 luma는 보존되었지만 색차가 이웃과 평균되면서 "빨강"이라는 정보가 대부분 사라진 것이다. 같은 선이 회색 바탕의 흰 선이라면 오차는 0이다. 무채색 디테일은 전부 \(Y'\)에 있기 때문이다.
화면 공유·원격 데스크톱·UI 녹화처럼 1화소 단위의 컬러 글자와 선이 있는 콘텐츠는 4:2:0에서 눈에 띄게 손상된다. 이런 콘텐츠는 4:4:4(또는 RGB) 모드로 인코딩하거나, 해상도를 그대로 둔 채 비트레이트를 조절하는 편이 낫다. 반대로 자연 영상은 4:2:0으로 데이터를 절반으로 줄여도 시청 거리에서 차이를 거의 느끼지 못한다. 또한 크로마 업샘플링은 마지막 단계에서 한 번만 해야 한다. 4:2:0 → 4:2:2 → 4:4:4 변환을 여러 장비에서 반복하면 필터가 누적되어 색 번짐이 커진다.
비일정 휘도와 일정 휘도
2절의 \(Y'\)는 감마 인코딩된 R′G′B′의 가중합이므로, 진짜 휘도 \(Y\)는 \(Y'\) 하나로 정해지지 않는다. 색차 값이 바뀌면 \(Y'\)가 같아도 휘도가 달라진다. 이런 방식을 비일정 휘도(non-constant luminance, NCL)라 한다. BT.601, BT.709, 그리고 실제로 쓰이는 BT.2020 신호는 모두 NCL이다.
예를 들어 BT.2020 빨강 (1, 0, 0)과 초록 (0, 1, 0)이 홀수 위치에서 만나는 경계를 4:2:0(가로 2:1, 중앙 사이팅, 최근접)으로 처리하면, 경계 바로 왼쪽 빨간 화소의 색차가 초록과 섞인다. 복원 R′G′B′는 (0.29, 0.29, −0.21)이 되고, 클리핑하면 휘도가 0.2627에서 0.095로 64% 떨어진다. 고채도 경계에 어두운 선이 생기는 현상의 원인이다. 거꾸로 무채색 근처에서는 오차가 거의 없다. 같은 경계에서 채도를 50%로 낮추면 오차는 21%, 25%로 낮추면 5%가 된다.
일정 휘도(constant luminance, CL) 방식은 BT.2020이 선택 사양으로 정의한 \(Y'_cC'_{BC}C'_{RC}\)다. 휘도를 선형 영역에서 먼저 계산한 뒤 인코딩한다.
NCL 휘도 누출: 고채도 경계의 4:2:0 후 실제 휘도
CL은 휘도 보존 면에서 우월하지만, 디코더가 선형 영역으로 내려가 G를 계산해야 하고 색차 정규화 계수가 부호에 따라 달라 구현이 복잡하다. 기존 NCL 장비 체인과도 호환되지 않는다. 실제 배포에서는 거의 쓰이지 않으며, HDR 표준 BT.2100은 그 대신 "일정 강도"(constant intensity)를 근사하는 ICtCp를 도입했다(14장). 대부분의 HDR10 콘텐츠는 여전히 BT.2020 NCL Y′CbCr(matrix_coefficients = 9)로 전송된다. NCL 오차를 줄이는 인코더 측 기법도 있다. 서브샘플링 후 복원했을 때 원래 휘도가 나오도록 \(Y'\)를 다시 계산해 넣는 방식이다(luma adjustment).
비트 깊이와 Y′CbCr 코드 공간
R′G′B′ 단위 큐브는 Y′CbCr 공간에서 기울어진 평행육면체가 된다(그림 15-2). 그 부피는 행렬식이고, 2절의 행렬에서 \(B'\)·\(R'\) 행에서 \(Y'\) 행을 빼는 행 연산은 행렬식을 바꾸지 않으므로 닫힌 꼴로 구할 수 있다.
즉 Y′CbCr 코드 조합 가운데 약 1/4만 실제 색에 대응한다. 리미티드 레인지는 공칭 범위 바깥까지 코드를 쓰므로 비율이 더 낮다. 8비트 BT.709 리미티드에서 \(0.2447 \times 219 \times 224^2 / 256^3 = 16.0\%\)이다. 전수 계산으로 확인하면 \(2^{24} = 16{,}777{,}216\)개 코드 중 R′G′B′ ∈ [0, 1]로 디코딩되는 코드는 2,689,428개(16.03%)다. 그 결과 8비트 RGB의 1,677만 색을 8비트 리미티드 Y′CbCr로 바꾸면 서로 다른 코드는 2,760,488개뿐이다(경계 바깥으로 반올림되는 코드 포함). 8비트 RGB 색의 83%는 왕복 후 정확히 돌아오지 않는다(최대 오차 2코드). 10비트 리미티드에서는 8비트 RGB 1,677만 색이 모두 서로 다른 코드로 가고, 왕복 오차도 0이다.
| 행렬 | |det M| | 풀 8비트 유효 비율 | 리미티드 8비트 유효 비율 | 8비트 RGB → 서로 다른 리미티드 코드 | 8비트 RGB 왕복 실패율 (리미티드 8 / 10) |
|---|---|---|---|---|---|
| BT.601 | 0.2363 | 23.4% | 15.5% | 2,667,708 | — |
| BT.709 | 0.2447 | 24.2% | 16.0% | 2,760,488 | 83% / 0% |
| BT.2020 | 0.2444 | 24.2% | 16.0% | — | — |
Y′ 단면에서 본 유효 CbCr 영역
Y′, Cb, Cr을 각각 공칭 범위로 클리핑해도 결과가 유효한 색이 된다는 보장은 없다. 위 시뮬레이터의 빗금 영역처럼 상자 안이지만 큐브 밖인 코드가 4분의 3이다. 색 보정이나 합성을 Y′CbCr에서 하면 이런 코드가 쉽게 생기고, 디코더의 R′G′B′ 클리핑 방식(채널별 클리핑인지, 색상 보존 클리핑인지)에 따라 결과 색이 달라진다. 가멋 경고는 반드시 R′G′B′로 되돌린 뒤 판정한다. 8비트에서 생기는 밴딩과 디더링은 6장에서 다룬다. Y′CbCr 변환은 유효 코드를 RGB의 약 1/6로 줄이므로, 같은 8비트라도 RGB 직접 저장보다 밴딩에 불리하다. 10비트 인코딩이 8비트 소스에도 이로운 이유 중 하나다.
색 메타데이터: H.273 코드 포인트
그림 15-1의 각 단계를 디코더에 알려 주는 표준 어휘가 ITU-T H.273 = ISO/IEC 23091-2 "코딩 독립 코드 포인트"(coding-independent code points, CICP)다. H.264/AVC·H.265/HEVC의 VUI, AV1의 시퀀스 헤더, MP4/MKV 컨테이너의 colr(nclx) 박스, PNG의 cICP 청크가 모두 같은 번호를 쓴다. 핵심 필드는 네 개다.
| 필드 | 값 | 의미 |
|---|---|---|
colour_primaries | 1 | BT.709 (sRGB와 같은 원색, D65) |
| 2 | 미지정 (디코더가 추측) | |
| 5 / 6 | BT.601 625줄(EBU) / 525줄(SMPTE C) | |
| 9 | BT.2020 / BT.2100 | |
| 12 | Display P3 계열 (SMPTE EG 432-1, D65) | |
transfer_characteristics | 1 | BT.709 OETF |
| 13 | sRGB (IEC 61966-2-1) | |
| 14 / 15 | BT.2020 10비트 / 12비트 (BT.709와 같은 곡선) | |
| 16 | PQ (SMPTE ST 2084) | |
| 18 | HLG (ARIB STD-B67, BT.2100) | |
matrix_coefficients | 0 | 항등 (GBR, 행렬 없음 — RGB 4:4:4 전송) |
| 1 | BT.709 (\(K_R=0.2126, K_B=0.0722\)) | |
| 5 / 6 | BT.601 (두 값의 계수는 같다) | |
| 9 / 10 | BT.2020 NCL / BT.2020 CL | |
| 14 | ICtCp (BT.2100) | |
video_full_range_flag | 0 / 1 | 리미티드 / 풀 레인지 |
대표 조합은 SDR HD 영상이 (1, 1, 1, 0), HDR10이 (9, 16, 9, 0), HLG 방송이 (9, 18, 9, 0)이다. 크로마 사이팅은 별도 필드(chroma_sample_loc_type, AV1은 chroma_sample_position)로 전달된다. 마스터링 디스플레이 색 볼륨(SMPTE ST 2086)과 MaxCLL/MaxFALL 같은 HDR 정적 메타데이터는 14장에서 다룬다.
잘못된 태그의 증상
- PQ·BT.2020 신호를 BT.709로 태깅(또는 태그 누락): PQ 곡선을 감마 곡선으로 해석하므로 PQ에서 100 cd/m²에 해당하는 신호 약 0.51이 SDR 피크의 20% 정도로 표시된다. 2020 원색을 709 원색으로 해석하므로 채도도 크게 낮아진다. 화면이 회색빛으로 탁하고 밋밋하게 보인다.
- BT.709 신호를 BT.2020으로 태깅: 709 원색 값이 더 넓은 2020 원색으로 해석되어 색이 과포화된다. 피부가 붉고 잔디가 형광색에 가까워진다.
- matrix 1과 5/6의 혼동: 3절의 행렬 불일치다. 회색은 멀쩡하고 채도 높은 색만 ΔE00 3–9 수준으로 틀어진다.
- 레인지 플래그 오류: 4절의 블랙 띄움 또는 클리핑이 생긴다.
- 컨테이너와 비트스트림의 불일치: 컨테이너(MP4
colr)와 비트스트림 VUI의 값이 다르면 어느 쪽을 따를지는 플레이어마다 다르다. 같은 파일이 브라우저와 데스크톱 플레이어에서 다르게 보이는 흔한 원인이다.
인코딩 파이프라인에서는 "미지정(2)"을 남기지 말고 네 필드를 모두 명시한다. 예를 들어 FFmpeg에서는 -color_primaries, -color_trc, -colorspace, -color_range 옵션으로 지정하고, 출력 파일을 분석 도구로 열어 비트스트림 VUI와 컨테이너 양쪽에 값이 실렸는지 확인한다. 태그는 픽셀 값을 바꾸지 않는다. 태그를 바꾸는 것(재해석)과 픽셀을 변환하는 것(색공간 변환)을 혼동하지 말아야 한다.
압축과 색: 크로마 블로킹과 밴딩
블록 기반 코덱(JPEG, MPEG-2, H.264, HEVC, AV1)은 Y′, Cb, Cr 평면을 각각 블록으로 나누어 DCT 같은 변환을 하고 계수를 양자화한다. 4:2:0에서는 색차 평면이 가로·세로 절반이므로, 색차 8×8 변환 블록 하나가 화면에서 16×16 화소를 덮는다. 색차 계수를 거칠게 양자화하면 휘도 블록보다 두 배 큰 단위의 색 얼룩이 생긴다. 이것이 크로마 블로킹(chroma blocking)이며, 하늘이나 피부처럼 색이 천천히 변하는 영역에서 계단 모양의 색 경계로 보인다. 인코더는 보통 색차에 별도의 양자화 오프셋(예: HEVC의 chroma QP offset)을 두어 휘도와 색차 사이에 비트를 배분한다.
밴딩(banding)은 매끈한 그라디언트가 눈에 보이는 계단으로 끊기는 현상이다. 원인은 두 겹이다. (1) 비트 깊이: 8비트 리미티드에서 Y′는 220코드뿐이고, 7절에서 본 것처럼 유효 Y′CbCr 코드는 더 적다. 어두운 하늘처럼 느린 그라디언트에서 한 코드 차이가 보일 수 있다(6장의 비트 깊이와 밴딩). (2) 코덱 양자화: 저비트레이트에서 인코더는 고주파 계수를 0으로 만들어 블록 내부를 평탄하게 하므로, 원래 디더링이나 필름 그레인이 만들던 미세 변화가 사라지고 계단이 드러난다. 그래서 10비트 인코딩은 8비트 소스에서도 밴딩을 줄이는 데 도움이 되고, 일부 인코더는 그레인을 걷어 냈다가 디코더에서 합성하는 방식(AV1의 film grain synthesis)을 쓴다.
한 영상 체인의 색 오차를 따질 때는 원인을 분리해서 본다. 행렬 불일치(무채색 0, 고채도 ΔE00 최대 약 9), 레인지 오류(블랙 0.13% 띄움 또는 38코드 클리핑), 4:2:0 서브샘플링(고채도 1화소 디테일 ΔE00 수십, 자연 영상 평균은 작음), NCL 휘도 누출(고채도 경계 휘도 수십 %), 8비트 양자화(왕복 최대 2코드)는 크기도 공간 분포도 다르다. 테스트 패턴도 원인별로 다르게 써야 한다. 컬러 바와 벡터스코프는 행렬, 근흑색·근백색 패치는 레인지, 1화소 컬러 선과 글자는 서브샘플링·사이팅, 느린 그라디언트는 양자화·밴딩을 점검한다.
핵심 정리
- 영상은 R′G′B′를 밝기 1개(\(Y'\))와 색차 2개(\(C_B, C_R\))로 바꿔 보낸다. 시각의 색 해상도가 낮고 채널 상관이 크기 때문이며, 흑백 TV와의 호환에서 시작되었다.
- \(Y' = K_R R' + K_G G' + K_B B'\), \(C_B = (B'-Y')/2(1-K_B)\), \(C_R = (R'-Y')/2(1-K_R)\). BT.601 (0.299, 0.114), BT.709 (0.2126, 0.0722), BT.2020 (0.2627, 0.0593). 역행렬의 첫 열은 모두 1이다.
- \(Y'\)는 감마 인코딩된 값의 가중합인 luma이며 휘도 \(Y\)가 아니다. BT.2020 빨강의 \(Y'\)는 0.263이지만 \(\mathrm{OETF}(Y)\)는 0.503이다.
- 행렬 불일치(709↔601)는 무채색을 보존하고 채도 높은 색만 바꾼다. 24색 견본에서 유채색 평균 ΔE00 약 3, 최대 약 9다.
- 리미티드 레인지는 8비트 Y′ 16–235, C 16–240(10비트 64–940, 64–960)이다. 리미티드를 풀로 읽으면 블랙이 뜨고(피크의 0.13%), 풀을 리미티드로 읽으면 38개 코드가 클리핑된다. JPEG(JFIF)는 풀 레인지 BT.601이다.
- 4:2:2는 색차 가로 1/2, 4:2:0은 가로·세로 1/2(8비트 12 bit/px)다. 사이팅(중앙/왼쪽/왼쪽 위)은 다운·업샘플링이 같은 가정을 해야 하며, 피해는 고채도 고주파(빨간 글자, 1화소 색 선)에 집중된다.
- NCL에서는 색차를 흐리면 휘도가 바뀐다(빨강|초록 경계에서 −64%). BT.2020 CL은 선형 휘도로 \(Y'_C\)를 만들어 이를 막지만 거의 쓰이지 않고, HDR은 ICtCp를 택했다.
- Y′CbCr 코드 중 유효한 색은 \(|\det M| = K_G/4(1-K_R)(1-K_B) \approx 0.24\)(풀), 8비트 리미티드에서 16%다. 8비트 RGB의 83%는 8비트 리미티드 왕복에서 정확히 돌아오지 않고, 10비트에서는 모두 돌아온다.
- H.273 코드 포인트(원색 1/9, 전달 1/13/16/18, 행렬 0/1/5/6/9/14, 레인지 플래그)가 디코딩 방법을 정한다. SDR HD (1,1,1), HDR10 (9,16,9), HLG (9,18,9). 태그 오류는 행렬·레인지·원색·전달 함수의 오해석으로 이어진다.
확인 퀴즈
BT.709의 \(Y'\)에 대한 설명으로 옳은 것은?
BT.709의 \(C_R\) 정의는?
BT.709로 인코딩한 영상을 BT.601 행렬로 디코딩했다. 오차가 0인 색은?
8비트 리미티드 레인지 신호를 풀 레인지로 잘못 해석한 화면의 증상은?
8비트 4:2:0 영상의 화소당 평균 비트 수는?
4:2:0에서 빨간 글자가 특히 심하게 손상되는 이유로 가장 적절한 것은?
NCL Y′CbCr에서 크로마 서브샘플링이 고채도 경계의 휘도까지 바꾸는 이유는?
풀 레인지 BT.709에서 Y′CbCr 코드 조합 가운데 유효한(R′G′B′ ∈ [0,1]) 비율에 가장 가까운 것은?
일반적인 HDR10 영상의 H.273 (colour_primaries, transfer_characteristics, matrix_coefficients) 조합은?
JPEG(JFIF) 파일의 Y′CbCr에 대한 설명으로 옳은 것은?