Finding Number of Colored Shapes from Picture Using Python

How to find all the color shapes in an image open CV python

Sample Image

In order to solve this problem. You need to do the follows:

  1. Find the correct region of the green color in HSV space
  2. Find the possible areas using contour detection
  3. Sort the candidates by the size of areas
  4. Find the bounding box of that area with max size
  5. Compute the center of the bounding box
  6. Fix the background of the monkey image.
  7. Put the monkey image in the correct position.

Here is the code:

import cv2
import numpy as np

big_img = cv2.imread("color_img.jpg", 1)
monkey_img = cv2.imread("monkey.png", 1)

# define green value range
big_img_hsv = cv2.cvtColor(big_img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(big_img_hsv, (36, 0, 0), (70, 255,255))

# find the contours in the mask
img, contours, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# find the contour with max area
cnt = sorted(contours, key=cv2.contourArea, reverse=True)[0]
# cv2.drawContours(big_img, [cnt], 0, (0,0,255), 3)

# Find the bounding box in that region
x,y,w,h = cv2.boundingRect(cnt)
rect = (x, y), (x + w, y + h)
#cv2.rectangle(big_img,(x,y),(x+w,y+h),(0,255,0),2)

# Put the monkey to that region
img_height, img_width = monkey_img.shape[:2]

# you like to put the monkey image to the center of this region
center_x = int(round(x + w / 2))
center_y = int(round(y + h / 2))
# so the starting point should be
start_x = int(round(center_x - img_width / 2))
start_y = int(round(center_y - img_height / 2))

mask_img = np.where(monkey_img==[0,0,0])

# Grap information from original image
crop_from_original = big_img[start_y: start_y + img_height, start_x: start_x+img_width ]

# put the pixel to monkey image
monkey_img[mask_img] = crop_from_original[mask_img]

# put the monkey to the right image
big_img[start_y:start_y+img_height,start_x: start_x+img_width]=monkey_img

cv2.imshow("big_img", big_img)
cv2.waitKey()

Count the number of objects of different colors in an image in Python


Here's a simpler solution based on scikit-image:

Code:

import numpy as np
from skimage import io, morphology, measure
from sklearn.cluster import KMeans

img = io.imread('https://i.stack.imgur.com/du0XZ.png')

rows, cols, bands = img.shape
X = img.reshape(rows*cols, bands)

kmeans = KMeans(n_clusters=5, random_state=0).fit(X)
labels = kmeans.labels_.reshape(rows, cols)

for i in np.unique(labels):
blobs = np.int_(morphology.binary_opening(labels == i))
color = np.around(kmeans.cluster_centers_[i])
count = len(np.unique(measure.label(blobs))) - 1
print('Color: {} >> Objects: {}'.format(color, count))

Output:

Color: [ 254.  253.  253.  255.]  >>  Objects: 1
Color: [ 255. 144. 36. 255.] >> Objects: 288
Color: [ 39. 215. 239. 255.] >> Objects: 288
Color: [ 255. 38. 135. 255.] >> Objects: 288
Color: [ 192. 231. 80. 255.] >> Objects: 288

Remarks:

  • I've clustered colors through KMeans to make the program robust to slight variations in pixel colors.

  • The RGB coordinates of the cluster centers have been rounded through around just for visualization purposes.

  • I've also performed an opening operation through binary_opening in order to get rid of isolated pixels.

  • It is necessary to subtract 1 from the number of labels yielded by label to take into account only those connected regions with the considered color label.

  • The first line of the output obviously corresponds to the white background.

How to counts the corner points in the image of a given color

In response to questions raised in the comments, here is one way to get the list of unique colors ignoring the anti-aliasing of the colors.

(You could also use morphology to thin your colored lines to remove the anti-aliased pixels)

  • Read the input
  • Reshape to 1D image of 3 channels
  • Use np.unique to get the colors and counts
  • Zip the colors and counts
  • Put the zip into a list
  • Sort the zipped list on count in reverse order
  • Print only those colors that have counts above some threshold.
  • (Note: other filters could be used to check colors against each other to be sure not too close or to remove colors near the background color. Etc)

Input:

Sample Image

import cv2
import numpy as np

# read image
img = cv2.imread('colored_polygons.png')

# reshape img to 1 column of 3 colors
# -1 means figure out how big it needs to be for that dimension
img2 = img.reshape(-1,3)

# get the unique colors
colors, counts = np.unique(img2, return_counts=True, axis=0)

# zip colors, counts
unique = zip(colors,counts)

# make list of color, count
cc_list = []
for color, count in unique:
cc_list.append((color, count))

# function to define key as second element (count)
def takeSecond(elem):
return elem[1]

# sort cc_list on counts
cc_list.sort(key=takeSecond, reverse=True)

# print sorted list and threshold on count
index = 0
for item in cc_list:
color = item[0]
count = item[1]
if count > 5000:
index += 1
print("index:", index, "count:", count, "color:", color)

List of Top Unique Colors:

index: 1 count: 428771 color: [255 255 255]
index: 2 count: 15735 color: [0 0 0]
index: 3 count: 9760 color: [ 14 127 0]
index: 4 count: 9160 color: [255 38 0]
index: 5 count: 8893 color: [ 0 0 255]

Count total number of pixels for each color

This is the simplest way to get the colours and their corresponding counts:

#!/usr/bin/env python3

from PIL import Image
import numpy as np

# Open image and ensure RGB
im = Image.open('UMN9c.png').convert('RGB')

# Make into Numpy array
na = np.array(im)

# Get colours and corresponding counts
colours, counts = np.unique(na.reshape(-1,3), axis=0, return_counts=1)

print(colours, counts)

Sample Outputs

 [[ 37  36  36]
[226 44 11]
[228 239 80]] [255169 1059 5916]

If you don't want to write any Python code, you can just use ImageMagick in the Terminal

magick -verbose UMN9c.png -format %c histogram:info:-

Output

UMN9c.png PNG 512x512 512x512+0+0 8-bit sRGB 3572B 0.020u 0:00.008
255169: (37,36,36,255) #252424FF srgba(37,36,36,1)
1059: (226,44,11,255) #E22C0BFF srgba(226,44,11,1)
5916: (228,239,80,255) #E4EF50FF srgba(228,239,80,1)
UMN9c.png=>info:- PNG 512x512 512x512+0+0 8-bit sRGB 3572B 0.320u 0:00.166

I'm not sure if outright speed is an issue for you. If so, there is a significantly faster method using np.dot() at the end of this answer.

count colored dots in image

Here is a sample solution based on OpenCV 3.2 and Python 2.7.

To count the colored dots, repeat below 4 steps once per color type.

  1. Apply median filter to reduce noise - cv2.medianBlur().
  2. Apply color threshold to segment the colored dots - use cv2.inRange().
  3. Use Hough Circle Transform to detect the circles - use circles = cv2.HoughCircles(mask,cv2.HOUGH_GRADIENT,...)
  4. Loop through each detected circles to draw its center and a circle around it, and count the numbers of colored dots.

Sample images of dots detected:

Red - 10 dots
Sample Image

Green - 39 dots
Sample Image

Yellow - 30 dots
Sample Image

Take note that the last yellow dots at the right side with less than half a circle hasn't been detected. This is likely a limitation of the Hough Circle Transform cv2.HoughCircles(). So you need to decide how to handle this type of issue if it happens.

Here is the sample code:

import cv2
import numpy

red = [(0,0,240),(10,10,255)] # lower and upper
green = [(0,240,0),(10,255,10)]
yellow = [(0,240,250),(10,255,255)]
dot_colors = [red, green, yellow]

img = cv2.imread('./imagesStackoverflow/count_colored_dots.jpg')
# apply medianBlur to smooth image before threshholding
blur= cv2.medianBlur(img, 7) # smooth image by 7x7 pixels, may need to adjust a bit

for lower, upper in dot_colors:
output = img.copy()
# apply threshhold color to white (255,255, 255) and the rest to black(0,0,0)
mask = cv2.inRange(blur,lower,upper)

circles = cv2.HoughCircles(mask,cv2.HOUGH_GRADIENT,1,20,param1=20,param2=8,
minRadius=0,maxRadius=60)
index = 0
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = numpy.round(circles[0, :]).astype("int")

# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
# draw the circle in the output image,
# then draw a rectangle corresponding to the center of the circle
cv2.circle(output, (x, y), r, (255, 0, 255), 2)
cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (255, 0, 255), -1)

index = index + 1
#print str(index) + " : " + str(r) + ", (x,y) = " + str(x) + ', ' + str(y)
print 'No. of circles detected = {}'.format(index)

Hope this help.



Related Topics



Leave a reply



Submit