简介:这份PDF名为《基于深度学习的视频图像压缩编码方法优化》,面向从事深度学习与视频编解码技术交叉领域的科研人员、算法工程师及高校相关专业学生。资源为单文件PDF,压缩包仅1.18MB,便于快速下载阅读。文章针对移动设备解压方式与编码方式不匹配导致无法浏览视频图像的问题,提出JND压缩编码模型。模型结合Retinxe理论增强局部像素特征,并通过MSRCG算法进行照度分量估测与色彩恒常性分析,由主机自动选择最优压缩策略。文中包含100个视频/图像的对比实验,从压缩时间和解压成功率两维度证明JND模型优于传统MDLVQ模型。目前该资源已有275人学习,适合需要了解视频压缩编码优化思路、查阅JND模型理论依据与实验数据的用户。通过阅读可掌握基于深度学习的压缩编码改进方向,并获取可引用的实验对比结果,为相关研究或项目提供专业参考。
1. 视频压缩编码遇上深度学习:不再只是改变换和量化
传统视频编码标准从 H.264 到 H.265、H.266,核心改进都落在更细的块划分、更多的帧内预测方向和更复杂的环路滤波上。这些方法有一个共同前提:编码器的每一个模块都是人工设计的,率失真优化只能在一个固定的框架内寻找量化参数、运动矢量等有限变量的最优组合。深度学习的入场改变了这个前提,它把“怎么做预测、怎么估计概率、怎么分配比特”全部变成可学习的映射。基于深度学习的视频图像压缩编码方法优化,做的事情不再是给 H.266 换一个 CNN 滤波,而是从特征域直接学习一个压缩表示,再通过自回归概率模型逼近真实的熵界。
这篇内容面向两类人:一类是刚接触学习压缩、想知道端到端模型怎么搭和怎么训练的同学;另一类是已经在跑实验、但卡在训练不稳或部署调优上的工程师。我会把模型结构、损失函数、训练参数、量化部署和对标 BD-Rate 的方法串成一条可执行的路径,尽量让每个环节都能对应到具体代码和命令。
2. 端到端视频压缩的模型结构:光流、上下文与率失真权衡
端到端视频压缩的典型结构是三段式:先做帧间预测,把当前帧减去运动补偿后的参考帧得到残差;再把残差和运动信息分别送进自编码器压缩;最后用熵模型估计压缩表示的概率分布,用算术编码写比特流。和图像压缩相比,视频多了一个时间维度的运动信息,所以模型里最关键的差异在帧间预测和运动编码。
2.1 帧间预测用光流代替传统运动估计
传统编码器用块匹配找运动矢量,假设块内像素做平移运动。深度学习方案常用光流网络直接估计像素级运动,例如把两个相邻帧输入一个小型 CNN,输出两通道的光流图。常见做法是用一个轻量的 SpyNet 或者 PWC-Net 的裁剪版本,光流图再经过一个运动自编码器压缩成潜在表示。
# 伪代码:光流估计与运动补偿 import torch import torch.nn as nn class FlowNet(nn.Module): def __init__(self): super().__init__() # 实际实现里通常是多尺度金字塔结构 self.conv = nn.Sequential( nn.Conv2d(6, 64, 7, stride=2, padding=3), nn.ReLU(), nn.Conv2d(64, 128, 5, stride=2, padding=2), nn.ReLU(), nn.Conv2d(128, 2, 3, padding=1) ) def forward(self, ref, cur): return self.conv(torch.cat([ref, cur], dim=1)) def motion_compensate(ref, flow): # 使用双线性采样进行warp n, _, h, w = ref.size() y, x = torch.meshgrid(torch.linspace(-1, 1, h), torch.linspace(-1, 1, w)) grid = torch.stack([x + flow[:, 0], y + flow[:, 1]], dim=-1) return torch.nn.functional.grid_sample(ref, grid, mode="bilinear", padding_mode="border")这里的核心参数是光流网络的下采样倍数和 warp 时采用的采样方式。实际训练中,光流估计结果的精度直接影响残差能量:光流越准,残差越小,压缩率越高。但光流本身也要消耗比特,因此模型必须学习在“多花比特存光流”和“少花比特存残差”之间权衡。我一般会在训练初期冻结光流网络,只训练自编码器,等残差分布稳定后再解冻,这样可以避免两个模块互相拉扯导致训练不收敛。
2.2 熵编码由概率模型接管:超先验与上下文
端到端图像压缩里的熵模型一般用超先验(hyperprior)结构,视频压缩在此基础上加入时间上下文。超先验假设每个潜在元素的概率分布由一个辅助信息(超潜在表示)来预测,这个辅助信息也被压缩进比特流。对于视频,还可以把前一帧的潜在表示作为当前帧的上下文,这样概率估计更准,码率更低。
熵模型输出的是均值 μ 和尺度 σ,然后假设潜在 y 服从高斯分布。算术编码时用这个分布把 y 编码成二进制串。训练时,这个分布的交叉熵近似于码率,所以损失函数里,率项就是“编码 y 和 z 所需的比特数估计”。
# 训练时码率估计的简化实现 def rate_estimate(y_hat, mu, sigma): # 对潜在表示添加均匀噪声模拟量化,再计算对数似然 y_q = y_hat + torch.rand_like(y_hat) - 0.5 gaussian = torch.distributions.Normal(mu, sigma) # 比特数 = -log p(y_q) return -gaussian.log_prob(y_q).sum(dim=(1, 2, 3))注意这里的“量化”是加均匀噪声而不是真正的取整。原因在于取整不可导,用噪声替代可以保持梯度回传。推理阶段才做真正的四舍五入,然后交给算术编码器。这个替换是学习压缩里最常用的技巧,常叫“量化代理”。
2.3 损失函数:率失真误差如何调和
率失真优化要最小化码率和失真之和。通常写成:
L = R + λ * D其中 R 是码率,D 是失真。失真可以用 MSE(对应 PSNR),也可以用 MS-SSIM 或感知距离。λ 控制压缩质量,λ 越大,允许更高的码率换取更小的失真。不同 λ 会训出不同码率点的模型,实际做实验时常训练 6 到 8 个 λ 值,得到一组码率-失真曲线。
训练时要注意 D 的取值范围。如果用像素值归一化到 [0, 1] 的 MSE,它和 PSNR 的关系是PSNR = -10*log10(MSE)。很多新人直接把 λ 设成 0.01 之类的固定值,结果模型要么质量极差,要么码率爆炸。一般做法是参考 CompressAI 官方配置,λ 从 0.001 到 0.05 之间按对数均匀取几个值。
2.4 训练策略与关键参数:lambda、batch size与epoch
端到端压缩模型的训练比普通图像分类更敏感,以下参数我建议先按这个表起步:
| 参数 | 建议值 | 说明 |
|---|---|---|
| 输入尺寸 | 256x256 随机裁剪 | 视频帧通常分辨率大,裁剪可以增强多样性 |
| batch size | 4~8 | 视频序列本身帧间相关性强,batch过大容易让运动估计模块过拟合 |
| 学习率 | 1e-4,配合余弦退火 | Adam 优化器,初始 1e-4 比较稳 |
| lambda | 按目标码率选 | 例如 MVQE 的参考配置:0.0016~0.03 |
| epoch | 100~200 | 在 Vimeo-90K 上,一般 80 个 epoch 后 PSNR 提升变缓 |
| 梯度裁剪 | max_norm=1.0 | 防止光流网络训练初期梯度爆炸 |
训练时最难判断的问题是“当前模型有没有过拟合”。视频压缩模型在训练集上 PSNR 很高,但在测试集上掉得厉害,这往往是因为帧间预测模块学会了记住特定序列的运动模式。我通常会每 5 个 epoch 在验证集上计算一次 BD-Rate,而不是只看训练 loss。BD-Rate 下降趋势正常,说明模型确实在学通用的压缩表示。
3. 从论文到可运行代码:环境、训练与推理实现
看完结构,下一步是把模型跑起来。这里以一个简化的端到端视频压缩训练为例,完整代码通常包括数据加载、模型定义、训练循环和验证。下面只给最小可运行路径,重点在环境依赖和命令参数。
3.1 依赖与数据组织:把Vimeo-90K整理成训练集
视频压缩训练最常用的公开数据集是 Vimeo-90K,它有 89800 个短视频片段,每个片段 7 帧。组织方式按seq_num/00000.png到00006.png存放。如果你的网络环境下载受限,也可以自己用摄像头或者从公开视频网站截取约 10000 个 7 帧片段,但效果会差一些。
依赖安装建议使用 conda:
conda create -n vc python=3.8 conda activate vc pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install compressai opencv-python tqdm注释:compressai是用于图像压缩的库,但它的熵模型、算术编码和 GDN 激活函数可以直接复用到视频压缩里。视频压缩模型一般是自己写光流和 warp,复用 compressai 的编码器和超先验模块。
3.2 训练脚本与命令行参数说明
下面是一个简单的训练循环核心部分,去掉了断点续训和 logging 的冗余代码:
# train.py import torch from torch.optim import Adam from model import EndtoEndVideoCompressor # 自定义模型 def train_one_epoch(model, dataloader, optimizer, lambda_val): model.train() total_loss = 0 for batch in dataloader: # batch: [B, T, C, H, W], T=7 ref = batch[:, 0].cuda() # 参考帧 target = batch[:, 2].cuda() # 当前帧(隔帧预测更常见) optimizer.zero_grad() y_hat, z_hat, mu, sigma, warped = model(ref, target) # 码率 rate_y = rate_estimate(y_hat, mu, sigma).mean() rate_z = rate_estimate(z_hat, torch.zeros_like(z_hat), torch.ones_like(z_hat)).mean() # 失真 mse = ((warped + target - target.detach() + target.detach() - target) ** 2).mean() # 示例写法 # 更准确:重建帧 = warped + residual_decoded recon = model.reconstruct(y_hat, z_hat, warped) d = ((recon - target) ** 2).mean() loss = rate_y + rate_z + lambda_val * d loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() total_loss += loss.item() return total_loss / len(dataloader)这段代码里参数lambda_val在命令行传入。注意rate_z直接用了标准正态分布做先验,实际模型里超先验 z 的频率是由熵模型估计的,这里为了简化写成近似。训练时建议先固定lambda=0.01跑通全流程,再换多个 λ 值做曲线。
3.3 混合精度与梯度累积:让显存不够的卡也能跑
视频压缩模型显存占用大头在光流估计和 warp 操作,尤其输入是 256x256 的 7 帧序列时,一张 24G 的 3090 也只能跑到 batch size 8 附近。显存不够时有两个手段:
第一个是自动混合精度(AMP)。PyTorch 里只需加两行:
scaler = torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): y_hat, z_hat, mu, sigma, warped = model(ref, target) loss = ... scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()注意光流网络的梯度在 fp16 下可能下溢,建议在光流网络内部保持 fp32,或在 warp 之前手动转回 fp32。
第二个是梯度累积。小 batch 多步累积可以等效增大 batch:
python train.py --batch-size 4 --accumulate-steps 4实现时维护一个变量,每accumulate_steps步才调用一次 optimizer.step。这个方法对大 batch 有正则化效果,但注意 BN 层在累积模式下效果会变差,视频压缩模型一般用 GDN 激活而不是 BN,所以影响不大。
4. 优化落地的五个关键点:从模型压缩到推理加速
训练好的模型要在实际场景用,必须要经过优化,否则动辄几百兆的参数量和每秒几帧的编解码速度无法接受。下面按我自己的经验顺序讲。
4.1 量化感知训练比训练后量化更稳
把 32 位浮点权重变成 8 位整数后,模型体积降到四分之一,推理速度在支持 INT8 的硬件上提升 2 到 4 倍。但训练后量化(PTQ)对熵模型很敏感,因为概率分布的精度直接决定算术编码的压缩率,微小误差会导致码率上升 10% 以上。因此更可靠的做法是量化感知训练(QAT)。
QAT 的关键是在训练时插入伪量化节点,用直通估计器让反向传播通过取整操作。对压缩模型来说,不仅要量化权重,还要量化潜在表示和熵模型的输出。实际落地时我只会量化自编码器的卷积和光流网络,把最后的算术编码器保留为浮点,因为算术编码本身是串行运算,量化它并不加速多少,反而容易崩溃。
4.2 评价指标不能只用PSNR:BD-Rate才是压缩效率标尺
PSNR 只能反映单点的失真。两个编码器在不同码率下各有高低,单点对比没有说服力。行业标准是 BD-Rate,它计算在同等的 PSNR 或 MS-SSIM 下,目标编码器相比参考编码器节省了多少码率。
计算 BD-Rate 需要至少四个码率点的 RD 曲线。常用的工具是 VCE 的脚本或者用 Python 实现三次样条插值:
from scipy.interpolate import interp1d def bd_rate(r1, p1, r2, p2): # r1 p1 是参考编码器的码率与PSNR,r2 p2 是测试编码器 # 映射到对数域,再在公共PSNR区间积分 log_r1 = np.log(r1) log_r2 = np.log(r2) p_min = max(p1.min(), p2.min()) p_max = min(p1.max(), p2.max()) f1 = interp1d(p1, log_r1, fill_value="extrapolate") f2 = interp1d(p2, log_r2, fill_value="extrapolate") ps = np.linspace(p_min, p_max, 100) return (np.trapezoid(f2(ps) - f1(ps), ps) / (p_max - p_min)) * 100 # 负值为节省BD-Rate 为负表示节省码率。比如 BD-Rate = -25%,意味着在同一 PSNR 下码率降低了 25%。如果你在 1080p 测试集上看到模型对比 H.265 节约 30% 以上,说明优化有效。
4.3 显存溢出与收敛不稳的排查路径
显存溢出(OOM)在视频压缩训练里几乎是必遇问题。排查顺序:先降 batch size 到 1,如果还溢出,说明是模型内部张量尺寸问题。常见原因有三个:光流估计在输入分辨率不是 16 的倍数时,输出尺度没有取整;自编码器的下采样层数导致潜在层空间尺寸为奇数;或在多尺度损失中上采样倍数不匹配。解决办法是在每次 forward 后检查y_hat.shape,并打印日志。
收敛不稳表现是 loss 在训练到一半时突然 NaN。通常原因有:光流网络输出数值过大导致 warp 采样越界,需要把光流归一化到 [-1, 1];或学习率太高,Adam 在自适应估计上崩溃。启动训练时加一个 loss 的 NaN 检测,一旦出现就把学习率减半并回滚到上一个检查点。
4.4 剪枝和蒸馏在视频压缩里的使用边界
结构化剪枝可以直接去掉光流网络里影响较小的通道,对自编码器的卷积效果一般,因为熵模型对通道数很敏感。剪掉一个通道会改变潜在表示的概率分布,后续超先验需要重训。知识蒸馏在视频压缩里主要把大模型学到的光流结果当作软标签,教小光流网络,这样可以保持运动补偿质量同时降低参数。相对而言,蒸馏比剪枝更容易控制性能回退。
5. 在RKNN等边缘平台部署时的模型优化与编码耗时
当编码器要跑在算力受限的设备上,模型的参数量、算子类型和数据精度都要重新设计。RKNN 是常见的 Rockchip 平台神经网络推理框架,对 PyTorch 模型支持先把模型导出为 ONNX,再转换成 RKNN 格式。转换时最容易遇到的问题是动态尺寸不支持。视频压缩模型里的 warp 操作需要根据输入分辨率动态生成网格,这个操作在 RKNN 上可能没有对应算子,常见做法是用固定尺寸的输入进行编译,或者把 warp 拆解为几个基础算子。
# 将PyTorch模型转换为RKNN(示意命令) python export_onnx.py --input_size 256 256 --save model.onnx rknn_toolkit2 -t model.onnx -o model.rknn --quantized true --bit 8转换后的模型会执行 INT8 量化,因此编码耗时的主要瓶颈从卷积变成了算术编码。因为算术编码是逐符号串行计算,几乎无法并行,迁移到边缘平台后,你会发现即使模型推理从 50ms 降到 15ms,整个编码流程仍然要 100ms 以上。这时优化的重点就不在模型结构,而在码率控制策略:先跳过低价值的潜在符号,或者对显著区域采用更高的量化步长,从而减少需要串行编码的符号数。
验证优化效果的方法很简单:在开发板上跑同一个视频片段,统计编码耗时和 BD-Rate。把模型推理耗时和算术编码耗时分开打点,如果后者占比超过 60%,就别再折腾模型量化了,转向概率模型缓存或批处理。这也是这类项目落地时最容易被忽略的环节——模型优化做得再快,算术编码器不上速度,整体编码实时性依然上不去。
本文还有配套的精品资源,点击获取