diff --git a/Stuff/Untitled-1.txt b/Stuff/Untitled-1.txt new file mode 100644 index 0000000..57816ac --- /dev/null +++ b/Stuff/Untitled-1.txt @@ -0,0 +1,196 @@ +import cv2 +import mediapipe as mp +import numpy as np + +# ----- Helper Functions ----- +def compute_distance(p1, p2): + return np.hypot(p2[0] - p1[0], p2[1] - p1[1]) + +# ----- Configuration ----- +CAMERA_INDICES = [0] # List of camera device indices +FRAME_WIDTH = 1280 +FRAME_HEIGHT = 720 +PIXELS_PER_INCH = 20 # will be set by calibration +PINCH_THRESHOLD = 40 # px to start touch +RELEASE_THRESHOLD = 60 # px to end touch +CIRCLE_TOUCH_THRESHOLD = 20 # px tolerance for circle touch + +# ----- Initialize Hand Detector ----- +mp_hands = mp.solutions.hands +mp_draw = mp.solutions.drawing_utils +hands = mp_hands.Hands( + static_image_mode=False, + max_num_hands=1, + min_detection_confidence=0.7, + min_tracking_confidence=0.5 +) + +# ----- Global State ----- +measuring = False # measurement in progress +start_pt = None # measurement start point +calibrating = False # pinch-based calibration flag +cal_start = None # pinch calibration start point +object_calibrating = False # object calibration flag +cal_circle = None # reference circle (x,y,r) +fingertip_idx_global = None # last detected fingertip position + +# ----- Per-camera Processing ----- +def process_frame(frame): + global measuring, start_pt, calibrating, cal_start, object_calibrating, cal_circle, PIXELS_PER_INCH, fingertip_idx_global + frame_out = cv2.flip(frame, 1) + h, w, _ = frame_out.shape + + # Hand detection + rgb = cv2.cvtColor(frame_out, cv2.COLOR_BGR2RGB) + rgb.flags.writeable = False + results = hands.process(rgb) + rgb.flags.writeable = True + frame_vis = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) + + fingertip_idx = None + fingertip_mid = None + + if results.multi_hand_landmarks: + hand = results.multi_hand_landmarks[0] + mp_draw.draw_landmarks(frame_vis, hand, mp_hands.HAND_CONNECTIONS) + # get index and middle finger tips and PIP joints + idx_tip = hand.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP] + idx_pip = hand.landmark[mp_hands.HandLandmark.INDEX_FINGER_PIP] + mid_tip = hand.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP] + mid_pip = hand.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_PIP] + ix, iy = int(idx_tip.x * w), int(idx_tip.y * h) + mx, my = int(mid_tip.x * w), int(mid_tip.y * h) + fingertip_idx = (ix, iy) + fingertip_mid = (mx, my) + fingertip_idx_global = fingertip_idx + # draw fingertips + cv2.circle(frame_vis, fingertip_idx, 8, (0,255,0), -1) + cv2.circle(frame_vis, fingertip_mid, 8, (0,255,0), -1) + # check extension + index_ext = idx_tip.y < idx_pip.y + middle_ext = mid_tip.y < mid_pip.y + # if measurement in progress but fingers no longer both extended, stop measuring + if measuring and not (index_ext and middle_ext): + measuring = False + # pinch distance + pinch = compute_distance(fingertip_idx, fingertip_mid) + cv2.putText(frame_vis, f"Pinch: {int(pinch)} px", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0),2) + + # pinch calibration + if calibrating: + if pinch < PINCH_THRESHOLD and cal_start is None: + cal_start = fingertip_idx + print("Pinch calibration start set") + elif pinch > RELEASE_THRESHOLD and cal_start is not None: + cal_end = fingertip_idx + px = compute_distance(cal_start, cal_end) + inches = float(input("Enter actual distance between points (inches): ")) + PIXELS_PER_INCH = px / inches + print(f"Calibrated: {PIXELS_PER_INCH:.2f} px/inch") + calibrating = False + cal_start = None + # measurement gesture + elif not object_calibrating and index_ext and middle_ext: + if pinch < PINCH_THRESHOLD and not measuring: + measuring = True + start_pt = fingertip_idx + elif pinch > RELEASE_THRESHOLD and measuring: + measuring = False + + # object calibration + if object_calibrating and fingertip_idx is not None: + hsv = cv2.cvtColor(frame_vis, cv2.COLOR_BGR2HSV) + mask = cv2.inRange(hsv, np.array([10,100,100]), np.array([25,255,255])) + masked = cv2.bitwise_and(frame_vis, frame_vis, mask=mask) + gray = cv2.cvtColor(masked, cv2.COLOR_BGR2GRAY) + gray = cv2.medianBlur(gray,5) + circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 100, + param1=50, param2=30, minRadius=10, maxRadius=300) + if circles is not None: + circles = np.round(circles[0]).astype(int) + touched = [(x,y,r) for x,y,r in circles + if abs(compute_distance((x,y), fingertip_idx)-r) < CIRCLE_TOUCH_THRESHOLD] + if touched: + touched.sort(key=lambda c: abs(compute_distance((c[0],c[1]), fingertip_idx)-c[2])) + x,y,r = touched[0] + cal_circle = (x,y,r) + PIXELS_PER_INCH = 2 * r + print(f"Circle calib: {PIXELS_PER_INCH:.2f} px/inch") + object_calibrating = False + + # permanent reference circle + if cal_circle: + cx,cy,cr = cal_circle + cv2.circle(frame_vis,(cx,cy),cr,(0,0,255),2) + cv2.drawMarker(frame_vis,(cx,cy),(0,0,255),cv2.MARKER_TILTED_CROSS,15,1) + cv2.putText(frame_vis,f"Ref r={cr} px",(cx-cr,cy+cr+20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,255),1) + + return frame_vis + +# ----- Main ----- +def main(): + caps = [] + for idx in CAMERA_INDICES: + cap = cv2.VideoCapture(idx) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT) + caps.append(cap) + if not all(cap.isOpened() for cap in caps): + print("Error: could not open all cameras") + return + + print("Press 'c' for pinch calib, 'o' for circle calib, 'q' to quit.") + while True: + frames = [cap.read()[1] for cap in caps] + frame = next((f for f in frames if f is not None), None) + if frame is None: + break + # process + full_view = process_frame(frame) + # create proj output + proj = np.zeros_like(full_view) + if cal_circle: + cx,cy,cr = cal_circle + cv2.circle(proj,(cx,cy),cr,(0,0,255),2) + if measuring and start_pt and fingertip_idx_global: + cv2.line(proj, start_pt, fingertip_idx_global, (255,0,0),2) + px = compute_distance(start_pt, fingertip_idx_global) + inch = px/PIXELS_PER_INCH; cm = inch*2.54 + mid = ((start_pt[0]+fingertip_idx_global[0])//2,(start_pt[1]+fingertip_idx_global[1])//2) + cv2.putText(proj,f"{inch:.2f}in/{cm:.1f}cm",(mid[0]+10,mid[1]-10), + cv2.FONT_HERSHEY_SIMPLEX,0.7,(255,0,0),2) + # overlay proj onto debug + debug = full_view.copy() + # overlay ref circle + if cal_circle: + cx,cy,cr = cal_circle + cv2.circle(debug,(cx,cy),cr,(0,0,255),2) + # overlay measurement + if measuring and start_pt and fingertip_idx_global: + cv2.line(debug, start_pt, fingertip_idx_global, (255,0,0),2) + cv2.putText(debug,f"{inch:.2f}in/{cm:.1f}cm",(mid[0]+10,mid[1]-10), + cv2.FONT_HERSHEY_SIMPLEX,0.7,(255,0,0),2) + + # show windows + cv2.imshow('Hand Measure', debug) + cv2.imshow('Projector Output', proj) + + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): + break + elif key == ord('c'): + global calibrating + calibrating = True + cal_start = None + print("Entered pinch calibration mode.") + elif key == ord('o'): + global object_calibrating + object_calibrating = True + print("Entered circle calibration mode.") + + for cap in caps: + cap.release() + cv2.destroyAllWindows() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Stuff/import cv2.py b/Stuff/import cv2.py new file mode 100644 index 0000000..f0e9f95 --- /dev/null +++ b/Stuff/import cv2.py @@ -0,0 +1,194 @@ + +import cv2 +import mediapipe as mp +import numpy as np + +# ----- Helper Functions ----- +def compute_distance(p1, p2): + """Compute Euclidean distance between two points p1 and p2""" + return np.hypot(p2[0] - p1[0], p2[1] - p1[1]) + +# ----- Configuration ----- +CAMERA_INDEX = 0 # Change if multiple cameras +FRAME_WIDTH = 1280 +FRAME_HEIGHT = 720 +# Initial calibration: approximate pixels per inch +PIXELS_PER_INCH = 20 +# Gesture thresholds +PINCH_THRESHOLD = 40 # px distance index-middle to start action +RELEASE_THRESHOLD = 60 # px distance to end action +# Circle touch threshold for object calibration +CIRCLE_TOUCH_THRESHOLD = 20 # px tolerance to detect finger on circle + +# ----- Initialize Hand Detector ----- +mp_hands = mp.solutions.hands +mp_draw = mp.solutions.drawing_utils +hands = mp_hands.Hands( + static_image_mode=False, + max_num_hands=1, + min_detection_confidence=0.7, + min_tracking_confidence=0.5 +) + +# ----- State Variables ----- +measuring = False # Flag for measurement gesture +start_pt = None +calibrating = False # Flag for pinch-based calibration mode +cal_start = None +object_calibrating = False # Flag for object-based calibration mode +cal_circle = None # Stores calibrated circle (x, y, r) + +# ----- Main Loop ----- +def main(): + global measuring, start_pt, calibrating, cal_start, object_calibrating, PIXELS_PER_INCH + + cap = cv2.VideoCapture(CAMERA_INDEX) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT) + + if not cap.isOpened(): + print(f"Error: cannot open camera {CAMERA_INDEX}") + return + + print("Press 'c' for pinch calibration, 'o' for object circle calibration, 'q' to quit.") + + while True: + ret, frame = cap.read() + if not ret: + break + + frame = cv2.flip(frame, 1) + h, w, _ = frame.shape + + # Hand detection + img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + img_rgb.flags.writeable = False + results = hands.process(img_rgb) + img_rgb.flags.writeable = True + frame = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR) + + fingertip_idx = None + fingertip_mid = None + index_extended = False + middle_extended = False + + if results.multi_hand_landmarks: + hand = results.multi_hand_landmarks[0] + mp_draw.draw_landmarks(frame, hand, mp_hands.HAND_CONNECTIONS) + # get index and middle finger tips and PIP to check extension + idx_tip = hand.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP] + idx_pip = hand.landmark[mp_hands.HandLandmark.INDEX_FINGER_PIP] + mid_tip = hand.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP] + mid_pip = hand.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_PIP] + ix, iy = int(idx_tip.x * w), int(idx_tip.y * h) + mx, my = int(mid_tip.x * w), int(mid_tip.y * h) + fingertip_idx = (ix, iy) + fingertip_mid = (mx, my) + # draw fingertips + cv2.circle(frame, fingertip_idx, 8, (0,255,0), -1) + cv2.circle(frame, fingertip_mid, 8, (0,255,0), -1) + # determine if fingers are extended (tip above PIP) + index_extended = idx_tip.y < idx_pip.y + middle_extended = mid_tip.y < mid_pip.y + # pinch distance between index and middle + pinch_dist = compute_distance(fingertip_idx, fingertip_mid) + cv2.putText(frame, f"Pinch: {int(pinch_dist)}px", (10,30), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2) + + # Pinch-based calibration + if calibrating: + if pinch_dist < PINCH_THRESHOLD and cal_start is None: + cal_start = fingertip_idx + print("Pinch calibration start point set.") + elif pinch_dist > RELEASE_THRESHOLD and cal_start is not None: + cal_end = fingertip_idx + px_dist = compute_distance(cal_start, cal_end) + inches = float(input("Enter actual distance between points in inches: ")) + PIXELS_PER_INCH = px_dist / inches + print(f"Pinch calibration done: {PIXELS_PER_INCH:.2f} pixels/inch") + calibrating = False + cal_start = None + + # Measurement gesture (only when not calibrating) + elif not object_calibrating and index_extended and middle_extended: + if pinch_dist < PINCH_THRESHOLD and not measuring: + measuring = True + start_pt = fingertip_idx + elif pinch_dist > RELEASE_THRESHOLD and measuring: + measuring = False + + # Object-based calibration + if object_calibrating: + # Mask for orange color to find printed reference circle + hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) + # HSV range for orange (tune as needed) + lower_orange = np.array([10, 100, 100]) + upper_orange = np.array([25, 255, 255]) + color_mask = cv2.inRange(hsv, lower_orange, upper_orange) + masked_frame = cv2.bitwise_and(frame, frame, mask=color_mask) + # Convert masked area to grayscale for Hough + gray = cv2.cvtColor(masked_frame, cv2.COLOR_BGR2GRAY) + gray = cv2.medianBlur(gray, 5) + circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1.2, minDist=100, + param1=50, param2=30, minRadius=10, maxRadius=300) + if circles is not None and fingertip_idx is not None: + circles = np.round(circles[0, :]).astype(int) + # Filter circles by proximity of fingertip to circumference + touched = [] + for x, y, r in circles: + dist_c = compute_distance((x, y), fingertip_idx) + if abs(dist_c - r) < CIRCLE_TOUCH_THRESHOLD: + touched.append((x, y, r)) + if touched: + # choose circle closest to exact touch point + touched.sort(key=lambda c: abs(compute_distance((c[0], c[1]), fingertip_idx) - c[2])) + x, y, r = touched[0] + # store calibrated circle permanently + cal_circle = (x, y, r) + # draw selected calibration circle + cv2.circle(frame, (x, y), r, (0, 0, 255), 3) + cv2.drawMarker(frame, (x, y), (0, 0, 255), markerType=cv2.MARKER_CROSS, markerSize=20, thickness=2) + cv2.line(frame, (x - r, y), (x + r, y), (0, 0, 255), 2) + cv2.putText(frame, f"Cal Circle r={r}px", (x - r, y - r - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2) + # compute pixels per inch from diameter + PIXELS_PER_INCH = (2 * r) / 1.0 + print(f"Object calibration done: {PIXELS_PER_INCH:.2f} pixels/inch") + object_calibrating = False + + # Overlay mode text + if calibrating: + cv2.putText(frame, "PINCH CALIBRATING...", (10,60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2) + if object_calibrating: + cv2.putText(frame, 'PLACE 1" CIRCLE & POINT AT IT', (10,90), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2) + + # Draw measurement line and values + if measuring and start_pt and fingertip_idx: + cv2.line(frame, start_pt, fingertip_idx, (255,0,0), 2) + px = compute_distance(start_pt, fingertip_idx) + inch = px / PIXELS_PER_INCH + cm = inch * 2.54 + midpt = ((start_pt[0] + fingertip_idx[0])//2, + (start_pt[1] + fingertip_idx[1])//2) + cv2.putText(frame, f"{inch:.2f} in / {cm:.1f} cm", (midpt[0]+10, midpt[1]-10), + cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,0,0), 2) + + # Display + cv2.imshow('Hand Measure', frame) + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): + break + elif key == ord('c'): + calibrating = True + cal_start = None + print("Entered pinch calibration mode.") + elif key == ord('o'): + object_calibrating = True + print("Entered object calibration mode. Present a 1-inch circle & point at it.") + + cap.release() + cv2.destroyAllWindows() + +if __name__ == '__main__': + main() diff --git a/Table.blend b/Table.blend new file mode 100644 index 0000000..a73139e Binary files /dev/null and b/Table.blend differ diff --git a/TableIdk.py b/TableIdk.py index e69de29..43ff530 100644 --- a/TableIdk.py +++ b/TableIdk.py @@ -0,0 +1,279 @@ +import cv2 +import mediapipe as mp +import numpy as np + +# ----- Helper Functions ----- +def compute_distance(p1, p2): + return np.hypot(p2[0] - p1[0], p2[1] - p1[1]) + +# ----- Configuration ----- +CAMERA_INDICES = [0] # List of camera device indices +FRAME_WIDTH = 1280 +FRAME_HEIGHT = 720 +PIXELS_PER_INCH = 38 # will be set by calibration +PINCH_THRESHOLD = 40 # px to start touch +RELEASE_THRESHOLD = 60 # px to end touch +CIRCLE_TOUCH_THRESHOLD = 20 # px tolerance for circle touch + +# HSV range for shape color (tune for your arrow: now tailored for orange) +LOWER_SHAPE = np.array([10, 100, 100]) # hue from 10° (orange) to +UPPER_SHAPE = np.array([30, 255, 255]) # hue up to 30°, full sat/val range + +# dynamic list of registered arrows +# each entry will be {'lower': np.array, 'upper': np.array, 'tip': (x,y) or None} +shape_ranges = [] + +# ----- Initialize Hand Detector ----- +mp_hands = mp.solutions.hands +mp_draw = mp.solutions.drawing_utils +hands = mp_hands.Hands( + static_image_mode=False, + max_num_hands=1, + min_detection_confidence=0.7, + min_tracking_confidence=0.5 +) + +# ----- Global State ----- +measuring = False # measurement in progress +start_pt = None # measurement start point +calibrating = False # pinch-based calibration flag +cal_start = None # pinch calibration start point +object_calibrating = False # object calibration flag +cal_circle = None # reference circle (x,y,r) +fingertip_idx_global = None # last detected fingertip position +arrow_tip = None # detected arrow tip position + +# ----- Per-camera Processing ----- +def process_frame(frame): + global measuring, start_pt, calibrating, cal_start + global object_calibrating, cal_circle, PIXELS_PER_INCH + global fingertip_idx_global, arrow_tip + + frame_out = cv2.flip(frame, 1) + h, w, _ = frame_out.shape + + # Hand detection + rgb = cv2.cvtColor(frame_out, cv2.COLOR_BGR2RGB) + rgb.flags.writeable = False + results = hands.process(rgb) + rgb.flags.writeable = True + frame_vis = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) + + fingertip_idx = None + fingertip_mid = None + + if results.multi_hand_landmarks: + hand = results.multi_hand_landmarks[0] + mp_draw.draw_landmarks(frame_vis, hand, mp_hands.HAND_CONNECTIONS) + # get index and middle finger tips and PIP joints + idx_tip = hand.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP] + idx_pip = hand.landmark[mp_hands.HandLandmark.INDEX_FINGER_PIP] + mid_tip = hand.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP] + mid_pip = hand.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_PIP] + ix, iy = int(idx_tip.x * w), int(idx_tip.y * h) + mx, my = int(mid_tip.x * w), int(mid_tip.y * h) + fingertip_idx = (ix, iy) + fingertip_mid = (mx, my) + fingertip_idx_global = fingertip_idx + # draw fingertips + cv2.circle(frame_vis, fingertip_idx, 8, (0,255,0), -1) + cv2.circle(frame_vis, fingertip_mid, 8, (0,255,0), -1) + # check extension + index_ext = idx_tip.y < idx_pip.y + middle_ext = mid_tip.y < mid_pip.y + # if measurement in progress but fingers no longer both extended, stop measuring + if measuring and not (index_ext and middle_ext): + measuring = False + # pinch distance + pinch = compute_distance(fingertip_idx, fingertip_mid) + cv2.putText(frame_vis, f"Pinch: {int(pinch)} px", (10,30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0),2) + + # pinch calibration + if calibrating: + if pinch < PINCH_THRESHOLD and cal_start is None: + cal_start = fingertip_idx + print("Pinch calibration start set") + elif pinch > RELEASE_THRESHOLD and cal_start is not None: + cal_end = fingertip_idx + px = compute_distance(cal_start, cal_end) + inches = float(input("Enter actual distance between points (inches): ")) + PIXELS_PER_INCH = px / inches + print(f"Calibrated: {PIXELS_PER_INCH:.2f} px/inch") + calibrating = False + cal_start = None + # measurement gesture + elif not object_calibrating and index_ext and middle_ext: + if pinch < PINCH_THRESHOLD and not measuring: + measuring = True + start_pt = fingertip_idx + elif pinch > RELEASE_THRESHOLD and measuring: + measuring = False + + + + # object calibration + if object_calibrating and fingertip_idx is not None: + hsv = cv2.cvtColor(frame_vis, cv2.COLOR_BGR2HSV) + mask = cv2.inRange(hsv, np.array([10,100,100]), np.array([25,255,255])) + masked = cv2.bitwise_and(frame_vis, frame_vis, mask=mask) + gray = cv2.cvtColor(masked, cv2.COLOR_BGR2GRAY) + gray = cv2.medianBlur(gray,5) + circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 100, + param1=50, param2=30, minRadius=10, maxRadius=300) + if circles is not None: + circles = np.round(circles[0]).astype(int) + touched = [(x,y,r) for x,y,r in circles + if abs(compute_distance((x,y), fingertip_idx)-r) < CIRCLE_TOUCH_THRESHOLD] + if touched: + touched.sort(key=lambda c: abs(compute_distance((c[0],c[1]), fingertip_idx)-c[2])) + x,y,r = touched[0] + cal_circle = (x,y,r) + PIXELS_PER_INCH = 2 * r + print(f"Circle calib: {PIXELS_PER_INCH:.2f} px/inch") + object_calibrating = False + + # permanent reference circle + if cal_circle: + cx,cy,cr = cal_circle + cv2.circle(frame_vis,(cx,cy),cr,(0,0,255),2) + cv2.drawMarker(frame_vis,(cx,cy),(0,0,255),cv2.MARKER_TILTED_CROSS,15,1) + cv2.putText(frame_vis,f"Ref r={cr} px",(cx-cr,cy+cr+20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,255),1) + + # ----- Arrow Shape Detection & HUD Placement ----- + # 1) Build a clean orange mask + hsv = cv2.cvtColor(frame_vis, cv2.COLOR_BGR2HSV) + mask = cv2.inRange(hsv, LOWER_SHAPE, UPPER_SHAPE) + kern = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) + mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kern) + mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kern) + + # 2) Find and filter contours + contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + for cnt in contours: + area = cv2.contourArea(cnt) + if area < 1000: + continue + + # Approximate to polygon and require exactly 5 corners + peri = cv2.arcLength(cnt, True) + approx = cv2.approxPolyDP(cnt, 0.02 * peri, True) + if len(approx) != 5: + continue + + pts = approx.reshape(-1,2) + centroid = np.mean(pts, axis=0) + + # Find the arrow tip as the corner farthest from centroid + dists = [np.linalg.norm(pt - centroid) for pt in pts] + tip_pt = pts[int(np.argmax(dists))] + raw_tip = (int(tip_pt[0]), int(tip_pt[1])) + + # 3) Smooth the tip over time + alpha = 0.2 + if arrow_tip is None: + arrow_tip = raw_tip + else: + arrow_tip = ( + int(alpha * raw_tip[0] + (1-alpha) * arrow_tip[0]), + int(alpha * raw_tip[1] + (1-alpha) * arrow_tip[1]) + ) + + # Draw the arrow & tip + cv2.drawContours(frame_vis, [pts], -1, (0,255,255), 2) + cv2.circle(frame_vis, arrow_tip, 8, (0,255,255), -1) + cv2.putText(frame_vis, "Arrow Tip", arrow_tip, + cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,255), 2) + + # Place the HUD box in the arrow’s pointing direction + dir_vec = tip_pt - centroid + norm = np.linalg.norm(dir_vec) + if norm>0: + dir_unit = dir_vec / norm + offset = 50 + hud_center = (int(arrow_tip[0] + dir_unit[0]*offset), + int(arrow_tip[1] + dir_unit[1]*offset)) + + # Build and rotate a 120×60 HUD rectangle + w2, h2 = 60, 30 + theta = np.arctan2(dir_unit[1], dir_unit[0]) + R = np.array([[ np.cos(theta), -np.sin(theta)], + [ np.sin(theta), np.cos(theta)]]) + corners = np.array([[-w2,-h2], [w2,-h2], [w2,h2], [-w2,h2]]) + hud_pts = (corners @ R.T) + np.array(hud_center) + hud_pts = hud_pts.astype(int) + + cv2.drawContours(frame_vis, [hud_pts], -1, (255,0,255), 2) + cv2.putText(frame_vis, "HUD", hud_center, + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,0,255), 2) + + + + return frame_vis + +# ----- Main ----- +def main(): + caps = [] + for idx in CAMERA_INDICES: + cap = cv2.VideoCapture(idx) + cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT) + caps.append(cap) + if not all(cap.isOpened() for cap in caps): + print("Error: could not open all cameras") + return + + print("Press 'c' for pinch calib, 'o' for circle calib, 'q' to quit.") + while True: + frames = [cap.read()[1] for cap in caps] + frame = next((f for f in frames if f is not None), None) + if frame is None: + break + # process + full_view = process_frame(frame) + # create proj output + proj = np.zeros_like(full_view) + if cal_circle: + cx,cy,cr = cal_circle + cv2.circle(proj,(cx,cy),cr,(0,0,255),2) + if measuring and start_pt and fingertip_idx_global: + cv2.line(proj, start_pt, fingertip_idx_global, (255,0,0),2) + px = compute_distance(start_pt, fingertip_idx_global) + inch = px/PIXELS_PER_INCH; cm = inch*2.54 + mid = ((start_pt[0]+fingertip_idx_global[0])//2,(start_pt[1]+fingertip_idx_global[1])//2) + cv2.putText(proj,f"{inch:.2f}in/{cm:.1f}cm",(mid[0]+10,mid[1]-10), + cv2.FONT_HERSHEY_SIMPLEX,0.7,(255,0,0),2) + # overlay proj onto debug + debug = full_view.copy() + # overlay ref circle + if cal_circle: + cx,cy,cr = cal_circle + cv2.circle(debug,(cx,cy),cr,(0,0,255),2) + # overlay measurement + if measuring and start_pt and fingertip_idx_global: + cv2.line(debug, start_pt, fingertip_idx_global, (255,0,0),2) + cv2.putText(debug,f"{inch:.2f}in/{cm:.1f}cm",(mid[0]+10,mid[1]-10), + cv2.FONT_HERSHEY_SIMPLEX,0.7,(255,0,0),2) + + # show windows + cv2.imshow('Hand Measure', debug) + cv2.imshow('Projector Output', proj) + + key = cv2.waitKey(1) & 0xFF + if key == ord('q'): + break + elif key == ord('c'): + global calibrating + calibrating = True + cal_start = None + print("Entered pinch calibration mode.") + elif key == ord('o'): + global object_calibrating + object_calibrating = True + print("Entered circle calibration mode.") + + for cap in caps: + cap.release() + cv2.destroyAllWindows() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Table_Marker.blend b/Table_Marker.blend new file mode 100644 index 0000000..33f74c7 Binary files /dev/null and b/Table_Marker.blend differ diff --git a/workspace.code-workspace b/workspace.code-workspace new file mode 100644 index 0000000..362d7c2 --- /dev/null +++ b/workspace.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file diff --git a/yolo11n.pt b/yolo11n.pt new file mode 100644 index 0000000..45b273b Binary files /dev/null and b/yolo11n.pt differ