Source code for Utils

"""
This module defines utility functions for file selection, image processing, and feature detection
used in the MoilApp application.

Dependencies:
- PyQt5.QtWidgets: Provides the QFileDialog and QMessageBox for file selection and notifications.
- cv2: OpenCV library for image processing.
- numpy: Library for numerical operations.
- datetime: Library to handle date and time operations.
- os: Library to interact with the operating system.
- math: Library for mathematical functions.

Functions:
- select_file: Opens a file dialog to select a file.
- read_image: Reads an image from the specified path.
- saveImage: Saves an image with a timestamped filename.
- drawPoint: Draws a point on the image.
- image_resize: Resizes an image while maintaining its aspect ratio.
- corner_detect: Detects corners in an image using Harris corner detection.
- draw_corners: Draws detected corners on an image.
- get_corner_list: Extracts corner coordinates from the detected corners.
- distance: Calculates the distance between two points.
- draw_matches: Draws lines between matched points in two images.
"""

from PyQt5 import QtWidgets
import datetime
import os
import cv2
import numpy as np
import math


[docs] def select_file(title, dir_path, file_filter): """ Opens a file dialog to select a file. Args: title (str): The title of the file dialog window. dir_path (str): The directory path to start the file dialog. file_filter (str): The filter for the file types to select. Returns: str: The selected file path. """ options = QtWidgets.QFileDialog.DontUseNativeDialog file_path, _ = QtWidgets.QFileDialog.getOpenFileName(None, title, dir_path, file_filter, options=options) return file_path
[docs] def read_image(image_path): """ Reads an image from the specified path. Args: image_path (str): The path of the image file to read. Returns: ndarray: The image read from the file. Raises: FileNotFoundError: If the image cannot be loaded. """ img = cv2.imread(image_path) if img is None: raise FileNotFoundError("`{}` not cannot be loaded".format(image_path)) return img
[docs] def saveImage(filename, image): """ Saves an image with a timestamped filename. Args: filename (str): The base filename to use for saving the image. image (ndarray): The image to save. """ ss = datetime.datetime.now().strftime("%H_%M_%S") name = "../result/Images/" + filename + "_" + str(ss) + ".png" os.makedirs(os.path.dirname(name), exist_ok=True) cv2.imwrite(name, image) QtWidgets.QMessageBox.information(None, "Information", "Image saved !!")
[docs] def drawPoint(image, heightImage, coordinatePoint): """ Draws a point on the image. Args: image (ndarray): The image on which to draw the point. heightImage (int): The height of the image to determine the point size. coordinatePoint (tuple): The coordinates of the point to draw. Returns: ndarray: The image with the point drawn on it. """ if heightImage >= 1000: cv2.circle(image, coordinatePoint, 10, (0, 255, 0), 20, -1) else: cv2.circle(image, coordinatePoint, 6, (0, 255, 0), 12, -1) return image
[docs] def image_resize(image, width=None, height=None, inter=cv2.INTER_AREA): """ Resizes an image while maintaining its aspect ratio. Args: image (ndarray): The image to resize. width (int, optional): The desired width of the resized image. height (int, optional): The desired height of the resized image. inter: The interpolation method for resizing. Returns: ndarray: The resized image. """ dim = None (h, w) = image.shape[:2] if width is None and height is None: return image if width is None: r = height / float(h) dim = (int(w * r), height) else: r = width / float(w) dim = (width, int(h * r)) resized = cv2.resize(image, dim, interpolation=inter) return resized
[docs] def corner_detect(image, sigma=1, threshold=0.01): """ Detects corners in an image using Harris corner detection. Args: image (ndarray): The image in which to detect corners. sigma (float): The standard deviation of the Gaussian filter. threshold (float): The threshold for detecting corners. Returns: list: A list of detected corners. """ # height, width = image.shape # shape = (height, width) # Calculate the dx,dy gradients of the image (np.gradient doesnt work) dx = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=5) dy = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=5) # Get angle for rotation _, ang = cv2.cartToPolar(dx, dy, angleInDegrees=True) # Square the derivatives (A,B,C of H) and apply apply gaussian filters to each sigma = (sigma, sigma) Ixx = cv2.GaussianBlur(dx * dx, sigma, 0) Ixy = cv2.GaussianBlur(dx * dy, sigma, 0) Iyy = cv2.GaussianBlur(dy * dy, sigma, 0) H = np.array([[Ixx, Ixy], [Ixy, Iyy]]) # Find the determinate num = (H[0, 0] * H[1, 1]) - (H[0, 1] * H[1, 0]) # Find the trace denom = H[0, 0] + H[1, 1] # Find the response using harmonic mean of the eigenvalues (Brown et. al. variation) R = np.nan_to_num(num / denom) # Adaptive non-maximum suppression, keep the top 1% of values and remove non-maximum points in a 9x9 neighbourhood R_flat = R[:].flatten() # Get number of values in top threshold % N = int(len(R_flat) * threshold) # Get values in top threshold % top_k_percentile = np.partition(R_flat, -N)[-N:] # Find lowest value in top threshold % minimum = np.min(top_k_percentile) # Set all values less than this to 0 R[R < minimum] = 0 # Set non-maximum points in an SxS neighbourhood to 0 s = 9 for h in range(R.shape[0] - s): for w in range(R.shape[1] - s): maximum = np.max(R[h:h + s + 1, w:w + s + 1]) for i in range(h, h + s + 1): for j in range(w, w + s + 1): if R[i, j] != maximum: R[i, j] = 0 # Return harris corners in [H, W, R] format features = list(np.where(R > 0)) features.append(ang[np.where(R > 0)]) corners = zip(*features) return list(corners)
[docs] def draw_corners(corners, image): """ Draws detected corners on an image. Args: corners (list): The list of detected corners. image (ndarray): The image on which to draw the corners. Returns: ndarray: The image with the corners drawn on it. """ i = 0 for h, w in corners: cv2.circle(image, (w, h), 3, (0, 0, 255), -1) # caption = '{},{}'.format(h, w) cv2.putText(image, str(i), (w - 5, h + 10), cv2.FONT_HERSHEY_COMPLEX, 0.3, (0, 255, 0)) i += 1 return image
[docs] def get_corner_list(corners): """ Extracts corner coordinates from the detected corners. Args: corners (list): The list of detected corners. Returns: list: A list of coordinates of the corners. """ coor = [] for h, w, r in corners: caption = '{},{}'.format(h, w) res = tuple(map(int, caption.split(','))) coor.append(res) return coor
[docs] def distance(point_a, point_b): """ Calculates the distance between two points. Args: point_a (tuple): The first point. point_b (tuple): The second point. Returns: float: The distance between the points. """ x0, y0 = point_a x1, y1 = point_b return math.fabs(x0 - x1) + math.fabs(y0 - y1)
[docs] def draw_matches(matches, img_left, img_right, verbose=False): """ Draws lines between matched points in two images. Args: matches (list): A list of matched points. img_left (ndarray): The left image. img_right (ndarray): The right image. verbose (bool): If True, displays the image with matches. Returns: ndarray: The image with matches drawn on it. """ # Determine the max height height = max(img_left.shape[0], img_right.shape[0]) # Width is the two images side-by-side width = img_left.shape[1] + img_right.shape[1] img_out = np.zeros((height, width, 3), dtype=np.uint8) # Place the images in the empty image img_out[0:img_left.shape[0], 0:img_left.shape[1], :] = img_left img_out[0:img_right.shape[0], img_left.shape[1]:, :] = img_right # The right image coordinates are offset since the image is no longer at (0,0) ow = img_left.shape[1] # Draw a line between the matched pairs in green for p1, p2 in matches: p1o = (int(p1[1]), int(p1[0])) p2o = (int(p2[1] + ow), int(p2[0])) color = list(np.random.random(size=3) * 256) cv2.line(img_out, p1o, p2o, color, thickness=2) if verbose: print("Press enter to continue ... ") cv2.imshow("matches", img_out) cv2.waitKey(0)