Spaces:
Sleeping
Sleeping
File size: 3,089 Bytes
70ebc90 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 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"
)
|