Skip to content

Instantly share code, notes, and snippets.

@anilsathyan7
Last active October 24, 2019 08:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anilsathyan7/991d2b0f2aa753a28c499bbc7086f516 to your computer and use it in GitHub Desktop.
Save anilsathyan7/991d2b0f2aa753a28c499bbc7086f516 to your computer and use it in GitHub Desktop.
Cluster similar shapes in an image using hu moments, with the help of opencv and scikit-learn

Sample Output

Similar shapes in the input image(left) are grouped into same clusters, using hu moments. Members of the same cluster are shown with same coloured bounding boxes in the output image(right).














In the above image there are four different clusters corresponding to four shapes: Square, Rectangle, Triangle and Ellipse

import numpy as np
import cv2
from scipy.cluster.hierarchy import linkage, fcluster
from scipy.spatial import distance as ssd
# Read input image and convert to grayscale
img = cv2.imread('shapes.jpg')
print("Image shape: " + str(img.shape))
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply adaptive threshold
cv2.imwrite('gray.png',imgray)
thresh = cv2.adaptiveThreshold(imgray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,11,2)
cv2.imwrite('edge.png',thresh)
# Find all contours of binary image
_, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Number of contours
num_contours=len(contours)
print("Total number of contours: " + str(num_contours))
# Fill up all the shapes
filled=cv2.fillPoly(thresh, pts=contours, color=(255,255,255))
# Remove noise by morphological opening
kernel = np.ones((4,1),np.uint8)
filled=cv2.morphologyEx(filled, cv2.MORPH_OPEN, kernel)
# Crop and save proper contours separately [filter by area]
contour_crop=[]
colour_crop=[]
num_contours=0
min_area=50
for cnt in contours:
if(cv2.contourArea(cnt)>min_area):
x,y,w,h = cv2.boundingRect(cnt)
contour_crop.append(filled[y:y+h, x:x+w])
colour_crop.append(img[y:y+h, x:x+w])
cv2.imwrite("contour_binary"+str(num_contours)+".png",filled[y:y+h, x:x+w])
num_contours=num_contours+1
# Number of proper contours
print("Number of proper contours: " + str(num_contours))
# Initialize similarity matrix
sim_mat=np.zeros((num_contours, num_contours))
# Configure cluster settings
cluster=np.zeros(num_contours, dtype=int)
num_classes=4
colours=[(255,0,0),(0,255,0),(0,0,255),(0,0,0),(255,255,0), (0,255,255),(255,0,255),(128,128,128),(165,42,42)]
# Compute similarity matrix [Uses Hu-Moments]
print("Similarity Matrix - Hu Moements")
for i in range(0,num_contours):
for j in range(0, num_contours):
im1=np.array(contour_crop[i])
im2=np.array(contour_crop[j])
sim_mat[i][j]=cv2.matchShapes(im1,im2,cv2.CONTOURS_MATCH_I2,0)
print(str(round(sim_mat[i][j],4))+",",end =" ")
print("\n")
'''
Cluster the contours using similarity matrix and max cluster no.
Returns cluster vector [Range: 0 - (num_classes-1)]
'''
Zd = linkage(ssd.squareform(sim_mat), method="complete")
cluster = fcluster(Zd, num_classes, criterion='maxclust') - 1
print("Cluster:-\n" + str(cluster))
# Draw contour clusters
c=0
for cnt in contours:
if(cv2.contourArea(cnt)>min_area):
x,y,w,h = cv2.boundingRect(cnt)
cv2.imwrite("contour_coloured"+str(c)+".png",img[y:y+h, x:x+w])
cv2.rectangle(img,(x,y),(x+w,y+h),colours[cluster[c]],2)
c=c+1
# Show output in window
cv2.imshow("Bounding Boxes", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment