first commit

This commit is contained in:
Johannes Guter 2025-02-17 14:52:43 +01:00
parent d715387448
commit 123f64f9ec
12 changed files with 2792 additions and 0 deletions

55
1280 x 800/Camera.py Normal file
View File

@ -0,0 +1,55 @@
from picamera2 import Picamera2, Preview
import PIL.Image, PIL.ImageTk
class Camera():
def __init__(self, still_image_processing):
#Auflösung des Vorschaubildes
self.preview_width = 1280
self.preview_height = 800
#Auflösung des finalen Bildes
self.still_width = 4056
self.still_height = 3040
self.camera = Picamera2()
#Low-Resolution-Config, für flüssigeres Vorschaubild
self.lowres_preview_config = self.camera.create_preview_configuration(main={"size": (self.preview_width, self.preview_height)})
#Einstellung für Vorschaubild in höherer AUflsung (für Zoom)
self.highres_preview_config = self.camera.create_preview_configuration(main={"size": (2028, 1080)})
#Einstellung für finales Bild, maximale Auflösung
self.capture_config = self.camera.create_still_configuration(main={"size": (self.still_width, self.still_height)})
self.preview_config = self.lowres_preview_config
self.still_image_processing = still_image_processing
self.set_highres_preview_config = False
def start_camera(self):
self.camera.configure(self.preview_config)
self.camera.start()
def get_preview_image(self):
image = self.camera.capture_array()
return image
#Kameravorschau auf höhere Auflösung umstellen
def get_highres_preview(self):
self.camera.stop()
if self.set_highres_preview_config == False:
self.preview_config = self.highres_preview_config
self.set_highres_preview_config = True
else:
self.preview_config = self.lowres_preview_config
self.set_highres_preview_config = False
self.camera.configure(self.preview_config)
self.camera.start()
def get_still_image(self):
image = self.camera.switch_mode_and_capture_array(self.capture_config)
self.still_image_processing.process_image(image)
def get_aspect_ratio(self):
preview_ratio = self.preview_width / self.preview_height
return preview_ratio

638
1280 x 800/Gui_Grid.py Normal file
View File

