Использование numpy для эффективного преобразования 16-битных данных изображения в 8 бит для отображения с масштабированием интенсивности
Я часто конвертирую 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)
Ответы
Ответ 1
Что вы делаете, это полутонирование вашего изображения.
Методы, предлагаемые другими, отлично работают, но они повторяют много дорогостоящих вычислений снова и снова. Поскольку в 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
Итак, есть ускорение x5, что неплохо, я думаю...
Ответ 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)
Обратите внимание: что временный массив float все равно будет создан в последней строке до создания массива uint8
.
Ответ 3
Я бы не стал бросать изображение в float, вы могли бы сделать что-то вроде:
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-битная копия - в последней строке.