Python 使用numpy高效地将16位图像数据转换为8位进行显示,并进行强度缩放
我经常将16位灰度图像数据转换为8位图像数据进行显示。调整最小和最大显示强度以突出显示图像的“有趣”部分几乎总是有用的 下面的代码大致满足了我的要求,但它既丑陋又低效,并且生成了许多图像数据的中间副本如何以最小的内存占用和处理时间获得相同的结果?Python 使用numpy高效地将16位图像数据转换为8位进行显示,并进行强度缩放,python,image,image-processing,numpy,Python,Image,Image Processing,Numpy,我经常将16位灰度图像数据转换为8位图像数据进行显示。调整最小和最大显示强度以突出显示图像的“有趣”部分几乎总是有用的 下面的代码大致满足了我的要求,但它既丑陋又低效,并且生成了许多图像数据的中间副本如何以最小的内存占用和处理时间获得相同的结果? import numpy image_data = numpy.random.randint( #Realistic images would be much larger low=100, high=14000, size=(1, 5, 5
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)
要减少内存使用,请在适当的位置执行剪裁,并避免创建布尔数组
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
数组之前,仍将在最后一行中创建一个临时浮点数组。为了避免将图像强制转换为浮点,可以执行以下操作:
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位副本。您所做的就是您的图像
其他人提出的方法工作得很好,但他们一次又一次地重复大量昂贵的计算。由于在uint16
中最多有65536个不同的值,因此使用查找表(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
所以有一个x5的加速,这不是一件坏事,我想…这是我在CrossValidatedBoard上在这个解决方案下的评论中找到的答案 基本上,从uint16到uint8的转换算法如下所示
a = (255 - 0) / (65535 - 0)
b = 255 - a * 65535
newvalue = (a * img + b).astype(np.uint8)
def convert(img, target_type_min, target_type_max, target_type):
imin = img.min()
imax = img.max()
a = (target_type_max - target_type_min) / (imax - imin)
b = target_type_max - a * imax
new_img = (a * img + b).astype(target_type)
return new_img
一个通用的版本应该是这样的
a = (255 - 0) / (65535 - 0)
b = 255 - a * 65535
newvalue = (a * img + b).astype(np.uint8)
def convert(img, target_type_min, target_type_max, target_type):
imin = img.min()
imax = img.max()
a = (target_type_max - target_type_min) / (imax - imin)
b = target_type_max - a * imax
new_img = (a * img + b).astype(target_type)
return new_img
e、 g
imgu8=convert(img16u,0,255,np.uint8)
我知道这是一个旧胎面,但我们现在有了带gpu加速的cupy。
cupy总是比cupy更快(Jaime great comment中的两种方法都以更接近的速度运行)
import numpy as np
import cupy as cp
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)
def displaycp(image2, 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.
image2 = cp.array(image2, copy=True)
image2.clip(display_min, display_max, out=image2)
image2 -= display_min
cp.floor_divide(image2, (display_max - display_min + 1) / 256,
out=image2, casting='unsafe')
return image2.astype(cp.uint8)
def lut_displaycp(image2, display_min, display_max) :
lut = cp.arange(2**16, dtype='uint16')
lut = displaycp(lut, display_min, display_max)
return cp.take(lut, image2)
np.all(display(image, display_min, display_max) ==
lut_display(image, display_min, display_max))
imagecp = cp.asarray(image)
type(imagecp)
cp.all(displaycp(imagecp, display_min, display_max) ==
lut_displaycp(imagecp, display_min, display_max))
np.all(cp.asnumpy(displaycp(imagecp, display_min, display_max)) ==
display(image, display_min, display_max))
时间安排
timeit.timeit('display(image, display_min, display_max)',
'from __main__ import display, image, display_min, display_max',
number=100)
1.2715457340000285
timeit.timeit('lut_display(image, display_min, display_max)',
'from __main__ import lut_display, image, display_min, display_max',
number=100)
0.27357000399933895
timeit.timeit('displaycp(imagecp, display_min, display_max)',
'from __main__ import displaycp, imagecp, display_min, display_max',
number=100)
0.018452465999871492
timeit.timeit('lut_displaycp(imagecp, display_min, display_max)',
'from __main__ import lut_displaycp, imagecp, display_min, display_max',
number=100)
0.01503061499886893不错!我不知道
clip
。我想知道使用numpy.multiply
和out
参数设置为image\u data是否等效?我知道它试图将浮点数据写入uint16数组,但这可能正是我们想要的?想法是,消除浮点数组。这是一个很好的观点。我在回答中编辑了第二个版本,使用了out=
关键字。非常好!这是我自己不会想到的。美丽而优雅的解决方案!有趣的是,正如预期的那样,如果您使用cupy,这并不重要,两个函数都以相同的速度运行。我会写在下面。