BBongiovanni's picture
added coordinates to app
b0c9f2b
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)
target_coords = compute_target_coords(pts)
homography = direct_linear_transformation(roi_coords, target_coords)
coordinates = {
"input": {
"top-left": "({}, {})".format(roi_coords[0][0], roi_coords[0][1]),
"top-right": "({}, {})".format(roi_coords[1][0], roi_coords[1][1]),
"bottom-right": "({}, {})".format(roi_coords[2][0], roi_coords[2][1]),
"bottom-left": "({}, {})".format(roi_coords[3][0], roi_coords[3][1])
},
"projected": {
"top-left": "({}, {})".format(int(target_coords[0].tolist()[0]), int(target_coords[0].tolist()[1])),
"top-right": "({}, {})".format(int(target_coords[1].tolist()[0]), int(target_coords[1].tolist()[1])),
"bottom-right": "({}, {})".format(int(target_coords[2].tolist()[0]), int(target_coords[2].tolist()[1])),
"bottom-left": "({}, {})".format(int(target_coords[3].tolist()[0]), int(target_coords[3].tolist()[1]))
}
}
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 coordinates, contour_crop, np.around(homography, 2), *contour_crop.shape
else:
return None, 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)
numpy_t_matrix = gr.Numpy(label="Transformation Matrix", row_count=3, col_count=3, headers=['', '', ''], interactive=False)
json_coordinates = gr.JSON(label="Coordinates")
image_input.select(click_callback, image_input, [image_input, json_coordinates, image_output, numpy_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()