336 lines
15 KiB
Python
336 lines
15 KiB
Python
import cv2
|
|
import time
|
|
import numpy as np
|
|
import math
|
|
import datetime
|
|
|
|
class Image_Processing():
|
|
def __init__(self):
|
|
|
|
#Parameter des Vorschaubildes
|
|
self.roi_width = 1280
|
|
self.roi_height = 800
|
|
self.preview_roi_height_offset = 0
|
|
self.preview_roi_width_offset = 0
|
|
self.preview_width = 1280
|
|
self.preview_height = 800
|
|
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
|
|
|
|
|
|
|
|
|