Spaces:
Running
Running
import cv2 | |
import numpy as np | |
import os | |
import argparse | |
from typing import Union | |
from matplotlib import pyplot as plt | |
class ScalingSquareDetector: | |
def __init__(self, feature_detector="ORB", debug=False): | |
""" | |
Initialize the detector with the desired feature matching algorithm. | |
:param feature_detector: "ORB" or "SIFT" (default is "ORB"). | |
:param debug: If True, saves intermediate images for debugging. | |
""" | |
self.feature_detector = feature_detector | |
self.debug = debug | |
self.detector = self._initialize_detector() | |
def _initialize_detector(self): | |
""" | |
Initialize the chosen feature detector. | |
:return: OpenCV detector object. | |
""" | |
if self.feature_detector.upper() == "SIFT": | |
return cv2.SIFT_create() | |
elif self.feature_detector.upper() == "ORB": | |
return cv2.ORB_create() | |
else: | |
raise ValueError("Invalid feature detector. Choose 'ORB' or 'SIFT'.") | |
def find_scaling_square( | |
self, reference_image_path, target_image, known_size_mm, roi_margin=30 | |
): | |
""" | |
Detect the scaling square in the target image based on the reference image. | |
:param reference_image_path: Path to the reference image of the square. | |
:param target_image_path: Path to the target image containing the square. | |
:param known_size_mm: Physical size of the square in millimeters. | |
:param roi_margin: Margin to expand the ROI around the detected square (in pixels). | |
:return: Scaling factor (mm per pixel). | |
""" | |
contours, _ = cv2.findContours( | |
target_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE | |
) | |
if not contours: | |
raise ValueError("No contours found in the cropped ROI.") | |
# # Select the largest square-like contour | |
largest_square = None | |
largest_square_area = 0 | |
for contour in contours: | |
x_c, y_c, w_c, h_c = cv2.boundingRect(contour) | |
aspect_ratio = w_c / float(h_c) | |
if 0.9 <= aspect_ratio <= 1.1: | |
peri = cv2.arcLength(contour, True) | |
approx = cv2.approxPolyDP(contour, 0.02 * peri, True) | |
if len(approx) == 4: | |
area = cv2.contourArea(contour) | |
if area > largest_square_area: | |
largest_square = contour | |
largest_square_area = area | |
# if largest_square is None: | |
# raise ValueError("No square-like contour found in the ROI.") | |
# Draw the largest contour on the original image | |
target_image_color = cv2.cvtColor(target_image, cv2.COLOR_GRAY2BGR) | |
cv2.drawContours( | |
target_image_color, largest_square, -1, (255, 0, 0), 3 | |
) | |
# if self.debug: | |
cv2.imwrite("largest_contour.jpg", target_image_color) | |
# Calculate the bounding rectangle of the largest contour | |
x, y, w, h = cv2.boundingRect(largest_square) | |
square_width_px = w | |
square_height_px = h | |
# Calculate the scaling factor | |
avg_square_size_px = (square_width_px + square_height_px) / 2 | |
scaling_factor = 0.5 / avg_square_size_px # mm per pixel | |
return scaling_factor #, square_height_px, square_width_px, roi_binary | |
def draw_debug_images(self, output_folder): | |
""" | |
Save debug images if enabled. | |
:param output_folder: Directory to save debug images. | |
""" | |
if self.debug: | |
if not os.path.exists(output_folder): | |
os.makedirs(output_folder) | |
debug_images = ["largest_contour.jpg"] | |
for img_name in debug_images: | |
if os.path.exists(img_name): | |
os.rename(img_name, os.path.join(output_folder, img_name)) | |
def calculate_scaling_factor( | |
reference_image_path, | |
target_image, | |
known_square_size_mm=12.7, | |
feature_detector="ORB", | |
debug=False, | |
roi_margin=30, | |
): | |
# Initialize detector | |
detector = ScalingSquareDetector(feature_detector=feature_detector, debug=debug) | |
# Find scaling square and calculate scaling factor | |
scaling_factor = detector.find_scaling_square( | |
reference_image_path=reference_image_path, | |
target_image=target_image, | |
known_size_mm=known_square_size_mm, | |
roi_margin=roi_margin, | |
) | |
# Save debug images | |
if debug: | |
detector.draw_debug_images("debug_outputs") | |
return scaling_factor | |
# Example usage: | |
if __name__ == "__main__": | |
import os | |
from PIL import Image | |
from ultralytics import YOLO | |
from app import yolo_detect, shrink_bbox | |
from ultralytics.utils.plotting import save_one_box | |
for idx, file in enumerate(os.listdir("./sample_images")): | |
img = np.array(Image.open(os.path.join("./sample_images", file))) | |
img = yolo_detect(img, ['box']) | |
model = YOLO("./last.pt") | |
res = model.predict(img, conf=0.6) | |
box_img = save_one_box(res[0].cpu().boxes.xyxy, im=res[0].orig_img, save=False) | |
# img = shrink_bbox(box_img, 1.20) | |
cv2.imwrite(f"./outputs/{idx}_{file}", box_img) | |
print("File: ",f"./outputs/{idx}_{file}") | |
try: | |
scaling_factor = calculate_scaling_factor( | |
reference_image_path="./Reference_ScalingBox.jpg", | |
target_image=box_img, | |
known_square_size_mm=12.7, | |
feature_detector="ORB", | |
debug=False, | |
roi_margin=90, | |
) | |
# cv2.imwrite(f"./outputs/{idx}_binary_{file}", roi_binary) | |
# Square size in mm | |
# square_size_mm = 12.7 | |
# # Compute the calculated scaling factors and compare | |
# calculated_scaling_factor = square_size_mm / height_px | |
# discrepancy = abs(calculated_scaling_factor - scaling_factor) | |
# import pprint | |
# pprint.pprint({ | |
# "height_px": height_px, | |
# "width_px": width_px, | |
# "given_scaling_factor": scaling_factor, | |
# "calculated_scaling_factor": calculated_scaling_factor, | |
# "discrepancy": discrepancy, | |
# }) | |
print(f"Scaling Factor (mm per pixel): {scaling_factor:.6f}") | |
except Exception as e: | |
from traceback import print_exc | |
print(print_exc()) | |
print(f"Error: {e}") | |