ropecam/1280 x 800/Image_Processing.py
2025-02-17 14:52:43 +01:00

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