Kornia 角度转换精度修复解析:rad2deg/deg2rad 的整数输入与 float64 精度保障
【免费下载链接】kornia🐍 Geometric Computer Vision Library for Spatial AI项目地址: https://gitcode.com/gh_mirrors/ko/kornia
导读
本篇文章围绕 Kornia 仓库变更记录changelog.d/+migration-034.fixed.md(对应上游 issue #4358)中的一项核心修复展开:rad2deg、deg2rad两个角度单位转换函数此前在整数张量输入下会静默产生错误结果,且在 float64 双精度下丢失精度;而angle_to_rotation_matrix由于内部直接调用deg2rad受同样问题影响。读者在阅读本文后将理解这三个函数的确切行为、精度问题的根因、仓库中的回归测试与修复实现,并掌握在实际几何计算中正确使用这些函数的方法。
修复背景:一个容易被忽视的精度陷阱
角度单位转换是计算机视觉中最基础、最频繁的操作之一。Kornia 中大量下游功能——特征点方向估计、姿态评估、图像仿射变换、坐标变换——都在内部依赖角度转换。该修复变更记录指出:
rad2deganddeg2radnow handle integer tensor inputs correctly and preserve float64 precision.angle_to_rotation_matrixinherits the corrected conversion, while the implementation preserves ONNX export compatibility. (#4358)
也就是说,本次修复涵盖三个层面:
- 整数张量输入:此前对整数类型(如
torch.int64、torch.int32)张量调用rad2deg/deg2rad会得到错误甚至不可预期的结果; - float64 精度保留:此前双精度输入在转换过程中精度受损(与 issue #3937 相关);
- 下游函数继承:
angle_to_rotation_matrix通过deg2rad继承了修正后的行为,同时保持 ONNX 导出兼容性。
修复后的实现:三个函数的源码全貌
rad2deg:弧度转角度
修复后的实现位于 kornia/geometry/conversions.py:
def rad2deg(tensor: torch.Tensor) -> torch.Tensor: r"""Convert angles from radians to degrees. Convention: - the input is in **radians** and the output in **degrees**; the conversion is elementwise and preserves shape, device and float dtype ... """ if not isinstance(tensor, torch.Tensor): raise TypeError(f"Input type is not a torch.Tensor. Got {type(tensor)}") return tensor * (180.0 / math.pi)关键实现要点:
- 类型守卫:非
torch.Tensor输入直接抛出TypeError,避免静默错误; - 标量乘法实现:
tensor * (180.0 / math.pi)中,Python 浮点标量与张量相乘会触发 PyTorch 的类型提升(type promotion),整数张量被自动提升为浮点类型(默认torch.float32),从而避免整数截断问题;同时 float64 输入因乘数本身就是 float64 精度的标量(180.0 / math.pi),精度得以完整保留; - 逐元素且保形:输出 shape、device 与输入一致,浮点 dtype 不变。
deg2rad:角度转弧度
同样位于 kornia/geometry/conversions.py:
def deg2rad(tensor: torch.Tensor) -> torch.Tensor: r"""Convert angles from degrees to radians. Convention: - the input is in **degrees** and the output in **radians**; it performs the opposite conversion to :func:`~kornia.geometry.conversions.rad2deg` ... """ if not isinstance(tensor, torch.Tensor): raise TypeError(f"Input type is not a torch.Tensor. Got {type(tensor)}") return tensor * (math.pi / 180.0)与rad2deg对称,乘数math.pi / 180.0为 float64 标量,同样通过类型提升处理整数输入并保留双精度。
angle_to_rotation_matrix:角度的旋转矩阵化
修复的核心价值在于下游函数angle_to_rotation_matrix,见 kornia/geometry/conversions.py:
def angle_to_rotation_matrix(angle: torch.Tensor) -> torch.Tensor: r"""Create a rotation matrix out of angles in degrees. Convention: - ``angle`` is in **degrees**, shape :math:`(*)` in and :math:`(*, 2, 2)` out - the matrix is ``[[cos, sin], [-sin, cos]]`` with ``det = +1``; for ``angle = 30`` it is ``[[0.8660, 0.5000], [-0.5000, 0.8660]]`` ... """ ang_rad = deg2rad(angle) cos_a: torch.Tensor = torch.cos(ang_rad) sin_a: torch.Tensor = torch.sin(ang_rad) return torch.stack([cos_a, sin_a, -sin_a, cos_a], dim=-1).view(*angle.shape, 2, 2)三个值得注意的实现细节:
- 角度单位约定是「度」而非弧度:该函数接收degrees输入,内部第一行就调用
deg2rad(angle)完成单位转换——这正是它“继承”修复行为的途径。测试 tests/geometry/test_conversions.py 中专门固定了这一约定(convention pin):输入30.0得到[[0.8660254, 0.5], [-0.5, 0.8660254]];如果某实现错误地按弧度读取,则pi/2会被当作 1.5708 度,退化为近单位矩阵,测试中的对比值可清晰暴露此类错误; - 输出方向约定:矩阵为
[[cos, sin], [-sin, cos]],即教科书数学坐标系中逆时针矩阵的转置,适配 Kornia 图像坐标 y 轴朝下的约定,左乘列向量(x, y)时在图像上呈现逆时针旋转; - 纯张量算子组合:整个实现仅由
deg2rad、torch.cos、torch.sin、torch.stack、view构成,全部是标准 ONNX 可导出算子,这正是变更记录中“preserves ONNX export compatibility”的源码依据。测试文件 tests/augmentation/test_onnx_export.py 的注释也印证:仿射/shear 的compute_transformation路径依赖deg2rad的内联(inlining)来保证 eager 与 ONNX 数值等价。
回归测试:修复如何被验证
仓库在 tests/geometry/test_conversions.py 中通过TestRadDegConversions测试类系统性地固定了修复行为,与变更记录形成一一对应:
float64 精度回归(对应 issue #3937)
@pytest.mark.parametrize( ("op_name", "arg", "expected"), [ ("rad2deg", torch.pi, 180.0), ("deg2rad", 180.0, torch.pi), ("angle_to_rotation_matrix", 90.0, [[0.0, 1.0], [-1.0, 0.0]]), ], ) def test_convention_float64_results_are_exact_3937(self, device, op_name, arg, expected): # Regression for #3937: rad2deg and deg2rad must preserve float64 precision. # angle_to_rotation_matrix inherits the corrected conversion through deg2rad, # so a 90-degree input should produce the expected quarter-turn matrix without # the previous float32 pi bias. # float64 is hardcoded because this regression specifically checks double precision. ... out = op(torch.tensor(arg, device=device, dtype=torch.float64)) self.assert_close(out, torch.tensor(expected, device=device, dtype=torch.float64), atol=1e-12, rtol=1e-12)这段测试(tests/geometry/test_conversions.py)揭示了修复前的典型缺陷:float32 的 π 偏差(float32 pi bias)。此前如果转换路径中先将 π 截断为 float32,90°转出的旋转矩阵就不会是精确的[[0, 1], [-1, 0]]四分之一转;修复后三个函数在 float64 下均以atol=rtol=1e-12的严格容差通过。测试还注明了 MPS 设备因不支持 float64 而跳过。
整数输入提升回归
@pytest.mark.parametrize( ("op_name", "arg", "expected"), [ ("rad2deg", [1, 2, 3], [57.29577951308232, 114.59155902616465, 171.88733853924697]), ("deg2rad", [180, 90], [3.141592653589793, 1.5707963267948966]), ("angle_to_rotation_matrix", [90], [[[0.0, 1.0], [-1.0, 0.0]]]), ], ) def test_integer_input_promotes_to_float_3937(self, device, op_name, arg, expected): op = getattr(kornia.geometry.conversions, op_name) out = op(torch.tensor(arg, device=device)) assert out.dtype == torch.float32 ...该测试(tests/geometry/test_conversions.py)验证整数张量输入被自动提升为torch.float32(默认浮点 dtype),且数值结果与期望完全一致——例如deg2rad([180, 90])得到[π, π/2]的完整双精度数值。
往返一致性测试
此外,test_rad2deg与test_deg2rad(tests/geometry/test_conversions.py)对多种 batch shape((2, 3)、(1, 2, 3)、(2, 3, 3)、(5, 5, 3))执行了“转过去再转回来”的往返闭合验证,并用gradcheck确认梯度可微性,保证修复没有破坏自动微分链路。
上游与下游的实际影响
下游调用链
从源码搜索可见,本次修复波及的不仅仅是三个孤立函数:
- kornia/feature/orientation.py 在特征方向估计(orientation estimation)中调用
rad2deg(angles_radians)输出角度; - kornia/feature/laf.py 与 kornia/feature/sift/pyramid.py、kornia/feature/sift/scale_space.py 在 SIFT 金字塔与尺度空间构建中通过
torch.rad2deg处理旋转角度; - kornia/metrics/pose.py 在姿态评估指标中用
torch.rad2deg(cos_theta.acos())计算角度误差; - kornia/geometry/transform/imgwarp.py 在图像仿射变换中执行
torch.deg2rad(angles)。
其中angle_to_rotation_matrix还被 tests/feature/test_laf.py 直接用于 LAF 旋转矩阵的构造与torch.jit.script脚本化验证,说明其行为正确性直接影响特征模块的输出。
对 ONNX 导出的意义
变更记录特别强调修复“preserves ONNX export compatibility”,结合 tests/augmentation/test_onnx_export.py 的注释可以确认:在 Kornia 中,随机仿射等增强模块的compute_transformation路径会将deg2rad内联进导出的计算图。因此,修复必须以“不引入不可导出的新算子”为前提——当前实现完全由乘法与三角函数等标准算子构成,天然满足该约束。这也提醒使用者:若自行改写实现(例如引入torch.deg2rad之外的自定义分支),需额外验证 ONNX 导出路径的数值等价性(参见 tests/augmentation/test_onnx_export.py 中的 eager 与 ONNX Runtime 数值对比机制)。
实际使用建议
结合本次修复,在实际项目中调用这三个 API 时值得注意以下几点:
- 角度单位务必分清:
rad2deg/deg2rad的输入输出单位在 docstring 中已有明确约定(kornia/geometry/conversions.py),而angle_to_rotation_matrix接收的是degrees,不要混用; - 整数输入现在是安全的:修复后可以直接传入
torch.tensor([90, 180], dtype=torch.int64)这类整数张量,类型提升机制会将其转为 float32 并给出正确结果;但若追求更高数值精度,建议显式构造dtype=torch.float64输入; - 高精度场景显式使用 float64:涉及相机标定、姿态评估、SLAM 等对角度误差敏感的场景,建议显式指定
dtype=torch.float64,以利用修复后保留的双精度;注意 MPS 设备不支持 float64(测试中已跳过); - 依赖 gradcheck 验证可微性:两个转换函数均通过
gradcheck测试(tests/geometry/test_conversions.py),可直接用于可微渲染、姿态优化等梯度场景。
结语
+migration-034.fixed.md所记录的这次修复虽然改动量小(本质上是乘法标量选择与类型提升的配合),却消除了几何计算链路中一个隐蔽的精度隐患:整数输入的错误结果与 float64 精度丢失。通过 kornia/geometry/conversions.py 的源码与 tests/geometry/test_conversions.py 中针对性回归测试,我们完整还原了问题根因、修复方案与验证手段。对于所有在特征匹配、姿态估计、图像变换中依赖角度运算的 Kornia 使用者而言,这一修复意味着更可靠、更精确的角度数值基础。
【免费下载链接】kornia🐍 Geometric Computer Vision Library for Spatial AI项目地址: https://gitcode.com/gh_mirrors/ko/kornia
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考