116 lines
4.5 KiB
Python
116 lines
4.5 KiB
Python
|
|
#!/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)
|