Reputation: 423
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:
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
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:
Image labeled (in false colors):
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:
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):
Upvotes: 5
Reputation: 736
Use cv2.findContours
and cv2.drawContours
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