你应该将RGB值归一化为255还是256?
假设你正在编写一个图像处理程序。该程序接收一张图片,将其转换为浮点数,进行一些处理,最后将修改后的像素以8位颜色保存到磁盘。今天的问题是,整数到浮点数的转换应该如何进行。 有两种方法,在Python和NumPy中表示如下:标准的255除法;替代的256除法: pixels = img / 255.0 result = process(pixels) output = np.trunc(result * 255 + 0.5) pixels = (img + 0.5) / 256.0 result = process(pixels) output = np.trunc(result * 256) 我假设在这两种情况下,输出值在最终类型转换之前都会被限制: # 限制并转换为8位 g输出_8位 = output.clip(0, 255).astype(np.uint8) 标准方法将整数0映射到0.0,将255映射到1.0。这是完全可行的,也是GPU的做法。替代方法则增加了0.5的偏差,并用256除法,因此整数0被映射为0.5/256=0.001953125。这非常不方便,因为你的图像处理代码不能检测黑色像素,例如,如果不知道上述常数。因此,即使你在浮点数上进行计算,也会将你的逻辑绑定到8位输入上。使用标准方法时,你始终可以假定黑色是0.0。但一些程序员仍然感受到对替代方法的吸引。这是怎么回事?他们发现了什么? 反对255.0的理由 当在数轴上绘制时,标准方法的确看起来相当奇怪。下面你可以看到一个夸张版本,用3位整数在[0..7]范围内映射到[0,1]:在X轴上,我们有一个数轴,棕色圆圈的位置表示解码后的浮点值。内部的数字是整数输入。每个整数都有箭头指向它,这些箭头显示出一系列四舍五入到它的浮点值。我将在这篇文章的其余部分称这些范围为“桶”。极端的较小桶 图中明显的第一个问题是,标准公式的极端桶突出了[0,1]范围以外。或许这种可视化并不公平——两种方法都会限制它们的输出,因此极端桶可以无限延伸——但它清楚地表明了标准范围如何“被拉伸”。被拉伸的范围比假定的[0, 1]工作范围更宽。这意味着在将[0, 1]范围内的浮点值转换回整数时,极端桶的宽度实际上只有其他桶的一半。因此,从算法中输出极端值会“更困难”。例如,如果你生成均匀的[0,1]噪声并使用标准公式对其进行四舍五入,值0和255的出现频率仅为其他整数的一半。我们可以通过生成一百万个均匀的随机数,绘制它们作为直方图,观察0和255桶的高度确实仅为其他桶的一半来验证这一说法: 突出部分:直方图代码 import numpy as np import matplotlib.pyplot as plt result = np.random.uniform(0, 1, 1000000) final_values = np.trunc(result * 255 + 0.5).clip(0, 255).astype(np.uint8) plt.hist(final_values, bins=256, range=(0, 255)) plt.show() 尽管如此,我很难想出一个偏离极端值可能造成问题的示例情况。诚然,标准方法的浮点值分布在更宽的范围内,但原始图像仍然是无损的回合转换(uint8 → 浮点数 → uint8)。此外,任何位于0.0或1.0之后的结果值仍然会四舍五入到正确的桶,平均化输出分布。 我所指的一个例子。假设你的处理将0.005从浮点颜色中减去。在标准方法中,这会将黑色推低到零以下——超出[0,1]范围——但在替代方法中,值仍然保持为正。在最后,两个方法的输出都是整数0: 标准:trunc(255 * (-0.005) + 0.5) = 0 替代:trunc(256 * (0.5 / 256 - 0.005)) = 0 在标准方法中,零桶只有“半个大小”并不重要。 不精确性 第二个问题是,标准方法的浮点值并不精确。例如128/255.0约等于0.501961,但128/256.0=0.5。由于这种舍入误差,浮点值之间的距离略有差异。但这并不是真正的问题,因为误差确实微小。一个32位浮点数有23位小数(“有效位”)。我们正在讨论的是其最低有效位的舍入误差;颤动的幅度小于2^{-23}。即使在最复杂的图像处理任务中,0.00001%的相对误差也是微不足道的。在这种情况下,不精确性是一个审美问题,而不是一个技术问题。 整数之间没有值 替代方法始终将每个浮点值放置在两个整数之间的中间位置。请查看上面的数轴图中垂直条的对齐情况。中间位置可以被看作是一个折衷;
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