| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| import cv2 |
| import numpy as np |
| import gradio as gr |
| from gradio_client import Client, handle_file |
|
|
| client = Client("leonelhs/rembg") |
|
|
| def unwrap(image, mask): |
| img = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) |
| gray = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY) |
|
|
| _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
| contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| contours = sorted(contours, key=cv2.contourArea, reverse=True) |
|
|
| if len(contours) > 0: |
| cnt = contours[0] |
| peri = cv2.arcLength(cnt, True) |
| approx = cv2.approxPolyDP(cnt, 0.02 * peri, True) |
|
|
| if len(approx) == 4: |
| corners = approx.reshape(4, 2).astype(np.float32) |
|
|
| |
| rect = np.zeros((4, 2), dtype="float32") |
| s = corners.sum(axis=1) |
| rect[0] = corners[np.argmin(s)] |
| rect[2] = corners[np.argmax(s)] |
|
|
| diff = np.diff(corners, axis=1) |
| rect[1] = corners[np.argmin(diff)] |
| rect[3] = corners[np.argmax(diff)] |
|
|
| (tl, tr, br, bl) = rect |
|
|
| |
| widthA = np.linalg.norm(br - bl) |
| widthB = np.linalg.norm(tr - tl) |
| maxWidth = int(max(widthA, widthB)) |
|
|
| heightA = np.linalg.norm(tr - br) |
| heightB = np.linalg.norm(tl - bl) |
| maxHeight = int(max(heightA, heightB)) |
|
|
| dst = np.array([ |
| [0, 0], |
| [maxWidth - 1, 0], |
| [maxWidth - 1, maxHeight - 1], |
| [0, maxHeight - 1] |
| ], dtype="float32") |
|
|
| |
| M = cv2.getPerspectiveTransform(rect, dst) |
| warped = cv2.warpPerspective(img, M, (maxWidth, maxHeight)) |
|
|
| return cv2.cvtColor(warped, cv2.COLOR_BGR2RGB), mask, corners |
|
|
| |
| return image, mask, contours |
|
|
| def predict(img): |
| """ |
| Unwrap an image using AI-assisted preprocessing and OpenCV. |
| |
| The algorithm first leverages an AI service to generate a cleaner, |
| well-bounded intermediate image. This helps OpenCV detect borders |
| more reliably before performing the perspective unwrap. |
| |
| Parameters: |
| img (string): File path to the input image to be unwrapped. |
| |
| Returns: |
| path (string): File path to the generated, unwrapped image. |
| """ |
|
|
| |
| |
| |
| crop, mask = client.predict(image=handle_file(img), session="u2net", api_name="/predict") |
|
|
| |
| |
| crop = cv2.imread(crop) |
| mask = cv2.imread(mask) |
| return unwrap(crop, mask) |
|
|
|
|
| with gr.Blocks() as app: |
| gr.Markdown("## 🖼️ Rectangle Detection & Perspective Unwrap") |
| with gr.Row(): |
| with gr.Column(scale=1): |
| inp = gr.Image(type="filepath", label="Upload Image") |
| btn_unwrap = gr.Button("📐 Perspective Unwrap") |
| with gr.Column(scale=2): |
| with gr.Row(): |
| with gr.Column(scale=1): |
| out_unwrap = gr.Image(type="numpy", label="Unwrapped Rectangle") |
| with gr.Accordion("See intermediates", open=False): |
| out_mask = gr.Image(type="numpy", label="Detected Corners") |
| out_corners = gr.JSON(label="Corners (x,y)") |
|
|
| btn_unwrap.click(predict, inputs=inp, outputs=[out_unwrap, out_mask, out_corners]) |
|
|
| app.launch(share=False, debug=True, show_error=True, mcp_server=True) |
| app.queue() |
|
|
|
|