Esther
Esther

Reputation: 65

Image segmentation in python

I have the image shape image

I am looking for python solution to break the shape in this image into smaller parts according to the contour in the image.

I have looked into solution on Canny and findContours in OpenCV but none of them works for me.

Edit:

Code used:

using Canny method

import cv2 import numpy as np

img = cv2.imread('area_of_blob_maxcontrast_white.jpg') edges = cv2.Canny(img, 100, 200)
cv2.imwrite('area_of_blob_maxcontrast_white_edges.jpg',edges)

using findContours method

import numpy as np 
import argparse 
import cv2

image = cv2.imread('area_of_blob_maxcontrast_white.png')

lower = np.array([0, 0, 0]) upper = np.array([15, 15, 15]) shapeMask = cv2.inRange(image, lower, upper)

(_,cnts, _) = cv2.findContours(shapeMask.copy(), cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE) print "I found %d black shapes" % (len(cnts)) cv2.imshow("Mask", shapeMask)

for c in cnts:
    # draw the contour and show it
    cv2.drawContours(image, [c], -1, (0, 255, 0), 2)
    cv2.imshow("Image", image)
    cv2.waitKey(0)

Upvotes: 4

Views: 4968

Answers (1)

Andriy Makukha
Andriy Makukha

Reputation: 8304

The trick is to make your faint single pixel boundary slightly bolder. I do it by changing any white pixel that has two adjacent black pixels (above, below, to the left or to the right) to black. (I do it extremely slow, though. I'm pretty sure there must be a smarter way to do it with OpenCV or Numpy.)

Here is my code:

#!/usr/bin/env python 

import numpy as np
import cv2

THRESH = 240

orig = cv2.imread("map.png")
img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)

# Make the faint 1-pixel boundary bolder
rows, cols = img.shape
new_img = np.full_like(img, 255)    # pure white image
for y in range(rows):
    if not (y % 10):
        print ('Row = %d (%.2f%%)' % (y, 100.*y/rows))
    for x in range(cols):
        score  = 1 if y > 0 and img.item(y-1, x) < THRESH else 0
        score += 1 if x > 0 and img.item(y, x-1) < THRESH else 0
        score += 1 if y < rows-1 and img.item(y+1, x) < THRESH else 0
        score += 1 if x < cols-1 and img.item(y, x+1) < THRESH else 0
        if img.item(y, x) < THRESH or score >= 2:
            new_img[y, x] = 0       # black pixels show boundary

cv2.imwrite('thresh.png', new_img)

# Find all contours on the map
_th, contours, hierarchy = cv2.findContours(new_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
print "Number of contours detected = %d" % len(contours)

# Fill second level regions on the map
coln = 0
colors = [
    [127, 0, 255],
    [255, 0, 127],
    [255, 127, 0],
    [127, 255, 0],
    [0, 127, 255],
    [0, 255, 127],
]
hierarchy = hierarchy[0]
for i in range(len(contours)):
    area = cv2.contourArea(contours[i])
    if hierarchy[i][3] == 1:
        print (i, area)
        coln = (coln + 1) % len(colors)
        cv2.drawContours(orig, contours, i, colors[coln], -1)

cv2.imwrite("colored_map.png", orig)

Input image:

Faint outline of regions

Output image:

Map with four colored regions

Here I color only the direct descendants of the outmost contour (hierarchy[i][3] == 1). But you can change it to exclude the lakes.

Upvotes: 6

Related Questions