"""Trace the Falcon Eye mark from the reference board PNG into mark.json. Run: python3 trace_mark.py "" (needs opencv-python, numpy, pillow) """ import sys, os import cv2, numpy as np from PIL import Image src = Image.open(sys.argv[1]).convert("RGB") # the reference board PNG X0,Y0,X1,Y1 = 20,40,290,252 im = np.array(src.crop((X0,Y0,X1,Y1))).astype(np.float32) # mask out wordmark text im[190-Y0:, 125-X0:] = 0 K = 6 big = cv2.resize(im, None, fx=K, fy=K, interpolation=cv2.INTER_CUBIC) big = cv2.GaussianBlur(big, (0,0), 2.2) r,g,b = big[...,0],big[...,1],big[...,2] lum = 0.3*r+0.59*g+0.11*b lime = (g>150)&(r>110)&(b<120)&(g-b>80) silver = (lum>70)&~lime & (np.abs(r-b)<60) def clean(m, k=5): m = m.astype(np.uint8)*255 m = cv2.morphologyEx(m, cv2.MORPH_OPEN, np.ones((k,k),np.uint8)) m = cv2.morphologyEx(m, cv2.MORPH_CLOSE, np.ones((k,k),np.uint8)) return m def paths(m, minarea, eps): cs,h = cv2.findContours(m, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) out=[] for i,c in enumerate(cs): if cv2.contourArea(c) < minarea: continue c = cv2.approxPolyDP(c, eps, True)[:,0,:].astype(float)/K n=len(c) if n<3: continue # Catmull-Rom -> cubic bezier d=f"M{c[0][0]:.2f} {c[0][1]:.2f}" for j in range(n): p0,p1,p2,p3 = c[j-1],c[j],c[(j+1)%n],c[(j+2)%n] t=0.5/3 c1=p1+(p2-p0)*t; c2=p2-(p3-p1)*t d+=f"C{c1[0]:.2f} {c1[1]:.2f} {c2[0]:.2f} {c2[1]:.2f} {p2[0]:.2f} {p2[1]:.2f}" out.append(d+"Z") return out sil = paths(clean(silver,7), 800, 3.0) lim = paths(clean(lime,3), 80, 1.5) # --- cleaner eye: solid lime shape (holes filled) + fitted pupil circle --- lm = clean(lime,3) cs,_ = cv2.findContours(lm, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) c = max(cs, key=cv2.contourArea) solid = np.zeros_like(lm); cv2.drawContours(solid,[cv2.convexHull(c)],-1,255,-1) hole = cv2.subtract(solid, lm) hole = cv2.morphologyEx(hole, cv2.MORPH_OPEN, np.ones((5,5),np.uint8)) hc,_ = cv2.findContours(hole, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) hcm = max(hc, key=cv2.contourArea) (px,py),pr = cv2.minEnclosingCircle(hcm) (ex,ey),er = cv2.minEnclosingCircle(c) eye = paths(solid, 80, 2.5) # all geometry bbox allpts = np.vstack([cv2.findContours(clean(silver,7), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0][i] for i in range(len(cv2.findContours(clean(silver,7), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]))])[:,0,:]/K x0,y0 = allpts.min(0); x1,y1 = allpts.max(0) import json json.dump({"silver":sil,"eye":eye,"pupil":[px/K,py/K,pr/K],"eyec":[ex/K,ey/K,er/K],"bbox":[float(x0),float(y0),float(x1),float(y1)]}, open(os.path.join(os.path.dirname(os.path.abspath(__file__)),"mark.json"),"w")) print("pupil",px/K,py/K,pr/K,"eye",ex/K,ey/K,er/K,"bbox",x0,y0,x1,y1)