from picamera2 import Picamera2
import cv2
import numpy as np
import time
import RPi.GPIO as GPIO
from sklearn.cluster import DBSCAN
from datetime import datetime
import os
# =========================
# GPIO setup
# =========================
STP_PIN = 12
EN_PIN = 20
SLP_PIN = 16
RST_PIN = 19
DIR_PIN = 21
GPIO.setmode(GPIO.BCM)
GPIO.setup([STP_PIN, EN_PIN, SLP_PIN, RST_PIN, DIR_PIN], GPIO.OUT)
GPIO.output(EN_PIN, GPIO.LOW)
GPIO.output(SLP_PIN, GPIO.HIGH)
GPIO.output(RST_PIN, GPIO.HIGH)
TIMER = 0.0007
STEPS = 380
# =========================
# Camera setup
# =========================
picam2 = Picamera2()
picam2.configure(picam2.create_still_configuration())
picam2.set_controls({
"AfMode": 0,
"LensPosition": 6.5
})
picam2.start()
time.sleep(0.2)
# =========================
# Utility functions
# =========================
def resize_with_aspect_ratio(image, max_w, max_h):
h, w = image.shape[:2]
scale = min(max_w / w, max_h / h)
return cv2.resize(image, (int(w * scale), int(h * scale)))
def capture_and_preprocess():
img = picam2.capture_array()
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
img = resize_with_aspect_ratio(img, 800, 600)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
return img, gray
def apply_square_roi(gray):
h, w = gray.shape
side = int(min(w, h) * 0.9)
cx, cy = w // 2, h // 2
x1 = max(0, cx - side // 2)
y1 = max(0, cy - side // 2)
x2 = min(w, cx + side // 2)
y2 = min(h, cy + side // 2)
mask = np.zeros((h, w), np.uint8)
cv2.rectangle(mask, (x1, y1), (x2, y2), 255, -1)
return cv2.bitwise_and(gray, mask), (x1, y1, x2, y2)
def detect_and_cluster(gray_masked):
params = cv2.SimpleBlobDetector_Params()
params.minThreshold = 5
params.maxThreshold = 200
params.filterByArea = True
params.minArea = 200
params.maxArea = 1200
params.filterByColor = True
params.blobColor = 0
params.filterByCircularity = True
params.minCircularity = 0.75
params.filterByConvexity = True
params.minConvexity = 0.6
params.filterByInertia = True
params.minInertiaRatio = 0.4
detector = cv2.SimpleBlobDetector_create(params)
keypoints = detector.detect(gray_masked)
if not keypoints:
return None, None
points = np.array([kp.pt for kp in keypoints])
db = DBSCAN(eps=60, min_samples=1).fit(points)
clusters = {}
for pt, lbl in zip(points, db.labels_):
clusters.setdefault(lbl, []).append(pt)
return keypoints, clusters
# =========================
# ORDERED VALIDATION
# =========================
def validate_and_order_dice(clusters):
"""
Orders dice by vertical position:
lowest (largest Y) -> first
"""
dice_info = []
for pts in clusters.values():
pts = np.array(pts)
center_y = pts[:, 1].mean()
dice_info.append((center_y, len(pts)))
# Sort: lowest first
dice_info.sort(key=lambda x: x[0], reverse=True)
dice_values = [dots for _, dots in dice_info]
valid = (
len(dice_values) == 3 and
all(d <= 6 for d in dice_values) and
sum(dice_values) <= 18
)
return valid, dice_values
def draw_output(image, keypoints, clusters, roi):
out = image.copy()
dice_info = []
for pts in clusters.values():
pts = np.array(pts)
center = pts.mean(axis=0).astype(int)
dice_info.append((center[1], center, len(pts)))
# Sort lowest -> highest
dice_info.sort(key=lambda x: x[0], reverse=True)
for _, center, dots in dice_info:
cv2.putText(
out,
str(dots),
tuple(center),
cv2.FONT_HERSHEY_SIMPLEX,
1.5,
(0, 255, 0),
3
)
out = cv2.drawKeypoints(
out,
keypoints,
None,
(0, 0, 255),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
x1, y1, x2, y2 = roi
cv2.rectangle(out, (x1, y1), (x2, y2), (255, 0, 0), 2)
return out
def flipp():
timer = 0.0004
stepps = 380
GPIO.output(DIR_PIN, GPIO.LOW)
for _ in range(190):
GPIO.output(STP_PIN, GPIO.HIGH)
GPIO.output(STP_PIN, GPIO.LOW)
time.sleep(timer)
GPIO.output(DIR_PIN, GPIO.HIGH)
for _ in range(stepps):
GPIO.output(STP_PIN, GPIO.HIGH)
GPIO.output(STP_PIN, GPIO.LOW)
time.sleep(timer)
GPIO.output(DIR_PIN, GPIO.LOW)
for _ in range(stepps):
GPIO.output(STP_PIN, GPIO.HIGH)
GPIO.output(STP_PIN, GPIO.LOW)
time.sleep(timer)
GPIO.output(DIR_PIN, GPIO.HIGH)
for _ in range(stepps):
GPIO.output(STP_PIN, GPIO.HIGH)
GPIO.output(STP_PIN, GPIO.LOW)
time.sleep(timer)
GPIO.output(DIR_PIN, GPIO.LOW)
for _ in range(190):
GPIO.output(STP_PIN, GPIO.HIGH)
GPIO.output(STP_PIN, GPIO.LOW)
time.sleep(0.005)
# =========================
# Save dice results
# =========================
def save_dice_results(dice_values, is_valid,
filepath="/home/warlock/results.txt"):
os.makedirs(os.path.dirname(filepath), exist_ok=True)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
status = "VALID" if is_valid else "INVALID"
result_str = " ".join(str(v) for v in dice_values)
with open(filepath, "a") as f:
f.write(f"{timestamp} | {status} | {result_str}\n")
# =========================
# MAIN LOOP
# =========================
try:
while True:
image, gray = capture_and_preprocess()
gray_masked, roi = apply_square_roi(gray)
keypoints, clusters = detect_and_cluster(gray_masked)
if clusters:
is_valid, dice_values = validate_and_order_dice(clusters)
output = draw_output(image, keypoints, clusters, roi)
print("Dice:", dice_values, "VALID" if is_valid else "INVALID")
save_dice_results(dice_values, is_valid)
else:
output = image
cv2.imshow("Dice Detection", output)
key = cv2.waitKey(1000) & 0xFF
if key == ord('q') or key == 27:
break
flipp()
time.sleep(0.5)
finally:
cv2.destroyAllWindows()
picam2.stop()
picam2.close()
GPIO.cleanup()
Comments