Segment an image using python and PIL to calculate centroid and rotations of multiple rectangular objects
Question: I am using python and PIL to find the centroid and rotation of various
rectangles (and squares) in a 640x480 image, similar to this one ![enter image
description here](http://i.stack.imgur.com/6JYjd.png)

So far my code works for a single rectangle in an image.

    
    
    import Image, math
    
    def find_centroid(im):
        width, height = im.size
        XX, YY, count = 0, 0, 0
        for x in xrange(0, width, 1):
            for y in xrange(0, height, 1):
                if im.getpixel((x, y)) == 0:
                    XX += x
                    YY += y
                    count += 1
        return XX/count, YY/count
    
    #Top Left Vertex
    def find_vertex1(im):
        width, height = im.size
        for y in xrange(0, height, 1):
            for x in xrange (0, width, 1):
                if im.getpixel((x, y)) == 0:
                    X1=x
                    Y1=y
                    return X1, Y1
    
    #Bottom Left Vertex
    def find_vertex2(im):
        width, height = im.size
        for x in xrange(0, width, 1):
            for y in xrange (height-1, 0, -1):
                if im.getpixel((x, y)) == 0:
                    X2=x
                    Y2=y
                    return X2, Y2
    
    #Top Right Vertex
    def find_vertex3(im):
        width, height = im.size
        for x in xrange(width-1, 0, -1):
            for y in xrange (0, height, 1):
                if im.getpixel((x, y)) == 0:
                    X3=x
                    Y3=y
                    return X3, Y3
    
    #Bottom Right Vertex
    def find_vertex4 (im):
        width, height = im.size
        for y in xrange(height-1, 0, -1):
            for x in xrange (width-1, 0, -1):
                if im.getpixel((x, y)) == 0:
                    X4=x
                    Y4=y
                    return X4, Y4
    
    def find_angle (V1, V2, direction):
        side1=math.sqrt(((V1[0]-V2[0])**2))
        side2=math.sqrt(((V1[1]-V2[1])**2))
        if direction == 0:
            return math.degrees(math.atan(side2/side1)), 'Clockwise'
        return 90-math.degrees(math.atan(side2/side1)), 'Counter Clockwise'
    
    #Find direction of Rotation; 0 = CW, 1 = CCW
    def find_direction (vertices, C):
        high=480
        for i in range (0,4):
            if vertices[i][1]<high:
                high = vertices[i][1]
                index = i
        if vertices[index][0]<C[0]:
            return 0
        return 1
    
    def main():
        im = Image.open('hopperrotated2.png')
        im = im.convert('1') # convert image to black and white
        print 'Centroid ', find_centroid(im)
        print 'Top Left ', find_vertex1 (im)
        print 'Bottom Left ', find_vertex2 (im)
        print 'Top Right', find_vertex3 (im)
        print 'Bottom Right ', find_vertex4 (im)
        C = find_centroid (im)
        V1 = find_vertex1 (im)
        V2 = find_vertex3 (im)
        V3 = find_vertex2 (im)
        V4 = find_vertex4 (im)
        vertices = [V1,V2,V3,V4]
        direction = find_direction(vertices, C)
        print 'angle: ', find_angle(V1,V2,direction)
    
    if __name__ == '__main__':
      main()
    

Where I am having problems is when there is more than one object in the image.

I know PIL has a find_edges method that gives an image of just the edges, but
I have no idea how to use this new edge image to segment the image into the
separate objects.

    
    
    from PIL import Image, ImageFilter
    
    im = Image.open('hopperrotated2.png')
    
    im1 = im.filter(ImageFilter.FIND_EDGES)
    im1 = im1.convert('1')
    print im1
    im1.save("EDGES.jpg")
    

if I can use the edges to segment the image into individual rectangles then i
can just run my first bit of code on each rectangle to get centroid and
rotation.

But what would be better is to be able to use the edges to calculate rotation
and centroid of each rectangle without needing to split the image up.

Everyone's help is greatly appreciated!


Answer: You need to identify each object before finding the corners. You only need the
border of the objects, so you could also reduce your initial input to that.
Then it is only a matter of following each distinct border to find your
corners, the centroid is directly found after you know each distinct border.

Using the code below, here is what you get (centroid is the red point, the
white text is the rotation in degrees):

![enter image description here](http://i.stack.imgur.com/9UMKm.png)

Note that your input is not binary, so I used a really simple threshold for
that. Also, the following code is the simplest way to achieve this, there are
faster methods in any decent library.

    
    
    import sys
    import math
    from PIL import Image, ImageOps, ImageDraw
    
    orig = ImageOps.grayscale(Image.open(sys.argv[1]))
    orig_bin = orig.point(lambda x: 0 if x < 128 else 255)
    im = orig_bin.load()
    
    border = Image.new('1', orig.size, 'white')
    width, height = orig.size
    bim = border.load()
    # Keep only border points
    for x in xrange(width):
        for y in xrange(height):
            if im[x, y] == 255:
                continue
            if im[x+1, y] or im[x-1, y] or im[x, y+1] or im[x, y-1]:
                bim[x, y] = 0
            else:
                bim[x, y] = 255
    
    # Find each border (the trivial dummy way).
    def follow_border(im, x, y, used):
        work = [(x, y)]
        border = []
        while work:
            x, y = work.pop()
            used.add((x, y))
            border.append((x, y))
            for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1),
                    (1, 1), (-1, -1), (1, -1), (-1, 1)):
                px, py = x + dx, y + dy
                if im[px, py] == 255 or (px, py) in used:
                    continue
                work.append((px, py))
    
        return border
    
    used = set()
    border = []
    for x in xrange(width):
        for y in xrange(height):
            if bim[x, y] == 255 or (x, y) in used:
                continue
            b = follow_border(bim, x, y, used)
            border.append(b)
    
    # Find the corners and centroid of each rectangle.
    rectangle = []
    for b in border:
        xmin, xmax, ymin, ymax = width, 0, height, 0
        mean_x, mean_y = 0, 0
        b = sorted(b)
        top_left, bottom_right = b[0], b[-1]
        for x, y in b:
            mean_x += x
            mean_y += y
        centroid = (mean_x / float(len(b)), mean_y / float(len(b)))
        b = sorted(b, key=lambda x: x[1])
        curr = 0
        while b[curr][1] == b[curr + 1][1]:
            curr += 1
        top_right = b[curr]
        curr = len(b) - 1
        while b[curr][1] == b[curr - 1][1]:
            curr -= 1
        bottom_left = b[curr]
    
        rectangle.append([
            [top_left, top_right, bottom_right, bottom_left], centroid])
    
    
    result = orig.convert('RGB')
    draw = ImageDraw.Draw(result)
    for corner, centroid in rectangle:
        draw.line(corner + [corner[0]], fill='red', width=2)
        cx, cy = centroid
        draw.ellipse((cx - 2, cy - 2, cx + 2, cy + 2), fill='red')
        rotation = math.atan2(corner[0][1] - corner[1][1],
                corner[1][0] - corner[0][0])
        rdeg = math.degrees(rotation)
        draw.text((cx + 10, cy), text='%.2f' % rdeg)
    
    result.save(sys.argv[2])
    

