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("