import cv2 import time import numpy as np import math import datetime class Image_Processing(): def __init__(self): #Parameter des Vorschaubildes self.roi_width = 800 self.roi_height = 480 self.preview_roi_height_offset = 0 self.preview_roi_width_offset = 0 self.preview_width = 800 self.preview_height = 480 self.center_y = self.preview_height // 2 self.center_x = self.preview_width // 2 #Einstellungen Gitter self.grid_line_color = (255, 0, 0, 255) self.grid_line_thickness = 2 self.grid_line_spacing = 100 self.show_grid = False self.show_zoom = False #Zoom-Faktor für das Vorschaubild self.zoom_factor = 1.5 #Einstellungen Lineal self.show_ruler = False self.show_clock = False self.horizontal_ruler_start = 0 self.horizontal_ruler_end = 100 self.vertical_ruler_start = 0 self.vertical_ruler_end = 50 self.ruler_thickness = 30 self.ruler_font = cv2.FONT_HERSHEY_PLAIN self.ruler_color = (255, 255, 255, 255) # Weiß self.ruler_text_color = (0, 0, 0, 255) # Schwarz self.ruler_line_color = (0, 0, 0, 255) # Schwarz self.ruler_initial_width = self.preview_width self.ruler_initial_height = self.preview_height self.update_ruler_settings() #Einstellungen Uhr self.font = cv2.FONT_HERSHEY_SIMPLEX self.font_size = 0.5 self.font_color = (0, 0, 0, 255) self.background_color = (255, 255, 255, 255) self.text_thickness = 1 self.line_type = cv2.LINE_AA self.right_border = self.preview_width self.left_border = 0 self.upper_border = self.preview_height self.bottom_border = 0 #Bild wird aus dem GUI-Code an Image_Processing übergeben (sorgt für flüssigeres Vorschaubild) def get_preview_image(self, image): self.preview_image = image self.preview_image_processing() return self.preview_image #Einstellungen werden aus der Benutzeroberfläche geladen def get_settings(self, show_grid, show_ruler, show_clock, show_zoom): self.show_grid = show_grid self.show_ruler = show_ruler self.show_clock = show_clock self.show_zoom = show_zoom #Durchgeführte Schritte zur Bildbearbeitung des Vorschaubilds def preview_image_processing(self): self.crop_preview_image() self.zoom_picture() self.add_grid_lines() self.add_ruler() self.add_clock() #Aktualisieren der Parameter zum Zuschneiden des ROI (symmetrischer Zuschnitt) def update_roi(self, roi_width, roi_height): self.roi_width = roi_width self.roi_height = roi_height self.preview_roi_width_offset = round(((self.preview_width - self.roi_width) / 2)) self.preview_roi_height_offset = round(((self.preview_height - self.roi_height) / 2)) self.right_border = self.center_x + (self.roi_width // 2) self.left_border = self.center_x - (self.roi_width // 2) self.upper_border = self.center_y + (self.roi_height // 2) self.bottom_border = self.center_y - (self.roi_height // 2) self.update_ruler_settings() #Position des Lineals soll mit dem ROI mitwandern def crop_preview_image(self): if self.roi_width != self.preview_width or self.roi_height != self.preview_height: cropped_image = self.preview_image[self.preview_roi_height_offset:(self.preview_height - self.preview_roi_height_offset), self.preview_roi_width_offset:(self.preview_width - self.preview_roi_width_offset), :] # Erstellen eines neuen, schwarzen Bildes mit der gewünschten Größe (800x480) final_image = np.zeros((self.preview_height, self.preview_width, 4), dtype=np.uint8) # Berechnen der Startkoordinaten für das Einfügen des zugeschnittenen Bildes in das schwarze Bild start_y = (self.preview_height - (cropped_image.shape[0])) // 2 start_x = (self.preview_width - (cropped_image.shape[1])) // 2 # Einfügen des zugeschnittenen Bildes in das schwarze Bild final_image[start_y:start_y + cropped_image.shape[0], start_x:start_x + cropped_image.shape[1]] = cropped_image # Aktualisieren von self.preview_image mit dem neuen Bild self.preview_image = final_image else: return #Gitterlinien zur Vorschau hinzufügen def add_grid_lines(self): if self.show_grid: # Horizontale Linien for y in range(self.center_y, self.bottom_border, -self.grid_line_spacing): # Nach oben cv2.line(self.preview_image, (self.left_border, y), (self.right_border, y), self.grid_line_color, self.grid_line_thickness) for y in range(self.center_y, self.upper_border, self.grid_line_spacing): # Nach unten cv2.line(self.preview_image, (self.left_border, y), (self.right_border, y), self.grid_line_color, self.grid_line_thickness) # Vertikale Linien for x in range(self.center_x, self.left_border, -self.grid_line_spacing): # Nach links cv2.line(self.preview_image, (x, self.bottom_border), (x, self.upper_border), self.grid_line_color, self.grid_line_thickness) for x in range(self.center_x, self.right_border, self.grid_line_spacing): # Nach rechts cv2.line(self.preview_image, (x, self.bottom_border), (x, self.upper_border), self.grid_line_color, self.grid_line_thickness) #Einstellungen für das Lineal aktualisieren def update_ruler_settings(self): #Prüfen, ob der Bereich des Lineals gestreckt/gestaucht werden muss self.ruler_scaling_width = self.roi_width / self.ruler_initial_width self.ruler_scaling_height = self.roi_height / self.ruler_initial_height #Endwert des Lineals berechnen self.ruler_horizontal_end = (self.horizontal_ruler_end - self.horizontal_ruler_start) * self.ruler_scaling_width self.ruler_vertical_end = (self.vertical_ruler_end - self.vertical_ruler_start) * self.ruler_scaling_height #Ausgehend vom Bildbereich und des Endwerts wird die räumliche Auflösung in Pixel pro mm berechnet, um das Lineal anschließend flexibel zu skalieren self.px_mm_horizontal = math.ceil(self.roi_width / self.ruler_horizontal_end) self.px_mm_vertical = math.ceil(self.roi_height / self.ruler_vertical_end) #Lineal im Vorschaubild anzeigen def add_ruler(self): if self.show_ruler: # ROI Position und Größe in der Benutzeroberfläche berechnen roi_x_start = max(0, self.center_x - self.roi_width // 2) roi_y_start = max(0, self.center_y - self.roi_height // 2) # Erstellen des vertikalen Lineals adjusted_height = self.roi_height self.ruler_vertical = np.zeros((adjusted_height, self.ruler_thickness, 4), dtype=np.uint8) self.ruler_vertical[:, :] = self.ruler_color # Erstellen des horizontalen Lineals self.ruler_horizontal = np.zeros((self.ruler_thickness, self.roi_width, 4), dtype=np.uint8) self.ruler_horizontal[:, :] = self.ruler_color # Markierungen und Beschriftungen zum vertikalen Lineal hinzufügen for pos in range(self.ruler_thickness, self.roi_height, self.px_mm_vertical * 5): is_thick_line = (pos - self.ruler_thickness) % (self.px_mm_vertical * 10) == 0 #Alle 10 mm eine dicke Linie line_thickness = 2 if is_thick_line else 1 line_length = self.ruler_thickness if is_thick_line else self.ruler_thickness // 2 line_start = 0 if is_thick_line else int(self.ruler_thickness * 0.5) # Dünne Linien sind halb so lang cv2.line(self.ruler_vertical, (line_start, pos), (self.ruler_thickness, pos), self.ruler_line_color, line_thickness) if is_thick_line: text = f"{int((pos - self.ruler_thickness) / self.px_mm_vertical)}" cv2.putText(self.ruler_vertical, text, (2, pos - 5), self.ruler_font, 0.8, self.ruler_text_color, 1) # Markierungen und Beschriftungen zum horizontalen Lineal hinzufügen for pos in range(0, self.roi_width, self.px_mm_horizontal * 5): is_thick_line = pos % (self.px_mm_horizontal * 10) == 0 line_thickness = 2 if is_thick_line else 1 line_start = 0 if is_thick_line else int(self.ruler_thickness * 0.5) cv2.line(self.ruler_horizontal, (pos + self.ruler_thickness, line_start), (pos + self.ruler_thickness, self.ruler_thickness), self.ruler_line_color, line_thickness) if is_thick_line: text = f"{int((pos / self.px_mm_horizontal))}" text_x = pos +25- cv2.getTextSize(text, self.ruler_font, 0.8, 1)[0][0] text_y = self.ruler_thickness - 5 #Abstand zum oberen Rand cv2.putText(self.ruler_horizontal, text, (text_x, text_y), self.ruler_font, 0.8, self.ruler_text_color, 1) # Platzieren des vertikalen Lineals im Vorschaubild self.preview_image[roi_y_start:roi_y_start + self.roi_height, roi_x_start:roi_x_start + self.ruler_thickness] = self.ruler_vertical # Platzieren des horizontalen Lineals im Vorschaubild self.preview_image[roi_y_start:roi_y_start + self.ruler_thickness, roi_x_start:roi_x_start + self.roi_width] = self.ruler_horizontal #Uhrzeit unten rechts hinzufügen def add_clock(self): if self.show_clock: timestamp = datetime.datetime.now().strftime("%H:%M") image_height, image_width = self.preview_image.shape[:2] # Abstand vom linken Rand abhängig vom Lineal margin_x = self.ruler_thickness + 5 if self.show_ruler else 5 # Konstanter Abstand vom unteren Rand margin_y = 30 # Padding erhöhen, um die Box größer zu machen padding = 5 # Dynamische Schriftgröße basierend auf Bildhöhe font_scale = image_height / 800 # Textgröße und -position berechnen (text_width, text_height), base_line = cv2.getTextSize(timestamp, self.font, font_scale, self.text_thickness) # Die y-Position des Textes unter Berücksichtigung des unteren Randes text_position_y = image_height - margin_y - base_line - padding text_position = (margin_x + padding, text_position_y) # Berechnung der Hintergrundpositionen unter Berücksichtigung des zusätzlichen Padding background_l = (text_position[0] - padding, text_position[1] - text_height - padding) background_r = (text_position[0] + text_width + padding, text_position[1] + base_line + padding) # Hintergrund und Text zeichnen cv2.rectangle(self.preview_image, background_l, background_r, self.background_color, -1) cv2.putText(self.preview_image, timestamp, text_position, self.font, font_scale, self.font_color, self.text_thickness, self.line_type) #Start- und Endwerte des Lineals werden durch die Benutzeroberfläche definiert def update_ruler_values(self, ruler_vertical_start, ruler_vertical_end, ruler_horizontal_start, ruler_horizontal_end): self.vertical_ruler_start = ruler_vertical_start self.vertical_ruler_end = ruler_vertical_end self.horizontal_ruler_start = ruler_horizontal_start self.horizontal_ruler_end = ruler_horizontal_end self.update_ruler_settings() #Vorschaubild an einen Bereich gezoomt def zoom_picture(self): if self.show_zoom: image_height, image_width = self.preview_image.shape[:2] # Größe des zentralen Bereichs berechnen, der mit doppeltem Zoom dargestellt werden soll # Die Größe des zentralen Bereichs ist halb so groß wie die Originalgröße geteilt durch den Zoomfaktor central_width = round(image_width // (2 * self.zoom_factor)) central_height = round(image_height // (2 * self.zoom_factor)) # Mittelpunkt des Bildes berechnen center_x = image_width // 2 center_y = image_height // 2 # Zentralen Bereich definieren crop_x_start = max(center_x - central_width // 2, 0) crop_x_end = min(center_x + central_width // 2, image_width) crop_y_start = max(center_y - central_height // 2, 0) crop_y_end = min(center_y + central_height // 2, image_height) # Bildausschnitt extrahieren cropped_image = self.preview_image[crop_y_start:crop_y_end, crop_x_start:crop_x_end] # Bildausschnitt auf die ursprüngliche Größe des `preview_image` skalieren scaled_image = cv2.resize(cropped_image, (self.preview_width, self.preview_height)) # Das skalierte Bild als neues Vorschaubild festlegen self.preview_image = scaled_image #Methode um das Bild verdunkelt darzustellen, ist aktuell nicht implementiert, da die Performance etwas schlechter ist... #Sieht aber besser aus, hierbei wird der Bereich außerhalb der ROI verdunkelt dargestellt def crop_preview_image_Darkened(self): start_time = time.time() if self.roi_width != self.preview_width or self.roi_height != self.preview_height: # Erstellen einer Kopie des Bildes für die Verdunkelung darkened_image = self.preview_image.copy() # Definition des Verdunkelungsfaktors darken_factor = 0.7 # Erstellen einer Maske für die Bereiche, die verdunkelt werden sollen mask = np.zeros_like(self.preview_image, dtype=bool) # Setzen der Bereiche außerhalb des ROI in der Maske auf True top = self.preview_roi_height_offset bottom = self.preview_height - self.preview_roi_height_offset left = self.preview_roi_width_offset right = self.preview_width - self.preview_roi_width_offset mask[:top] = True mask[bottom:] = True mask[top:bottom, :left] = True mask[top:bottom, right:] = True # Anwenden der Verdunkelung nur auf die durch die Maske ausgewählten Bereiche darkened_image[mask] = (darkened_image[mask].astype(np.float32) * darken_factor).astype(np.uint8) # Aktualisieren des Bildes mit der verdunkelten Version self.preview_image = darkened_image print(f"Zeit: {time.time() - start_time}") else: return