4

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

Josef M. Schomburg
  • 477
  • 1
  • 5
  • 11
  • Please show what you have tried, possibly with the image. – amanb Apr 07 '19 at 17:50
  • There are quite a few things going on, but I tried to sum it all up into a few screenshots. – Josef M. Schomburg Apr 08 '19 at 06:46
  • 1
    Nota: the reason I'm doing this is because existing algorithms result in text that, although black, has missing patches and gaps. OpenCv manages to find countours quite well, though, and I use these to create the mask that defines what regions should be averaged, and it is this that gives the two values to be knocked up and down.. – Josef M. Schomburg Apr 08 '19 at 06:55
  • 1
    Also see: [Histogram equalization of grayscale images with NumPy](https://stackoverflow.com/questions/28518684/histogram-equalization-of-grayscale-images-with-numpy) – NoDataDumpNoContribution Apr 09 '19 at 07:51
  • 1
    You could do just what you described: (1) Threshold all pixels above 246 to 255 (2) Threshold all pixels below 186 to zero (3) Normalize all pixels in between to a maximum of 244. I don't think there is any ready-made algorithm for this, as all 3 steps to achieve your desired result are trivial. – T A Apr 10 '19 at 12:41

2 Answers2

2

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)
Divakar
  • 218,885
  • 19
  • 262
  • 358
1

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')
Paul Panzer
  • 51,835
  • 3
  • 54
  • 99