如何将int16 numpy数组高效地转换为int8 numpy数组?

4

我正在寻找一种有效的方法来使用特定的缩放函数将 2 字节(-32K -> +32K)的 numpy int 数组缩放为 8 位(0 -> 255)。 一个非常低效的方法是(其中 minVal 和 maxVal 是原始 2 字节 numpy 数组中的最小和最大值,原始数组中的 paddingVal 将设置为 0):

...

pixel_array = np.zeros( length, dtype=np.int16)
byte_array = np.zeros( length, dtype=np.uint8)

....

i = 0
for val in np.nditer(pixel_array):
    value = 0.0
    if val == paddingVal:
        byte_array[i] = 0
    else:
        value = 255.0 * ( val - minVal ) / (maxVal - minVal - 1.0)    
        byte_array[i] = (round(value))
    i += 1  

我无法想出如何避免循环并仍然执行if语句和应用缩放函数。谢谢。

4
提供信息:这个循环是完全没有必要的。如果内存不受严格限制,只需执行 byte_array = (255.0 * (pixel_array - minVal) / (maxVal - minVal - 1.0)).astype(np.uint8) 即可。之后可以使用 byte_array[pixel_array == paddingVal] = 0 设置“填充”值。虽然它不是内存有效的,但比你目前正在做的要快得多。 - Joe Kington
那确实更快了。Python 多么紧凑啊,简直太棒了。 - thefog
1
提醒一下,我在评论中发布的版本隐式地取值的底部而不是四舍五入。如果您可以接受这一点,它比调用numpy.round要快一些,但它与您的原始代码不同(@jorgeca的答案应该与您的原始解决方案给出相同的结果)。 - Joe Kington
2个回答

3

尝试:

byte_array[i] = (((val << 16) >> 8) & 0xFF0000) >> 16

它假设val是介于0和65535之间的32位整数。

在这种情况下,val是一个负的16位有符号整数,因此上述方法无法解决问题。无论如何,性能与原始性能并没有根本区别。我认为必须想办法摆脱显式循环。 - thefog
@thefog,你可以使用(int + 0x10000) & 0xFFFEFFFF将一个介于-3276832767之间的32位整数转换为一个介于065535之间的32位整数。这意味着在缩放后,-1(因为它是2的补码中的所有位)变成了255等等。 - Esailija
@thefog 避免了浮点运算、乘法和调用 round 函数。我猜如果代码能够得到相同的结果,它就不需要与硬件过于接近 :P - Esailija

2
您可以使用掩码来利用numpy的矢量化(隐式循环),这将更快:
mask = pixel_array == paddingVal
byte_array[mask] = 0
byte_array[~mask] = np.round(255.0 * (pixel_array[~mask] - minVal) / (maxVal - minVal - 1.0))

也可以这样做,更加简洁,因为您避免了先创建 byte_array 的步骤:

byte_array = np.round(255.0 * (pixel_array - minVal) / (maxVal - minVal - 1.0)).astype(np.uint8)
byte_array[pixel_array == paddingVal] = 0

编辑:正如Joe Kington在问题的评论中指出的那样,这是以内存换速度为代价的。

这个操作失败了,我之前尝试过类似的方法,但是出现了以下错误:byte_array [mask] = round(255.0 * (pixel_array [mask] - minVal) / (maxVal - minVal - 1.0)) TypeError: 只有长度为1的数组可以转换为Python标量。 - thefog
抱歉,round是Python内置函数,仅适用于标量。我会修复它的。 - jorgeca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接