Kornia 颜色转换精度修复解析:YUV 与 XYZ 整数输入的计算路径统一(4053)
2026/9/24 8:31:22 网站建设 项目流程
  • 计算机视觉
  • 深度学习
  • 人工智能
  • 图像处理

【免费下载链接】kornia

🐍 空间人工智能的几何计算机视觉库

项目地址:https://gitcode.com/kornia/kornia
点击查看免费下载

本文基于 Kornia 仓库 changelog.d/+migration-108.fixed.md 的变更记录,深入剖析一次针对kornia.color模块的精度修复:YUV(rgb_to_yuv/yuv_to_rgb)与 XYZ(rgb_to_xyz/xyz_to_rgb)转换现在以float32计算整数输入而非截断卷积核,同时保留直接构造的float64系数。文章将结合 kornia/color/utils.py、kornia/color/yuv.py、kornia/color/xyz.py 及对应测试,说明改动动机、底层机制与迁移影响。

一、这次修复解决什么问题

在旧实现中,当输入图像张量为整数类型(如uint8int32int64)时,YUV 与 XYZ 转换会直接使用整数构造的卷积核参与运算,导致卷积核被整数类型截断(例如0.299被截为0),得到完全错误的输出。同时,如果用户直接以float64构造卷积核,旧路径会丢失float64精度。

