Skip to content

Instantly share code, notes, and snippets.

@akleemans
Created February 3, 2025 20:01
Show Gist options
  • Save akleemans/b78d04c5d93af99beb74a3dc86044d9a to your computer and use it in GitHub Desktop.
Save akleemans/b78d04c5d93af99beb74a3dc86044d9a to your computer and use it in GitHub Desktop.
Count jigsaw puzzle pieces using k-means clustering
# Counts jigsaw puzzle pieces
# See https://www.kleemans.ch/counting-jigsaw-puzzle-pieces
# Uses opencv-python==4.11.0.86 and numpy==2.2.2
import statistics
import cv2
import numpy as np
def kmeans_color_quantization(image, clusters=8, rounds=1):
h, w = image.shape[:2]
samples = np.zeros([h * w, 3], dtype=np.float32)
count = 0
for x in range(h):
for y in range(w):
samples[count] = image[x][y]
count += 1
compactness, labels, centers = cv2.kmeans(samples, clusters, None,
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001),
rounds, cv2.KMEANS_RANDOM_CENTERS)
centers = np.uint8(centers)
res = centers[labels.flatten()]
return res.reshape(image.shape)
# Load image and perform kmeans
filename = 'test'
image = cv2.imread(f'{filename}.jpg')
image_orig = image
# k-means color quantization
kmeans = kmeans_color_quantization(image, clusters=2)
result = kmeans.copy()
cv2.imwrite(f'{filename}_kmeans.png', kmeans)
# Find colors
print('Finding colors...')
image = kmeans
clusters = []
print('Clustering...')
# Convert to grey, threshold
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Threshold
_, image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Invert if needed
print('First pixel:', image[0][0])
if image[0][0] == 255:
print('Inverting image!')
image = cv2.bitwise_not(image)
cv2.imwrite(f'{filename}_kmeans_threshold.png', image)
# Laplace
img_laplace2 = cv2.Laplacian(image, cv2.CV_8U, ksize=3)
cv2.imwrite(f'{filename}_kmeans_laplace.png', img_laplace2)
# Find contours
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Drawing contours
img_contours = np.zeros(image_orig.shape)
cv2.drawContours(img_contours, contours, -1, (0, 255, 0), 1)
cv2.imwrite(f'{filename}_kmeans_contours.png', img_contours)
# Final piece counting
all_counts = sorted([len(c) for c in contours])
print('Total initial contours:', len(all_counts))
print('All pixel counts:', all_counts)
median = statistics.median(all_counts)
print('Median:', median)
contour_count = 0
for count in all_counts:
for i in range(10):
if count < (i + 0.5) * median:
contour_count += i
break
print('Final contour count:', contour_count)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment