As you may know, I intend to extend my login software to include optical recognition of my flexNExT’s blinkies - as in, it identifies the NFC chip’s UID of course, but it doesn’t grant access unless it also recognizes the triangular pattern of the blinkies from an overhead camera exactly at the same time the reader is activated.
So I’ve been playing with my webcam at home to get the ball rolling and code some working code. If you’re interested in playing with this also, here’s the Python script, and what it produces on the screen:
#!/usr/bin/python3
"""DT flexNExT implant's blinkies optical detector
"""
import cv2
import numpy as np
import math
# flexNExT blinkies detection parameters
min_blinky_radius = 5 #pixels
max_blinky_radius = 30 #pixels
min_blinkies_distance = 20 #pixels
max_blinkies_distance = 100 #pixels
blinkies_size_diff_tolerance = 50 #%
blinkies_equilateral_triangle_tolerance = 20 #%
blur_radius = 5
hough_gradient_dp = 1.0
hough_gradient_param1 = 50
hough_gradient_param2 = 25
# Overlay options
overlay_flexnext_outline = False
overlay_blinky_outlines = True
overlay_triangle = True
# Various convenience variables
red = (0, 0, 255)
bstmin = (100 - blinkies_size_diff_tolerance) / 100
bstmax = (100 + blinkies_size_diff_tolerance) / 100
betmin = (100 - blinkies_equilateral_triangle_tolerance) / 100
betmax = (100 + blinkies_equilateral_triangle_tolerance) / 100
# Open the default video capture device
cap = cv2.VideoCapture(0)
while True:
# Grab an image
image = cap.read()[1]
# Find circles in the image
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (blur_radius, blur_radius), 0)
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT,
hough_gradient_dp, min_blinkies_distance,
minRadius = min_blinky_radius, maxRadius = max_blinky_radius,
param1 = hough_gradient_param1, param2 = hough_gradient_param2)
if circles is not None:
# Convert the coordinates and radii of the circles to integers
circles = np.round(circles[0, :]).astype("int")
# Loop over possible first vertices of the blinkies' triangle
for i1, (x1, y1, r1) in enumerate(circles):
# If that circle is already used, skip it
if not r1:
continue
# Loop over possible second vertices of the blinkies' triangle
for i2, (x2, y2, r2) in enumerate(circles):
# If that circle is already used or identical to the first circle,
# skip it
if not r2 or i2 == i1:
continue
# Distance between the first two possible vertices
v1v2 = math.sqrt((x2-x1)**2 + (y2-y1)**2)
# If that circle is too far or too dissimilar to the first one, skip it
if v1v2 > max_blinkies_distance or not (r1 * bstmin < r2 < r1 * bstmax):
continue
# Loop over possible third vertices of the blinkies' triangle
for i3, (x3, y3, r3) in enumerate(circles):
# If that circle is already used, or identical to the first two,
# skip it
if not r3 or i3 == i1 or i3 == i2:
continue
# Distances between the third possible vertex and the two others
v1v3 = math.sqrt((x3-x1)**2 + (y3-y1)**2)
v2v3 = math.sqrt((x3-x2)**2 + (y3-y2)**2)
# If that circle is too far or too dissimilar to the first two,
# or the distances between the three vertices are too far apart
# (i.e. the triangle isn't "equilateral enough"), skip it
if v1v3 > max_blinkies_distance or v2v3 > max_blinkies_distance or \
not(r1 * bstmin < r3 < r1 * bstmax) or \
not(r2 * bstmin < r3 < r2 * bstmax) or \
not(v2v3 * betmin < v1v2 < v2v3 * betmax) or \
not(v1v3 * betmin < v1v2 < v1v3 * betmax) or \
not(v2v3 * betmin < v1v3 < v2v3 * betmax):
continue
# We found a flexNExT: mark the three circles as used
circles[i1][2] = circles[i2][2] = circles[i3][2] = 0
# Draw a circle around each blinky
if overlay_blinky_outlines:
rb = int((r1 + r2 + r3) / 3)
cv2.circle(image, (x1, y1), rb, red, 2)
cv2.circle(image, (x2, y2), rb, red, 2)
cv2.circle(image, (x3, y3), rb, red, 2)
# Draw a circle around the flexNExT
if overlay_flexnext_outline:
xfn = int((x1 + x2 + x3) / 3)
yfn = int((y1 + y2 + y3) / 3)
rfn = int((v1v2 + v2v3 + v1v3) / 3 * 1.1)
cv2.circle(image, (xfn, yfn), rfn, red, 2)
# Draw a triangle inside the three vertices
if overlay_triangle:
cv2.line(image, (x1, y1), (x2, y2), red, 2)
cv2.line(image, (x2, y2), (x3, y3), red, 2)
cv2.line(image, (x3, y3), (x1, y1), red, 2)
# Show the image in a window
cv2.imshow("flexNExT blinkies detector", image)
# Press Q to quit
if cv2.waitKey(1) == ord("q"):
break
# Release the video capture device
cap.release()
# Destroy the window
cv2.destroyAllWindows()
You’ll need to install the Python OpenCV library. Other than that, it runs without any other library. Tested on Linux, but it ought to run on Windows also.