本次变更(对应 PR [#4053])的核心目标有三个:

  1. 整数输入以float32计算rgb_to_yuv(uint8)在输入的原始 0–255 量纲上返回float32结果,而不是先归一化到 0–1,也不再把核截断成整数。
  2. 修复有符号整数 YUV 结果:修复前有符号整数(如int32/int64)输入会产生错误的 YUV 数值(因为核被截断);修复后得到正确的float32输出。
  3. 消除 float64 XYZ 转换的系数丢失:直接构造的float64卷积核现在被完整保留并参与计算,不再被降为float32

二、从两个私有辅助函数到统一实现

变更前,YUV 与 XYZ 各自维护一个私有的线性变换辅助函数;变更后,这两个私有线性变换辅助函数合并为一个实现,即 kornia/color/utils.py 中的_apply_linear_transformation。所有四个转换函数(rgb_to_yuvyuv_to_rgbrgb_to_xyzxyz_to_rgb)都复用这一个实现:

def _apply_linear_transformation( image: torch.Tensor, kernel: torch.Tensor, bias: Optional[torch.Tensor] = None ) -> torch.Tensor: # Handle Integer inputs by casting to float safely if image.is_floating_point(): image_compute = image else: image_compute = image.float() # Match kernel dtype to the image (propagates float64 if needed) kernel_compute = kernel.to(dtype=image_compute.dtype, device=image_compute.device) bias_compute = bias.to(dtype=image_compute.dtype, device=image_compute.device) if bias is not None else None input_shape = image_compute.shape ...

关键设计点在于**“dtype/device 对齐的计算操作数”**(dtype/device-aligned compute operands):

  • 输入为浮点类型时,直接以原 dtype 计算,float64输入会得到float64结果;
  • 输入为整数类型时,先通过image.float()安全地提升为float32,卷积核随后被kernel.to(dtype=...)对齐到同一个float32计算 dtype。这样既避免了整数截断,又保留了float64内核的完整精度。

CPU 与 GPU 的双分支优化

该辅助函数内部根据设备类型选择了两种计算路径(源码注释明确说明这是基于经验基准测试的优化):

# BRANCH 1: CPU and empty tensors (Einsum) if image_compute.device.type == "cpu" or image_compute.numel() == 0: out = torch.einsum("oi, ...ihw -> ...ohw", kernel_compute, image_compute) if bias_compute is not None: out = out + bias_compute.view(-1, 1, 1) return out.contiguous() # BRANCH 2: GPU/Accelerators (Conv2d) input_flat = image_compute.reshape(-1, 3, input_shape[-2], input_shape[-1]) weight = kernel_compute.view(3, 3, 1, 1) out_flat = F.conv2d(input_flat, weight, bias=bias_compute) out = out_flat.reshape(input_shape)
  • CPU / 空张量路径:使用torch.einsum("oi, ...ihw -> ...ohw", ...),它能处理任意空 shape,并且让 image、kernel、bias 都保留在计算图中(便于反向传播)。
  • GPU / 加速器路径:将张量展平为(B*..., C, H, W)后调用F.conv2d,因为 1×1 卷积在 CUDA 上比 einsum 有显著加速。

三、YUV 转换:整数输入与精度行为

3.1 函数级 API

kornia/color/yuv.py 中的rgb_to_yuv采用 BT.470-5 M/PAL 标准的 YUV 模型:

def rgb_to_yuv(image: torch.Tensor) -> torch.Tensor: KORNIA_CHECK_SHAPE(image, ["*", "3", "H", "W"]) dtype = image.dtype if image.is_floating_point() else torch.float32 kernel = torch.tensor( [ [0.299, 0.587, 0.114], [-0.147, -0.289, 0.436], [0.615, -0.515, -0.100], ], device=image.device, dtype=dtype, ) return _apply_linear_transformation(image, kernel)

这里dtype = image.dtype if image.is_floating_point() else torch.float32与辅助函数内部的提升逻辑配合:对于uint8/int32/int64输入,卷积核以float32构造,输入也会被提升为float32,最终在输入的原始 0–255 量纲上返回float32结果。

文档规定的输出范围:Y(luma)在(0, 1),U 在(-0.436, 0.436),V 在(-0.615, 0.615)(浮点输入归一化到 0–1 的前提下)。

3.2 反向转换的“精确逆矩阵”

yuv_to_rgb(kornia/color/yuv.py)的卷积核不是对公开逆关系的独立舍入副本,而是rgb_to_yuv卷积核的精确逆

# The exact inverse of the rounded M/PAL kernel in ``rgb_to_yuv``, rather than a separately # rounded copy of the published inverse relations, so a round trip is lossless to the dtype. dtype = image.dtype if image.is_floating_point() else torch.float32 kernel = torch.tensor( [ [1.0, -3.945707070707071e-05, 1.139827967171717], [1.0, -0.39461016414141414, -0.5805003156565657], [1.0, 2.0319996843434343, -0.00048137626262626264], ], device=image.device, dtype=dtype, ) return _apply_linear_transformation(image, kernel)

源码注释透露了一个重要的数值细节:该逆矩阵在有理数域上的解析形式为

[[1, -1/25344, 144439/126720], [1, -10001/25344, -73561/126720], [1, 51499/25344, -61/126720]]

而代码中的每个字面量都是这些精确分数正确舍入到 float64的结果。注释还说明:torch.linalg.inv对正核的求逆只能精确到约3.3e-16(其 LU 分解在第一列甚至无法还原出精确的1.0),因此这些常数是从精确分数手动舍入得到的,而非依赖数值求逆。这使得RGB → YUV → RGB 往返误差仅受输入 dtype 精度限制(float64 下往返恒等误差约1.1e-16,float32 下约1.2e-7)。

3.3 模块级 API

YUV 家族还提供模块封装:RgbToYuvRgbToYuv420RgbToYuv422YuvToRgbYuv420ToRgbYuv422ToRgb(kornia/color/yuv.py),forward 均直接委托给对应函数。其中 420/422 变体要求 H、W 可被 2 整除,否则抛出ShapeError;这两个变体的ONNX_EXPORTABLE = False(多输入/多输出暂不支持 ONNX 导出)。

四、XYZ 转换:float64 系数不再丢失

kornia/color/xyz.py 的rgb_to_xyz使用 CIE RGB → XYZ(D65 白点)矩阵:

dtype = image.dtype if image.is_floating_point() else torch.float32 kernel = torch.tensor( [ [0.412453, 0.357580, 0.180423], [0.212671, 0.715160, 0.072169], [0.019334, 0.119193, 0.950227], ], device=image.device, dtype=dtype, ) return _apply_linear_transformation(image, kernel)

xyz_to_rgb(kornia/color/xyz.py)则使用 CIE XYZ → RGB(D65 白点)矩阵,其中的系数保留了大量小数位(如3.2404813432005266-1.5371515162713185),属于典型的 float64 级精度常数:

dtype = image.dtype if image.is_floating_point() else torch.float32 kernel = torch.tensor( [ [3.2404813432005266, -1.5371515162713185, -0.4985363261688878], [-0.9692549499965682, 1.8759900014898907, 0.0415559265582928], [0.0556466391351772, -0.2040413383665112, 1.0573110696453443], ], device=image.device, dtype=dtype, ) return _apply_linear_transformation(image, kernel)

修复前后的行为差异

输入类型修复前修复后
uint8(0–255 量纲)核被整数截断,结果错误输入与核均提升为float32,在原始 0–255 量纲返回float32
int32/int64有符号整数 YUV 结果错误返回正确float32结果
float64(直接构造核)系数被降为 float32 丢失精度核以 float64 参与计算,系数完整保留

关键点在于:卷积核的 dtype 由输入张量决定——浮点输入下核以相同 dtype 构造(float64输入 →float64核),整数输入下核以float32构造,从而兼顾正确性与性能。

五、测试验证:三组回归测试

本次修复在 tests/color/test_yuv.py 与 tests/color/test_xyz.py 中新增了三类以4053命名的回归测试,直接对应变更记录中的每一项声明。

5.1 整数输入(test_yuv.py#L244-L252 与 test_xyz.py#L121-L128)

@pytest.mark.parametrize("integer_dtype", [torch.int32, torch.int64]) def test_integer_input_4053(self, device, integer_dtype): rgb = torch.eye(3).to(device=device, dtype=integer_dtype).unsqueeze(-2) expected = torch.tensor(_RGB_TO_YUV_KERNEL, device=device, dtype=torch.float32).unsqueeze(-2) actual = kornia.color.rgb_to_yuv(rgb) assert actual.dtype == torch.float32 self.assert_close(actual, expected, atol=0.0 if device.type == "cpu" else 1e-3, rtol=0.0)

该测试断言:有符号整数输入的输出 dtype 为float32,且结果与 float32 核计算完全一致(CPU 上 atol=0,GPU 上放宽到 1e-3 以容纳后端差异)。

5.2 uint8 输入的 0–255 量纲(test_yuv.py#L254-L261)

def test_uint8_input_4053(self, device): rgb = (torch.eye(3) * 255).to(device=device, dtype=torch.uint8).unsqueeze(-2) expected = (torch.tensor(_RGB_TO_YUV_KERNEL, device=device, dtype=torch.float32) * 255).unsqueeze(-2) actual = kornia.color.rgb_to_yuv(rgb) assert actual.dtype == torch.float32 self.assert_close(actual, expected, atol=0.0 if device.type == "cpu" else 1e-3, rtol=0.0)

验证rgb_to_yuv(uint8)在原始 0–255 量纲上返回float32:期望值正是 float32 核乘以 255,而非归一化到 0–1 的结果。

5.3 float64 系数保留(test_yuv.py#L263-L267 与 test_xyz.py#L131-L135)

def test_float64_kernel_precision_4053(self): rgb = torch.eye(3, dtype=torch.float64).unsqueeze(-2) expected = torch.tensor(_RGB_TO_YUV_KERNEL, dtype=torch.float64).unsqueeze(-2) self.assert_close(kornia.color.rgb_to_yuv(rgb), expected, atol=0.0, rtol=0.0)

atol=0.0, rtol=0.0的严格条件下断言 float64 输入 + float64 核得到精确一致的输出——这正是“消除 float64 XYZ 转换系数丢失”的直接证据。

此外,tests/color/test_utils.py 还针对统一后的辅助函数覆盖了int64输入 +float64核 + bias 的组合(验证输出为float32、连续、形状保持)以及空张量的自动求导(image/kernel/bias 梯度均正确传播)。

六、迁移影响与升级注意事项

作为.fixed类型的变更,本次修复属于缺陷修复,但对已有用户存在行为变化,升级时需要注意:

  1. 整数输入的返回 dtype 变化rgb_to_yuv(uint8)现在返回float32(而非被截断的整数或错误数值),且量纲保持 0–255。如果下游代码假定输入已归一化到 0–1,需要自行除以 255。
  2. float64 精度保留:直接构造float64输入/核时,XYZ 转换不再丢失系数精度,输出 dtype 也会保持float64,与旧版float32输出不同。
  3. API 完全向后兼容rgb_to_yuvyuv_to_rgbrgb_to_xyzxyz_to_rgb及其模块封装(RgbToYuvXyzToRgb等)的签名、形状校验((*, 3, H, W))与语义均未变化,浮点输入路径行为不变,因此绝大多数既有代码无需改动。
  4. YUV 往返精度更好:由于yuv_to_rgb使用精确逆矩阵,rgb_to_yuvyuv_to_rgb的往返误差仅受 dtype 精度限制,适用于对颜色精度敏感的场景(如色彩管理、无损管线)。

七、快速验证示例

以下代码可直接在安装本仓库的环境中使用,验证修复后的行为:

import torch import kornia # 1) uint8 输入在 0-255 量纲上返回 float32 rgb_uint8 = (torch.eye(3) * 255).to(torch.uint8).unsqueeze(-2) # (3, 1, 3) -> 见 shape 校验 rgb_uint8 = torch.eye(3, dtype=torch.uint8).unsqueeze(-2).expand(3, 1, 3, 1).contiguous() yuv = kornia.color.rgb_to_yuv(rgb_uint8) print(yuv.dtype, yuv.shape) # torch.float32, torch.Size([3, 3, 3, 1]) # 2) float64 核精度保留 rgb64 = torch.eye(3, dtype=torch.float64).unsqueeze(-2) xyz = kornia.color.rgb_to_xyz(rgb64) print(xyz.dtype) # torch.float64 # 3) RGB -> YUV -> RGB 往返仅受 dtype 精度限制 rgb = torch.rand(3, 4, 5, dtype=torch.float64) rgb_rt = kornia.color.yuv_to_rgb(kornia.color.rgb_to_yuv(rgb)) print((rgb_rt - rgb).abs().max().item()) # 约 1e-16 量级 # 4) 模块级 API 行为一致 module = kornia.color.RgbToYuv() print(module(torch.rand(2, 3, 4, 5)).shape) # torch.Size([2, 3, 4, 5])

注意:示例中的张量形状需满足(*, 3, H, W)校验,uint8用例请按实际批次维度构造合法输入。

八、总结

本次+migration-108变更通过合并两个私有线性变换辅助函数为统一的_apply_linear_transformation,为 Kornia 的 YUV 与 XYZ 转换确立了三条明确规则:整数输入以float32安全计算(保留原始 0–255 量纲)、直接构造的float64系数完整保留、CPU(einsum)与 GPU(conv2d)双路径保证性能。结合 tests/color/test_yuv.py、tests/color/test_xyz.py 中的三组回归测试,这套行为已形成可验证的契约,为依赖颜色转换精度的下游管线(色彩管理、视频编码、相机标定)提供了可靠基础。

  • 计算机视觉
  • 深度学习
  • 人工智能
  • 图像处理

【免费下载链接】kornia

🐍 空间人工智能的几何计算机视觉库

项目地址:https://gitcode.com/kornia/kornia
点击查看免费下载

相关推荐

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

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

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

立即咨询