From 208da789b6b8a671a6d48c7b15b9fae098252386 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Mon, 21 Apr 2025 15:20:22 +0200 Subject: [PATCH] =?UTF-8?q?Pineapple=20(CH5)=20=20=20=20=20+=20=C2=B5Scan?= =?UTF-8?q?=20better=20=20=20=20=20+=20Versioning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PfandApplication/main.py | 7 +- PfandApplication/pfand_scanner.py | 190 ++++++++++++++++++------------ 2 files changed, 121 insertions(+), 76 deletions(-) diff --git a/PfandApplication/main.py b/PfandApplication/main.py index 6c6e261..5ea0cef 100644 --- a/PfandApplication/main.py +++ b/PfandApplication/main.py @@ -385,8 +385,8 @@ class PfandCalculator: label = tk.Label( about_window, text=( - "PfandApp V.8.15.100 - 'Pineapple'\n" - "PaketVersion 1.1 ( PKG1.1 )\n\n" + "PfandApp V.8.15.200 - 'Pineapple'\n" + "PaketVersion 1.2 ( PKG1.2 )\n\n" "Erstellt mit TKinter, CV2, Numpy, PyZbar, TGTG-API, TKCalendar, Datetime\n" ), padx=10, @@ -562,9 +562,10 @@ class PfandCalculator: about_uscan, text=( "µScan - Der bessere Barcode Scanner\n" - "Version 2.1.0\n" + "Version 2.2.2\n" "µScan erfordert einen UI Reload (Strg+R) in der Root Anwendung\n" "µScan ist für mehrere Barcodes gemacht, die in einer kurzen Zeit gescannt werden sollten\n" + "Beachte das µScan eine Kamera benötigt die mindestens 30FPS aufnehmen kann (Process-FPS können eingestellt werden!)" ), justify="left", anchor="center" diff --git a/PfandApplication/pfand_scanner.py b/PfandApplication/pfand_scanner.py index 1c1b31f..7b66633 100644 --- a/PfandApplication/pfand_scanner.py +++ b/PfandApplication/pfand_scanner.py @@ -1,4 +1,4 @@ -# µScan V2.1.0 +# µScan V2.2.2 import tkinter as tk from tkinter import ttk, simpledialog, messagebox import cv2 @@ -9,11 +9,16 @@ import threading import queue import json import os +import time class PfandScanner: def __init__(self, window, window_title): self.window = window self.window.title(window_title) + self.window.geometry("1920x1080") + self.window.minsize(960, 540) + self.window.columnconfigure(0, weight=1) + self.window.rowconfigure(0, weight=1) self.data_file = "quantities.json" self.load_json() @@ -21,18 +26,80 @@ class PfandScanner: self.barcode_times = {} self.prompted_barcodes = set() - self.camera_frame = ttk.Frame(window) - self.camera_frame.pack(side="left", padx=10, pady=5) + self.selected_device_index = tk.IntVar(value=0) + self.last_process_time = time.time() - self.control_frame = ttk.Frame(window) - self.control_frame.pack(side="left", padx=10, pady=5, fill="y") + # FPS Einstellung ist hier! + # FPS Setting is here! + self.process_interval = 0.30 # 30 FPS - self.info_frame = ttk.Frame(window) - self.info_frame.pack(side="right", padx=10, pady=5, fill="both", expand=True) + self.init_gui() + self.init_camera() + + self.queue = queue.Queue() + self.pfand_values = { + "EINWEG": 0.25, + "MEHRWEG": 0.15, + "DOSE": 0.25, + } + + self.update_preview() + self.window.protocol("WM_DELETE_WINDOW", self.on_closing) + self.process_queue() + + def init_gui(self): + self.main_frame = ttk.Frame(self.window) + self.main_frame.grid(sticky="nsew") + self.main_frame.columnconfigure(0, weight=3) + self.main_frame.columnconfigure(1, weight=1) + self.main_frame.columnconfigure(2, weight=3) + self.main_frame.rowconfigure(0, weight=1) + + self.camera_frame = ttk.Frame(self.main_frame) + self.camera_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") + + self.control_frame = ttk.Frame(self.main_frame) + self.control_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew") + + self.info_frame = ttk.Frame(self.main_frame) + self.info_frame.grid(row=0, column=2, padx=5, pady=5, sticky="nsew") self.camera_label = ttk.Label(self.camera_frame) - self.camera_label.pack() + self.camera_label.pack(expand=True, fill="both") + self.init_device_selector() + self.init_controls() + self.init_treeview() + + def init_device_selector(self): + device_frame = ttk.LabelFrame(self.control_frame, text="Video Device") + device_frame.pack(pady=5, padx=5, fill="x") + + ttk.Label(device_frame, text="Choose Camera:").pack(anchor="w", padx=5) + + self.device_combo = ttk.Combobox(device_frame, state="readonly") + self.device_combo.pack(fill="x", padx=5) + + available_devices = self.list_video_devices() + self.device_combo['values'] = [f"Camera {i}" for i in available_devices] + self.device_combo.current(0) + self.device_combo.bind("<>", self.change_camera) + + def list_video_devices(self, max_devices=10): + available = [] + for i in range(max_devices): + cap = cv2.VideoCapture(i) + if cap.isOpened(): + available.append(i) + cap.release() + return available + + def change_camera(self, event=None): + index = self.device_combo.current() + self.selected_device_index.set(index) + self.init_camera() + + def init_controls(self): focus_frame = ttk.LabelFrame(self.control_frame, text="Camera Controls") focus_frame.pack(pady=5, padx=5, fill="x") @@ -59,40 +126,27 @@ class PfandScanner: self.contrast_slider.set(50) self.contrast_slider.pack(pady=2, padx=5, fill="x") + def init_treeview(self): self.tree = ttk.Treeview(self.info_frame, columns=("Time", "Barcode", "Type", "Deposit"), show="headings") - self.tree.heading("Time", text="Time") - self.tree.heading("Barcode", text="Barcode") - self.tree.heading("Type", text="Type") - self.tree.heading("Deposit", text="Deposit (€)") - + for col in ["Time", "Barcode", "Type", "Deposit"]: + self.tree.heading(col, text=col) self.tree.column("Time", width=150) self.tree.column("Barcode", width=200) self.tree.column("Type", width=100) self.tree.column("Deposit", width=100) - self.tree.pack(fill="both", expand=True) scrollbar = ttk.Scrollbar(self.info_frame, orient="vertical", command=self.tree.yview) scrollbar.pack(side="right", fill="y") self.tree.configure(yscrollcommand=scrollbar.set) - - self.cap = cv2.VideoCapture(0) + def init_camera(self): + if hasattr(self, 'cap') and self.cap and self.cap.isOpened(): + self.cap.release() + device_index = self.selected_device_index.get() + self.cap = cv2.VideoCapture(device_index, cv2.CAP_DSHOW if os.name == 'nt' else 0) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) - self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0) - self.cap.set(cv2.CAP_PROP_FOCUS, 0) - - self.queue = queue.Queue() - - self.pfand_values = { - "EINWEG": 0.25, - "MEHRWEG": 0.15, - "DOSE": 0.25, - } - - self.process_video() - self.window.protocol("WM_DELETE_WINDOW", self.on_closing) - self.process_queue() + self.toggle_autofocus() def load_json(self): if os.path.exists(self.data_file): @@ -106,13 +160,14 @@ class PfandScanner: json.dump(self.quantities, f, indent=4) def toggle_autofocus(self): - if self.autofocus_var.get(): - self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1) - self.focus_slider.state(['disabled']) - else: - self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0) - self.focus_slider.state(['!disabled']) - self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get()) + if self.cap: + if self.autofocus_var.get(): + self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1) + self.focus_slider.state(['disabled']) + else: + self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0) + self.focus_slider.state(['!disabled']) + self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get()) def adjust_image(self, frame): brightness = self.brightness_slider.get() / 50.0 - 1.0 @@ -120,27 +175,24 @@ class PfandScanner: adjusted = cv2.convertScaleAbs(frame, alpha=contrast, beta=brightness * 127) gray = cv2.cvtColor(adjusted, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) - binary = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, - cv2.THRESH_BINARY, 11, 2) - return binary + return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, + cv2.THRESH_BINARY, 11, 2) - def process_video(self): + def update_preview(self): try: ret, frame = self.cap.read() if ret: if not self.autofocus_var.get(): self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get()) - processed_frame = self.adjust_image(frame) - barcodes = decode(processed_frame) or decode(frame) - - for barcode in barcodes: - points = barcode.polygon - if len(points) == 4: - pts = [(p.x, p.y) for p in points] - cv2.polylines(frame, [cv2.convexHull(cv2.UMat(cv2.Mat(pts))).get()], True, (0, 0, 255), 2) - barcode_data = barcode.data.decode('utf-8') - self.queue.put(barcode_data) + current_time = time.time() + if current_time - self.last_process_time >= self.process_interval: + processed_frame = self.adjust_image(frame) + barcodes = decode(processed_frame) or decode(frame) + for barcode in barcodes: + barcode_data = barcode.data.decode('utf-8') + self.queue.put(barcode_data) + self.last_process_time = current_time cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) img = Image.fromarray(cv2image) @@ -148,9 +200,9 @@ class PfandScanner: self.camera_label.imgtk = imgtk self.camera_label.configure(image=imgtk) except Exception as e: - print(f"Error in processing video: {e}") + print(f"Error in video preview: {e}") - self.window.after(100, self.process_video) + self.window.after(10, self.update_preview) # ~100 FPS preview def show_product_selection(self, barcode_data): if hasattr(self, 'product_win') and self.product_win.winfo_exists(): @@ -168,7 +220,7 @@ class PfandScanner: def confirm(): prod = selected_product.get() if prod: - self.quantities[prod] += 1 + self.quantities[prod] = self.quantities.get(prod, 0) + 1 self.save_json() self.product_win.destroy() else: @@ -179,27 +231,19 @@ class PfandScanner: def process_queue(self): try: barcode_data = self.queue.get(timeout=0.1) - now = datetime.now() - if barcode_data in self.barcode_times: - timestamps = self.barcode_times[barcode_data] - timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)] - if len(timestamps) >= 3: - return - timestamps.append(now) - self.barcode_times[barcode_data] = timestamps - else: - self.barcode_times[barcode_data] = [now] + timestamps = self.barcode_times.get(barcode_data, []) + timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)] + if len(timestamps) >= 3: + return + timestamps.append(now) + self.barcode_times[barcode_data] = timestamps current_time = now.strftime("%Y-%m-%d %H:%M:%S") - if len(barcode_data) == 13: - pfand_type = "EINWEG" - elif len(barcode_data) == 8: - pfand_type = "MEHRWEG" - else: - pfand_type = "DOSE" + pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE" deposit = self.pfand_values.get(pfand_type, 0.00) + self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"{deposit:.2f}")) if barcode_data not in self.prompted_barcodes: @@ -211,14 +255,14 @@ class PfandScanner: except Exception as e: print(f"Error in queue processing: {e}") finally: - self.window.after(100, self.process_queue) # freez delay + self.window.after(100, self.process_queue) def on_closing(self): - if self.cap.isOpened(): + if self.cap and self.cap.isOpened(): self.cap.release() self.window.destroy() if __name__ != "__main__": def launch_pfand_scanner(): scanner_window = tk.Toplevel() - PfandScanner(scanner_window, "µScan V2.1.0") \ No newline at end of file + PfandScanner(scanner_window, "µScan V2.2.2")