Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'Stretching' histograms (levels) in Numpy, Python

I have grayscale image whose background is, on a 0-255 color scale, a mid-white color with an average pixel color value of 246; the foreground is mid-grey with an average pixel-color value of 186.

I would like to 'shift' every pixel above 246 to 255, every pixel below 186 to zero, and 'stretch' everything between. Is there any ready-made algorithm/process to do this in numpy or python, or must the new levels/histogram be calculated 'manually' (as I have done thus far)?

This is the equivalent of, in Gimp or Photoshop, opening the levels window and selecting, with the white and black eyedropper respectively, a light region we want to make white and a darker region we want to make black: the application modifies the levels/histogram ('stretches' the values between the points selected) accordingly.

Some images of what I'm attempting:

page after page shadow elimination Sampled colours Result

like image 712
Josef M. Schomburg Avatar asked Sep 16 '25 00:09

Josef M. Schomburg


2 Answers

Here's one way -

def stretch(a, lower_thresh, upper_thresh):
    r = 255.0/(upper_thresh-lower_thresh+2) # unit of stretching
    out = np.round(r*(a-lower_thresh+1)).astype(a.dtype) # stretched values
    out[a<lower_thresh] = 0
    out[a>upper_thresh] = 255
    return out

As per OP, the criteria set was :

  • 'shift' every pixel above 246 to 255, hence 247 and above should become 255.

  • every pixel below 186 to zero, hence 185 and below should become 0.

  • Hence, based on above mentioned two requirements, 186 should become something greater than 0 and so on, until 246 which should be lesser than 255.

Alternatively, we can also use np.where to make it a bit more compact -

def stretch(a, lower_thresh, upper_thresh):
    r = 255.0/(upper_thresh-lower_thresh+2) # unit of stretching
    out = np.round(r*np.where(a>=lower_thresh,a-lower_thresh+1,0)).clip(max=255)
    return out.astype(a.dtype)

Sample run -

# check out first row input, output for variations
In [216]: a
Out[216]: 
array([[186, 187, 188, 246, 247],
       [251, 195, 103,   9, 211],
       [ 21, 242,  36,  87,  70]], dtype=uint8)

In [217]: stretch(a, lower_thresh=186, upper_thresh=246)
Out[217]: 
array([[  4,   8,  12, 251, 255], 
       [255,  41,   0,   0, 107],
       [  0, 234,   0,   0,   0]], dtype=uint8)
like image 108
Divakar Avatar answered Sep 17 '25 13:09

Divakar


If your picture is uint8 and typical picture size, one efficient method is setting up a lookup table:

L, H = 186, 246
lut = np.r_[0:0:(L-1)*1j, 0.5:255.5:(H-L+3)*1j, 255:255:(255-H-1)*1j].astype('u1')

# example
from scipy.misc import face
f = face()

rescaled = lut[f]

For smaller images it is faster (on my setup it crosses over at around 100,000 gray scale pixels) to transform directly:

fsmall = (f[::16, ::16].sum(2)//3).astype('u1')

slope = 255/(H-L+2)
rescaled = ((1-L+0.5/slope+fsmall)*slope).clip(0, 255).astype('u1')
like image 37
Paul Panzer Avatar answered Sep 17 '25 13:09

Paul Panzer