12 votes

Utilisation de numpy pour convertir efficacement des données d'image de 16 bits en 8 bits pour l'affichage, avec mise à l'échelle de l'intensité

Je convertis fréquemment des données d'image en niveaux de gris 16 bits en données d'image 8 bits pour l'affichage. Il est presque toujours utile de régler l'intensité minimale et maximale de l'affichage pour mettre en évidence les parties "intéressantes" de l'image.

Le code ci-dessous fait à peu près ce que je veux, mais il est laid et inefficace, et fait de nombreuses copies intermédiaires des données de l'image. Comment puis-je obtenir le même résultat avec une empreinte mémoire et un temps de traitement minimum ?

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)

17voto

Jaime Points 25540

Ce que vous faites, c'est demi-teinte votre image.

Les méthodes proposées par d'autres fonctionnent très bien, mais elles répètent sans cesse un grand nombre de calculs coûteux. Puisque dans un uint16 il y a au maximum 65 536 valeurs différentes, l'utilisation d'une table de consultation (LUT) peut simplifier considérablement les choses. Et puisque la table de recherche est petite, vous n'avez pas à vous soucier de faire les choses à la place, ou de ne pas créer de tableaux booléens. Le code suivant réutilise la fonction de Bi Rico pour créer la 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

Il y a donc une accélération de x5, ce qui n'est pas une mauvaise chose, je suppose...

2voto

bogatron Points 4412

Pour réduire l'utilisation de la mémoire, effectuez le découpage en place et évitez de créer les tableaux booléens.

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)

Si vous conservez vos limites d'écrêtage sous forme de valeurs entières, vous pouvez également procéder ainsi :

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)

Remarque : un tableau temporaire de flottants sera toujours créé dans la dernière ligne avant la fonction uint8 est créé.

2voto

Bi Rico Points 9851

J'éviterais de couler l'image en flottant, vous pourriez faire quelque chose comme :

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

Ici, une copie optionnelle de l'image est faite dans son type de données natif et une copie 8 bits est faite sur la dernière ligne.

0voto

Vasyl Vaskivskyi Points 597

Voici la réponse que j'ai trouvée sur crossvalidated board dans les commentaires sous cette solution https://stats.stackexchange.com/a/70808/277040

En gros, pour convertir un uint16 en uint8, l'algorithme ressemble à ceci

a = (255 - 0) / (65535 - 0)
b = 255 - a * 65535
newvalue = (a * img + b).astype(np.uint8)

Une version généralisée ressemblerait à ceci

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

par exemple

imgu8 = convert(img16u, 0, 255, np.uint8)

0voto

João Mamede Points 109

Je sais que c'est un vieux sujet, mais nous avons maintenant Cupy avec l'accélération du gpu. Avec cupy, c'est toujours plus rapide (les deux méthodes du commentaire de Jaime s'exécutent à une vitesse plus proche).

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))

Horaires

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.015030614999886893

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X