色度二次采样是通过对色度 信息实现比对亮度信息更低的分辨率来编码图像的做法,利用人类视觉系统对色差的敏锐度低于对亮度的敏锐度。[1]
它用于许多视频和静止图像编码方案——模拟和数字——包括JPEG编码。
基本原理
数字信号通常被压缩以减小文件大小并节省传输时间。由于人类视觉系统对亮度变化比对颜色更敏感,因此可以通过将更多带宽用于亮度分量(通常表示为 Y')而不是色差分量 Cb 和 Cr 来优化视频系统。例如,在压缩图像中,4:2:2 Y'CbCr方案需要非二次采样 "4:4:4" R'G'B'的三分之二的带宽。这种减少导致几乎没有观察者感知的视觉差异。
这张图片以全尺寸显示了四种二次采样方案之间的差异。请注意彩色图像的相似程度。下面一行显示颜色信息的分辨率。
二次抽样的工作原理
在正常观看距离下,以较低的速率(即较低的分辨率)对颜色细节进行采样不会引起可察觉的损失。在视频系统中,这是通过使用色差组件来实现的。信号分为一个亮度(Y')分量和两个色差分量(色度)。可以使用多种过滤方法来获得分辨率降低的色度值。[2]
亮度 (Y') 与亮度 (Y) 的区别在于其计算中存在伽马校正,因此此处添加了撇号。伽马校正信号具有模拟人类视觉的对数灵敏度的优势,与较亮的水平相比,更多的水平专用于较暗的水平。因此,它广泛用于源三色刺激信号,即 R'G'B' 输入。这种色彩空间的示例包括sRGB、TV Rec。601,建议。709和建议书。2020 年;这个概念也被推广到Rec.中的光学传递函数。2020 年。[2] [3]
[4]
采样系统和比率
子采样方案通常表示为三部分比率J : a : b(例如 4:2:2)或四个部分,如果存在 alpha 通道(例如 4:2:2:4),它们描述了在J像素宽和 2 像素高的概念区域中的亮度和色度样本。这些部分是(按各自的顺序):
- J:水平采样参考(概念区域的宽度)。通常,4。
- a : 第一行J像素中的色度样本数 (Cr, Cb)。
- b : 第一行和第二行J像素之间色度样本 (Cr, Cb) 的变化次数。请注意,b必须为零或等于a(除了不遵循此约定的罕见不规则情况,如 4:4:1 和 4:2:1)。
- Alpha:水平因子(相对于第一个数字)。如果 alpha 分量不存在,则可以省略,如果存在则等于J。
此表示法并非对所有组合都有效,并且有例外,例如 4:1:0(其中区域的高度不是 2 个像素,而是 4 个像素,因此如果使用每个组件 8 位,则媒体将是每个组件 9 位像素)和 4:2:1。
给出的映射示例仅是理论性的和说明性的。另请注意,该图未指示任何色度过滤,应应用此过滤以避免混叠。
要计算相对于 4:4:4(或 4:4:4:4)所需的带宽因子,需要将所有因子相加并将结果除以 12(或 16,如果存在 alpha)。
抽样和二次抽样的类型
4:4:4
三个Y'CbCr分量中的每一个都具有相同的采样率,因此没有色度二次采样。这种方案有时用于高端胶片扫描仪和电影后期制作。
请注意,“4:4:4”可能会错误地指代R'G'B'颜色空间,它隐含地也没有任何色度二次采样(JPEG R'G'B' 可以进行二次采样除外)。HDCAM SR等格式可以通过双链路HD-SDI录制 4:4:4 R'G'B' 。
4:2:2
两个色度分量以亮度水平采样率的一半进行采样:水平色度分辨率减半。这将未压缩视频信号的带宽减少了三分之一,这意味着对于没有 alpha 的每个分量 8 位(每个像素 24 位)只有 16 位就足够了,就像在 NV16 中一样。
许多高端数字视频格式和接口都使用这种方案:
- AVC-内部 100
- 数字Betacam
- Betacam SX
- DVCPRO50和DVCPRO HD
- 数字-S
- CCIR 601 /串行数字接口/ D1
- ProRes(总部、422、LT 和代理)
- XDCAM HD422
- 佳能 MXF HD422
4:2:1
这种采样模式不能用 J:a:b 表示法表示。“4:2:1”是以前的符号方案中的一个过时术语,很少有软件或硬件编解码器使用它。Cb水平分辨率是Cr的一半(和Y的水平分辨率的四分之一)。
4:1:1
在 4:1:1 色度二次采样中,水平颜色分辨率是四分之一,与没有色度二次采样相比,带宽减半。最初, DV格式的 4:1:1 色度二次采样不被认为是广播质量,仅适用于低端和消费应用。[5] [6]然而,基于 DV 的格式(其中一些使用 4:1:1 色度二次采样)已专业地用于电子新闻采集和播出服务器。DV 也偶尔用于故事片和数字电影摄影。
在 NTSC 系统中,如果 luma 以 13.5 MHz 采样,那么这意味着Cr和Cb信号将分别以 3.375 MHz 采样,这对应于 1.6875 MHz 的最大 Nyquist 带宽,而传统的“高端广播模拟NTSC 编码器”将具有 1.5 MHz 的奈奎斯特带宽和用于 I/Q 通道的 0.5 MHz。然而,在大多数设备中,尤其是廉价电视机和 VHS/Betamax VCR,色度通道对于Cr和Cb(或等效于 I/Q)只有 0.5 MHz 的带宽。因此,与 NTSC 的最佳复合模拟规格相比,DV 系统实际上提供了卓越的色彩带宽,尽管它只有“全”数字信号的色度带宽的 1/4。
使用 4:1:1 色度二次采样的格式包括:
4:2:0
在 4:2:0 中,水平采样是 4:1:1 的两倍,但由于Cb和Cr通道仅在此方案中的每条交替线上进行采样,因此垂直分辨率减半。因此数据速率是相同的。这非常适合PAL颜色编码系统,因为它的垂直色度分辨率只有NTSC的一半。它也非常适合SECAM颜色编码系统,因为与该格式一样,4:2:0 每行仅存储和传输一个颜色通道(另一个通道从前一行恢复)。然而,实际生产的输出 SECAM 模拟视频信号的设备很少。一般来说,SECAM 地区要么必须使用支持 PAL 的显示器,要么使用转码器将 PAL 信号转换为 SECAM 进行显示。
4:2:0 色度配置的不同变体位于:
- 所有ISO / IEC MPEG和ITU-T VCEG H.26x 视频编码标准,包括H.262/MPEG-2 Part 2实施(尽管MPEG-4 Part 2和H.264/MPEG-4 AVC的某些配置文件允许更高质量采样方案,例如 4:4:4)
- DVD 视频和蓝光光盘。[7] [8]
- PAL DV和DVCAM
- 高密度病毒
- AVCHD和AVC-Intra 50
- Apple 中级编解码器
- 最常见的JPEG/JFIF和MJPEG实现
- VC-1
- WebP
Cb 和 Cr 在水平和垂直方向均以 2 的因子进行二次采样。
4:2:0 方案有三种变体,具有不同的水平和垂直位置。[9]
- 在 MPEG-2 中,MPEG-4 和 AVC Cb 和 Cr 水平并存。Cb 和 Cr 位于垂直方向的像素之间(位于间隙位置)。
- 在 JPEG/JFIF、H.261 和 MPEG-1 中,Cb 和 Cr 位于间隙位置,介于交替亮度样本之间。
- 在 4:2:0 DV 中,Cb 和 Cr 在水平方向上共存。在垂直方向上,它们共同位于交替的线上。这也是 HEVC 在 BT.2020 和BT.2100内容(尤其是蓝光)中使用的内容。也称为左上角。
大多数与 PAL 对应的数字视频格式都使用 4:2:0 色度二次采样,但 DVCPRO25 除外,它使用 4:1:1 色度二次采样。与没有色度二次采样相比,4:1:1 和 4:2:0 方案都将带宽减半。
对于隔行素材,如果以与渐进素材相同的方式实现 4:2:0 色度二次采样,则可能会导致运动伪影。亮度样本来自不同的时间间隔,而色度样本将来自两个时间间隔。正是这种差异会导致运动伪影。MPEG-2 标准允许交替的隔行采样方案,其中 4:2:0 应用于每个场(而不是同时应用于两个场)。这解决了运动伪影问题,将垂直色度分辨率降低了一半,并且可以在图像中引入梳状伪影。
应用于静止图像的4:2:0隔行采样。显示两个字段。
如果要对隔行扫描的素材进行去隔行扫描,则可以通过垂直模糊色度来去除梳状色度伪影(来自 4:2:0 隔行采样)。[10]
4:1:0
这个比率是可能的,并且一些编解码器支持它,但它没有被广泛使用。此比率使用一半的垂直颜色分辨率和四分之一的水平颜色分辨率,而带宽仅为使用的最大颜色分辨率的八分之一。这种 8 位量化格式的未压缩视频对每个宏像素(即 4×2 像素)使用 10 个字节或对每个像素使用 10 位。它具有与使用延迟线解码器解码的 PAL I 信号相当的色度带宽,并且仍然非常优于 NTSC。
- 一些视频编解码器可以选择以 4:1:0.5 或 4:1:0.25 运行,以允许类似于 VHS 的质量。
3:1:1
索尼在其 HDCAM 高清录像机(不是 HDCAM SR)中使用。在水平维度上,亮度以全高清采样率的四分之三水平采样——每行 1440 个样本而不是 1920 个。色度以每行 480 个样本进行采样,是亮度采样率的三分之一。
在垂直维度上,亮度和色度都以全高清采样率(垂直 1080 个样本)进行采样。
文物
色度二次采样受到两种主要类型的伪影的影响,在颜色突然变化的情况下,会导致比预期更明显的退化。
伽马错误
像 Y'CbCr 这样的伽马校正信号存在色度错误“渗入”亮度的问题。在这些信号中,低色度实际上使颜色看起来不如具有同等亮度的颜色亮。因此,当饱和颜色与不饱和颜色或互补颜色混合时,边界处会出现亮度损失。这可以在品红色和绿色之间的示例中看到。[2]为了得到一组更接近原始值的子采样值,有必要撤消伽马校正,执行计算,然后退回到伽马校正空间。更有效的近似也是可能的,例如使用亮度加权平均值或迭代使用WebP中的查找表和 sjpeg 的“Sharp YUV”功能。[11]
没有颜色子采样的原始图像。200% 缩放。
色域外的颜色
色度二次采样可能出现的另一个伪影是色度重建时可能出现色域外颜色。假设图像由交替的 1 像素红线和黑线组成,并且二次采样省略了黑色像素的色度。来自红色像素的色度将被重建到黑色像素上,导致新像素具有正的红色和负的绿色和蓝色值。由于显示器不能输出负光(负光不存在),这些负值将被有效地削波,产生的亮度值会太高。[2]类似的伪影出现在相当锐利的红/黑边界附近的不那么人工的渐变示例中。
二次采样期间的其他类型的过滤也可能导致颜色超出色域。
术语
术语Y'UV是指模拟电视编码方案(ITU-R Rec. BT.470),而 Y'CbCr 是指数字编码方案。[3]两者之间的一个区别是色度分量(U、V、Cb 和 Cr)上的比例因子不同。然而,术语 YUV 经常被错误地用来指代 Y'CbCr 编码。因此,像“4:2:2 YUV”这样的表达式总是指代 4:2:2 Y'CbCr,因为在模拟编码(例如 YUV)中根本没有 4:x:x 这样的东西。Y'CbCr 中使用的像素格式也可以称为 YUV,例如 yuv420p、yuvj420p 等。
类似地,术语亮度和符号 Y 经常被错误地用来指代用符号 Y' 表示的亮度。请注意,视频工程的亮度(Y') 不同于色彩科学的亮度(Y)(由CIE定义)。亮度形成为伽马校正(三色)RGB 分量的加权和。亮度形成为线性(三色)RGB 分量 的加权和。
在实践中,CIE符号 Y 经常被错误地用于表示亮度。1993 年,SMPTE采用了工程指南 EG 28,澄清了这两个术语。请注意,主要符号 ' 用于表示伽马校正。
同样,视频工程的色度不同于色彩科学的色度。视频工程的色度由加权三刺激分量(伽马校正,OETF)形成,而不是线性分量。在视频工程实践中,术语chroma、chrominance和saturation通常可互换使用来指代色度,但这不是一个好的实践,正如 ITU-T Rec H.273 所述。
///
通过使用色度二次采样技术,它将图像的内存需求降低到每像素 12 位(32 位 RGBA 图像的 x2.67 压缩),而不会引入明显的伪影。该算法非常简单,甚至上一代移动 GPU 也可以即时对其进行解码。ChromaPack 支持 1 位 alpha 透明度。导入带有 Alpha 通道的图像时,它会将透明度信息添加到转换后的纹理资源中。使用“ChromaPack/Cutout”着色器以透明显示。
using UnityEngine; | |
using UnityEditor; | |
using System.Collections; | |
class ChromaPackProcessor : AssetPostprocessor | |
{ | |
static float RGB_Y(Color rgb) | |
{ | |
return 0.299f * rgb.r + 0.587f * rgb.g + 0.114f * rgb.b; | |
} | |
static float RGB_Cb(Color rgb) | |
{ | |
return -0.168736f * rgb.r -0.331264f * rgb.g + 0.5f * rgb.b; | |
} | |
static float RGB_Cr(Color rgb) | |
{ | |
return 0.5f * rgb.r - 0.418688f * rgb.g - 0.081312f * rgb.b; | |
} | |
static float RGB_Ya(Color rgb) | |
{ | |
if (rgb.a < 0.5f) | |
return 0; | |
else | |
return RGB_Y(rgb) * 255 / 256 + 1.0f / 256; | |
} | |
void OnPreprocessTexture() | |
{ | |
var importer = assetImporter as TextureImporter; | |
if (!assetPath.EndsWith("CP.png")) return; | |
importer.textureType = TextureImporterType.GUI; | |
importer.textureFormat = TextureImporterFormat.RGBA32; | |
} | |
void OnPostprocessTexture(Texture2D texture) | |
{ | |
var importer = assetImporter as TextureImporter; | |
if (!assetPath.EndsWith("CP.png")) return; | |
var hasAlpha = importer.DoesSourceTextureHaveAlpha(); | |
var tw = texture.width; | |
var th = texture.height; | |
var source = texture.GetPixels(); | |
texture.Resize(tw * 3 / 2, th, TextureFormat.Alpha8, false); | |
var pixels = texture.GetPixels(); | |
var i1 = 0; | |
var i2 = 0; | |
if (hasAlpha) | |
{ | |
for (var iy = 0; iy < th; iy++) | |
{ | |
for (var ix = 0; ix < tw; ix++) | |
{ | |
pixels[i2++].a = RGB_Ya(source[i1++]); | |
} | |
i2 += tw / 2; | |
} | |
} | |
else | |
{ | |
for (var iy = 0; iy < th; iy++) | |
{ | |
for (var ix = 0; ix < tw; ix++) | |
{ | |
pixels[i2++].a = RGB_Y(source[i1++]); | |
} | |
i2 += tw / 2; | |
} | |
} | |
i1 = 0; | |
i2 = tw; | |
var i3 = (tw * 3 / 2) * th / 2 + tw; | |
for (var iy = 0; iy < th / 2; iy++) | |
{ | |
for (var ix = 0; ix < tw / 2; ix++) | |
{ | |
var ws = (source[i1] + source[i1 + 1] + source[i1 + tw] + source[i1 + tw + 1]) / 4; | |
pixels[i2++].a = RGB_Cr(ws) + 0.5f; | |
pixels[i3++].a = RGB_Cb(ws) + 0.5f; | |
i1 += 2; | |
} | |
i1 += tw; | |
i2 += tw; | |
i3 += tw; | |
} | |
texture.SetPixels(pixels); | |
importer.isReadable = false; | |
} | |
} |
Shader "ChromaPack/Cutout" | |
{ | |
Properties | |
{ | |
_MainTex("Base", 2D) = "white" {} | |
} | |
CGINCLUDE | |
#include "UnityCG.cginc" | |
sampler2D _MainTex; | |
half3 YCbCrtoRGB(half y, half cb, half cr) | |
{ | |
return half3( | |
y + 1.402 * cr, | |
y - 0.344136 * cb - 0.714136 * cr, | |
y + 1.772 * cb | |
); | |
} | |
half4 frag(v2f_img i) : SV_Target | |
{ | |
float2 uv = i.uv; | |
half y = tex2D(_MainTex, uv * float2(2.0 / 3, 1.0)).a; | |
half cb = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.5)).a - 0.5; | |
half cr = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.0)).a - 0.5; | |
clip(y - 1.0 / 256); | |
y = (y - 1.0 / 256) * 256.0 / 255; | |
return half4(YCbCrtoRGB(y, cb, cr), 1); | |
} | |
ENDCG | |
SubShader | |
{ | |
Tags { "Queue"="AlphaTest" } | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert_img | |
#pragma fragment frag | |
ENDCG | |
} | |
} | |
} |
Shader "ChromaPack/Opaque" | |
{ | |
Properties | |
{ | |
_MainTex("Base", 2D) = "white" {} | |
} | |
CGINCLUDE | |
#include "UnityCG.cginc" | |
sampler2D _MainTex; | |
half3 YCbCrtoRGB(half y, half cb, half cr) | |
{ | |
return half3( | |
y + 1.402 * cr, | |
y - 0.344136 * cb - 0.714136 * cr, | |
y + 1.772 * cb | |
); | |
} | |
half4 frag(v2f_img i) : SV_Target | |
{ | |
float2 uv = i.uv; | |
half y = tex2D(_MainTex, uv * float2(2.0 / 3, 1.0)).a; | |
half cb = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.5)).a - 0.5; | |
half cr = tex2D(_MainTex, uv * float2(1.0 / 3, 0.5) + float2(2.0 / 3, 0.0)).a - 0.5; | |
return half4(YCbCrtoRGB(y, cb, cr), 1); | |
} | |
ENDCG | |
SubShader | |
{ | |
Tags { "Queue"="Geometry" } | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert_img | |
#pragma fragment frag | |
ENDCG | |
} | |
} | |
} |
/