2. 编码树单元(CTU)与四叉树划分:CTU/CU/PU/TU结构、递归四叉树划分策略、编码性能影响分析
咱们直接切入正题。HEVC 相比 H.264,最大的变革之一就是引入了 CTU 和灵活的块划分结构。说白了,H.264 里的宏块是 16x16 固定大小,到了 HEVC,这个块可以大到 64x64,小到 4x4。你想想看,这给编码器带来的灵活性有多大?
我个人习惯把 CTU 比作一块「画布」。编码器拿到这块画布后,需要决定怎么切分才能画得最省颜料、最清晰。这个切分过程,就是递归四叉树划分。
2.1 CTU/CU/PU/TU 结构解析
先理清这四个概念。很多初学者容易搞混,我当年也踩过坑。
| 单元 | 全称 | 核心作用 | 大小范围 |
|---|---|---|---|
| CTU | Coding Tree Unit | 编码树单元,顶层根节点 | 16x16 ~ 64x64 |
| CU | Coding Unit | 编码单元,决定帧内/帧间模式 | 8x8 ~ 64x64 |
| PU | Prediction Unit | 预测单元,承载运动估计/补偿 | 4x4 ~ 64x64 |
| TU | Transform Unit | 变换单元,负责 DCT/DST 变换 | 4x4 ~ 32x32 |
关键理解:CTU 是根,CU 是决策层,PU 是预测层,TU 是变换层。它们之间是树形嵌套关系,不是平级的。
我在项目中遇到过一个问题:有人把 CU 和 PU 混为一谈,结果运动估计的参考块大小设错了,导致码率暴涨。记住,CU 决定「怎么分」,PU 决定「怎么预测」,TU 决定「怎么变换」。
2.2 递归四叉树划分策略
HEVC 的划分策略是递归的。什么意思?就是 CTU 先尝试不划分,如果效果不好,就一分为四;每个子块再尝试不划分,如果还不好,再分……直到达到最小 CU 尺寸。
这个过程的伪代码大致如下:
void splitCTU(CTU, depth) {
if (depth >= maxDepth) {
encodeCU(CTU); // 达到最大深度,直接编码
return;
}
// 计算当前 CU 的 RD-Cost
cost_no_split = computeRDCost(CTU);
// 尝试划分
cost_split = 0;
for (each sub-block in 4 children) {
splitCTU(sub-block, depth + 1);
cost_split += sub-block->bestCost;
}
// 加上划分代价
cost_split += lambda * splitBits;
// 决策
if (cost_no_split <= cost_split) {
CTU->bestMode = NO_SPLIT;
CTU->bestCost = cost_no_split;
} else {
CTU->bestMode = SPLIT;
CTU->bestCost = cost_split;
}
}
实战技巧:我曾经在优化编码速度时发现,对于平坦区域(如天空、墙壁),递归深度通常只需要 1~2 层。我建议在编码前先做一次简单的纹理检测,平坦区域直接限制最大深度,能省下 30% 以上的编码时间。
为什么会这样?因为平坦区域的像素值变化小,用大块编码就能达到很好的压缩效果,没必要递归划分。反之,纹理复杂的区域(如草地、头发丝),小块的编码效率更高。
2.3 四叉树划分的视觉化
下面这张图展示了 CTU 从 64x64 递归划分到 8x8 的过程。每个节点代表一个 CU,红色节点表示最终采用的划分方式。
注意:递归深度不是越深越好。深度每增加一层,编码时间大约翻 4 倍。我曾经在 4K 视频上测试过,最大深度从 4 降到 3,编码速度提升了 3.2 倍,而 BD-Rate 只增加了 1.8%。这个 trade-off 值得你仔细权衡。
2.4 编码性能影响分析
四叉树划分对编码性能的影响,主要体现在三个方面:
- 压缩效率:灵活的块大小让编码器能自适应图像内容。平坦区域用大块,节省码流;纹理区域用小块,保留细节。相比 H.264 的固定 16x16 宏块,HEVC 在同等画质下能节省约 40% 的码率。
- 编码复杂度:这是最大的代价。每个 CTU 都要做完整的 RD-Cost 计算,递归遍历所有可能的划分方式。一个 64x64 的 CTU,理论上最多有 85 种划分方式(包括非对称划分)。
- 并行化潜力:CTU 之间是独立的,可以并行编码。但 CTU 内部的递归划分是串行的,无法并行。这也是为什么硬件编码器通常会在 CTU 级别做并行,而不是 CU 级别。
性能数据参考:我在优化一个实时编码器时,统计过不同划分策略的耗时占比:
- 帧内模式决策(含划分):约 35%
- 帧间模式决策(含划分):约 45%
- 变换量化:约 12%
- 熵编码:约 8%
你看,光是划分决策就占了 80% 的编码时间。所以优化划分策略,是提升编码速度的关键。
2.5 实战中的划分优化策略
嗯,这里要注意。理论讲完了,咱们聊聊实战中怎么优化。
策略一:早期终止
在递归过程中,如果当前 CU 的 RD-Cost 已经低于某个阈值,就直接停止划分。这个阈值怎么定?我建议根据目标码率动态调整。码率越低,阈值设得越低,倾向于用大块。
策略二:纹理预分析
在正式编码前,先对 CTU 做一次快速的纹理分析。计算每个子块的方差或梯度。如果方差小于阈值,直接判定为平坦区域,限制最大划分深度。
// 纹理预分析示例
int analyzeTexture(CTU* ctu) {
int variance = computeVariance(ctu->pixels);
if (variance < LOW_THRESHOLD) {
return MAX_DEPTH_1; // 平坦区域,最多分到32x32
} else if (variance < MED_THRESHOLD) {
return MAX_DEPTH_2; // 中等纹理,最多分到16x16
} else {
return MAX_DEPTH_3; // 复杂纹理,允许分到8x8
}
}
我的经验:这个预分析的计算量很小,大约只占编码时间的 2%,但能减少 40% 以上的无效递归。我在一个 1080p 的实时编码项目中用了这个策略,编码速度从 25fps 提升到了 42fps,画质几乎没有下降。
策略三:PU 模式剪枝
对于帧间编码的 CU,PU 的划分模式有 8 种(2Nx2N、2NxN、Nx2N、NxN、2NxnU、2NxnD、nLx2N、nRx2N)。不是所有模式都需要尝试。我建议根据运动向量的方向来剪枝:水平运动多的场景,优先尝试水平划分(2NxN);垂直运动多的场景,优先尝试垂直划分(Nx2N)。
好了,这一章的内容就到这里。CTU 的四叉树划分是 HEVC 的基石,理解透了,后面的帧内预测、帧间预测、变换量化都会更容易上手。记住,灵活性和复杂度是一对矛盾,我们的目标就是在两者之间找到最优平衡点。