report-detect/scripts/unwarp_seal_v2.py

116 lines
4.5 KiB
Python
Raw Normal View History

2026-02-05 13:57:22 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unwarp Seal with Auto-Centering (V2)
1. Detects the main seal circle to find true Center (cx, cy) and exact Radius.
2. Unwarps using these precise coordinates.
"""
import sys
import cv2
import numpy as np
def unwarp_seal_centered(image_path, output_path, warp_factor=2.0):
img = cv2.imread(image_path)
if img is None:
print(f"Error: Could not load {image_path}")
sys.exit(1)
# 1. Detect Circle
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.medianBlur(gray, 5)
rows = gray.shape[0]
# HoughCircles parameters (tuned for typical seal images)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, rows / 8,
param1=100, param2=30,
minRadius=int(rows / 4), maxRadius=int(rows / 1.5))
if circles is not None:
circles = np.uint16(np.around(circles))
# Take the largest circle (assumption: seal is the main object)
best_circle = circles[0][0]
cx, cy, radius = best_circle
cx, cy, radius = int(cx), int(cy), int(radius)
print(f"Detected Seal Center: ({cx}, {cy}), Radius: {radius}")
else:
print("Warning: No circle detected. Fallback to image center.")
h, w = img.shape[:2]
cx, cy = w // 2, h // 2
radius = min(cx, cy)
# 2. Setup Warp Parameters
# Output width based on circumference * factor
out_w = int(radius * 2 * np.pi * warp_factor)
out_h = radius # Height is just radius
# 3. Rotate 90 CCW BEFORE warp
# This places "Top" (12 o'clock) at "Left" (180 deg) in polar space
# BUT wait, rotate changes coordinates.
# Better approach: warpPolar maps 3 o'clock (0 deg) to top of output if not rotated.
# Actually, standard warpPolar:
# Angle 0 (3 o'clock) -> Row 0 (Top) ?? No.
# warpPolar(..., WARP_POLAR_LINEAR) maps:
# X-axis (Angle) usually 0..360.
# Let's stick to the rotation trick which worked previously, BUT we must rotate the CENTER too or rotate the Image first then find center?
# Simpler: Rotate image 90 CCW. New center coordinates?
# New X = Original Y
# New Y = (Width - 1) - Original X
# Let's just use the previous logic: Rotate image, then assume center is roughly rotated.
# OR better: Don't rotate image. Warp Polar normally. Then just roll the output array?
# warpPolar starts at 3 o'clock. 12 o'clock is -90 degrees.
# Let's just warp and see where the text lands.
# Let's stick to the proven pipeline: Rotate 90 CCW first.
# We need to map (cx, cy) to rotated coordinates.
h, w = img.shape[:2]
rotated_src = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
# Rotated 90 CCW:
# (x, y) -> (y, w - 1 - x)
# BUT cv2.rotate might just handle it automatically if we found center on rotated image?
# Safest: Find circle ON THE ROTATED IMAGE.
gray_rot = cv2.cvtColor(rotated_src, cv2.COLOR_BGR2GRAY)
gray_rot = cv2.medianBlur(gray_rot, 5)
rows_rot = gray_rot.shape[0]
circles_rot = cv2.HoughCircles(gray_rot, cv2.HOUGH_GRADIENT, 1, rows_rot / 8,
param1=100, param2=30,
minRadius=int(rows_rot / 4), maxRadius=int(rows_rot / 1.5))
if circles_rot is not None:
best_circle_rot = np.uint16(np.around(circles_rot))[0][0]
cx_r, cy_r, r_r = best_circle_rot
print(f"Rotated Center: ({cx_r}, {cy_r}), Radius: {r_r}")
else:
print("Warning: No circle detected on rotated image. Using calculated.")
cx_r = cy
cy_r = w - 1 - cx
r_r = radius
# Warp
unwarped = cv2.warpPolar(rotated_src, (out_w, int(r_r)), (cx_r, cy_r), r_r, cv2.WARP_FILL_OUTLIERS + cv2.WARP_POLAR_LINEAR)
# Crop outer ring (text area)
# Text is usually in the outer 30%
crop_start_y = int(r_r * 0.65) # Capture a bit more
unwarped_crop = unwarped[crop_start_y:int(r_r), :]
# Flip logic
# Previous script: flip(0)
unwarped_final = cv2.flip(unwarped_crop, 0)
cv2.imwrite(output_path, unwarped_final)
print(f"Saved centered unwarp to {output_path}")
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python unwarp_seal_v2.py <image_path> <output_path> [factor]")
sys.exit(1)
factor = 2.0
if len(sys.argv) > 3:
factor = float(sys.argv[3])
unwarp_seal_centered(sys.argv[1], sys.argv[2], factor)