15
\$\begingroup\$

I am using OpenCV to process an image, and in my code, I have to check / edit each pixel separately:

import cv2, numpy

# we just use an empty image for the purpose of this MCVE
img = cv2.imread("source.jpg")   
width = len(img[0])
height = len(img)

empty_img = numpy.zeros((height, width, 3), numpy.uint8)

i = 0
r = 0
c = 0

for line in img:
    c = 0

    for pixel in line:
        blue = pixel[0]
        green = pixel[1]
        red = pixel[2]

        if green != max(red, green, blue) or green < 35:
            # this has a greenishy hue
            empty_img.itemset((r, c, 0), 255)

        c += 1
    r += 1

This code works, but is quite slow (it takes about 23 seconds to process the image). How could/should I speed this up?

source.jpg: enter image description here

result should be: enter image description here

\$\endgroup\$
11
\$\begingroup\$

There's a simpler way to create the empty image using numpy.zeros_like:

empty_img = numpy.zeros_like(img)

As Austin Hastings correctly pointed out, the trick is to use vectorized operations provided by numpy:

RED, GREEN, BLUE = (2, 1, 0)

reds = img[:, :, RED]
greens = img[:, :, GREEN]
blues = img[:, :, BLUE]

mask = (greens < 35) | (reds > greens) | (blues > greens)

or, using numpy.amax

mask = (greens < 35) | (numpy.amax(img, axis=2) != greens)

Now, one option is to use conditional indexing to modify empty_img. Since it's a 3 channel image (represented as 3 dimensional array), and our mask is only 1 channel (represented as 2 dimensional array) there are two possibilities:

  • assign 3-tuples: empty_img[mask] = (255,0,0)
  • provide the 3rd index: empty_img[mask,0] = 255

If all you care about is just a single channel mask, then numpy.where is a possibility.

result = numpy.where(mask, 255, 0)
\$\endgroup\$
12
\$\begingroup\$

Use conditional indexing:

RED, GREEN, BLUE = (2, 1, 0) # Your numbers

empty_img = numpy.zeros((height, width, 3), numpy.uint8)

reds = img[:, :, RED]
greens = img[:, :, GREEN]
blues = img[:, :, BLUE]

empty_img[(greens < 35) | (reds <= greens >= blues)][BLUE] = 255

Edit:

empty_img[(greens < 35) | ((reds <= greens) & (blues <= greens))][BLUE] = 255

I was wondering if the compound relation would work. Sadly, not. This should. If it doesn't you can try composing it step by step - look at just the greens < 35 case, then the reds <=greens, etc.

\$\endgroup\$
  • 1
    \$\begingroup\$ Curses! You'll have to break down the compound <= >= statement. Let me edit. \$\endgroup\$ – Austin Hastings Jan 1 '18 at 18:27
  • 1
    \$\begingroup\$ I get some result, but not the same as with my own code. I uploaded source and result images. \$\endgroup\$ – Bart Friederichs Jan 1 '18 at 18:42
  • 1
    \$\begingroup\$ @AustinHastings this doesn't seem to work at all. Looks like assignment doesn't work. Doesn't matter what I put in, the image always stays black. \$\endgroup\$ – Bart Friederichs Jan 1 '18 at 19:14
  • 1
    \$\begingroup\$ @DanMašek great, thanks! That should be the answer. For my application it doesn't matter that it is blue (in fact white on black is better), so by just writing out the mask I shaved another 0.1s off. \$\endgroup\$ – Bart Friederichs Jan 1 '18 at 20:30
  • 1
    \$\begingroup\$ @BartFriederichs No problem. I was just writing it up when you deleted the original on SO :D | There's one more problem with this answer: green != max(red, green, blue) means that (green < blue) or (green < red) \$\endgroup\$ – Dan Mašek Jan 1 '18 at 21:16
1
\$\begingroup\$

It seems like assigning a value like in the answer from @austin-hastings doesn't work. I fiddled a bit more and found that just using his conditionals in a mask does work (the boolean is a little different, but that is besides the point):

mask = ((greens < 35) | (reds >= greens) | (blues >= greens)) * 255

this creates a single layer (one color) mask that I can write out to a file:

cv2.imwrite("result.jpg", mmask)

I guess using it as a blue colour is not hard, but not relevant for my application. (In fact, a white mask is even better)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.