简介:UVG 4K视频数据集介绍论文系统整理了芬兰坦佩雷大学超视频组发布的十六个超高清测试序列,分辨率均为3840×2160,帧率覆盖50/120fps,提供8位与10位色深的YUV原始格式,素材源于索尼摄像机拍摄的自然场景,以非商业知识共享许可向学界公开。资源面向视频编码算法研究者和标准制定者,也适合超高清流媒体与实时视频通信开发人员,重点弥补现有4K测试集在高帧率场景下的素材缺口,可作为HEVC/H.265与VVC/H.266编码性能评估的统一基准。论文逐一介绍各序列的画面内容与运动特性,给出空间/时间感知信息、率失真曲线、编码复杂度及主观实验设计,并与参考编解码器进行横向对比,论证该数据集能有效补充新一代VVC标准化测试集。下载包共1个PDF文件,容量为931KB,保留完整图表与参考文献;已有141人学习使用,适合作为视频编码方向论文选题、实验对比与算法验证的权威参考资料。
1. UVG 4K视频数据集:为什么50/120fps才是它的真正价值
做视频编码的开发者,手里几乎都备着一两份高清测试序列。可一旦要验证高帧率场景——体育转播、游戏录屏、无人机航拍——常规的24/30fps序列就不够用了。UVG 4K视频数据集(Ultra Video Group)就是为这种诉求准备的:一组未压缩的4K原始YUV序列,原生提供50fps和120fps两档高帧率,场景覆盖人物特写、高速运动、自然微距和户外街景等典型纹理分布,是HEVC/VVC编码器率失真分析、码控调参和端到端系统验证的高频选择。它解决的问题很具体:用统一、可复现的输入源,把不同编码器放在同一把尺子上量。适合做编码器研发、流媒体参数优化和视频质量评测的从业者上手,也适合刚接手视频压缩方向的学生快速建立实验基线。
2. 数据集构成与格式:把UVG的YUV原始文件弄明白
2.1 场景与帧率分布:高纹理、强运动的样本设计逻辑
UVG的数据集不像普通演示视频那样“挑好看的”,它的序列选择有明显的编码分析意图。常见实验集里包含人物特写、蜜蜂微距、赛马起跑、人群冲刺、游艇海岸、水花高速飞溅等场景。这些内容分别对应皮肤渐变纹理、自然物体细密纹理、剧烈全局运动、复杂局部运动、水面反光和高频噪点。编码器在平坦区域、边缘区域和高纹理区域的失真表现差异很大,没有这样的样本分布,单测一两段素材很难暴露问题。
帧率方面,数据集按50fps和120fps两档存放。120fps的序列时间冗余大,相邻帧之间的位移小,运动补偿和分级B帧更容易发挥作用;50fps则更接近广电和体育制作中常用的高帧率档位,运动矢量跨度更大,对运动估计质量的要求更苛刻。两档配合,恰好覆盖了编码器在时间维度上两种截然不同的工作条件。做对比实验时,一个常见的误区是只看分辨率不看帧率,把两种帧率序列混在一起求平均码率,结果往往被某一档拉偏。
2.2 未压缩10bit 4:2:0的单帧体积:存储底线怎么算
UVG提供的是未压缩的原始YUV,不是压缩过的MP4或MXF。原始格式意味着没有量化噪声、没有块效应、没有码控波动,编码器输入的就是传感器或渲染管线直接产出的画面。这样才能把率失真测量中的变量收敛到编码器本身。存储代价很高,但这是编码研究的正常成本。
格式方面,序列普遍采用4K分辨率、4:2:0色度采样、10bit位深。Y平面尺寸为3840x2160,两个色度平面各为960x1080。每个样本用两个字节存放,采用小端序。单帧体积可以精确算出来:Y平面占3840乘2160乘2字节,U和V平面加起来正好是Y平面的一半,单帧合计约24,883,200字节,也就是23.7MiB左右。如果一段序列截取600帧用于实验,体积大约是13.9GiB。按一组7条序列估算,全量拉下来一百多GB是常态,磁盘规划必须前置。
10bit位深不是噱头。编码对比实验里,如果源是8bit,暗部渐变区域在编码前就已经有可见色阶断层,量化损失会被误判为编码器的问题;换成10bit源之后,皮肤、天空这类低频渐变区域的条带效应才能真实反映编码器的量化特性。加上4:2:0是视频编码标准里最常用的像素格式,编解码器、播放器、质量评价工具都默认支持,做实验选型时不需要额外适配。
2.3 为什么编码分析离不开未压缩源:率失真测量的前提
很多刚接触编码评测的人会直接拿蓝光原盘或者平台压好的片子做测试源,这会带来一个隐蔽问题:源本身就带压缩伪影。测PSNR时,这些伪影会叠加到编码器的失真里;测SSIM时,纹理细节已经被前一代编码器抹掉一部分,编码器“实际恢复的细节”会被低估。用UVG这类未压缩YUV作为输入,相当于把编码器的起点拉到一致,不同编码器之间的差异才是可比较的。
另一个容易忽略的点是帧率元数据不存在于文件内部。YUV文件没有封装头,没有帧率字段,没有色彩范围标志。fps、像素格式、分辨率这些信息全部要靠使用者通过命令行参数手动指定。这既是灵活性的来源,也是翻车的高发区。拿到文件先别急着编码,先把参数理顺,否则后面每一步都是错。
3. 把UVG序列接入本地流程:下载规划、校验命令与最小播放方案
3.1 存储规划先行:单序列体积公式与目录布局
我一般会在下载之前先跑一个小脚本确认总规模,而不是等磁盘满了再清理。YUV文件不像压缩视频,删掉一段就得重新下载,后悔药不好买。下面这段脚本按格式参数直接算出预期体积:
python3 - <<'PY' w, h = 3840, 2160 frames = 600 bytes_per_sample = 2 # 10bit 用2字节,不是1.25字节 y_plane = w * h * bytes_per_sample uv_planes = y_plane // 2 # 4:2:0 两个色度平面合计为 Y 的一半 frame_size = y_plane + uv_planes print(f"单帧: {frame_size / 1024**2:.2f} MiB") print(f"{frames}帧合计: {frame_size * frames / 1024**3:.2f} GiB") PY这段逻辑的关键是把10bit样本当成2字节算。很多人在这一步想当然用1.25字节,那样算出来的体积直接小一半,规划必然失守。实际存放时,Y平面之后紧跟U平面,最后是V平面,没有对齐填充,帧与帧之间也没有间隔,seek时可以直接按固定帧大小跳转。
目录结构我习惯分成三层:raw放原始YUV,clips放截取出来的片段,enc放编码输出和日志。原始文件很大,不一定要复制多份,用符号链接就能把同一份源映射到多个项目目录里。
mkdir -p ~/uvg4k/raw ~/uvg4k/clips ~/uvg4k/enc ln -s /data/video_corpus/Beauty_120fps.yuv ~/uvg4k/raw/beauty_120fps.yuv ln -s /data/video_corpus/YachtRide_50fps.yuv ~/uvg4k/raw/yachtride_50fps.yuv符号链接在高帧率高分辨率测试里特别有用。同一个YUV文件可能要同时喂给HM、VTM、x265和x266做对比,每个工具目录下都放一份物理副本既占空间又拖慢备份。链接之后,多份逻辑路径共享一份物理数据,出问题也好排查。
3.2 用ffprobe正确读取UVG:强制pix_fmt、分辨率与帧率
原始YUV没有文件头,直接丢给ffprobe会得到“Invalid data”之类的报错,因为它连宽度都不知道。正确做法是把所有格式信息当作参数喂进去:
ffprobe -f rawvideo -pix_fmt yuv420p10le -s 3840x2160 -r 120 \ -i Beauty_120fps.yuv输出会显示类似 “Video: rawvideo, yuv420p10le, 3840x2160, 120 fps” 的信息。注意这里有几个坑。第一,像素格式要写成 yuv420p10le,带 le 后缀表示小端序,写成 yuv420p10 会按大端序读取,画面会偏色。第二,与帧率必须显式给出,否则ffprobe只能按默认值猜。看到的 Duration 是根据帧数和指定帧率算出来的,不是文件里真实存在的时长标签。第三,如果文件是50fps版本,把 -r 改成50即可,其他参数不用动。
在正式编码之前,我建议先用这条命令把所有序列过一遍,确认每个文件的分辨率和帧率与命名一致。数据集传输过程中偶尔会出现文件名对不上内容的情况,提前发现比等到实验跑完再校验成本低得多。ffprobe对rawvideo的解析不校验文件大小,即使文件不完整也可能正常显示,所以还要结合文件大小和帧数公式做交叉验证,帧数等于文件字节数除以单帧字节数。
3.3 按帧号截取片段:不建索引的rawvideo如何裁剪
很多实验不需要跑完整序列,比如只要测试码控响应,截取300到499帧就够。在封装格式里可以用ss快速seek,但原始YUV没有索引表,seek做不到精准跳帧。要么先算出字节偏移直接读取,要么用帧选择过滤器逐帧解码并挑选:
ffmpeg -f rawvideo -pix_fmt yuv420p10le -s 3840x2160 -r 120 \ -i Beauty_120fps.yuv \ -vf "select='between(n,300,499)'" \ -f rawvideo -pix_fmt yuv420p10le clip_300_499.yuv这条命令会把每一帧都解出来,然后只保留帧号在300到499之间的部分,再封装成新的原始YUV。帧号从0开始数,所以between(n,300,499)得到的是第301帧到第500帧。由于没有索引,它的耗时和整个文件都解码一遍相近,但胜在精确。
如果想跳过大文件直接读取某一帧,可以用Python按固定偏移做随机访问:
frame_bytes = 24883200 with open("Beauty_120fps.yuv", "rb") as f: f.seek(300 * frame_bytes) frame = f.read(frame_bytes)这段代码基于YUV帧大小固定的事实,算出目标帧在文件中的字节位置,直接跳跃读取。对做帧级特征提取的人来说,这是比ffmpeg更省时间的做法。需要注意,seek的单位是字节,不是帧号,计算偏移时务必乘上frame_bytes。如果项目里有人改动过截取逻辑但忘了更新这个常量,踩坑概率极高。
4. 用UVG做编码器对比实验:QP序列、fps分组与结果判读
4.1 x265最小可复现命令:从YUV直接产生码流
拿到UVG序列之后最常用的一步,是用编码器直接吃YUV出码流。以x265为例,命令行参数相对直观,适合做实验基线:
x265 --input-csp i420 --input-res 3840x2160 --input-depth 10 --fps 120 \ --input Beauty_120fps.yuv --frames 600 --qp 32 --keyint 120 \ --min-keyint 24 --bframes 4 --rc-lookahead 40 --preset medium \ --tune psnr --psnr --csv beauty_qp32_120fps.csv --csv-log-level 2 \ --output beauty_qp32_120fps.hevc逐个看参数含义。input-csp i420对应4:2:0,当输入是yuv420p10le时这里必须是i420。input-depth 10告诉编码器样本位深。fps 120不仅影响输出封装的时间基,还会影响x265内部的时间层级和GOP结构,必须和输入序列真实帧率一致。keyint 120表示120帧一个GOP,对120fps序列刚好是1秒,对50fps序列如果还设120就变成了2.4秒,做对比时要把这个因素考虑进去。bframes 4决定B帧数量和层级复杂度,rc-lookahead控制码率控制前瞻窗口,这两项直接影响运动补偿效率。tune psnr和psnr选项打开失真统计,输出码流的同时记录每帧PSNR。
注意这里没有指定码率,而是用固定QP。固定QP模式下编码行为更接近失真受控,适合先看编码器本身的编码效率;码控调优阶段再换用bitrate或ABR模式。输出文件是裸HEVC码流,没有MP4容器,后续可以用ffmpeg加容器,也可以直接用码流分析器打开。
4.2 多QP扫描并记录日志:留下可回溯的实验脚印
单点QP只能看一个工作点,无法画出率失真曲线。常规做法是拿同一序列在多个QP下各跑一遍,然后把码率和失真汇总到一张表里。这里有一个实验卫生问题:每次跑完的日志和码流要能对应上,否则回头写报告时根本分不清哪条曲线是哪条命令产生的。
我习惯用循环批量跑,并把每个QP的日志文件名写明确:
for qp in 22 27 32 37 42; do x265 --input-csp i420 --input-res 3840x2160 --input-depth 10 --fps 120 \ --input Beauty_120fps.yuv --frames 600 --keyint 120 --bframes 4 \ --qp "$qp" --preset medium --tune psnr --psnr \ --csv-log-level 2 --csv "beauty_120fps_qp${qp}.csv" \ --output "beauty_120fps_qp${qp}.hevc" done循环本身不复杂,但有个隐藏坑:CSV日志文件名里如果漏掉了fps,不同帧率的同一QP结果会被覆盖。之前有同事跑完5个QP才发现所有CSV都写进了同一文件,统计阶段完全无法区分,只能重跑。文件名里把序列名、帧率、QP三个要素都写进去,即使隔一个月回头查也能一眼看懂。
跑完后可以用下面命令快速看码流大小:
stat -c "%n %s bytes" beauty_120fps_qp*.hevcPSNR数据在CSV里,x265默认会输出Y、U、V三个分量的每帧PSNR以及合计值。做率失真分析时通常取Y分量PSNR,因为人眼对亮度敏感,而且不同编码器的色度处理差异会干扰对比。
4.3 50fps与120fps分开统计:时间分辨率如何稀释帧间增益
这是整个实验设计里最容易被忽视的一环。120fps序列每秒有120帧,时间冗余大,帧间预测残差小,同QP下码率自然比50fps高,但这不说明编码器差,只说明帧率高。把两种帧率的结果混在一起求平均码率或平均PSNR,会让某个编码器在120fps下的优势被另一组50fps的数据稀释,甚至颠倒结论。
正确的处理方式有两种。第一种是按帧率分组汇报,50fps的序列单独出表,120fps的序列单独出表,只在讨论整体趋势时横向对比。第二种是做归一化,把码率换算成每像素比特数,也就是bit per pixel:
bpp = bitrate / (width * height * fps)
其中bitrate是每秒比特数,width、height是分辨率,fps是实际帧率。除以帧率之后,不同帧率序列的码率被拉到了同一量纲,画率失真曲线时横坐标就能对齐。
还要注意GOP结构的可比性。如果120fps序列用keyint 120,50fps序列也用keyint 120,两个序列的关键帧间隔不相等,GOP长度引入了额外变量。常见做法是把keyint设为帧率的整数倍,比如120fps用120,50fps用50,保证GOP时长都是1秒左右。否则50fps序列的关键帧比例更高,码率会比较吃亏。
5. UVG实战避坑:从下载到编码最常见的6个具体问题
5.1 花屏条纹:被默认为8bit 4:2:0
现象:用ffplay或播放器直接打开.yuv文件,画面是雪花状或绿色条纹,完全看不出原始内容。
原因:原始YUV没有封装头,播放器不知道分辨率、像素格式、位深,默认按8bit 4:2:0处理。10bit数据被当成8bit解析后,字节边界错位,画面自然花掉。
解决:显式告诉播放器格式参数。最简单的方式是用ffplay:
ffplay -f rawvideo -pix_fmt yuv420p10le -s 3840x2160 -r 120 Beauty_120fps.yuv这里任何参数缺失都会导致显示异常。尤其是pix_fmt,写成yuv420p10le之外的值,即使分辨率正确也会偏色或花屏。
5.2 时长显示0:00或异常巨大:fps参数丢失
现象:ffprobe读出来的时长是0或者离谱的几十小时,后续转码时输出视频速度忽快忽慢。
原因:rawvideo流没有时间基准,ffprobe只能根据给定的帧率计算时长。如果不指定-r,某些版本会用默认帧率去套,导致时长和音视频同步全乱。
解决:任何工具读取UVG时都要同时给fps。转码时也要给,否则ffmpeg默认按25fps处理120fps的源,输出的视频相当于被拉长了5倍,肉眼可见的慢放。编码器参数里的fps往往不参与帧间预测,但影响输出时间基和码率计算,不能省。
5.3 磁盘跑满:没算清单序列体积
现象:跑到第三个序列时磁盘提示空间不足,编码中断,前面的数据也连带受到影响。
原因:一个600帧的4K 10bit序列接近14GiB,7个序列全量下载加转码临时文件轻松超过150GiB。很多人只预估了压缩视频的大小,完全没意识到未压缩YUV的膨胀率。
解决:下载前用2.1节的方式逐序列算体积,确认剩余空间够两倍后再拉数据。原始文件和中间文件放在不同挂载点,避免同一个分区同时承担源数据、剪辑片段和编码输出。磁盘实在紧张时,只挑4到5个代表性的序列做实验,不要追求全量。
5.4 混用fps导致统计结论翻车
现象:某编码器在50fps组表现好,在120fps组表现差,合并统计后反而得到“两者差不多”的错误结论。
原因:fps是混淆变量。高帧率序列时间冗余大,帧间编码收益天然高,混在一起会掩盖编码器本身在运动估计和码控上的差异。
解决:按fps分组统计,或者把码率换算成bpp之后再比较。GOP也按帧率比例统一,120fps用120帧GOP,50fps用50帧GOP。如果实验目的是对比“同一个编码器在不同帧率下的效率”,那就固定除了fps之外的所有参数,单独看帧率带来的变化。
5.5 裁剪不精准:rawvideo没有seek索引
现象:用ffmpeg的-ss参数从YUV里截取片段,结果截出来的画面不是目标帧号,甚至花屏。
原因:常规封装格式里,-ss通过索引快速跳到目标位置。rawvideo没有索引,seek只能从头解码,某些版本的ffmpeg会给出错误帧或空帧。
解决:用select过滤器按帧号选择,前一章的脚本就是可靠做法。或者直接用Python按帧大小计算字节偏移读取,速度最快。不要依赖-seek在原始YUV上的表现,它的精确性没有保证。
5.6 颜色偏灰或过艳:色彩范围和转换矩阵被忽略
现象:转出MP4后用播放器看,画面灰蒙蒙或饱和度异常,和原始YUV观感不一致。
原因:YUV文件本身没有色彩范围标志。大多数播放器默认按有限范围处理,如果源数据实际是全范围,就会把0到1023的满量程压缩到64到940,画面发灰。反之,把有限范围数据按全范围解,色彩就会过艳。
解决:转码时显式声明色彩参数,例如在ffmpeg命令里加上:
ffmpeg -f rawvideo -pix_fmt yuv420p10le -s 3840x2160 -r 120 \ -i Beauty_120fps.yuv -color_range tv \ -colorspace bt709 -color_primaries bt709 -color_trc bt709 \ -c:v libx265 -crf 24 beauty_preview.mkv这样播放器就知道按有限范围BT.709处理。如果要用PSNR等指标做数值对比,建议直接对比YUV域,而不要在转码链路上引入额外的色彩转换误差。
6. 把UVG沉淀成个人测试集:固定清单与回归验证两件事
第一次跑通之后,UVG的用法慢慢会固化成一套固定动作。与其每次临时想参数,不如把序列信息和编码配置写进一个清单文件,让整个实验流程可回溯。这个习惯对长期做编码器研究的人特别重要。
6.1 建立固定测试清单:序列路径与参数写进JSON
我先维护一份JSON清单,记录每个序列的路径、fps、实际帧数和编码参数。内容大致是这个结构:
{ "name": "uvg_dev_set_v1", "x265_common": { "preset": "medium", "keyint": "fps", "bframes": 4, "rc-lookahead": 40, "tune": "psnr" }, "sequences": [ {"name": "beauty", "fps": 120, "frames": 600, "path": "raw/beauty_120fps.yuv"}, {"name": "honeybee", "fps": 120, "frames": 600, "path": "raw/honeybee_120fps.yuv"}, {"name": "readysetgo", "fps": 50, "frames": 600, "path": "raw/readysetgo_50fps.yuv"}, {"name": "yachtride", "fps": 50, "frames": 600, "path": "raw/yachtride_50fps.yuv"} ] }keyint写成"fps"不是真的让编码器读这个字符串,而是提醒自己在生成命令行时把keyint换算成fps的数值。这样120fps序列的GOP是120,50fps序列的GOP是50,GOP时长保持一致。相比之下,如果用固定keyint 120,时间基准不同的序列之间比较就会失真。
每次跑正式实验前,我会写一个Python脚本读这份JSON,批量生成x265命令并执行。脚本本身不复杂,但价值在于把参数来源统一,避免手工敲命令时的拼写差异。实验记录里的命令、CSV、码流文件全部以清单为基准,后续核对时有据可查。
6.2 回归验证:两次编码结果应在位级一致
固定工具版本和输入的前提下,x265的编码结果应当是可复现的。如果同一命令跑两次,输出码流的SHA256不一致,要么是工具链版本变了,要么是多线程调度引入了不确定性。拿UVG做回归测试时,我用这个习惯来验证实验环境是否干净:
sha256sum beauty_120fps_qp32.hevc beauty_120fps_qp32.csv对比两个时间点跑出的hash,如果码流一致,说明编码器行为稳定,率失真曲线的差异可以归因于参数或版本变化。如果hash不一致,先复查编译选项和线程数,再检查是否用了不稳定的汇编优化路径。之前我就遇到过一台机器用了不同编译版本的x265,导致同一命令输出码流和PSNR都有微小差异,最后是靠hash对比定位出来的。
这套流程还有一个衍生用途:调参后验证没有破坏既有质量。在固定测试集上跑回归,PSNR波动超过0.01dB就要回去查改动。UVG序列的高帧率特性对这类回归尤其敏感,时间层结构调整会在120fps序列上暴露得比普通24fps序列快得多。把UVG用顺之后,你会发现它对编码器变化的反馈非常灵敏,这就是高时间分辨率素材在研发流程里的价值。
我做码控调参时,曾经把50fps和120fps的序列混在一个表格里算平均,结论刚好相反。后来把序列按fps分组,把QP曲线分开画,才真正看到哪个编码器在高帧率下更省码率。现在这套UVG实验流程已经成了我的默认动作:先定JSON清单,再跑QP扫描,最后验hash。希望这套思路帮到你。
本文还有配套的精品资源,点击获取