@ -0,0 +1,638 @@
import tkinter as tk
import PIL.Image, PIL.ImageTk
import numpy as np
from tkinter import ttk
from tkinter.font import Font
from tkinter import filedialog, messagebox
import subprocess
import os
import shutil
class User_Interface():
def __init__(self, camera, preview_processing, gpio_instance, still_image_processing):
#Instanzvariablen laden
self.still_image_processing = still_image_processing
self.preview_processing = preview_processing
self.gpio_instance = gpio_instance
self.camera = camera
self.root = tk.Tk()
#Verzögerung des Vorschaubildes
self.video_feed_delay = 5
#Seitenverhältnis der Kamera und des Widgets auslesen, zur Platzierung des Vorschaubildes in diesem
self.camera_aspect_ratio = self.camera.get_aspect_ratio()
self.video_widget_aspect_ratio = None
self.new_width = 1280
self.new_height = 800
#Flags für das derzeit aktive Menü
self.current_menu_right = None
self.current_menu_down = None
self.current_menu_down_right = None
self.submenu = False
#Ein leerer Pixel, wird zur quadratischen Form der Buttons benötigt
self.pixel = tk.PhotoImage()
self.keyboard_process = None
#Parameter des Lineals, mit Aktion verknüpft, sobald der Wert im Eingabefeld geändert wird
self.ruler_vertical_start = tk.StringVar()
self.ruler_vertical_end = tk.StringVar()
self.ruler_horizontal_start = tk.StringVar()
self.ruler_horizontal_end = tk.StringVar()
self.ruler_vertical_start.set(0)
self.ruler_vertical_end.set(50)
self.ruler_horizontal_start.set(0)
self.ruler_horizontal_end.set(100)
self.ruler_vertical_start.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_vertical_start"))
self.ruler_vertical_end.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_vertical_end"))
self.ruler_horizontal_start.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_horizontal_start"))
self.ruler_horizontal_end.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_horizontal_end"))
#Blitz / permanente Beleuchtung an / aus
self.permanent_lighting = False
self.activate_flash = False
#Vollbild an / aus
self.fullscreen_active = True
# Definiere Schriftarten
self.font_bold = Font(family="Helvetica", size=12, weight="bold")
self.font_normal = Font(family="Helvetica", size=12)
# Checkbox Größe anpassen
self.style = ttk.Style()
self.style.configure("LargeCheckbutton.TCheckbutton", font=self.font_normal, size=25)
#Variablen für den Dateinamen, ebenfalls mit Aktion verknüpft
self.storage_path_var = tk.StringVar(name="storage_path_var")
self.storage_path_var.set("/home/pi")
self.destination_path_var = tk.StringVar(name="destination_path_var")
self.filename_var = tk.StringVar(name="filename_var")
self.date_var = tk.BooleanVar(name="date_var")
self.time_var = tk.BooleanVar(name="time_var")
self.date_var.set(True)
self.time_var.set(True)
self.storage_path_var.trace_add("write", lambda *args: self.entry_fields_changed("Speicherpfad", *args))
self.destination_path_var.trace_add("write", lambda *args: self.entry_fields_changed("Zielpfad", *args))
self.filename_var.trace_add("write", lambda *args: self.entry_fields_changed("Dateiname", *args))
self.date_var.trace_add("write", lambda *args: self.entry_fields_changed("Datum", *args))
self.time_var.trace_add("write", lambda *args: self.entry_fields_changed("Uhrzeit", *args))
#GUI soll im Vollbild starten
def window_settings(self):
self.root.attributes('-fullscreen', True)
self.root.title('Titel')
def start_interface(self):
self.root.mainloop()
#Erstellen der Widgets aller Menüs
def build_widgets(self):
self.build_video_widget()
self.build_hide_menu_button()
self.main_menu_frame()
self.crop_menu_frame()
self.settings_menu_frame()
self.file_menu_frame()
self.main_menu_widgets()
self.crop_menu_widgets()
self.settings_menu_widgets()
self.file_menu_widgets()
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, minsize=0)
def build_video_widget(self):
self.video_widget = tk.Canvas(self.root, width=1280, height=800, highlightthickness=0)
self.video_widget.grid(row=0, column=0, sticky="nsew")
#Button "<", ">" zum Anzeigen / Verstecken des Menüs
def build_hide_menu_button(self):
self.hide_menu_button_container = tk.Frame(self.video_widget, background="white", bd=0, highlightthickness=0)
self.hide_menu_button_container.place(relx=1.0, rely=0.0, anchor="ne")
self.hide_menu_button_settings = {
"master": self.hide_menu_button_container,
"image": self.pixel,
"compound": "c",
"height": 30,
"width": 30,
"command": self.toggle_menu
}
self.hide_menu_button = tk.Button(**self.hide_menu_button_settings, text="<")
self.hide_menu_button.pack(side="right", padx=0, pady=0)
#Frames des Hauptmenüs, alle Menüs teilen sich in 3 Frames auf: Rechts vom Vorschaubild, Unter dem Vorschaubild sowie ein Frame unten rechts zwischen dem rechten und dem unteren Frame (2x2-Raster)
def main_menu_frame(self):
self.main_menu_frame_right = tk.Frame(self.root)
self.main_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.main_menu_frame_down = tk.Frame(self.root)
self.main_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.main_menu_frame_down_right = tk.Frame(self.root)
self.main_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.main_menu_frame_right.grid_remove()
self.main_menu_frame_down.grid_remove()
self.main_menu_frame_down_right.grid_remove()
#Frames des Zuschnitt-Menüs
def crop_menu_frame(self):
self.crop_menu_frame_right = tk.Frame(self.root)
self.crop_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.crop_menu_frame_down = tk.Frame(self.root)
self.crop_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.crop_menu_frame_down_right = tk.Frame(self.root)
self.crop_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.crop_menu_frame_right.grid_remove()
self.crop_menu_frame_down.grid_remove()
self.crop_menu_frame_down_right.grid_remove()
#Frames des Bildeinstellungsmenüs
def settings_menu_frame(self):
self.settings_menu_frame_right = tk.Frame(self.root)
self.settings_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.settings_menu_frame_down = tk.Frame(self.root)
self.settings_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.settings_menu_frame_down_right = tk.Frame(self.root)
self.settings_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.settings_menu_frame_right.grid_remove()
self.settings_menu_frame_down.grid_remove()
self.settings_menu_frame_down_right.grid_remove()
#Frames des Dateimenüs
def file_menu_frame(self):
self.file_menu_frame_right = tk.Frame(self.root)
self.file_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.file_menu_frame_down = tk.Frame(self.root)
self.file_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.file_menu_frame_down_right = tk.Frame(self.root)
self.file_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.file_menu_frame_right.grid_remove()
self.file_menu_frame_down.grid_remove()
self.file_menu_frame_down_right.grid_remove()
#Widgets für das Hauptmenü erstellen
def main_menu_widgets(self):
self.settings_menu_button = tk.Button(self.main_menu_frame_right, text="Bild-\neinstellungen", image=self.pixel, compound="c", height=80, width=80, command=self.show_settings_menu)
self.settings_menu_button.pack(pady=10)
self.crop_menu_button = tk.Button(self.main_menu_frame_right, text="Zuschnitt", image=self.pixel, compound="c", height=80, width=80, command=self.show_crop_menu)
self.crop_menu_button.pack(pady=30)
self.file_menu_button = tk.Button(self.main_menu_frame_right, text="Datei-\neinstellungen", image=self.pixel, compound="c", height=80, width=80, command=self.show_file_menu)
self.file_menu_button.pack(pady=30)
buttons_frame_down = tk.Frame(self.main_menu_frame_down)
buttons_frame_down.pack(pady=10, fill='x', anchor='center')
#Einen Frame erstellen, um die beiden unteren Buttons zentriert auszurichten
buttons_frame_down.grid_columnconfigure(0, weight=1)
buttons_frame_down.grid_columnconfigure(3, weight=1)
# Vollbild beenden Button zentriert im Grid platzieren
self.fullscreen_button = tk.Button(buttons_frame_down, text="Vollbild beenden", command=self.toggle_fullscreen, width=20)
self.fullscreen_button.grid(row=0, column=1, padx=20)
# Zoom Button zentriert im Grid und neben dem Vollbild beenden Button platzieren
self.zoom_button_state = tk.BooleanVar(value=False)
self.zoom_button = tk.Button(buttons_frame_down, text="Zoom", command=self.toggle_zoom, width=20)
self.zoom_button.grid(row=0, column=2, padx=20)
#Widgets des Zuschnittmenüs / Slider für ROI
def crop_menu_widgets(self):
back_button = tk.Button(self.crop_menu_frame_down_right, text="Zurück", image=self.pixel, compound="c", height=40, width=40, command=self.show_main_menu)
back_button.pack(pady=10)
self.crop_width_value = tk.IntVar()
self.crop_width_slider = tk.Scale(self.crop_menu_frame_down, from_=1280, to=0, resolution= 2, orient='horizontal', variable=self.crop_width_value, command=self.update_value, width=30)
self.crop_width_slider.pack(fill='x', padx=50)
self.crop_width_value.set(1280)
self.crop_height_value = tk.IntVar()
self.crop_height_slider = tk.Scale(self.crop_menu_frame_right, from_=800, to=0, resolution= 2, orient='vertical', variable=self.crop_height_value, command=self.update_value, width=30)
self.crop_height_slider.pack(fill='y', expand=True, pady=25, padx=10)
self.crop_height_value.set(800)
def settings_menu_widgets(self):
self.ext_lighting_state = tk.BooleanVar(value=False)
self.ext_lighting_button = tk.Button(self.settings_menu_frame_right, text="Beleuchtung \nPermanent", image=self.pixel, compound="c", height=80, width=80, command=self.toggle_ext_lighting)
self.ext_lighting_button.pack(pady=10)
self.ext_flash_state = tk.BooleanVar(value=False)
self.ext_flash_button = tk.Button(self.settings_menu_frame_right, text="Blitz", image=self.pixel, compound="c", height=80, width=80, command=self.toggle_flash)
self.ext_flash_button.pack(pady=10)
back_button = tk.Button(self.settings_menu_frame_down_right, text="Zurück", image=self.pixel, compound="c", height=40, width=40, command=self.show_main_menu)
back_button.pack(pady=10)
self.show_preview_grid_state = tk.BooleanVar(value=False)
self.show_preview_grid_button = tk.Button(self.settings_menu_frame_right, text="Gitter", image=self.pixel, compound="c", height=80, width=80, command=self.toggle_preview_grid)
self.show_preview_grid_button.pack(pady=30)
buttons_frame = tk.Frame(self.settings_menu_frame_down)
buttons_frame.pack(pady=15, fill='x', expand=True)
# Button für das Lineal
self.show_preview_ruler_state = tk.BooleanVar(value=False)
self.show_preview_ruler_button = tk.Button(buttons_frame, text="Lineal", image=self.pixel, compound="c", height=30, width=80, command=self.toggle_preview_ruler)
self.show_preview_ruler_button.pack(side='left', padx=20)
# Container für Lineal horizontal mit zentrierten Eingabefeldern
horizontal_ruler_frame = tk.Frame(buttons_frame, height=60)
horizontal_ruler_frame.pack(side='left', padx=10)
tk.Label(horizontal_ruler_frame, text="Lineal horizontal", height=1).pack()
# Frame für die Eingabefelder, um sie zentriert unter dem Text zu positionieren
horizontal_ruler_entries_frame = tk.Frame(horizontal_ruler_frame)
horizontal_ruler_entries_frame.pack()
tk.Entry(horizontal_ruler_entries_frame, width=5, textvariable=self.ruler_horizontal_start).pack(side='left', padx=(0, 5)) # Ein wenig Platz zwischen den Feldern
tk.Entry(horizontal_ruler_entries_frame, width=5, textvariable=self.ruler_horizontal_end).pack(side='left')
# Container für Lineal vertikal mit zentrierten Eingabefeldern
vertical_ruler_frame = tk.Frame(buttons_frame, height=60)
vertical_ruler_frame.pack(side='left', padx=10)
tk.Label(vertical_ruler_frame, text="Lineal vertikal", height=1).pack()
# Frame für die Eingabefelder, um sie zentriert unter dem Text zu positionieren
vertical_ruler_entries_frame = tk.Frame(vertical_ruler_frame)
vertical_ruler_entries_frame.pack()
tk.Entry(vertical_ruler_entries_frame, width=5, textvariable=self.ruler_vertical_start).pack(side='left', padx=(0, 5)) # Ein wenig Platz zwischen den Feldern
tk.Entry(vertical_ruler_entries_frame, width=5, textvariable=self.ruler_vertical_end).pack(side='left')
# Button für die Uhrzeit
self.show_preview_clock_state = tk.BooleanVar(value=False)
self.show_preview_clock_button = tk.Button(buttons_frame, text="Uhrzeit", image=self.pixel, compound="c", height=30, width=80, command=self.toggle_preview_clock)
self.show_preview_clock_button.pack(side='left', padx=20)
# Button für die Tastatur
keyboard_button = tk.Button(buttons_frame, text="Tastatur", image=self.pixel, compound="c", height=30, width=80, command=self.toggle_keyboard)
keyboard_button.pack(side='left', padx=20)
def file_menu_widgets(self):
back_button_frame = tk.Frame(self.file_menu_frame_down_right)
back_button_frame.pack(side='right', padx=10, pady=10)
back_button = tk.Button(back_button_frame, text="Zurück", image=self.pixel, compound="c", height=40, width=40, command=self.show_main_menu)
back_button.pack()
text_font = Font(family="Helvetica", size=14)
self.style.configure("LargeCheckbutton.TCheckbutton", font=('Helvetica', 14))
# "Kopiermenü"-Label
label_copy_menu = tk.Label(self.file_menu_frame_right, text="Kopieren - Zielordner", font=self.font_bold)
label_copy_menu.pack(pady=(15, 5))
# "Zielpfad"-Bereich
destination_path_frame = tk.Frame(self.file_menu_frame_right)
destination_path_frame.pack(pady=5, fill='x', padx=10)
self.entry_destination_path = tk.Entry(destination_path_frame, textvariable=self.destination_path_var, font=text_font, width=40)
self.entry_destination_path.pack(side='left')
browse_button_destination = tk.Button(destination_path_frame, text="...", command=lambda: self.choose_directory(self.entry_destination_path))
browse_button_destination.pack(side='left', padx=(5, 0))
# "Kopieren"-Button
button_copy = tk.Button(self.file_menu_frame_right, text="Kopieren", command=self.copy, font=self.font_normal)
button_copy.pack(pady=15)
keyboard_button = tk.Button(self.file_menu_frame_down, text="Tastatur", font=self.font_normal, command=self.toggle_keyboard)
keyboard_button.pack(pady=10)
# Horizontaler Trennstrich
separator = ttk.Separator(self.file_menu_frame_right, orient='horizontal')
separator.pack(fill='x', pady=5)
label_file_menu = tk.Label(self.file_menu_frame_right, text="Dateimenü", font=self.font_bold)
label_file_menu.pack(pady=(5, 5))
# "Speicherpfad"-Bereich
label_storage_path = tk.Label(self.file_menu_frame_right, text="Speicherpfad", font=self.font_normal)
label_storage_path.pack(pady=5)
storage_path_frame = tk.Frame(self.file_menu_frame_right)
storage_path_frame.pack(pady=5, fill='x', padx=10) # padx=10 fügt Abstand zum linken Rand hinzu
self.entry_storage_path = tk.Entry(storage_path_frame, textvariable=self.storage_path_var, font=text_font, width=40) # Reduzierte Breite
self.entry_storage_path.pack(side='left')
browse_button_storage = tk.Button(storage_path_frame, text="...", command=lambda: self.choose_directory(self.entry_storage_path))
browse_button_storage.pack(side='left', padx=(5, 0))
# "Dateiname"-Label und Eingabefeld
label_filename = tk.Label(self.file_menu_frame_right, text="Dateiname", font=self.font_normal)
label_filename.pack(pady=5)
self.entry_filename = tk.Entry(self.file_menu_frame_right, textvariable=self.filename_var, font=text_font, width=40)
self.entry_filename.pack(pady=5)
# Gemeinsamer Rahmen für Datum- und Uhrzeit-Checkboxen
datetime_frame = tk.Frame(self.file_menu_frame_right)
datetime_frame.pack(pady=5, fill='x')
# Datum-Checkbox und Label
date_frame = tk.Frame(datetime_frame)
date_frame.pack(side='left', expand=True, padx=5)
checkbox_date = ttk.Checkbutton(date_frame, text="Datum", style="LargeCheckbutton.TCheckbutton", variable=self.date_var)
checkbox_date.grid(row=0, column=0, sticky="w")
date_label = tk.Label(date_frame, text="", font=self.font_normal)
date_label.grid(row=0, column=1, sticky="w")
# Uhrzeit-Checkbox und Label
time_frame = tk.Frame(datetime_frame)
time_frame.pack(side='left', expand=True, padx=5)
checkbox_time = ttk.Checkbutton(time_frame, text="Uhrzeit", style="LargeCheckbutton.TCheckbutton", variable=self.time_var)
checkbox_time.grid(row=0, column=0, sticky="w")
time_label = tk.Label(time_frame, text="", font=self.font_normal)
time_label.grid(row=0, column=1, sticky="w")
#Methode, um die Daten vom Arbeitspfad in den Zielordner zu kopieren
def copy(self):
try:
if not os.path.exists(self.destination_path_var.get()):
os.makedirs(self.destination_path_var.get())
for item in os.listdir(self.storage_path_var.get()):
source_item = os.path.join(self.storage_path_var.get(), item)
destination_item = os.path.join(self.destination_path_var.get(), item)
if os.path.isdir(source_item):
shutil.copytree(source_item, destination_item, dirs_exist_ok=True)
else:
shutil.copy2(source_item, destination_item)
self.show_success_message("Kopieren erfolgreich", "Alle Inhalte wurden erfolgreich kopiert.")
except Exception as e:
messagebox.showerror("Fehler", f"Ein Fehler ist aufgetreten: {e}")
#Methode, um ein Nachrichtenfenster anzuzeigen
def show_success_message(self, title, message):
msg_window = tk.Toplevel(self.root)
msg_window.title(title)
msg_window.geometry("300x100")
tk.Label(msg_window, text=message).pack(pady=10)
# OK-Button zum Schließen des Fensters
ok_button = tk.Button(msg_window, text="OK", command=msg_window.destroy)
ok_button.pack(pady=5)
# Fenster automatisch nach 5 Sekunden schließen
msg_window.after(5000, msg_window.destroy)
#Aktuell aktives Menü schließen
def close_menu(self):
self.current_menu_right.grid_remove()
self.current_menu_down.grid_remove()
self.current_menu_down_right.grid_remove()
#Aktuell aktives Menü öffnen (z.B. nachdem der Button ">", "<" gedrückt wurde)
def open_menu(self):
self.current_menu_right.grid()
self.current_menu_down.grid()
self.current_menu_down_right.grid()
self.update_aspect_ratio()
def show_main_menu(self):
self.close_menu()
self.current_menu_right = self.main_menu_frame_right
self.current_menu_down = self.main_menu_frame_down
self.current_menu_down_right = self.main_menu_frame_down_right
self.open_menu()
def show_crop_menu(self):
self.close_menu()
self.current_menu_right = self.crop_menu_frame_right
self.current_menu_down = self.crop_menu_frame_down
self.current_menu_down_right = self.crop_menu_frame_down_right
self.open_menu()
def show_settings_menu(self):
self.close_menu()
self.current_menu_right = self.settings_menu_frame_right
self.current_menu_down = self.settings_menu_frame_down
self.current_menu_down_right = self.settings_menu_frame_down_right
self.open_menu()
def show_file_menu(self):
self.close_menu()
self.current_menu_right = self.file_menu_frame_right
self.current_menu_down = self.file_menu_frame_down
self.current_menu_down_right = self.file_menu_frame_down_right
self.open_menu()
#Methode, um den File-Browser zu öffnen
def choose_directory(self, entry_field):
folder_selected = filedialog.askdirectory()
if folder_selected:
entry_field.delete(0, tk.END)
entry_field.insert(0, folder_selected)
#Methode wird ausgeführt, sobald sich der Wert in einem der Eingabefelder geändert hat
def entry_fields_changed(self, var_name, *args):
self.still_image_processing.update_file_parameters(self.filename_var.get(), self.storage_path_var.get(),
self.destination_path_var.get(), self.date_var.get(),
self.time_var.get())
self.ip.update_ruler_values(int(self.ruler_vertical_start.get()), int(self.ruler_vertical_end.get()),
int(self.ruler_horizontal_start.get()), int(self.ruler_horizontal_end.get()))
self.still_image_processing.get_ruler_settings(int(self.ruler_vertical_start.get()), int(self.ruler_vertical_end.get()),
int(self.ruler_horizontal_start.get()), int(self.ruler_horizontal_end.get()))
#Blendet das Menü ein bzw. aus
def toggle_menu(self):
if self.current_menu_right and self.current_menu_right.grid_info():
self.root.grid_columnconfigure(1, minsize=0)
self.close_menu()
self.update_aspect_ratio()
self.video_widget.grid(row=0, column=0, sticky="nsew")
self.hide_menu_button.config(text="<")
else:
if not self.current_menu_right:
self.current_menu_right = self.main_menu_frame_right
self.current_menu_down = self.main_menu_frame_down
self.current_menu_down_right = self.main_menu_frame_down_right
self.root.grid_columnconfigure(1, minsize=30)
self.open_menu()
self.menu_width = self.current_menu_right.winfo_width()
self.video_widget.grid(row=0, column=0, sticky="nsew")
self.hide_menu_button.config(text=">")
#Methode um die ROI-Werte an die Bildbearbeitung zu übergeben
def update_value(self, value):
self.preview_processing.update_roi(self.crop_width_value.get(), self.crop_height_value.get())
self.still_image_processing.update_roi(self.crop_width_value.get(), self.crop_height_value.get())
#Externe Beleuchtung permanent ein- / ausschalten
def toggle_ext_lighting(self):
self.ext_lighting_state.set(not self.ext_lighting_state.get())
self.update_button_appearance(self.ext_lighting_button, self.ext_lighting_state)
if self.ext_lighting_state.get():
self.permanent_lighting = True
else:
self.permanent_lighting = False
self.gpio_instance.get_gui_settings(self.activate_flash, self.permanent_lighting)
#Blitz ein- / ausschalten
def toggle_flash(self):
self.ext_flash_state.set(not self.ext_flash_state.get())
self.update_button_appearance(self.ext_flash_button, self.ext_flash_state)
if self.ext_flash_state.get():
self.activate_flash = True
else:
self.activate_flash = False
self.gpio_instance.get_gui_settings(self.activate_flash, self.permanent_lighting)
#Button Gitter ein- aus
def toggle_preview_grid(self):
self.show_preview_grid_state.set(not self.show_preview_grid_state.get())
self.update_button_appearance(self.show_preview_grid_button, self.show_preview_grid_state)
self.send_settings()
#Button Lineal ein- aus
def toggle_preview_ruler(self):
self.show_preview_ruler_state.set(not self.show_preview_ruler_state.get())
self.update_button_appearance(self.show_preview_ruler_button, self.show_preview_ruler_state)
self.send_settings()
#Button Uhr ein - aus
def toggle_preview_clock(self):
self.show_preview_clock_state.set(not self.show_preview_clock_state.get())
self.update_button_appearance(self.show_preview_clock_button, self.show_preview_clock_state)
self.send_settings()
#Button Zoom ein- aus
def toggle_zoom(self):
self.zoom_button_state.set(not self.zoom_button_state.get())
self.update_button_appearance(self.zoom_button, self.zoom_button_state)
self.change_preview_resolution()
self.send_settings()
#Aussehen der Toggle-Buttons variieren
def update_button_appearance(self, button, state_var):
if state_var.get():
button.config(relief="sunken", bg="dimgray") # Eingeschalteter Zustand
else:
button.config(relief="raised", bg="lightgray") # Ausgeschalteter Zustand
#Einstellungen der Toggle-Buttons an die bildverarbeitenden Instanzen übergeben
def send_settings(self):
self.preview_processing.get_settings(self.show_preview_grid_state.get(), self.show_preview_ruler_state.get(),
self.show_preview_clock_state.get(), self.zoom_button_state.get())
self.still_image_processing.update_picture_parameters(self.show_preview_clock_state.get(),
self.show_preview_ruler_state.get())
#Seitenverhältnis anpassen, sodass das video_widget sich entsprechend der ROI anpasst
def update_aspect_ratio(self):
self.root.update_idletasks()
self.video_widget_width = self.video_widget.winfo_width()
self.video_widget_height = self.video_widget.winfo_height()
self.video_widget_aspect_ratio = self.video_widget_width / self.video_widget_height
if self.video_widget_aspect_ratio is None:
self.new_width = 1280
self.new_height = 800
else:
self.new_width = self.video_widget_width
self.new_height = int(self.new_width / self.camera_aspect_ratio)
#Tastatur ein- aus
def toggle_keyboard(self):
if self.keyboard_process:
self.keyboard_process.terminate()
self.keyboard_process = None
else:
self.keyboard_process = subprocess.Popen(['matchbox-keyboard'])
#Vorschaubild im video_widget aktualisieren
def update_video_widget(self):
image = self.camera.get_preview_image() #Bild aus der Kamera holen
image = self.preview_processing.get_preview_image(image) # Weitere Bildverarbeitung
#Abfragen, falls das Bild in einem unerwarteten Format auftritt
if image.shape[2] not in [3, 4]:
raise ValueError("Das Bild muss 3 (RGB) oder 4 (RGBA) Farbkanäle haben")
if image.dtype != np.uint8:
image = (image - image.min()) / (image.max() - image.min()) * 255.0
image = image.astype(np.uint8)
# Erzeugen des PIL-Bildes aus dem Array und Skalieren auf die neue Größe
resized_image = PIL.Image.fromarray(image).resize((self.new_width, self.new_height), PIL.Image.ANTIALIAS)
self.preview_image = PIL.ImageTk.PhotoImage(image=resized_image)
self.video_widget.create_image(0, 0, image=self.preview_image, anchor=tk.NW)
self.root.after(self.video_feed_delay, self.update_video_widget)
#Erhöhen der Vorschauauflösung, um den gezoomten Bereich klarer darzustellen
def change_preview_resolution(self):
self.camera.get_highres_preview()
#Vollbild ein- ausschalten
def toggle_fullscreen(self):
if self.fullscreen_active == True:
self.root.attributes('-fullscreen', False)
self.fullscreen_button.config(text="Vollbild")
self.fullscreen_active = False
else:
self.root.attributes('-fullscreen', True)
self.fullscreen_button.config(text="Vollbild beenden")
self.fullscreen_active = True

