1

So I am analyzing camera images and want to retrieve black letters from them. First I defined thresholds for all channels and applied them to the image, e.g.

low = [0, 0, 0]
up = [0.42, 0.42, 0.42]

Then, I retrieved a mask for further use from this by just thresholding my image with

mask = cv2.inRange(image, low, up)

Now this worked until I found that different light conditions made me run into problems. E.g., if the image is brighter, I could adapt the upper thresholds to 0.65 which would be good enough for a differentiation between black and white in all cases I analysed. BUT: Now other colors make problems, as many more lie within this interval. I figured that a second condition restricting all possible values to greyish colors would work, i.e. a condition only allowing for a certain variance between the values of the three channels for every pixel.

The question now is, how could I implement this second condition in a smooth way, so that a pixel with [0.4, 0.6, 0.4] would be kicked out and one with [0.6, 0.62, 0.57] would stay in (random examples, I'd adapt the parameters myself)? What is the smoothest way of combining these two that does not need timely iteration over the whole image?

Thanks a ton, this would help a lot!

Tonechas
  • 13,398
  • 16
  • 46
  • 80
mamamamama
  • 101
  • 1
  • 15
  • It's not entirely clear to me what you want to achieve. Can you please give an example (for instance a 3x3 "image" as input and expected output). – Willem Van Onsem Mar 17 '17 at 11:03
  • You might do well to convert to a HSL or YUV colorspsce, where you have a simpler measure of grayness – Eric Mar 17 '17 at 11:08
  • In the end, I want to obtain a mask where all "more or less greyish" pixels are True and then threshold it. As a black color in the image might have values [0.4, 0.38, 0.41] and colors would have higher deviations in one of the channels (eg [0.4, 0.58, 0.41] ), I want a second condition that does not allow for more than a certain differenc between channels of each pixel. I will check out the mentioned channels but still appreciate a concrete solution. Is it a little clearer now? – mamamamama Mar 17 '17 at 11:23

1 Answers1

1

The intensity range across channels is the difference between the maximum and minimum intensities along axis 2. You can take advantage of NumPy broadcasting to develop a fully vectorized solution:

import numpy as np

def in_range(rgb, rgb_min, rgb_max):
    masks = np.logical_and(rgb >= rgb_min, rgb <= rgb_max)
    return np.all(masks, axis=2)

def small_deviation(rgb, rgb_dev):
    return (np.max(rgb, axis=2) - np.min(rgb, axis=2)) < rgb_dev

DEMO

In the sample run below I use a randomly generated 3-channel image of 4 rows and 5 columns.

In [80]: np.random.seed(0)

In [81]: image = np.random.random(size=(4, 5, 3))

In [82]: np.set_printoptions(precision=2)

In [83]: np.rollaxis(image, 2, 0)
Out[83]: 
array([[[ 0.55,  0.54,  0.44,  0.38,  0.57],
        [ 0.09,  0.78,  0.8 ,  0.12,  0.94],
        [ 0.26,  0.57,  0.61,  0.68,  0.7 ],
        [ 0.67,  0.32,  0.44,  0.21,  0.25]],

       [[ 0.72,  0.42,  0.89,  0.79,  0.93],
        [ 0.02,  0.87,  0.46,  0.64,  0.52],
        [ 0.77,  0.02,  0.62,  0.36,  0.06],
        [ 0.21,  0.36,  0.99,  0.16,  0.47]],

       [[ 0.6 ,  0.65,  0.96,  0.53,  0.07],
        [ 0.83,  0.98,  0.78,  0.14,  0.41],
        [ 0.46,  0.62,  0.94,  0.44,  0.67],
        [ 0.13,  0.57,  0.1 ,  0.65,  0.24]]])

The thresholds were set to low = [.0, .0, .0], up = [.6, .5, 0.7] and dev = .4.

In [84]: low = [.0, .0, .0]

In [85]: up = [.6, .5, 0.7]

In [86]: mask1 = in_range(image, low, up)

In [87]: mask1
Out[87]: 
array([[False,  True, False, False, False],
       [False, False, False, False, False],
       [False,  True, False, False, False],
       [False,  True, False,  True,  True]], dtype=bool)

In [88]: dev = .4

In [89]: mask2 = small_deviation(image, dev)

In [90]: mask2
Out[90]: 
array([[ True,  True, False, False, False],
       [False,  True,  True, False, False],
       [False, False,  True,  True, False],
       [False,  True, False, False,  True]], dtype=bool)

In [91]: mask = np.logical_and(mask1, mask2)

In [92]: mask
Out[92]: 
array([[False,  True, False, False, False],
       [False, False, False, False, False],
       [False, False, False, False, False],
       [False,  True, False, False,  True]], dtype=bool)
Tonechas
  • 13,398
  • 16
  • 46
  • 80