Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
322 views
in Technique[技术] by (71.8m points)

python - Using numpy to efficiently convert 16-bit image data to 8 bit for display, with intensity scaling

I frequently convert 16-bit grayscale image data to 8-bit image data for display. It's almost always useful to adjust the minimum and maximum display intensity to highlight the 'interesting' parts of the image.

The code below does roughly what I want, but it's ugly and inefficient, and makes many intermediate copies of the image data. How can I achieve the same result with a minimum memory footprint and processing time?

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)
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

What you are doing is halftoning your image.

The methods proposed by others work great, but they are repeating a lot of expensive computations over and over again. Since in a uint16 there are at most 65,536 different values, using a look-up table (LUT) can streamline things a lot. And since the LUT is small, you don't have to worry that much about doing things in place, or not creating boolean arrays. The following code reuses Bi Rico's function to create the 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

So there is a x5 speed-up, which is not a bad thing, I guess...


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...