View File

@ -0,0 +1,335 @@
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

28
1280 x 800/Main.py Normal file
View File

@ -0,0 +1,28 @@
import threading
import Camera
import Gui_Grid
import Image_Processing
import gpio_control
import Still_Image_Processing
#Instanz zur Endbild-Bearbeitung starten
still_image_processing = Still_Image_Processing.Still_Image_Processing()
#Kamerainstanz starten
camera = Camera.Camera(still_image_processing)
camera.start_camera()
#Instanz zur Vorschaubild-Bearbeitung starten
preview_processing = Image_Processing.Image_Processing()
#GPIO-Überwachung starten
gpio_instance = gpio_control.GPIOControl(camera)
gpio_thread = threading.Thread(target=gpio_instance.start)
gpio_thread.start()
#Benutzeroberfläche starten
gui = Gui_Grid.User_Interface(camera, preview_processing, gpio_instance, still_image_processing)
gui.window_settings()
gui.build_widgets()
gui.update_video_widget()
gui.start_interface()

View File

@ -0,0 +1,266 @@
import cv2
import numpy as np
import math
import datetime
import os
class Still_Image_Processing():
def __init__(self):
#Einstellungen für den Dateinamen / Kopiermenü
self.file_name_gui = ""
self.storage_path = "/home/pi"
self.copy_path = ""
self.filename_add_date = False
self.filename_add_time = False
self.filename = ""
#Flags, ob die Zeit bzw. das Lineal im finalen Bild zu sehen sind
self.add_time = False
self.add_ruler = False
#Parameter des Vorschaubilds und des finalen Bilds, werden zur Skalierung benötigt
self.preview_width = 1280
self.preview_height = 800
self.preview_width_roi = 1280
self.preview_height_roi = 800
self.still_width_roi = 4056
self.still_height_roi = 3040
self.still_width = 4056
self.still_height = 3040
#Parameter für ROI
self.still_width_offset = 0
self.still_height_offset = 0
#Einstellungen für die Uhr
self.font = cv2.FONT_HERSHEY_SIMPLEX
self.font_size = 1
self.font_color = (0, 0, 0, 255)
self.background_color = (255, 255, 255, 255)
self.text_thickness = 2
self.line_type = cv2.LINE_AA
#Einstellungen für das Lineal
self.horizontal_ruler_start = 0
self.horizontal_ruler_end = 100
self.vertical_ruler_start = 0
self.vertical_ruler_end = 50
self.ruler_thickness = 80
self.ruler_font_size = 2
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.still_image = None
#Einstellungen für Dateinamen werden durch die Benutzeroberfläche definiert
def update_file_parameters(self, file_name, storage_path, copy_path, filename_add_date, filename_add_time):
self.file_name_gui = file_name
self.storage_path = storage_path
self.copy_path = copy_path
self.filename_add_date = filename_add_date
self.filename_add_time = filename_add_time
#Bildung des Dateinamens. Standard ist "Bild-{Nummerierung}". Ggf. kann das Bild mit Datum / Zeit versehen werden oder einen individuellen Namen erhalten
def update_filename(self):
base_name = self.file_name_gui.rstrip(".png") if self.file_name_gui else "Bild"
time_date_now = datetime.datetime.now()
if self.filename_add_date:
base_name += time_date_now.strftime("_%Y-%m-%d")
if self.filename_add_time:
base_name += time_date_now.strftime("_%H-%M-%S")
final_name = base_name
counter = 1
while os.path.exists(os.path.join(self.storage_path, final_name + ".png")): #Wenn Bild mit dem Namen bereits existiert, wird der Zähler erhöht, bis kein Bild gefunden wurde
final_name = f"{base_name}-{counter}"
counter += 1
self.file_name = os.path.join(self.storage_path, final_name + ".png")
#Methode zum Speichern des Bildes
def save_picture(self):
self.update_filename()
rgb_image = cv2.cvtColor(self.still_image, cv2.COLOR_BGR2RGB) #Konvertierung von BGR zu RGB erforderlich
cv2.imwrite(self.file_name, rgb_image)
#Es wird aus der GUI übergeben, ob die Uhr bzw. das Lineal angezeigt werden sollen
def update_picture_parameters(self, add_time_to_image, add_ruler_to_image):
self.add_time = add_time_to_image
self.add_ruler = add_ruler_to_image
#Aktualisieren der ROI und der erforderlichen Parameter
def update_roi(self, roi_width, roi_height):
self.preview_width_roi = roi_width
self.preview_height_roi = roi_height
self.roi_width_factor = self.preview_width_roi / self.preview_width
self.roi_height_factor = self.preview_height_roi / self.preview_height
self.still_width_roi = round(self.roi_width_factor * self.still_width)
self.still_height_roi = round(self.roi_height_factor * self.still_height)
self.still_width_offset = round(((self.still_width - self.still_width_roi) / 2))
self.still_height_offset = round(((self.still_height - self.still_height_roi) / 2))
#Uhrzeit im Bild einblenden
def add_time_to_image(self):
if self.add_time == True:
timestamp = datetime.datetime.now().strftime("%H:%M")
image_height, image_width = self.still_image.shape[:2]
# Abstand vom linken Rand abhängig vom Lineal
margin_x = self.ruler_thickness + 5 if self.add_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
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)
cv2.rectangle(self.still_image, background_l, background_r, self.background_color, -1)
cv2.putText(self.still_image, timestamp, text_position, self.font, font_scale, self.font_color, self.text_thickness, self.line_type)
#Einstellungen des Lineals aus der Benutzeroberfläche laden
def get_ruler_settings(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
#Erforderliche Einstellungen des Lineals bearbeiten, dieses passt sich an den linken bzw. oberen Bildrand an
def update_ruler_settings(self):
self.ruler_scaling_width = self.still_image.shape[1] / self.still_width
self.ruler_scaling_height = self.still_image.shape[0] / self.still_height
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
self.px_mm_horizontal = math.ceil(self.still_image.shape[1] / self.ruler_horizontal_end)
self.px_mm_vertical = math.ceil(self.still_image.shape[0] / self.ruler_vertical_end)
def add_ruler_to_image(self):
if self.add_ruler:
self.update_ruler_settings()
# Initialisierung des horizontalen Lineals am oberen Rand als RGB-Bild
self.ruler_horizontal = np.zeros((self.ruler_thickness, self.still_image.shape[1], 3), dtype=np.uint8)
self.ruler_horizontal[:, :] = self.ruler_color[:3]
self.ruler_vertical = np.zeros((self.still_image.shape[0], self.ruler_thickness, 3), dtype=np.uint8)
self.ruler_vertical[:, :] = self.ruler_color[:3]
# Markierungen und Beschriftungen zum horizontalen Lineal hinzufügen
for pos in range(self.ruler_thickness, self.still_image.shape[1], self.px_mm_horizontal * 5):
is_thick_line = (pos-self.ruler_thickness) % (self.px_mm_horizontal * 10) == 0
line_thickness = 2 if is_thick_line else 1
cv2.line(self.ruler_horizontal, (pos, 0), (pos, self.ruler_thickness), self.ruler_line_color, line_thickness)
if is_thick_line:
text = f"{int((pos-self.ruler_thickness) / self.px_mm_horizontal)}"
text_x = pos + self.ruler_thickness // 2 - cv2.getTextSize(text, self.ruler_font, self.ruler_font_size, 1)[0][0] // 2
text_y = self.ruler_thickness - 5
cv2.putText(self.ruler_horizontal, text, (text_x, text_y), self.ruler_font, self.ruler_font_size, self.ruler_text_color, 1)
# Markierungen und Beschriftungen zum vertikalen Lineal hinzufügen
for pos in range(self.ruler_thickness, self.still_image.shape[0], self.px_mm_vertical * 5):
is_thick_line = (pos-self.ruler_thickness) % (self.px_mm_vertical * 10) == 0
line_thickness = 2 if is_thick_line else 1
cv2.line(self.ruler_vertical, (0, 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, self.ruler_font_size, self.ruler_text_color, 1)
# Anbringen des horizontalen Lineals am oberen Rand des Bildes
self.still_image[:self.ruler_thickness, :] = self.ruler_horizontal
# Anbringen des vertikalen Lineals am linken Rand des Bildes
self.still_image[:, :self.ruler_thickness] = self.ruler_vertical
#Alle Schritte zur Bearbeitung des finalen Bildes
def process_image(self, image):
self.still_image = image
self.crop_image()
self.add_time_to_image()
self.add_ruler_to_image()
self.save_picture()
#Der in der ROI definierten Bildbereich extrahieren
def crop_image(self):
if self.still_width_roi != self.still_width or self.still_height_roi != self.still_height:
self.still_image = self.still_image[self.still_height_offset:(self.still_height - self.still_height_offset),
self.still_width_offset:(self.still_width - self.still_width_offset), :]

View File

@ -0,0 +1,74 @@
import RPi.GPIO as GPIO
import time
import threading
class GPIOControl:
def __init__(self, camera):
GPIO.setmode(GPIO.BCM)
self.camera = camera
#Verzögerung im Ein- und Ausschalten der Beleuchtung
self.lighting_delay = 100 / 1000
# Pin-Definitionen
self.lighting_trigger_gpio = 17 #Schalter für Beleuchtung Ein/Aus
self.camera_trigger_gpio = 27 #Schalter für Kamera auslösen
self.lighting_output_gpio = 23 #Ausgang für Beleuchtung
self.activate_flash = False
self.permanent_lighting = False
# Pin-Setup
GPIO.setup(self.lighting_trigger_gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(self.camera_trigger_gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(self.lighting_output_gpio, GPIO.OUT, initial=GPIO.LOW)
self.camera_trigger_time = None
self.running = True
def start(self):
# Startet die Überwachung in einem separaten Thread, ist wegen der blockierenden Ausführung der Benutzeroberfläche erforderlich
self.thread = threading.Thread(target=self.monitor_gpio)
self.thread.start()
def stop(self):
self.running = False
self.thread.join()
#Blitz / Dauerbeleuchtung an/aus wird durch GUI eingestellt
def get_gui_settings(self, activate_flash, permanent_lighting):
self.activate_flash = activate_flash
self.permanent_lighting = permanent_lighting
self.activate_lighting()
self.deactivate_lighting()
def activate_lighting(self):
if self.activate_flash == True or self.permanent_lighting == True:
GPIO.output(self.lighting_output_gpio, GPIO.HIGH)
def deactivate_lighting(self):
if self.permanent_lighting == False:
GPIO.output(self.lighting_output_gpio, GPIO.LOW)
def monitor_gpio(self):
try:
while self.running:
if GPIO.input(self.lighting_trigger_gpio) == GPIO.LOW:
self.activate_lighting()
if GPIO.input(self.camera_trigger_gpio) == GPIO.LOW:
self.activate_lighting()
time.sleep(self.lighting_delay)
self.camera.get_still_image()
time.sleep(self.lighting_delay)
self.deactivate_lighting()
else:
if GPIO.input(self.camera_trigger_gpio) == GPIO.HIGH and GPIO.input(self.lighting_trigger_gpio) == GPIO.HIGH:
self.deactivate_lighting()
time.sleep(0.1)
finally:
GPIO.cleanup()

55
800x480/Camera.py Normal file
View File

@ -0,0 +1,55 @@
from picamera2 import Picamera2, Preview
import PIL.Image, PIL.ImageTk
class Camera():
def __init__(self, still_image_processing):
#Auflösung des Vorschaubildes
self.preview_width = 800
self.preview_height = 480
#Auflösung des finalen Bildes
self.still_width = 4056
self.still_height = 3040
self.camera = Picamera2()
#Low-Resolution-Config, für flüssigeres Vorschaubild
self.lowres_preview_config = self.camera.create_preview_configuration(main={"size": (self.preview_width, self.preview_height)})
#Einstellung für Vorschaubild in höherer AUflsung (für Zoom)
self.highres_preview_config = self.camera.create_preview_configuration(main={"size": (2028, 1080)})
#Einstellung für finales Bild, maximale Auflösung
self.capture_config = self.camera.create_still_configuration(main={"size": (self.still_width, self.still_height)})
self.preview_config = self.lowres_preview_config
self.still_image_processing = still_image_processing
self.set_highres_preview_config = False
def start_camera(self):
self.camera.configure(self.preview_config)
self.camera.start()
def get_preview_image(self):
image = self.camera.capture_array()
return image
#Kameravorschau auf höhere Auflösung umstellen
def get_highres_preview(self):
self.camera.stop()
if self.set_highres_preview_config == False:
self.preview_config = self.highres_preview_config
self.set_highres_preview_config = True
else:
self.preview_config = self.lowres_preview_config
self.set_highres_preview_config = False
self.camera.configure(self.preview_config)
self.camera.start()
def get_still_image(self):
image = self.camera.switch_mode_and_capture_array(self.capture_config)
self.still_image_processing.process_image(image)
def get_aspect_ratio(self):
preview_ratio = self.preview_width / self.preview_height
return preview_ratio

638
800x480/Gui_Grid.py Normal file
View File

@ -0,0 +1,638 @@
import tkinter as tk
import PIL.Image, PIL.ImageTk
import numpy as np
from tkinter import ttk
from tkinter.font import Font
from tkinter import filedialog, messagebox
import subprocess
import os
import shutil
class User_Interface():
def __init__(self, camera, preview_processing, gpio_instance, still_image_processing):
#Instanzvariablen laden
self.still_image_processing = still_image_processing
self.preview_processing = preview_processing
self.gpio_instance = gpio_instance
self.camera = camera
self.root = tk.Tk()
#Verzögerung des Vorschaubildes
self.video_feed_delay = 5
#Seitenverhältnis der Kamera und des Widgets auslesen, zur Platzierung des Vorschaubildes in diesem
self.camera_aspect_ratio = self.camera.get_aspect_ratio()
self.video_widget_aspect_ratio = None
self.new_width = 800
self.new_height = 480
#Flags für das derzeit aktive Menü
self.current_menu_right = None
self.current_menu_down = None
self.current_menu_down_right = None
self.submenu = False
#Ein leerer Pixel, wird zur quadratischen Form der Buttons benötigt
self.pixel = tk.PhotoImage()
self.keyboard_process = None
#Parameter des Lineals, mit Aktion verknüpft, sobald der Wert im Eingabefeld geändert wird
self.ruler_vertical_start = tk.StringVar()
self.ruler_vertical_end = tk.StringVar()
self.ruler_horizontal_start = tk.StringVar()
self.ruler_horizontal_end = tk.StringVar()
self.ruler_vertical_start.set(0)
self.ruler_vertical_end.set(50)
self.ruler_horizontal_start.set(0)
self.ruler_horizontal_end.set(100)
self.ruler_vertical_start.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_vertical_start"))
self.ruler_vertical_end.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_vertical_end"))
self.ruler_horizontal_start.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_horizontal_start"))
self.ruler_horizontal_end.trace_add("write", lambda var, indx, mode: self.entry_fields_changed("ruler_horizontal_end"))
#Blitz / permanente Beleuchtung an / aus
self.permanent_lighting = False
self.activate_flash = False
#Vollbild an / aus
self.fullscreen_active = True
# Definiere Schriftarten
self.font_bold = Font(family="Helvetica", size=12, weight="bold")
self.font_normal = Font(family="Helvetica", size=12)
# Checkbox Größe anpassen
self.style = ttk.Style()
self.style.configure("LargeCheckbutton.TCheckbutton", font=self.font_normal, size=25)
#Variablen für den Dateinamen, ebenfalls mit Aktion verknüpft
self.storage_path_var = tk.StringVar(name="storage_path_var")
self.storage_path_var.set("/home/pi")
self.destination_path_var = tk.StringVar(name="destination_path_var")
self.filename_var = tk.StringVar(name="filename_var")
self.date_var = tk.BooleanVar(name="date_var")
self.time_var = tk.BooleanVar(name="time_var")
self.date_var.set(True)
self.time_var.set(True)
self.storage_path_var.trace_add("write", lambda *args: self.entry_fields_changed("Speicherpfad", *args))
self.destination_path_var.trace_add("write", lambda *args: self.entry_fields_changed("Zielpfad", *args))
self.filename_var.trace_add("write", lambda *args: self.entry_fields_changed("Dateiname", *args))
self.date_var.trace_add("write", lambda *args: self.entry_fields_changed("Datum", *args))
self.time_var.trace_add("write", lambda *args: self.entry_fields_changed("Uhrzeit", *args))
#GUI soll im Vollbild starten
def window_settings(self):
self.root.attributes('-fullscreen', True)
self.root.title('Titel')
def start_interface(self):
self.root.mainloop()
#Erstellen der Widgets aller Menüs
def build_widgets(self):
self.build_video_widget()
self.build_hide_menu_button()
self.main_menu_frame()
self.crop_menu_frame()
self.settings_menu_frame()
self.file_menu_frame()
self.main_menu_widgets()
self.crop_menu_widgets()
self.settings_menu_widgets()
self.file_menu_widgets()
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, minsize=0)
def build_video_widget(self):
self.video_widget = tk.Canvas(self.root, width=800, height=480, highlightthickness=0)
self.video_widget.grid(row=0, column=0, sticky="nsew")
#Button "<", ">" zum Anzeigen / Verstecken des Menüs
def build_hide_menu_button(self):
self.hide_menu_button_container = tk.Frame(self.video_widget, background="white", bd=0, highlightthickness=0)
self.hide_menu_button_container.place(relx=1.0, rely=0.0, anchor="ne")
self.hide_menu_button_settings = {
"master": self.hide_menu_button_container,
"image": self.pixel,
"compound": "c",
"height": 30,
"width": 30,
"command": self.toggle_menu
}
self.hide_menu_button = tk.Button(**self.hide_menu_button_settings, text="<")
self.hide_menu_button.pack(side="right", padx=0, pady=0)
#Frames des Hauptmenüs, alle Menüs teilen sich in 3 Frames auf: Rechts vom Vorschaubild, Unter dem Vorschaubild sowie ein Frame unten rechts zwischen dem rechten und dem unteren Frame (2x2-Raster)
def main_menu_frame(self):
self.main_menu_frame_right = tk.Frame(self.root)
self.main_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.main_menu_frame_down = tk.Frame(self.root)
self.main_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.main_menu_frame_down_right = tk.Frame(self.root)
self.main_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.main_menu_frame_right.grid_remove()
self.main_menu_frame_down.grid_remove()
self.main_menu_frame_down_right.grid_remove()
#Frames des Zuschnitt-Menüs
def crop_menu_frame(self):
self.crop_menu_frame_right = tk.Frame(self.root)
self.crop_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.crop_menu_frame_down = tk.Frame(self.root)
self.crop_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.crop_menu_frame_down_right = tk.Frame(self.root)
self.crop_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.crop_menu_frame_right.grid_remove()
self.crop_menu_frame_down.grid_remove()
self.crop_menu_frame_down_right.grid_remove()
#Frames des Bildeinstellungsmenüs
def settings_menu_frame(self):
self.settings_menu_frame_right = tk.Frame(self.root)
self.settings_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.settings_menu_frame_down = tk.Frame(self.root)
self.settings_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.settings_menu_frame_down_right = tk.Frame(self.root)
self.settings_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.settings_menu_frame_right.grid_remove()
self.settings_menu_frame_down.grid_remove()
self.settings_menu_frame_down_right.grid_remove()
#Frames des Dateimenüs
def file_menu_frame(self):
self.file_menu_frame_right = tk.Frame(self.root)
self.file_menu_frame_right.grid(row=0, column=1, sticky="nsew")
self.file_menu_frame_down = tk.Frame(self.root)
self.file_menu_frame_down.grid(row=1, column=0, sticky="nsew")
self.file_menu_frame_down_right = tk.Frame(self.root)
self.file_menu_frame_down_right.grid(row=1, column=1, sticky="nsew")
self.file_menu_frame_right.grid_remove()
self.file_menu_frame_down.grid_remove()
self.file_menu_frame_down_right.grid_remove()
#Widgets für das Hauptmenü erstellen
def main_menu_widgets(self):
self.settings_menu_button = tk.Button(self.main_menu_frame_right, text="Bild-\neinstellungen", image=self.pixel, compound="c", height=80, width=80, command=self.show_settings_menu)
self.settings_menu_button.pack(pady=10)
self.crop_menu_button = tk.Button(self.main_menu_frame_right, text="Zuschnitt", image=self.pixel, compound="c", height=80, width=80, command=self.show_crop_menu)
self.crop_menu_button.pack(pady=30)
self.file_menu_button = tk.Button(self.main_menu_frame_right, text="Datei-\neinstellungen", image=self.pixel, compound="c", height=80, width=80, command=self.show_file_menu)
self.file_menu_button.pack(pady=30)
buttons_frame_down = tk.Frame(self.main_menu_frame_down)
buttons_frame_down.pack(pady=10, fill='x', anchor='center')
#Einen Frame erstellen, um die beiden unteren Buttons zentriert auszurichten
buttons_frame_down.grid_columnconfigure(0, weight=1)
buttons_frame_down.grid_columnconfigure(3, weight=1)
# Vollbild beenden Button zentriert im Grid platzieren
self.fullscreen_button = tk.Button(buttons_frame_down, text="Vollbild beenden", command=self.toggle_fullscreen, width=20)
self.fullscreen_button.grid(row=0, column=1, padx=20)
# Zoom Button zentriert im Grid und neben dem Vollbild beenden Button platzieren
self.zoom_button_state = tk.BooleanVar(value=False)
self.zoom_button = tk.Button(buttons_frame_down, text="Zoom", command=self.toggle_zoom, width=20)
self.zoom_button.grid(row=0, column=2, padx=20)
#Widgets des Zuschnittmenüs / Slider für ROI
def crop_menu_widgets(self):
back_button = tk.Button(self.crop_menu_frame_down_right, text="Zurück", image=self.pixel, compound="c", height=40, width=40, command=self.show_main_menu)
back_button.pack(pady=10)
self.crop_width_value = tk.IntVar()
self.crop_width_slider = tk.Scale(self.crop_menu_frame_down, from_=800, to=0, resolution= 2, orient='horizontal', variable=self.crop_width_value, command=self.update_value, width=30)
self.crop_width_slider.pack(fill='x', padx=50)
self.crop_width_value.set(800)
self.crop_height_value = tk.IntVar()
self.crop_height_slider = tk.Scale(self.crop_menu_frame_right, from_=480, to=0, resolution= 2, orient='vertical', variable=self.crop_height_value, command=self.update_value, width=30)
self.crop_height_slider.pack(fill='y', expand=True, pady=25, padx=10)
self.crop_height_value.set(480)
def settings_menu_widgets(self):
self.ext_lighting_state = tk.BooleanVar(value=False)
self.ext_lighting_button = tk.Button(self.settings_menu_frame_right, text="Beleuchtung \nPermanent", image=self.pixel, compound="c", height=80, width=80, command=self.toggle_ext_lighting)
self.ext_lighting_button.pack(pady=10)
self.ext_flash_state = tk.BooleanVar(value=False)
self.ext_flash_button = tk.Button(self.settings_menu_frame_right, text="Blitz", image=self.pixel, compound="c", height=80, width=80, command=self.toggle_flash)
self.ext_flash_button.pack(pady=10)
back_button = tk.Button(self.settings_menu_frame_down_right, text="Zurück", image=self.pixel, compound="c", height=40, width=40, command=self.show_main_menu)
back_button.pack(pady=10)
self.show_preview_grid_state = tk.BooleanVar(value=False)
self.show_preview_grid_button = tk.Button(self.settings_menu_frame_right, text="Gitter", image=self.pixel, compound="c", height=80, width=80, command=self.toggle_preview_grid)
self.show_preview_grid_button.pack(pady=30)
buttons_frame = tk.Frame(self.settings_menu_frame_down)
buttons_frame.pack(pady=15, fill='x', expand=True)
# Button für das Lineal
self.show_preview_ruler_state = tk.BooleanVar(value=False)
self.show_preview_ruler_button = tk.Button(buttons_frame, text="Lineal", image=self.pixel, compound="c", height=30, width=80, command=self.toggle_preview_ruler)
self.show_preview_ruler_button.pack(side='left', padx=20)
# Container für Lineal horizontal mit zentrierten Eingabefeldern
horizontal_ruler_frame = tk.Frame(buttons_frame, height=60)
horizontal_ruler_frame.pack(side='left', padx=10)
tk.Label(horizontal_ruler_frame, text="Lineal horizontal", height=1).pack()
# Frame für die Eingabefelder, um sie zentriert unter dem Text zu positionieren
horizontal_ruler_entries_frame = tk.Frame(horizontal_ruler_frame)
horizontal_ruler_entries_frame.pack()
tk.Entry(horizontal_ruler_entries_frame, width=5, textvariable=self.ruler_horizontal_start).pack(side='left', padx=(0, 5)) # Ein wenig Platz zwischen den Feldern
tk.Entry(horizontal_ruler_entries_frame, width=5, textvariable=self.ruler_horizontal_end).pack(side='left')
# Container für Lineal vertikal mit zentrierten Eingabefeldern
vertical_ruler_frame = tk.Frame(buttons_frame, height=60)
vertical_ruler_frame.pack(side='left', padx=10)
tk.Label(vertical_ruler_frame, text="Lineal vertikal", height=1).pack()
# Frame für die Eingabefelder, um sie zentriert unter dem Text zu positionieren
vertical_ruler_entries_frame = tk.Frame(vertical_ruler_frame)
vertical_ruler_entries_frame.pack()
tk.Entry(vertical_ruler_entries_frame, width=5, textvariable=self.ruler_vertical_start).pack(side='left', padx=(0, 5)) # Ein wenig Platz zwischen den Feldern
tk.Entry(vertical_ruler_entries_frame, width=5, textvariable=self.ruler_vertical_end).pack(side='left')
# Button für die Uhrzeit
self.show_preview_clock_state = tk.BooleanVar(value=False)
self.show_preview_clock_button = tk.Button(buttons_frame, text="Uhrzeit", image=self.pixel, compound="c", height=30, width=80, command=self.toggle_preview_clock)
self.show_preview_clock_button.pack(side='left', padx=20)
# Button für die Tastatur
keyboard_button = tk.Button(buttons_frame, text="Tastatur", image=self.pixel, compound="c", height=30, width=80, command=self.toggle_keyboard)
keyboard_button.pack(side='left', padx=20)
def file_menu_widgets(self):
back_button_frame = tk.Frame(self.file_menu_frame_down_right)
back_button_frame.pack(side='right', padx=10, pady=10)
back_button = tk.Button(back_button_frame, text="Zurück", image=self.pixel, compound="c", height=40, width=40, command=self.show_main_menu)
back_button.pack()
text_font = Font(family="Helvetica", size=14)
self.style.configure("LargeCheckbutton.TCheckbutton", font=('Helvetica', 14))
# "Kopiermenü"-Label
label_copy_menu = tk.Label(self.file_menu_frame_right, text="Kopieren - Zielordner", font=self.font_bold)
label_copy_menu.pack(pady=(15, 5))
# "Zielpfad"-Bereich
destination_path_frame = tk.Frame(self.file_menu_frame_right)
destination_path_frame.pack(pady=5, fill='x', padx=10)
self.entry_destination_path = tk.Entry(destination_path_frame, textvariable=self.destination_path_var, font=text_font, width=40)
self.entry_destination_path.pack(side='left')
browse_button_destination = tk.Button(destination_path_frame, text="...", command=lambda: self.choose_directory(self.entry_destination_path))
browse_button_destination.pack(side='left', padx=(5, 0))
# "Kopieren"-Button
button_copy = tk.Button(self.file_menu_frame_right, text="Kopieren", command=self.copy, font=self.font_normal)
button_copy.pack(pady=15)
keyboard_button = tk.Button(self.file_menu_frame_down, text="Tastatur", font=self.font_normal, command=self.toggle_keyboard)
keyboard_button.pack(pady=10)
# Horizontaler Trennstrich
separator = ttk.Separator(self.file_menu_frame_right, orient='horizontal')
separator.pack(fill='x', pady=5)
label_file_menu = tk.Label(self.file_menu_frame_right, text="Dateimenü", font=self.font_bold)
label_file_menu.pack(pady=(5, 5))
# "Speicherpfad"-Bereich
label_storage_path = tk.Label(self.file_menu_frame_right, text="Speicherpfad", font=self.font_normal)
label_storage_path.pack(pady=5)
storage_path_frame = tk.Frame(self.file_menu_frame_right)
storage_path_frame.pack(pady=5, fill='x', padx=10) # padx=10 fügt Abstand zum linken Rand hinzu
self.entry_storage_path = tk.Entry(storage_path_frame, textvariable=self.storage_path_var, font=text_font, width=40) # Reduzierte Breite
self.entry_storage_path.pack(side='left')
browse_button_storage = tk.Button(storage_path_frame, text="...", command=lambda: self.choose_directory(self.entry_storage_path))
browse_button_storage.pack(side='left', padx=(5, 0))
# "Dateiname"-Label und Eingabefeld
label_filename = tk.Label(self.file_menu_frame_right, text="Dateiname", font=self.font_normal)
label_filename.pack(pady=5)
self.entry_filename = tk.Entry(self.file_menu_frame_right, textvariable=self.filename_var, font=text_font, width=40)
self.entry_filename.pack(pady=5)
# Gemeinsamer Rahmen für Datum- und Uhrzeit-Checkboxen
datetime_frame = tk.Frame(self.file_menu_frame_right)
datetime_frame.pack(pady=5, fill='x')
# Datum-Checkbox und Label
date_frame = tk.Frame(datetime_frame)
date_frame.pack(side='left', expand=True, padx=5)
checkbox_date = ttk.Checkbutton(date_frame, text="Datum", style="LargeCheckbutton.TCheckbutton", variable=self.date_var)
checkbox_date.grid(row=0, column=0, sticky="w")
date_label = tk.Label(date_frame, text="", font=self.font_normal)
date_label.grid(row=0, column=1, sticky="w")
# Uhrzeit-Checkbox und Label
time_frame = tk.Frame(datetime_frame)
time_frame.pack(side='left', expand=True, padx=5)
checkbox_time = ttk.Checkbutton(time_frame, text="Uhrzeit", style="LargeCheckbutton.TCheckbutton", variable=self.time_var)
checkbox_time.grid(row=0, column=0, sticky="w")
time_label = tk.Label(time_frame, text="", font=self.font_normal)
time_label.grid(row=0, column=1, sticky="w")
#Methode, um die Daten vom Arbeitspfad in den Zielordner zu kopieren
def copy(self):
try:
if not os.path.exists(self.destination_path_var.get()):
os.makedirs(self.destination_path_var.get())
for item in os.listdir(self.storage_path_var.get()):
source_item = os.path.join(self.storage_path_var.get(), item)
destination_item = os.path.join(self.destination_path_var.get(), item)
if os.path.isdir(source_item):
shutil.copytree(source_item, destination_item, dirs_exist_ok=True)
else:
shutil.copy2(source_item, destination_item)
self.show_success_message("Kopieren erfolgreich", "Alle Inhalte wurden erfolgreich kopiert.")
except Exception as e:
messagebox.showerror("Fehler", f"Ein Fehler ist aufgetreten: {e}")
#Methode, um ein Nachrichtenfenster anzuzeigen
def show_success_message(self, title, message):
msg_window = tk.Toplevel(self.root)
msg_window.title(title)
msg_window.geometry("300x100")
tk.Label(msg_window, text=message).pack(pady=10)
# OK-Button zum Schließen des Fensters
ok_button = tk.Button(msg_window, text="OK", command=msg_window.destroy)
ok_button.pack(pady=5)
# Fenster automatisch nach 5 Sekunden schließen
msg_window.after(5000, msg_window.destroy)
#Aktuell aktives Menü schließen
def close_menu(self):
self.current_menu_right.grid_remove()
self.current_menu_down.grid_remove()
self.current_menu_down_right.grid_remove()
#Aktuell aktives Menü öffnen (z.B. nachdem der Button ">", "<" gedrückt wurde)
def open_menu(self):
self.current_menu_right.grid()
self.current_menu_down.grid()
self.current_menu_down_right.grid()
self.update_aspect_ratio()
def show_main_menu(self):
self.close_menu()
self.current_menu_right = self.main_menu_frame_right
self.current_menu_down = self.main_menu_frame_down
self.current_menu_down_right = self.main_menu_frame_down_right
self.open_menu()
def show_crop_menu(self):
self.close_menu()
self.current_menu_right = self.crop_menu_frame_right
self.current_menu_down = self.crop_menu_frame_down
self.current_menu_down_right = self.crop_menu_frame_down_right
self.open_menu()
def show_settings_menu(self):
self.close_menu()
self.current_menu_right = self.settings_menu_frame_right
self.current_menu_down = self.settings_menu_frame_down
self.current_menu_down_right = self.settings_menu_frame_down_right
self.open_menu()
def show_file_menu(self):
self.close_menu()
self.current_menu_right = self.file_menu_frame_right
self.current_menu_down = self.file_menu_frame_down
self.current_menu_down_right = self.file_menu_frame_down_right
self.open_menu()
#Methode, um den File-Browser zu öffnen
def choose_directory(self, entry_field):
folder_selected = filedialog.askdirectory()
if folder_selected:
entry_field.delete(0, tk.END)
entry_field.insert(0, folder_selected)
#Methode wird ausgeführt, sobald sich der Wert in einem der Eingabefelder geändert hat
def entry_fields_changed(self, var_name, *args):
self.still_image_processing.update_file_parameters(self.filename_var.get(), self.storage_path_var.get(),
self.destination_path_var.get(), self.date_var.get(),
self.time_var.get())
self.ip.update_ruler_values(int(self.ruler_vertical_start.get()), int(self.ruler_vertical_end.get()),
int(self.ruler_horizontal_start.get()), int(self.ruler_horizontal_end.get()))
self.still_image_processing.get_ruler_settings(int(self.ruler_vertical_start.get()), int(self.ruler_vertical_end.get()),
int(self.ruler_horizontal_start.get()), int(self.ruler_horizontal_end.get()))
#Blendet das Menü ein bzw. aus
def toggle_menu(self):
if self.current_menu_right and self.current_menu_right.grid_info():
self.root.grid_columnconfigure(1, minsize=0)
self.close_menu()
self.update_aspect_ratio()
self.video_widget.grid(row=0, column=0, sticky="nsew")
self.hide_menu_button.config(text="<")
else:
if not self.current_menu_right:
self.current_menu_right = self.main_menu_frame_right
self.current_menu_down = self.main_menu_frame_down
self.current_menu_down_right = self.main_menu_frame_down_right
self.root.grid_columnconfigure(1, minsize=30)
self.open_menu()
self.menu_width = self.current_menu_right.winfo_width()
self.video_widget.grid(row=0, column=0, sticky="nsew")
self.hide_menu_button.config(text=">")
#Methode um die ROI-Werte an die Bildbearbeitung zu übergeben
def update_value(self, value):
self.preview_processing.update_roi(self.crop_width_value.get(), self.crop_height_value.get())
self.still_image_processing.update_roi(self.crop_width_value.get(), self.crop_height_value.get())
#Externe Beleuchtung permanent ein- / ausschalten
def toggle_ext_lighting(self):
self.ext_lighting_state.set(not self.ext_lighting_state.get())
self.update_button_appearance(self.ext_lighting_button, self.ext_lighting_state)
if self.ext_lighting_state.get():
self.permanent_lighting = True
else:
self.permanent_lighting = False
self.gpio_instance.get_gui_settings(self.activate_flash, self.permanent_lighting)
#Blitz ein- / ausschalten
def toggle_flash(self):
self.ext_flash_state.set(not self.ext_flash_state.get())
self.update_button_appearance(self.ext_flash_button, self.ext_flash_state)
if self.ext_flash_state.get():
self.activate_flash = True
else:
self.activate_flash = False
self.gpio_instance.get_gui_settings(self.activate_flash, self.permanent_lighting)
#Button Gitter ein- aus
def toggle_preview_grid(self):
self.show_preview_grid_state.set(not self.show_preview_grid_state.get())
self.update_button_appearance(self.show_preview_grid_button, self.show_preview_grid_state)
self.send_settings()
#Button Lineal ein- aus
def toggle_preview_ruler(self):
self.show_preview_ruler_state.set(not self.show_preview_ruler_state.get())
self.update_button_appearance(self.show_preview_ruler_button, self.show_preview_ruler_state)
self.send_settings()
#Button Uhr ein - aus
def toggle_preview_clock(self):
self.show_preview_clock_state.set(not self.show_preview_clock_state.get())
self.update_button_appearance(self.show_preview_clock_button, self.show_preview_clock_state)
self.send_settings()
#Button Zoom ein- aus
def toggle_zoom(self):
self.zoom_button_state.set(not self.zoom_button_state.get())
self.update_button_appearance(self.zoom_button, self.zoom_button_state)
self.change_preview_resolution()
self.send_settings()
#Aussehen der Toggle-Buttons variieren
def update_button_appearance(self, button, state_var):
if state_var.get():
button.config(relief="sunken", bg="dimgray") # Eingeschalteter Zustand
else:
button.config(relief="raised", bg="lightgray") # Ausgeschalteter Zustand
#Einstellungen der Toggle-Buttons an die bildverarbeitenden Instanzen übergeben
def send_settings(self):
self.preview_processing.get_settings(self.show_preview_grid_state.get(), self.show_preview_ruler_state.get(),
self.show_preview_clock_state.get(), self.zoom_button_state.get())
self.still_image_processing.update_picture_parameters(self.show_preview_clock_state.get(),
self.show_preview_ruler_state.get())
#Seitenverhältnis anpassen, sodass das video_widget sich entsprechend der ROI anpasst
def update_aspect_ratio(self):
self.root.update_idletasks()
self.video_widget_width = self.video_widget.winfo_width()
self.video_widget_height = self.video_widget.winfo_height()
self.video_widget_aspect_ratio = self.video_widget_width / self.video_widget_height
if self.video_widget_aspect_ratio is None:
self.new_width = 800
self.new_height = 480
else:
self.new_width = self.video_widget_width
self.new_height = int(self.new_width / self.camera_aspect_ratio)
#Tastatur ein- aus
def toggle_keyboard(self):
if self.keyboard_process:
self.keyboard_process.terminate()
self.keyboard_process = None
else:
self.keyboard_process = subprocess.Popen(['matchbox-keyboard'])
#Vorschaubild im video_widget aktualisieren
def update_video_widget(self):
image = self.camera.get_preview_image() #Bild aus der Kamera holen
image = self.preview_processing.get_preview_image(image) # Weitere Bildverarbeitung
#Abfragen, falls das Bild in einem unerwarteten Format auftritt
if image.shape[2] not in [3, 4]:
raise ValueError("Das Bild muss 3 (RGB) oder 4 (RGBA) Farbkanäle haben")
if image.dtype != np.uint8:
image = (image - image.min()) / (image.max() - image.min()) * 255.0
image = image.astype(np.uint8)
# Erzeugen des PIL-Bildes aus dem Array und Skalieren auf die neue Größe
resized_image = PIL.Image.fromarray(image).resize((self.new_width, self.new_height), PIL.Image.ANTIALIAS)
self.preview_image = PIL.ImageTk.PhotoImage(image=resized_image)
self.video_widget.create_image(0, 0, image=self.preview_image, anchor=tk.NW)
self.root.after(self.video_feed_delay, self.update_video_widget)
#Erhöhen der Vorschauauflösung, um den gezoomten Bereich klarer darzustellen
def change_preview_resolution(self):
self.camera.get_highres_preview()
#Vollbild ein- ausschalten
def toggle_fullscreen(self):
if self.fullscreen_active == True:
self.root.attributes('-fullscreen', False)
self.fullscreen_button.config(text="Vollbild")
self.fullscreen_active = False
else:
self.root.attributes('-fullscreen', True)
self.fullscreen_button.config(text="Vollbild beenden")
self.fullscreen_active = True

335
800x480/Image_Processing.py Normal file
View File

@ -0,0 +1,335 @@
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

28
800x480/Main.py Normal file
View File

@ -0,0 +1,28 @@
import threading
import Camera
import Gui_Grid
import Image_Processing
import gpio_control
import Still_Image_Processing
#Instanz zur Endbild-Bearbeitung starten
still_image_processing = Still_Image_Processing.Still_Image_Processing()
#Kamerainstanz starten
camera = Camera.Camera(still_image_processing)
camera.start_camera()
#Instanz zur Vorschaubild-Bearbeitung starten
preview_processing = Image_Processing.Image_Processing()
#GPIO-Überwachung starten
gpio_instance = gpio_control.GPIOControl(camera)
gpio_thread = threading.Thread(target=gpio_instance.start)
gpio_thread.start()
#Benutzeroberfläche starten
gui = Gui_Grid.User_Interface(camera, preview_processing, gpio_instance, still_image_processing)
gui.window_settings()
gui.build_widgets()
gui.update_video_widget()
gui.start_interface()

View File

@ -0,0 +1,266 @@
import cv2
import numpy as np
import math
import datetime
import os
class Still_Image_Processing():
def __init__(self):
#Einstellungen für den Dateinamen / Kopiermenü
self.file_name_gui = ""
self.storage_path = "/home/pi"
self.copy_path = ""
self.filename_add_date = False
self.filename_add_time = False
self.filename = ""
#Flags, ob die Zeit bzw. das Lineal im finalen Bild zu sehen sind
self.add_time = False
self.add_ruler = False
#Parameter des Vorschaubilds und des finalen Bilds, werden zur Skalierung benötigt
self.preview_width = 800
self.preview_height = 480
self.preview_width_roi = 800
self.preview_height_roi = 480
self.still_width_roi = 4056
self.still_height_roi = 3040
self.still_width = 4056
self.still_height = 3040
#Parameter für ROI
self.still_width_offset = 0
self.still_height_offset = 0
#Einstellungen für die Uhr
self.font = cv2.FONT_HERSHEY_SIMPLEX
self.font_size = 1
self.font_color = (0, 0, 0, 255)
self.background_color = (255, 255, 255, 255)
self.text_thickness = 2
self.line_type = cv2.LINE_AA
#Einstellungen für das Lineal
self.horizontal_ruler_start = 0
self.horizontal_ruler_end = 100
self.vertical_ruler_start = 0
self.vertical_ruler_end = 50
self.ruler_thickness = 80
self.ruler_font_size = 2
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.still_image = None
#Einstellungen für Dateinamen werden durch die Benutzeroberfläche definiert
def update_file_parameters(self, file_name, storage_path, copy_path, filename_add_date, filename_add_time):
self.file_name_gui = file_name
self.storage_path = storage_path
self.copy_path = copy_path
self.filename_add_date = filename_add_date
self.filename_add_time = filename_add_time
#Bildung des Dateinamens. Standard ist "Bild-{Nummerierung}". Ggf. kann das Bild mit Datum / Zeit versehen werden oder einen individuellen Namen erhalten
def update_filename(self):
base_name = self.file_name_gui.rstrip(".png") if self.file_name_gui else "Bild"
time_date_now = datetime.datetime.now()
if self.filename_add_date:
base_name += time_date_now.strftime("_%Y-%m-%d")
if self.filename_add_time:
base_name += time_date_now.strftime("_%H-%M-%S")
final_name = base_name
counter = 1
while os.path.exists(os.path.join(self.storage_path, final_name + ".png")): #Wenn Bild mit dem Namen bereits existiert, wird der Zähler erhöht, bis kein Bild gefunden wurde
final_name = f"{base_name}-{counter}"
counter += 1
self.file_name = os.path.join(self.storage_path, final_name + ".png")
#Methode zum Speichern des Bildes
def save_picture(self):
self.update_filename()
rgb_image = cv2.cvtColor(self.still_image, cv2.COLOR_BGR2RGB) #Konvertierung von BGR zu RGB erforderlich
cv2.imwrite(self.file_name, rgb_image)
#Es wird aus der GUI übergeben, ob die Uhr bzw. das Lineal angezeigt werden sollen
def update_picture_parameters(self, add_time_to_image, add_ruler_to_image):
self.add_time = add_time_to_image
self.add_ruler = add_ruler_to_image
#Aktualisieren der ROI und der erforderlichen Parameter
def update_roi(self, roi_width, roi_height):
self.preview_width_roi = roi_width
self.preview_height_roi = roi_height
self.roi_width_factor = self.preview_width_roi / self.preview_width
self.roi_height_factor = self.preview_height_roi / self.preview_height
self.still_width_roi = round(self.roi_width_factor * self.still_width)
self.still_height_roi = round(self.roi_height_factor * self.still_height)
self.still_width_offset = round(((self.still_width - self.still_width_roi) / 2))
self.still_height_offset = round(((self.still_height - self.still_height_roi) / 2))
#Uhrzeit im Bild einblenden
def add_time_to_image(self):
if self.add_time == True:
timestamp = datetime.datetime.now().strftime("%H:%M")
image_height, image_width = self.still_image.shape[:2]
# Abstand vom linken Rand abhängig vom Lineal
margin_x = self.ruler_thickness + 5 if self.add_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
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)
cv2.rectangle(self.still_image, background_l, background_r, self.background_color, -1)
cv2.putText(self.still_image, timestamp, text_position, self.font, font_scale, self.font_color, self.text_thickness, self.line_type)
#Einstellungen des Lineals aus der Benutzeroberfläche laden
def get_ruler_settings(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
#Erforderliche Einstellungen des Lineals bearbeiten, dieses passt sich an den linken bzw. oberen Bildrand an
def update_ruler_settings(self):
self.ruler_scaling_width = self.still_image.shape[1] / self.still_width
self.ruler_scaling_height = self.still_image.shape[0] / self.still_height
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
self.px_mm_horizontal = math.ceil(self.still_image.shape[1] / self.ruler_horizontal_end)
self.px_mm_vertical = math.ceil(self.still_image.shape[0] / self.ruler_vertical_end)
def add_ruler_to_image(self):
if self.add_ruler:
self.update_ruler_settings()
# Initialisierung des horizontalen Lineals am oberen Rand als RGB-Bild
self.ruler_horizontal = np.zeros((self.ruler_thickness, self.still_image.shape[1], 3), dtype=np.uint8)
self.ruler_horizontal[:, :] = self.ruler_color[:3]
self.ruler_vertical = np.zeros((self.still_image.shape[0], self.ruler_thickness, 3), dtype=np.uint8)
self.ruler_vertical[:, :] = self.ruler_color[:3]
# Markierungen und Beschriftungen zum horizontalen Lineal hinzufügen
for pos in range(self.ruler_thickness, self.still_image.shape[1], self.px_mm_horizontal * 5):
is_thick_line = (pos-self.ruler_thickness) % (self.px_mm_horizontal * 10) == 0
line_thickness = 2 if is_thick_line else 1
cv2.line(self.ruler_horizontal, (pos, 0), (pos, self.ruler_thickness), self.ruler_line_color, line_thickness)
if is_thick_line:
text = f"{int((pos-self.ruler_thickness) / self.px_mm_horizontal)}"
text_x = pos + self.ruler_thickness // 2 - cv2.getTextSize(text, self.ruler_font, self.ruler_font_size, 1)[0][0] // 2
text_y = self.ruler_thickness - 5
cv2.putText(self.ruler_horizontal, text, (text_x, text_y), self.ruler_font, self.ruler_font_size, self.ruler_text_color, 1)
# Markierungen und Beschriftungen zum vertikalen Lineal hinzufügen
for pos in range(self.ruler_thickness, self.still_image.shape[0], self.px_mm_vertical * 5):
is_thick_line = (pos-self.ruler_thickness) % (self.px_mm_vertical * 10) == 0
line_thickness = 2 if is_thick_line else 1
cv2.line(self.ruler_vertical, (0, 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, self.ruler_font_size, self.ruler_text_color, 1)
# Anbringen des horizontalen Lineals am oberen Rand des Bildes
self.still_image[:self.ruler_thickness, :] = self.ruler_horizontal
# Anbringen des vertikalen Lineals am linken Rand des Bildes
self.still_image[:, :self.ruler_thickness] = self.ruler_vertical
#Alle Schritte zur Bearbeitung des finalen Bildes
def process_image(self, image):
self.still_image = image
self.crop_image()
self.add_time_to_image()
self.add_ruler_to_image()
self.save_picture()
#Der in der ROI definierten Bildbereich extrahieren
def crop_image(self):
if self.still_width_roi != self.still_width or self.still_height_roi != self.still_height:
self.still_image = self.still_image[self.still_height_offset:(self.still_height - self.still_height_offset),
self.still_width_offset:(self.still_width - self.still_width_offset), :]

74
800x480/gpio_control.py Normal file
View File

@ -0,0 +1,74 @@
import RPi.GPIO as GPIO
import time
import threading
class GPIOControl:
def __init__(self, camera):
GPIO.setmode(GPIO.BCM)
self.camera = camera
#Verzögerung im Ein- und Ausschalten der Beleuchtung
self.lighting_delay = 100 / 1000
# Pin-Definitionen
self.lighting_trigger_gpio = 17 #Schalter für Beleuchtung Ein/Aus
self.camera_trigger_gpio = 27 #Schalter für Kamera auslösen
self.lighting_output_gpio = 23 #Ausgang für Beleuchtung
self.activate_flash = False
self.permanent_lighting = False
# Pin-Setup
GPIO.setup(self.lighting_trigger_gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(self.camera_trigger_gpio, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(self.lighting_output_gpio, GPIO.OUT, initial=GPIO.LOW)
self.camera_trigger_time = None
self.running = True
def start(self):
# Startet die Überwachung in einem separaten Thread, ist wegen der blockierenden Ausführung der Benutzeroberfläche erforderlich
self.thread = threading.Thread(target=self.monitor_gpio)
self.thread.start()
def stop(self):
self.running = False
self.thread.join()
#Blitz / Dauerbeleuchtung an/aus wird durch GUI eingestellt
def get_gui_settings(self, activate_flash, permanent_lighting):
self.activate_flash = activate_flash
self.permanent_lighting = permanent_lighting
self.activate_lighting()
self.deactivate_lighting()
def activate_lighting(self):
if self.activate_flash == True or self.permanent_lighting == True:
GPIO.output(self.lighting_output_gpio, GPIO.HIGH)
def deactivate_lighting(self):
if self.permanent_lighting == False:
GPIO.output(self.lighting_output_gpio, GPIO.LOW)
def monitor_gpio(self):
try:
while self.running:
if GPIO.input(self.lighting_trigger_gpio) == GPIO.LOW:
self.activate_lighting()
if GPIO.input(self.camera_trigger_gpio) == GPIO.LOW:
self.activate_lighting()
time.sleep(self.lighting_delay)
self.camera.get_still_image()
time.sleep(self.lighting_delay)
self.deactivate_lighting()
else:
if GPIO.input(self.camera_trigger_gpio) == GPIO.HIGH and GPIO.input(self.lighting_trigger_gpio) == GPIO.HIGH:
self.deactivate_lighting()
time.sleep(0.1)
finally:
GPIO.cleanup()