使用numpy高效地将16位图像数据转换为8位以便显示,并进行强度缩放

14 投票
5 回答
28528 浏览
提问于 2025-04-17 13:21

我经常需要把16位的灰度图像数据转换成8位的图像数据,以便显示。通常情况下,调整显示的最小和最大亮度是很有用的,这样可以突出图像中“有趣”的部分。

下面的代码大致上能实现我想要的效果,但它看起来很复杂而且效率低下,还会产生很多图像数据的中间副本。有没有办法用更少的内存和处理时间达到同样的效果呢?

import numpy

image_data = numpy.random.randint( #Realistic images would be much larger
    low=100, high=14000, size=(1, 5, 5)).astype(numpy.uint16)

display_min = 1000
display_max = 10000.0

print(image_data)
threshold_image = ((image_data.astype(float) - display_min) *
                   (image_data > display_min))
print(threshold_image)
scaled_image = (threshold_image * (255. / (display_max - display_min)))
scaled_image[scaled_image > 255] = 255
print(scaled_image)
display_this_image = scaled_image.astype(numpy.uint8)
print(display_this_image)

5 个回答

2

为了减少内存的使用,建议在原地进行裁剪,避免创建布尔数组。

dataf = image_data.astype(float)
numpy.clip(dataf, display_min, display_max, out=dataf)
dataf -= display_min
datab = ((255. / (display_max - display_min)) * dataf).astype(numpy.uint8)

如果你把裁剪的限制值保持为整数,你还可以这样做:

numpy.clip(image_data, display_min, display_max, out=image_data)
image_data-= display_min
datab = numpy.empty_like(image_data)
numpy.multiply(255. / (display_max - display_min), image_data, out=datab)

注意:在最后一行创建 uint8 数组之前,仍然会创建一个临时的浮点数组。

2

我建议不要把图像转换成浮点数,你可以这样做:

import numpy as np

def display(image, display_min, display_max):
    # Here I set copy=True in order to ensure the original image is not
    # modified. If you don't mind modifying the original image, you can
    # set copy=False or skip this step.
    image = np.array(image, copy=True)

    image.clip(display_min, display_max, out=image)
    image -= display_min
    image //= (display_min - display_max + 1) / 256.
    image = image.astype(np.uint8)
    # Display image

这里首先用图像的原始数据类型创建了一个可选的副本,然后在最后一行创建了一个8位的副本。

17

你正在对图像进行半色调处理

其他人提出的方法效果很好,但它们重复了很多昂贵的计算,效率不高。因为在一个uint16中最多有65,536个不同的值,所以使用查找表(LUT)可以大大简化这个过程。而且因为LUT很小,所以你不需要太担心是否在原地操作,或者是否会创建布尔数组。下面的代码重用了Bi Rico的函数来创建LUT:

import numpy as np
import timeit

rows, cols = 768, 1024
image = np.random.randint(100, 14000,
                             size=(1, rows, cols)).astype(np.uint16)
display_min = 1000
display_max = 10000

def display(image, display_min, display_max): # copied from Bi Rico
    # Here I set copy=True in order to ensure the original image is not
    # modified. If you don't mind modifying the original image, you can
    # set copy=False or skip this step.
    image = np.array(image, copy=True)
    image.clip(display_min, display_max, out=image)
    image -= display_min
    np.floor_divide(image, (display_max - display_min + 1) / 256,
                    out=image, casting='unsafe')
    return image.astype(np.uint8)

def lut_display(image, display_min, display_max) :
    lut = np.arange(2**16, dtype='uint16')
    lut = display(lut, display_min, display_max)
    return np.take(lut, image)


>>> np.all(display(image, display_min, display_max) ==
           lut_display(image, display_min, display_max))
True
>>> timeit.timeit('display(image, display_min, display_max)',
                  'from __main__ import display, image, display_min, display_max',
                   number=10)
0.304813282062
>>> timeit.timeit('lut_display(image, display_min, display_max)',
                  'from __main__ import lut_display, image, display_min, display_max',
                  number=10)
0.0591987428298

这样可以提高五倍的速度,这可不是一件坏事,我想...

撰写回答