Skip to content

Instantly share code, notes, and snippets.

@qgolsteyn
Last active December 16, 2025 20:02
Show Gist options
  • Select an option

  • Save qgolsteyn/261289d999a8d6288ce8c0b8472e5354 to your computer and use it in GitHub Desktop.

Select an option

Save qgolsteyn/261289d999a8d6288ce8c0b8472e5354 to your computer and use it in GitHub Desktop.
A small Python script that reads dice rolls out loud. See https://golsteyn.com/projects/dice/ for more info!
import cv2
import numpy as np
from sklearn import cluster
params = cv2.SimpleBlobDetector_Params()
params.filterByInertia
params.minInertiaRatio = 0.6
detector = cv2.SimpleBlobDetector_create(params)
def get_blobs(frame):
frame_blurred = cv2.medianBlur(frame, 7)
frame_gray = cv2.cvtColor(frame_blurred, cv2.COLOR_BGR2GRAY)
blobs = detector.detect(frame_gray)
return blobs
def get_dice_from_blobs(blobs):
# Get centroids of all blobs
X = []
for b in blobs:
pos = b.pt
if pos != None:
X.append(pos)
X = np.asarray(X)
if len(X) > 0:
# Important to set min_sample to 0, as a dice may only have one dot
clustering = cluster.DBSCAN(eps=40, min_samples=0).fit(X)
# Find the largest label assigned + 1, that's the number of dice found
num_dice = max(clustering.labels_) + 1
dice = []
# Calculate centroid of each dice, the average between all a dice's dots
for i in range(num_dice):
X_dice = X[clustering.labels_ == i]
centroid_dice = np.mean(X_dice, axis=0)
dice.append([len(X_dice), *centroid_dice])
return dice
else:
return []
def overlay_info(frame, dice, blobs):
# Overlay blobs
for b in blobs:
pos = b.pt
r = b.size / 2
cv2.circle(frame, (int(pos[0]), int(pos[1])),
int(r), (255, 0, 0), 2)
# Overlay dice number
for d in dice:
# Get textsize for text centering
textsize = cv2.getTextSize(
str(d[0]), cv2.FONT_HERSHEY_PLAIN, 3, 2)[0]
cv2.putText(frame, str(d[0]),
(int(d[1] - textsize[0] / 2),
int(d[2] + textsize[1] / 2)),
cv2.FONT_HERSHEY_PLAIN, 3, (0, 255, 0), 2)
# Initialize a video feed
cap = cv2.VideoCapture(0)
while(True):
# Grab the latest image from the video feed
ret, frame = cap.read()
# We'll define these later
blobs = get_blobs(frame)
dice = get_dice_from_blobs(blobs)
out_frame = overlay_info(frame, dice, blobs)
cv2.imshow("frame", frame)
res = cv2.waitKey(1)
# Stop if the user presses "q"
if res & 0xFF == ord('q'):
break
# When everything is done, release the capture
cap.release()
cv2.destroyAllWindows()
@namllihs
Copy link

namllihs commented Jun 6, 2025

Thanks for this, it has pointed me in the right direction for my project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment