简体   繁体   中英

Speeding up per pixel loop in opencv python

So i am trying to find pixels which are not white and create a bounding box around the image by examining the colors. I want to get the topmost, bottom most, leftmost and rightmost non white pixels, and use them to create a bounding box. I have used four loops to travel through each sides.also What i want to do is remove the background color (the background color is mostly grey) and change it to pure white. I have implemented all the functionality but now since i am using a lot of loops the code runs too slow. I need to optimize the loops while still having the functionality of finding the topmost, bottom most, leftmost and rightmost non white pixels and removing the colors. How can i do it?

The code below shows what i am doing to get the bounding box along with background removal at the same time. The mask is a black and white version of the image. If it is mask[i][j]==0 then it is a different color and hence i need to take the value and compare it with the values stored at p. It helps me find the bounding box. And if the mask[i][j]!=0 then i am changing the values of the image to white.

//for bounding box
p = []
p.append(5000)
p.append(0)
p.append(5000)
p.append(0)


for i in range(0, height):
    for j in range(0, width):
        if mask[i][j] == 0:
            if j < p[0]:
                p[0] = j
            break
        else:
            img[i, j] = [255, 255, 255]

for i in range(0, height):
    for j in reversed(range(0, width)):
        if mask[i][j] == 0:
            if j > p[1]:
                p[1] = j
            break
        else:
            img[i, j] = [255, 255, 255]

//topdown
for i in range(0, width):
    for j in range(0, height):
        if mask[j][i] == 0:
            if j < p[2]:
                p[2] = j
            break
        else:
            img[j, i] = [255, 255, 255]

for i in reversed(range(0, width)):
    for j in reversed(range(0, height)):
        if mask[j][i] == 0:
            if j > p[3]:
                p[3] = j
            break
        else:
            img[j, i] = [255, 255, 255]

So how can i optimize these loops while still getting the same functionality of getting pixel values and being able to change the color of some other image?

Background
To make the background white you can use a bitwise operation with the mask. To automate the creation of a mask read here .

Example:
在此处输入图片说明

import cv2
import numpy as np

# load image and mask
img = cv2.imread('image.png')
mask = cv2.imread('mask.png')

# combine images
res = cv2.bitwise_or(img,mask)

cv2.imshow("result", res)
cv2.waitKey(0)
cv2.destroyAllWindows() 

The mask needs to have the equal number of color-channels as the image. All white areas in the mask will also become white in the image. Black areas in the mask will remain unaffected in the image.

Boundingbox
To get the boundingbox you could use findContours . It takes a binary mask as input and returns a list of contours. You can use the contour to find the boundingbox, rotated boundingbox or minimum enclosing circle . The result may not be perfect depending on your input, but you can use it to increase performance as it greatly narrows the search.

Note: the input to findContours should have a black background. You can modify your mask using inverted_mask = cv2.bitwise_not(mask) . Or, if you obtained you mask using thresholding, you can choose an inverted threshold type .

Result:
在此处输入图片说明

Code:

    import cv2
    import numpy as np

    # load image // use your mask instead
    mask = cv2.imread('mask.png',0)

    # find contours 
    contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
        # get the boundingrect and draw a red line over it
        x,y,w,h = cv2.boundingRect(cnt)
        cv2.rectangle(mask2,(x,y),(x+w,y+h),(0,0,255),3)
        # get the minumum enclosing rectangle and draw it in blue
        rect = cv2.minAreaRect(cnt)
        box = cv2.boxPoints(rect)
        box = np.int0(box)
        cv2.drawContours(mask2,[box],0,(255,0,0),3)

    # diplay result
    cv2.imshow("img", mask)
    cv2.waitKey(0)
    cv2.destroyAllWindows() 

Maths
If you'd rather stick to checking array values, you can boost performance by first summing the rows and the columns. Summing is fast (and baked into numpy) and now you can discard rows/columns by checking one value. You can see an example of this process in this answer . I would suggest using the mask with background black for this, as you can compare the sum with zero. This will essentially result in the red boundingbox above. Of course, when a non zero row/col is found you will still have to loop that one to find the exact coordinate.

Pfiew, that turned out much longer than intended...

@JohnColeman has a point. Nested Python loops will be relatively slow even with the best algorithms and there are libraries that can optimize such operations.

The algorithm itself could be sped up by using the results of each loop to limit the range of following loop. For example, if when looking for the top non-white pixel, you scanned from top to bottom as the outer loop, and left to right as the inner loop, and found a pixel (a, b) (with a is the distance from the top), then in the next section you went looking for the left pixel, you know that you can start scanning from a+1 in the top down outer loop, and no further than b - 1 in the left-right inner loop. Let's call the result (c, d).

Similarly the bottom pixel can be no less than c in the vertical and d in the horizontal.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM