Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| import os | |
| def order_points(pts): | |
| """Order contour points as TL, TR, BR, BL consistently.""" | |
| rect = np.zeros((4, 2), dtype="float32") | |
| s = pts.sum(axis=1) | |
| diff = np.diff(pts, axis=1) | |
| rect[0] = pts[np.argmin(s)] # top-left | |
| rect[2] = pts[np.argmax(s)] # bottom-right | |
| rect[1] = pts[np.argmin(diff)] # top-right | |
| rect[3] = pts[np.argmax(diff)] # bottom-left | |
| return rect | |
| def detect_reference(image_path, ref_size_mm=20.0, save_path=None): | |
| """ | |
| Detects a near-square reference object in the image and calculates px/mm. | |
| Args: | |
| image_path (str): Path to the input image. | |
| ref_size_mm (float): Real-world size of reference square side in mm. | |
| save_path (str): Folder to save annotated image. If None, no image saved. | |
| Returns: | |
| tuple: (status, px_per_mm, ref_square_points) | |
| status = 'success' or 'failed' | |
| """ | |
| img = cv2.imread(image_path) | |
| if img is None: | |
| return "failed", None, None | |
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |
| blur = cv2.GaussianBlur(gray, (5, 5), 0) | |
| # Adaptive threshold (better for uneven lighting) | |
| thresh = cv2.adaptiveThreshold( | |
| blur, 255, | |
| cv2.ADAPTIVE_THRESH_GAUSSIAN_C, | |
| cv2.THRESH_BINARY_INV, | |
| 51, 10 | |
| ) | |
| contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| ref_square = None | |
| px_per_mm = None | |
| for cnt in contours: | |
| area = cv2.contourArea(cnt) | |
| if area < 500: # skip noise | |
| continue | |
| epsilon = 0.02 * cv2.arcLength(cnt, True) | |
| approx = cv2.approxPolyDP(cnt, epsilon, True) | |
| if len(approx) == 4: # quadrilateral | |
| box = approx.reshape(-1, 2) | |
| (tl, tr, br, bl) = order_points(box) | |
| width = np.linalg.norm(tr - tl) | |
| height = np.linalg.norm(bl - tl) | |
| aspect_ratio = min(width, height) / max(width, height) | |
| if aspect_ratio > 0.9: # near-square | |
| ref_square = np.array([tl, tr, br, bl], dtype=np.int32) | |
| side_px = (width + height) / 2.0 | |
| px_per_mm = side_px / ref_size_mm | |
| break | |
| if ref_square is not None: | |
| cv2.drawContours(img, [ref_square], -1, (0, 255, 0), 3) | |
| cx = int(np.mean(ref_square[:, 0])) | |
| cy = int(np.mean(ref_square[:, 1])) | |
| cv2.putText(img, f"Ref {ref_size_mm}mm", (cx - 60, cy - 10), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) | |
| if save_path is not None: | |
| os.makedirs(save_path, exist_ok=True) | |
| out_file = os.path.join(save_path, os.path.basename(image_path)) | |
| cv2.imwrite(out_file, img) | |
| return "success", px_per_mm, ref_square | |
| else: | |
| return "failed", None, None | |
| # ---------------- Test run ---------------- | |
| if __name__ == "__main__": | |
| status, px_per_mm, pts = detect_reference( | |
| r"Results\Detection\capture_1757686922_0_annotated.jpg", | |
| ref_size_mm=20.0, | |
| save_path=r"Results\Reference" | |
| ) | |