Spaces:
Runtime error
Runtime error
import copy | |
import math | |
import gradio as gr | |
import cv2 | |
import numpy as np | |
from perspective_transform import compute_target_coords, direct_linear_transformation | |
roi_coords = [] | |
input_image = None | |
def get_warped_image(): | |
""" | |
Computes the transformation matrix given the source and target points | |
:return: | |
""" | |
global roi_coords, input_image | |
if len(roi_coords) == 4: | |
pts = np.array(roi_coords, np.int32) | |
homography = direct_linear_transformation(roi_coords, compute_target_coords(pts)) | |
mask = np.zeros((input_image.shape[0], input_image.shape[1])) | |
cv2.fillConvexPoly(mask, pts, 1) | |
mask = mask.astype(bool) | |
cropped = np.zeros_like(input_image) | |
cropped[mask] = input_image[mask] | |
warped = cv2.warpPerspective(cropped, homography, (cropped.shape[:2][1], cropped.shape[:2][0])) | |
# Crop the warped image to be just the contents within target_coords | |
contours, hierachy = cv2.findContours(cv2.cvtColor(warped, cv2.COLOR_RGB2GRAY), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
x, y, w, h = cv2.boundingRect(contours[0]) | |
contour_crop = warped[y:y + h, x:x + w] | |
return contour_crop, np.around(homography, 2), *contour_crop.shape | |
else: | |
return None, None, None, None | |
def click_callback(img_path, evt: gr.SelectData): | |
""" | |
This callback is triggered when the user clicks on the image. | |
Whenever the user clicks on the image, add a new coordinate, or adjust the location of an existing coordinate. | |
If there are four coordinates, we automatically return the warped image. | |
:param img_path: (str) The path to the temporary image Gradio saves | |
:param evt: (gr.SelectData) If we specify the type hint, the type is automatically determined | |
:return: (tuple) The image with overlays, the expanded outputs of get_warped_image() | |
""" | |
global roi_coords, input_image | |
img = cv2.imread(img_path) | |
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) | |
# Save off a copy of the first input image | |
if len(roi_coords) == 0: | |
input_image = copy.copy(img) | |
# Either create a new coordinate, or adjust the position of an existing coordinate | |
if len(roi_coords) < 4: | |
roi_coords.append(evt.index) | |
else: | |
distances = [math.dist(evt.index, coord) for coord in roi_coords] | |
roi_coords[np.argmin(distances)] = evt.index | |
if len(roi_coords) == 4: | |
display_image = copy.copy(input_image) | |
# Overlay the corners of the ROI | |
pts = np.array(roi_coords, np.int32).reshape((-1, 1, 2)) | |
cv2.polylines(display_image, [pts], True, (255, 255, 255), 2) | |
# Always overlay the location of the coordinates | |
for coord in roi_coords: | |
cv2.circle(display_image, coord, radius=5, color=(255, 0, 0), thickness=-1) | |
return display_image, *get_warped_image() | |
else: | |
return input_image, *get_warped_image() | |
def clear_variables(*kwargs): | |
""" | |
Clears the defined coordinates and the input image. | |
:param kwargs: (tuple) Depending on who calls this function, there may be unecessary input arguments | |
:return: (None) Clears the image component in the Gradio app | |
""" | |
global roi_coords, input_image | |
roi_coords = [] | |
input_image = None | |
return input_image | |
def resize_image(img_path, width=None, height=None): | |
""" | |
Resizes the input image to the given width/height while maintaining the original remaining dimension. | |
:param img_path: (str) The path to the temporary image Gradio saves | |
:param width: (int) The desired image width | |
:param height: (int) The desired image height | |
:return: (np.ndarray) The resized image | |
""" | |
if img_path is not None: | |
img = cv2.imread(img_path) | |
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) | |
img_height, img_width = img.shape[:2] | |
img = cv2.resize(img, (width, img_height)) if width is not None else cv2.resize(img, (img_width, height)) | |
return img | |
else: | |
return None | |
with gr.Blocks() as demo: | |
gr.Markdown("<h1 style='text-align: center;font-size:50px'>Points-4-Perspective</h1>") | |
gr.Markdown("<h2 style='text-align: center;'>Click on the top-left, top-right, bottom-right, and bottom-left corners of the ROI</h2>") | |
button_clear = gr.Button("Clear Inputs") | |
with gr.Row(): | |
with gr.Column(): | |
image_input = gr.Image(label="Input Image", type="filepath", value="warp_test_images/test.jpg", height=900) | |
gallery = gr.Examples( | |
fn=clear_variables, | |
run_on_click=True, | |
examples=[ | |
"warp_test_images/test.jpg", | |
"warp_test_images/test2.jpg", | |
"warp_test_images/test4.png", | |
"warp_test_images/billboard.jpg", | |
"warp_test_images/billboard2.jpg", | |
"warp_test_images/venice.jpg", | |
"warp_test_images/palacio_vergara.jpg" | |
], | |
inputs=image_input | |
) | |
with gr.Column(): | |
image_output = gr.Image(label="Cropped Warp", type="filepath", tool="editor", interactive=True) | |
slider_image_width = gr.Slider(label="Width", minimum=10, maximum=900, step=1) | |
slider_image_height = gr.Slider(label="Height", minimum=10, maximum=900, step=1) | |
json_t_matrix = gr.Numpy(label="Transformation Matrix", row_count=3, col_count=3, headers=['', '', ''], interactive=False) | |
image_input.select(click_callback, image_input, [image_input, image_output, json_t_matrix, slider_image_width, slider_image_height]) | |
image_input.clear(clear_variables) | |
button_clear.click(clear_variables, None, image_input) | |
slider_image_width.release(resize_image, [image_output, slider_image_width, gr.State(None)], image_output) | |
slider_image_height.release(resize_image, [image_output, gr.State(None), slider_image_height], image_output) | |
demo.queue(concurrency_count=10, max_size=20) | |
# demo.launch(inbrowser=True) | |
demo.launch() | |