Sibh
Sibh

Reputation: 423

How to identify distinct objects in image in OpenCV Python

I'm trying to identify separate objects in an image in OpenCV. So far I've opened the image into a NumPy array and thresholded it so it is binary. Here's what it looks like:

Original Image

I'm trying to identify the NumPy array indices where different objects are for instance segmentation. Here is what I am trying to achieve: End goal (I didn't bother coloring every single object in this image with a different color but you get the idea)

Essentially, I'm trying to label every cluster of pixels considered an 'object' as a separate class and generate a list of array indices for each of these classes. I've tried using OpenCV's connectedComponentsWithStats, but I have no idea how to generate a list of array indices for the locations of each object in this image. How can I achieve this?

Upvotes: 4

Views: 3428

Answers (2)

ndrplz
ndrplz

Reputation: 1654

The one you are facing is connected component labeling so the best function you can use is exactly the connectedComponentsWithStats you mention.

However, its use can be a bit confusing at the beginning. Here you find a working example.

import cv2
import numpy as np

# Load the image in grayscale
input_image = cv2.imread(r"satellite.png", cv2.IMREAD_GRAYSCALE)

# Threshold your image to make sure that is binary
thresh_type = cv2.THRESH_BINARY + cv2.THRESH_OTSU
_, binary_image = cv2.threshold(input_image, 0, 255, thresh_type)

# Perform connected component labeling
n_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary_image,
                                                                      connectivity=4)

# Create false color image
colors = np.random.randint(0, 255, size=(n_labels , 3), dtype=np.uint8)
colors[0] = [0, 0, 0]  # for cosmetic reason we want the background black
false_colors = colors[labels]

cv2.imshow('binary', binary_image)
cv2.imshow('false_colors', false_colors)
cv2.waitKey(0)

Binary image:

binary image

Image labeled (in false colors):

labeled image

The centroids variable already contain the centroid (x, y) coordinates of each labeled object.

false_colors_draw = false_colors.copy()
for centroid in centroids:
    cv2.drawMarker(false_colors_draw, (int(centroid[0]), int(centroid[1])),
                   color=(255, 255, 255), markerType=cv2.MARKER_CROSS)
cv2.imshow('false_colors_centroids', false_colors_draw)
cv2.waitKey(0)

Centroids:

centroids image

As you can see, they are quite a lot. If you want to keep only the bigger objects, you can either i) use morphological operations on your binary image in the beginning or ii) use the area information which is already contained in stats.

MIN_AREA = 50
false_colors_draw = false_colors.copy()
for i, centroid in enumerate(centroids[1:], start=1):
    area = stats[i, 4]
    if area > min_area:
        cv2.drawMarker(false_colors_draw, (int(centroid[0]), int(centroid[1])),
                       color=(255, 255, 255), markerType=cv2.MARKER_CROSS)

Centroids (filtered by area):

centroid image filtered

Upvotes: 5

Ziri
Ziri

Reputation: 736

Use cv2.findContours and cv2.drawContours

enter image description here

Edite : code

import cv2
import numpy as np
import random as rng

im = cv2.imread('YourImagePath\\test2.png')
gray=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 113, 255, 0)
contours, hierarchy = 
cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[-2:]
idx =0

for cnt in contours:
    idx += 1
    x,y,w,h = cv2.boundingRect(cnt)
    roi=im[y:y+h,x:x+w]
    color = (rng.randint(0, 256), rng.randint(0, 256), rng.randint(0, 256))
    #cv2.rectangle(im,(x,y),(x+w,y+h),color,2)
    cv2.drawContours(im,[cnt],0 ,color,-1)
cv2.imshow('img',im)
cv2.waitKey(0)

Upvotes: 0

Related Questions