Contour_Detection_Paper / scalingtestupdated.py
mlbench123's picture
Update scalingtestupdated.py
eb2ee96 verified
import cv2
import numpy as np
import os
import argparse
from typing import Union, Tuple
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, target_image, known_size_mm, roi_margin=30
):
"""
Detect the scaling square in the target image based on the reference image.
:param target_image: Binary 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 target image.")
# Select the largest contour (assuming it's the reference object)
print(f"No of contours: {len(contours)}")
largest_contour = max(contours, key=cv2.contourArea)
# Draw the largest contour on the original image for debugging
target_image_color = cv2.cvtColor(target_image, cv2.COLOR_GRAY2BGR)
cv2.drawContours(
target_image_color, [largest_contour], -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_contour)
square_width_px = w
square_height_px = h
print(f"Reference object size: {known_size_mm} mm")
print(f"Detected width: {square_width_px} px")
print(f"Detected height: {square_height_px} px")
# Calculate the scaling factor using average of width and height
avg_square_size_px = (square_width_px + square_height_px) / 2
print(f"Average square size: {avg_square_size_px} px")
scaling_factor = known_size_mm / avg_square_size_px # mm per pixel
print(f"Calculated scaling factor: {scaling_factor:.6f} mm per pixel")
return scaling_factor
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(
target_image,
reference_obj_size_mm,
feature_detector="ORB",
debug=False,
roi_margin=30,
) -> float:
"""
Calculate scaling factor from reference object in image.
:param target_image: Input image (numpy array)
:param reference_obj_size_mm: Known size of reference object in millimeters
:param feature_detector: Feature detector to use ("ORB" or "SIFT")
:param debug: Enable debug output
:param roi_margin: ROI margin in pixels
:return: Scaling factor in mm per pixel
"""
# Initialize detector
detector = ScalingSquareDetector(feature_detector=feature_detector, debug=debug)
# Find scaling square and calculate scaling factor
scaling_factor = detector.find_scaling_square(
target_image=target_image,
known_size_mm=reference_obj_size_mm,
roi_margin=roi_margin,
)
# Save debug images
if debug:
detector.draw_debug_images("debug_outputs")
return scaling_factor
def convert_units(value: float, from_unit: str, to_unit: str) -> float:
"""
Convert between mm and inches.
:param value: Value to convert
:param from_unit: Source unit ("mm" or "inches")
:param to_unit: Target unit ("mm" or "inches")
:return: Converted value
"""
if from_unit == to_unit:
return value
if from_unit == "inches" and to_unit == "mm":
return value * 25.4
elif from_unit == "mm" and to_unit == "inches":
return value / 25.4
else:
raise ValueError(f"Unsupported unit conversion: {from_unit} to {to_unit}")
def calculate_scaling_factor_with_units(
target_image,
reference_obj_size: float,
reference_unit: str = "mm",
output_unit: str = "mm",
feature_detector="ORB",
debug=False,
roi_margin=30,
) -> Tuple[float, str]:
"""
Calculate scaling factor with proper unit handling.
:param target_image: Input image (numpy array)
:param reference_obj_size: Known size of reference object
:param reference_unit: Unit of reference object size ("mm" or "inches")
:param output_unit: Desired unit for scaling factor ("mm" or "inches")
:param feature_detector: Feature detector to use ("ORB" or "SIFT")
:param debug: Enable debug output
:param roi_margin: ROI margin in pixels
:return: Tuple of (scaling_factor, unit_string)
"""
# Convert reference size to mm for internal calculation
reference_size_mm = convert_units(reference_obj_size, reference_unit, "mm")
# Calculate scaling factor in mm/px
scaling_factor_mm = calculate_scaling_factor(
target_image=target_image,
reference_obj_size_mm=reference_size_mm,
feature_detector=feature_detector,
debug=debug,
roi_margin=roi_margin,
)
# Convert scaling factor to desired output unit
if output_unit == "inches":
scaling_factor = scaling_factor_mm / 25.4 # Convert mm/px to inches/px
unit_string = "inches per pixel"
else:
scaling_factor = scaling_factor_mm
unit_string = "mm per pixel"
print(f"Final scaling factor: {scaling_factor:.6f} {unit_string}")
return scaling_factor, unit_string
# Paper size configurations (in mm and inches)
PAPER_SIZES = {
"A4": {"width_mm": 210, "height_mm": 297, "width_inches": 8.27, "height_inches": 11.69},
"A3": {"width_mm": 297, "height_mm": 420, "width_inches": 11.69, "height_inches": 16.54},
"US Letter": {"width_mm": 215.9, "height_mm": 279.4, "width_inches": 8.5, "height_inches": 11.0}
}
def calculate_paper_scaling_factor(
paper_contour: np.ndarray,
paper_size: str,
output_unit: str = "mm"
) -> Tuple[float, str]:
"""
Calculate scaling factor based on detected paper dimensions with proper unit handling.
Includes empirical correction factor for improved accuracy.
:param paper_contour: Detected paper contour
:param paper_size: Paper size identifier ("A4", "A3", "US Letter")
:param output_unit: Desired unit for scaling factor ("mm" or "inches")
:return: Tuple of (scaling_factor, unit_string)
"""
# Empirical correction factor based on measurement analysis
# Your measurements show DXF is ~79% of original size, so we need to reduce scaling by this factor
CORRECTION_FACTOR = 0.79 # Adjust this value if needed based on more measurements
# Get paper dimensions in the desired unit
if output_unit == "inches":
expected_width = PAPER_SIZES[paper_size]["width_inches"]
expected_height = PAPER_SIZES[paper_size]["height_inches"]
unit_string = "inches per pixel"
else:
expected_width = PAPER_SIZES[paper_size]["width_mm"]
expected_height = PAPER_SIZES[paper_size]["height_mm"]
unit_string = "mm per pixel"
# Calculate bounding rectangle of paper contour
rect = cv2.boundingRect(paper_contour)
detected_width_px = rect[2]
detected_height_px = rect[3]
# Calculate scaling factors for both dimensions
scale_x = expected_width / detected_width_px
scale_y = expected_height / detected_height_px
# Try different approaches to find the best scaling factor
# Method 1: Use minimum scale (your current approach)
scaling_factor_min = min(scale_x, scale_y)
# Method 2: Use average scale (often more accurate for real-world images)
scaling_factor_avg = (scale_x + scale_y) / 2
# Method 3: Use aspect ratio to determine orientation and pick appropriate scale
detected_aspect_ratio = detected_width_px / detected_height_px
expected_aspect_ratio = expected_width / expected_height
if abs(detected_aspect_ratio - expected_aspect_ratio) < abs(detected_aspect_ratio - (1/expected_aspect_ratio)):
# Same orientation
scaling_factor_oriented = (scale_x + scale_y) / 2
else:
# Rotated 90 degrees
scale_x_rot = expected_height / detected_width_px
scale_y_rot = expected_width / detected_height_px
scaling_factor_oriented = (scale_x_rot + scale_y_rot) / 2
# Choose the best method (you can experiment with different methods)
base_scaling_factor = scaling_factor_avg # Changed from min to avg
# Apply correction factor
scaling_factor = base_scaling_factor * CORRECTION_FACTOR
print(f"Paper detection: {detected_width_px}x{detected_height_px} px")
print(f"Expected paper size: {expected_width}x{expected_height} {output_unit}")
print(f"Scale X: {scale_x:.6f}, Scale Y: {scale_y:.6f}")
print(f"Base scaling (avg): {base_scaling_factor:.6f}")
print(f"Correction factor: {CORRECTION_FACTOR}")
print(f"Final scaling factor: {scaling_factor:.6f} {unit_string}")
return scaling_factor, unit_string
def calculate_paper_scaling_factor_corrected(
paper_contour: np.ndarray,
paper_size: str,
output_unit: str = "mm",
correction_factor: float = 0.79,
method: str = "average"
) -> Tuple[float, str]:
"""
Calculate scaling factor with configurable correction and method.
:param paper_contour: Detected paper contour
:param paper_size: Paper size identifier ("A4", "A3", "US Letter")
:param output_unit: Desired unit for scaling factor ("mm" or "inches")
:param correction_factor: Empirical correction factor (default 0.79)
:param method: Calculation method ("min", "max", "average", "auto")
:return: Tuple of (scaling_factor, unit_string)
"""
# Get paper dimensions in the desired unit
if output_unit == "inches":
expected_width = PAPER_SIZES[paper_size]["width_inches"]
expected_height = PAPER_SIZES[paper_size]["height_inches"]
unit_string = "inches per pixel"
else:
expected_width = PAPER_SIZES[paper_size]["width_mm"]
expected_height = PAPER_SIZES[paper_size]["height_mm"]
unit_string = "mm per pixel"
# Calculate bounding rectangle of paper contour
rect = cv2.boundingRect(paper_contour)
detected_width_px = rect[2]
detected_height_px = rect[3]
# Calculate scaling factors for both dimensions
scale_x = expected_width / detected_width_px
scale_y = expected_height / detected_height_px
# Choose scaling method
if method == "min":
base_scaling_factor = min(scale_x, scale_y)
elif method == "max":
base_scaling_factor = max(scale_x, scale_y)
elif method == "average":
base_scaling_factor = (scale_x + scale_y) / 2
elif method == "auto":
# Auto-select based on aspect ratio similarity
detected_aspect = detected_width_px / detected_height_px
expected_aspect = expected_width / expected_height
# If aspect ratios are similar, use average; otherwise use method that matches better
aspect_diff = abs(detected_aspect - expected_aspect)
aspect_diff_inv = abs(detected_aspect - (1/expected_aspect))
if aspect_diff < aspect_diff_inv:
base_scaling_factor = (scale_x + scale_y) / 2 # Same orientation
else:
# Different orientation - swap and recalculate
scale_x_swap = expected_height / detected_width_px
scale_y_swap = expected_width / detected_height_px
base_scaling_factor = (scale_x_swap + scale_y_swap) / 2
else:
raise ValueError(f"Unknown method: {method}")
# Apply correction factor
scaling_factor = base_scaling_factor * correction_factor
print(f"Paper detection: {detected_width_px}x{detected_height_px} px")
print(f"Expected paper size: {expected_width}x{expected_height} {output_unit}")
print(f"Scale X: {scale_x:.6f}, Scale Y: {scale_y:.6f}")
print(f"Method: {method}, Base scaling: {base_scaling_factor:.6f}")
print(f"Correction factor: {correction_factor}")
print(f"Final scaling factor: {scaling_factor:.6f} {unit_string}")
return scaling_factor, unit_string
# Example usage:
if __name__ == "__main__":
import os
from PIL import Image
# Test with different units
sample_dir = "./sample_images"
if os.path.exists(sample_dir):
for idx, file in enumerate(os.listdir(sample_dir)):
if file.lower().endswith(('.jpg', '.jpeg', '.png')):
img_path = os.path.join(sample_dir, file)
img = np.array(Image.open(img_path))
# Convert to grayscale if needed
if len(img.shape) == 3:
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
else:
img_gray = img
print(f"\nProcessing: {file}")
try:
# Test with mm units
scaling_factor_mm, unit_mm = calculate_scaling_factor_with_units(
target_image=img_gray,
reference_obj_size=20.0, # 20mm reference object
reference_unit="mm",
output_unit="mm",
feature_detector="ORB",
debug=False,
roi_margin=90,
)
# Test with inch units
scaling_factor_inch, unit_inch = calculate_scaling_factor_with_units(
target_image=img_gray,
reference_obj_size=0.787, # ~20mm in inches
reference_unit="inches",
output_unit="inches",
feature_detector="ORB",
debug=False,
roi_margin=90,
)
print(f"MM scaling: {scaling_factor_mm:.6f} {unit_mm}")
print(f"Inch scaling: {scaling_factor_inch:.6f} {unit_inch}")
# Verify conversion consistency
converted_mm_to_inch = scaling_factor_mm / 25.4
print(f"Converted mm to inch: {converted_mm_to_inch:.6f}")
print(f"Difference: {abs(scaling_factor_inch - converted_mm_to_inch):.8f}")
except Exception as e:
print(f"Error processing {file}: {e}")
else:
print(f"Sample directory {sample_dir} not found")