first commit
This commit is contained in:
parent
d715387448
commit
123f64f9ec
55
1280 x 800/Camera.py
Normal file
55
1280 x 800/Camera.py
Normal 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
638
1280 x 800/Gui_Grid.py
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
335
1280 x 800/Image_Processing.py
Normal file
335
1280 x 800/Image_Processing.py
Normal 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
28
1280 x 800/Main.py
Normal 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()
|
266
1280 x 800/Still_Image_Processing.py
Normal file
266
1280 x 800/Still_Image_Processing.py
Normal 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), :]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
74
1280 x 800/gpio_control.py
Normal file
74
1280 x 800/gpio_control.py
Normal 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
55
800x480/Camera.py
Normal 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
638
800x480/Gui_Grid.py
Normal 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
335
800x480/Image_Processing.py
Normal 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
28
800x480/Main.py
Normal 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()
|
266
800x480/Still_Image_Processing.py
Normal file
266
800x480/Still_Image_Processing.py
Normal 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
74
800x480/gpio_control.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user