drawing a bounding box in large images

2019-02-27 07:19发布

问题:

I have a large binary image (4k x 7k pix) from which I want to extract the entire yellow portion as a single rectangle. I tried binary erosion to even out features inside the yellow region. Then I used the bbox method of skimage.regionprops but it does not seem to work fast enough for large image with one large bbox. Do you have any suggestion?

回答1:

As the image you provided includes distracting axes, and is the wrong colour and too small, I created as realistic a version as I could with ImageMagick like this in Terminal:

convert bbox.png -alpha off -crop 120x215+40+13 -colorspace gray -normalize -threshold 50% -scale 4200x7200\! bbox.png

The full-size version is 4200x7200.

I then wrote a numpy-based version of bbox as follows

#!/usr/local/bin/python3
import numpy as np
from PIL import Image

def bbox(image):
    """Find bounding box of image"""
    # Project all columns into row same width as image
    proj=np.any(image,axis=0)
    # Find first non-zero value from Left
    L=np.argmax(proj)
    # And right
    R=image.shape[1]-np.argmax(np.flipud(proj))-1
    # Project all rows into column same height as image
    proj=np.any(image,axis=1)
    # Find first non-zero value from Top
    T=np.argmax(proj)
    # And Bottom
    B=image.shape[0]-np.argmax(np.flipud(proj))-1
    return T,L,B,R

image=np.array(Image.open("a.png").convert("L"))
print(bbox(image))

That runs in 5.3ms on my Mac. Just for fun, I threaded it and ran the horizontal projection and vertical projection on separate parallel threads and it came down to 3.6ms with the same results.

#!/usr/local/bin/python3
import numpy as np
from PIL import Image

import threading
import queue

def DoOneDim(image,axis,q):
    """Find bounding box of image"""
    proj=np.any(image,axis=axis)
    # Find first non-zero value
    A=np.argmax(proj)
    # And and last
    B=image.shape[1-axis]-np.argmax(np.flipud(proj))-1
    q.put({axis:(A,B)})


def bboxTh(image):
    """Threaded version of bbox() that does vertical and horizontal on their own theads"""
    q = queue.Queue()
    Hthread=threading.Thread(target=DoOneDim, args=(image,0,q))
    Vthread=threading.Thread(target=DoOneDim, args=(image,1,q))
    Hthread.start()
    Vthread.start()
    Hthread.join()
    Vthread.join()
    results=dict()
    while not q.empty():
       results.update(q.get())
    return results

image=np.array(Image.open("a.png").convert("L"))
print(bboxTh(image))

The identified box looks likes this:



回答2:

Because you are looking for a single bounding box, don't use regionprops or any per-object function. This also makes it so that you don't need to try to make a single object out of all the yellow dots.

The simplest solution here is to walk over the image, and for each pixel, determine if it is "yellow enough" (whatever that means for your application). If so, add the pixel's coordinates to the running bounding box calculation.

The bounding box calculation is quite simple:

top_left = [1e9, 1e9]
bottom_right = [0, 0]
for ...:
   # within your loop over the pixels, [x, y] are the current coordinates
   top_left = [min(top_left[0], x), min(top_left[1], y)];
   bottom_right = [max(bottom_right[0], x), max(bottom_right[1], y)];

There might be a way with skimage to do this without loops, but I don't know that package at all.