Rerun PixelFormat 全解析:YUV 色度采样、取值范围与图像随机访问编码
2026/9/17 18:42:23 网站建设 项目流程

Rerun PixelFormat 全解析:YUV 色度采样、取值范围与图像随机访问编码

【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun

本文以 Rerun 可视化 SDK 中的encodings.PixelFormat枚举(官方类型参考)为核心,系统讲解其在多模态机器人数据可视化中处理 YUV/灰度图像的职责:如何描述色度下采样(chroma subsampling)、每通道位深差异与 limited/full range 取值,并结合仓库源码与示例说明各变体的内存布局、随机访问解码原理及在rr.Image/ImageFormat中的实际用法。读完本文,你将能够为 Rerun 的Image组件正确指定像素格式、理解ImageFormat字段的优先级关系,并掌握decode_rgb_at、GPU 端yuv_converter.wgsl等底层实现的原理与边界。

一、PixelFormat 是什么:为什么需要它

PixelFormat是 Rerun 数据模型(re_types)中用于描述archetypes.Image特定存储格式的枚举类型。大多数图像可以仅用两个枚举描述清楚:色彩模型encodings.ColorModel(如LRGBRGBA)加上通道数据类型encodings.ChannelDatatype(如U8F16)。

但当图像格式存在两类特殊性质时,仅靠这两个枚举就不够了:

  1. 色度下采样(chroma subsampling):为了压缩带宽,YUV 类格式常让色度通道(U/V)的分辨率低于亮度通道(Y),如 4:2:0、4:2:2。
  2. 每通道位数差异与取值范围:部分格式使用 limited range YUV(8bit 下 Y 有效区间 [16, 235]、U/V 有效区间 [16, 240]),而另一些使用 full range(0~255)。

这正是PixelFormat存在的原因。在官方文档中明确写到:所有这些格式都支持随机访问(random access)——即可以不经过整帧解码,直接按坐标取出任意像素。对于更压缩的编码格式(JPEG/PNG 等),Rerun 则使用archetypes.EncodedImage,二者是互补关系。

补充说明:Rerun 仓库代码中常以YUV/YCbCr/YPbPr混用称呼这一色彩空间。在 yuv_converter.rs 的注释中给出了严格定义:YUV是模拟信号;YCbCr是数字信号中经过缩放与偏移的版本(即本文所说的 "limited range YUV");YPbPr是传输YCbCr的物理分量线缆。实际业界用法比较混乱,例如 OpenCV 在讨论全范围时使用YCbCr,讨论下采样时使用YUV

二、全部 10 个变体逐一详解

PixelFormat共定义 10 个变体,对应的 Rust 定义位于 pixel_format.rs,以下数值即其在 Arrow 数据中的原始枚举值(#[repr(u8)])。

2.1 4:2:0 平面(planar)格式

变体数值别名range
Y_U_V12_LimitedRange20I420limited
Y_U_V12_FullRange44I420full
NV1226Y_UV12limited
YUY227YUYVYUYV16NV21limited

Y_U_V12_LimitedRange(=20,即I420:YUV 4:2:0 全平面(fully planar)格式,无交错。整个图像先以 Y 单独占一个平面,随后是 U 平面与 V 平面,U/V 平面的宽高都只有 Y 平面的一半。使用 limited range YUV。

Y_U_V12_FullRange(=44):与上面相同的I420平面布局,但改用 full range(0~255),与NV12的 limited range 形成对照。

NV12(=26,又名Y_UV12:YUV 4:2:0 下采样、12 bits/pixel、8 bits/channel 的半平面(semi-planar)格式,也是该枚举的默认变体(在 Rust 定义中以#[default]标注,见 pixel_format.rs)。内存布局为:先是整幅图像的 Y 平面,随后是一个交错平面,顺序为U0, V0, U1, V1, …。使用 limited range YUV。这是摄像头/视频采集中最常见的格式之一,仓库示例 examples/python/nv12/nv12.py 即演示了如何把 webcam 帧转成 NV12 后流式送入 Rerun。

YUY2(=27,别名YUYVYUYV16NV21:YUV 4:2:2 下采样、16 bits/pixel、8 bits/channel 的**单平面交错(interleaved)**格式。像素序列为Y0, U0, Y1, V0,全部位于同一平面。使用 limited range YUV。注意仓库文档特别提醒:YUY2常被误称为NV21,两者命名虽有混淆,但YUY2在 Rerun 中即指 4:2:2 交错格式。

2.2 4:4:4 平面格式

变体数值别名range
Y_U_V24_LimitedRange39I444limited
Y_U_V24_FullRange40I444full

Y_U_V24_LimitedRange(=39)/Y_U_V24_FullRange(=40)Y_U_V24是 YUV 4:4:4 全平面格式,无任何色度下采样,别名I444。内存布局为:先是完整 Y 平面,随后是 U 平面、V 平面(各平面尺寸相同)。区别仅在于前者使用 limited range(Y ∈ [16,235],U/V ∈ [16,240]),后者使用 full range(0~255)。

2.3 4:2:2 平面格式

变体数值别名range
Y_U_V16_LimitedRange49I422limited
Y_U_V16_FullRange50I422full

Y_U_V16_LimitedRange(=49)/Y_U_V16_FullRange(=50)Y_U_V16是 YUV 4:2:2 全平面格式,别名I422。布局为 Y 平面后跟 U、V 平面,U/V 平面只有 Y 平面水平分辨率的一半(垂直分辨率相同)。两者仅在 YUV range 上不同。

2.4 灰度(monochrome)格式

变体数值别名range
Y8_FullRange30gray / I400full
Y8_LimitedRange41gray / I400limited

Y8_FullRange(=30):只有 Y 平面、无任何色度的单色格式,本质上等价于 YUV 4:0:0 平面格式,也常被直接称为 "gray"。它与 8bit 亮度/灰度ColorModel几乎完全一致,使用 full range,Y ∈ [0, 255]。

Y8_LimitedRange(=41):同样只有 Y 平面的单色 "gray" 格式,但使用 limited range,Y ∈ [16, 235]。除该范围限制/重映射外,与 8bit 亮度/灰度几乎一致。

2.5 变体速查总表

变体数值采样结构位/像素平面数YUV range别名
Y_U_V12_LimitedRange204:2:0 planar123limitedI420
NV12(默认)264:2:0 semi-planar122limitedY_UV12
YUY2274:2:2 interleaved161limitedYUYV/YUYV16/NV21
Y8_FullRange304:0:0(单色)81fullgray
Y_U_V24_LimitedRange394:4:4 planar243limitedI444
Y_U_V24_FullRange404:4:4 planar243fullI444
Y8_LimitedRange414:0:0(单色)81limitedgray
Y_U_V12_FullRange444:2:0 planar123fullI420
Y_U_V16_LimitedRange494:2:2 planar163limitedI422
Y_U_V16_FullRange504:2:2 planar163fullI422

("位/像素" 与 "平面数" 依据 pixel_format_ext.rs 中num_bytesdecode_yuv_at的实现归纳)

三、与 ImageFormat 的关系:字段优先级

PixelFormat本身不单独出现,而是作为ImageFormat结构体中的一个字段使用。ImageFormat是描述components.ImageBuffer内容的元数据,共 5 个字段:

字段Arrow 类型含义
width非空UInt32图像宽度(像素)
height非空UInt32图像高度(像素)
pixel_formatUInt8像素格式,主要用于色度下采样与每通道位深差异的格式
color_modelUInt8色彩模型:L、RGB、RGBA…
channel_datatypeUInt8每个通道的数据类型(U8、F16…)

关键优先级规则(务必牢记):一旦指定了pixel_format,它优先于color_modelchannel_datatype,后两者将被忽略(见 image_format.md 的原文说明)。反过来,如果只提供color_model+channel_datatype,则必须二者齐备才能完整定义像素格式。在 Rust 实现中,PixelFormat的 Arrow 数据类型为UInt8(见 pixel_format.rs),枚举值直接以u8存储;非法值在反序列化时会被拒绝(missing_union_arm错误)。

四、随机访问解码:从原始字节到 RGB

官方文档强调所有PixelFormat均支持随机访问。这一能力由 pixel_format_ext.rs 中的扩展实现提供:

  • decode_yuv_at(buf, [w, h], [x, y]) -> Option<[u8; 3]>:按坐标直接取出该像素的[Y, U, V]三元组,越界返回None
  • decode_rgb_at(...):在其上调用rgb_from_yuv(y, u, v, is_limited_yuv_range(), yuv_matrix_coefficients())得到[R, G, B]
  • num_bytes([w, h]):计算该格式下整幅图像的字节数(见上文总表);
  • has_alpha():全部 10 个变体均返回false(无 alpha 通道);
  • is_float():全部返回false(均为 8bit 整型);
  • datatype():全部解码为ChannelDatatype::U8
  • color_model():全部映射为ColorModel::RGB

4.1 各格式的坐标寻址逻辑

decode_yuv_at的实现(pixel_format_ext.rs)清晰展示了各布局的寻址方式:

  • 4:2:0 平面(Y_U_V12_*:Y 平面大小w*h,U/V 平面大小为w*h/4(水平、垂直各减半)。Y 坐标y*w + x,U 坐标y*w/4 + x/2偏移到 Y 平面之后,V 坐标再偏移一个 U 平面大小。
  • 4:2:2 平面(Y_U_V16_*:U/V 平面大小为w*h/2(仅水平减半),UV 坐标(y*w + x)/2
  • 4:4:4 平面(Y_U_V24_*:三个平面大小相同,均为w*h,直接依次偏移。
  • NV12:Y 平面后接 UV 交错平面,U 在w*h + (y/2)*w + x,V 紧随其后 +1。
  • YUY2:单平面交错,像素偏移(y*w + x)*2;偶数 x 取[Y, U, V] = [i, i+1, i+3],奇数 x 取[i, i-1, i+1]
  • Y8_*:单色,直接y*w + x取 Y,U/V 固定为128(中性色度)。

4.2 YUV→RGB 转换矩阵与 range

decode_rgb_at依赖两个属性(pixel_format_ext.rs):

  • is_limited_yuv_range():10 个变体中,所有*_LimitedRangeNV12YUY2返回true,其余返回false
  • yuv_matrix_coefficients()Y_U_V12/16/24_*Y8_*使用BT.709系数,而NV12YUY2使用BT.601系数。

这与 GPU 侧的着色器枚举一一对应。在 yuv_converter.rs 中,YuvMatrixCoefficients定义了Identity(把 YUV 当作 GBR)、Bt601(SDTV/Rec.601)、Bt709(HDTV/Rec.709)三种矩阵,并注明 BT.2020 尚不支持。注释解释了为什么只关心矩阵差异:BT.709 与 sRGB 的原色(primaries)几乎一致、BT.601 PAL 与 BT.709 原色相同、BT.709 与 sRGB 的传递函数差异常被忽略,因此非 HDR 内容在转换后无需额外处理。

五、在 Python / Rust / C++ SDK 中的用法

5.1 Python 示例:以rr.Image指定pixel_format

仓库代码片段 docs/snippets/all/archetypes/image_formats.py 演示了完整的用法:

import numpy as np import rerun as rr rr.init("rerun_example_image_formats", spawn=True) # 普通 RGB 图像 image = np.array( [[[x, min(255, x + y), y] for x in range(256)] for y in range(256)], dtype=np.uint8, ) rr.log("image_rgb", rr.Image(image)) # 灰度:只取一个通道 + color_model="l" rr.log("image_green_only", rr.Image(image[:, :, 1], color_model="l")) # 4:2:2 平面格式:Y、U、V 三个平面按序拼成一个 bytes y = bytes([128 for y in range(256) for x in range(256)]) u = bytes([x * 2 for y in range(256) for x in range(128)]) # 水平减半 v = bytes([y for y in range(256) for x in range(128)]) rr.log( "image_yuv422", rr.Image( bytes=y + u + v, width=256, height=256, pixel_format=rr.PixelFormat.Y_U_V16_FullRange, ), )

要点:

  • 传入bytes=原始缓冲区时必须同时给出widthheight
  • pixel_format一经指定,color_model/channel_datatype即被忽略(见第三节优先级规则);
  • Y/U/V 三个平面的长度必须与num_bytes推算一致,否则后续随机访问解码会越界返回None

5.2 实战:摄像头 NV12 流式可视化

examples/python/nv12/nv12.py 演示了从 OpenCV 采集视频帧并转为 NV12 后实时送入 Rerun:

import cv2 import numpy as np import rerun as rr def bgr2nv12(bgr: cv2.typing.MatLike) -> cv2.typing.MatLike: yuv = cv2.cvtColor(bgr, cv2.COLOR_BGR2YUV_I420) uv_row_cnt = yuv.shape[0] // 3 uv_plane = np.transpose(yuv[uv_row_cnt * 2 :].reshape(2, -1), [1, 0]) yuv[uv_row_cnt * 2 :] = uv_plane.reshape(uv_row_cnt, -1) return yuv # 主循环中每帧: rr.log( "NV12", rr.Image( bytes=bytes(bgr2nv12(frame)), width=frame.shape[1], height=frame.shape[0], pixel_format=rr.PixelFormat.NV12, ), )

这段代码把 BGR 帧先转成 I420(三个平面),再把 UV 两个平面重排成交错的U0,V0,U1,V1,…序列,正是NV12变体期望的字节布局。运行该示例需要本机摄像头(依赖pip install rerun-sdk与 OpenCV)。

5.3 Rust / C++ API 对照

  • Rustrerun::encodings::PixelFormat枚举,定义于 pixel_format.rs,#[repr(u8)]保证与 ArrowUInt8存储一致;扩展方法(num_bytesdecode_rgb_at等)见 pixel_format_ext.rs。
  • C++rerun::encodings::PixelFormat,头文件位于 rerun_cpp/src/rerun,通过生成的 API 与 Python/Rust 对齐。

六、GPU 侧:YUV 下采样格式如何被渲染

PixelFormat在数据层负责描述与解码,而真正的渲染转换由 GPU 完成。渲染器 yuv_converter.rs 中定义了与PixelFormat对应的YuvPixelLayout(索引需与 yuv_converter.wgsl 保持同步):

YuvPixelLayout含义对应PixelFormat
Y_U_V4444:4:4 三平面(I444)Y_U_V24_*
Y_U_V4224:2:2 三平面(I422)Y_U_V16_*
Y_U_V4204:2:0 三平面(I420)Y_U_V12_*
Y_UV4204:2:0 半平面(NV12)NV12
YUYV4224:2:2 单平面交错YUY2
Y4004:0:0 单色(I400)Y8_*

实现要点:

  • 输入数据统一以R8Uint数据纹理上传(yuv_converter.rs),通过data_texture_width_height把解码尺寸换算为数据纹理尺寸(例如 4:2:0 为[w, h + h/2]YUY2[w*2, h]);
  • 输出为Rgba8Unorm(sRGB 编码的 8bit 纹理,yuv_converter.rs);
  • YuvRange枚举(Limited/Full)与YuvMatrixCoefficients(BT.601/BT.709/Identity)通过 uniform buffer 传给片段着色器,完成色度上采样与 YUV→RGB 矩阵换算。

七、常见问题与注意事项

  1. NV12是默认变体:在 Rust 定义中以#[default]标注(pixel_format.rs),若场景未明确指定且需要 YUV 下采样格式,默认即 NV12(limited range + BT.601)。
  2. limited vs full range 影响色彩还原:同为 4:2:0 平面布局,Y_U_V12_LimitedRange(20)与Y_U_V12_FullRange(44)只是取值范围不同,decode_rgb_at会依据is_limited_yuv_range()选择不同的缩放/偏移路径,选错会导致画面发灰或对比度异常。
  3. 平面长度必须匹配num_bytes([w, h])可用于校验:4:4:4 为w*h*3(扩展实现为num_pixels*4,含 RGB 对齐),4:2:2 为16*w*h/8,4:2:0 为12*w*h/8,灰度Y8w*h。缓冲区过短时decode_yuv_at返回None,过长的多余字节被忽略。
  4. 不要混淆YUY2NV21:文档明确列出YUY2的别名包括YUYVYUYV16NV21,业界命名混乱;在 Rerun 中该变体专指 4:2:2 单平面交错格式。
  5. 更压缩的格式请走EncodedImagePixelFormat面向的是支持随机访问的平面/半平面/交错 YUV 与灰度格式;JPEG/PNG 等需要整帧解码的压缩图像应使用archetypes.EncodedImage

八、延伸阅读

  • 类型定义与生成说明:pixel_format.def.rs(re_types_builder 的元定义源)
  • 解码/工具扩展方法:pixel_format_ext.rs
  • ImageFormat字段与优先级:image_format.md
  • GPU 渲染转换器:yuv_converter.rs 与 yuv_converter.wgsl
  • 完整 Python 示例:image_formats.py、nv12.py

【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询