Pineapple (CH5)

+ µScan better
    + Versioning
This commit is contained in:
rattatwinko
2025-04-21 15:20:22 +02:00
parent 0635e34d6f
commit 208da789b6
2 changed files with 121 additions and 76 deletions

View File

@@ -385,8 +385,8 @@ class PfandCalculator:
label = tk.Label( label = tk.Label(
about_window, about_window,
text=( text=(
"PfandApp V.8.15.100 - 'Pineapple'\n" "PfandApp V.8.15.200 - 'Pineapple'\n"
"PaketVersion 1.1 ( PKG1.1 )\n\n" "PaketVersion 1.2 ( PKG1.2 )\n\n"
"Erstellt mit TKinter, CV2, Numpy, PyZbar, TGTG-API, TKCalendar, Datetime\n" "Erstellt mit TKinter, CV2, Numpy, PyZbar, TGTG-API, TKCalendar, Datetime\n"
), ),
padx=10, padx=10,
@@ -562,9 +562,10 @@ class PfandCalculator:
about_uscan, about_uscan,
text=( text=(
"µScan - Der bessere Barcode Scanner\n" "µ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 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" "µ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", justify="left",
anchor="center" anchor="center"

View File

@@ -1,4 +1,4 @@
# µScan V2.1.0 # µScan V2.2.2
import tkinter as tk import tkinter as tk
from tkinter import ttk, simpledialog, messagebox from tkinter import ttk, simpledialog, messagebox
import cv2 import cv2
@@ -9,11 +9,16 @@ import threading
import queue import queue
import json import json
import os import os
import time
class PfandScanner: class PfandScanner:
def __init__(self, window, window_title): def __init__(self, window, window_title):
self.window = window self.window = window
self.window.title(window_title) 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.data_file = "quantities.json"
self.load_json() self.load_json()
@@ -21,18 +26,80 @@ class PfandScanner:
self.barcode_times = {} self.barcode_times = {}
self.prompted_barcodes = set() self.prompted_barcodes = set()
self.camera_frame = ttk.Frame(window) self.selected_device_index = tk.IntVar(value=0)
self.camera_frame.pack(side="left", padx=10, pady=5) self.last_process_time = time.time()
self.control_frame = ttk.Frame(window) # FPS Einstellung ist hier!
self.control_frame.pack(side="left", padx=10, pady=5, fill="y") # FPS Setting is here!
self.process_interval = 0.30 # 30 FPS
self.info_frame = ttk.Frame(window) self.init_gui()
self.info_frame.pack(side="right", padx=10, pady=5, fill="both", expand=True) 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 = 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("<<ComboboxSelected>>", 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 = ttk.LabelFrame(self.control_frame, text="Camera Controls")
focus_frame.pack(pady=5, padx=5, fill="x") focus_frame.pack(pady=5, padx=5, fill="x")
@@ -59,40 +126,27 @@ class PfandScanner:
self.contrast_slider.set(50) self.contrast_slider.set(50)
self.contrast_slider.pack(pady=2, padx=5, fill="x") 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 = ttk.Treeview(self.info_frame, columns=("Time", "Barcode", "Type", "Deposit"), show="headings")
self.tree.heading("Time", text="Time") for col in ["Time", "Barcode", "Type", "Deposit"]:
self.tree.heading("Barcode", text="Barcode") self.tree.heading(col, text=col)
self.tree.heading("Type", text="Type")
self.tree.heading("Deposit", text="Deposit (€)")
self.tree.column("Time", width=150) self.tree.column("Time", width=150)
self.tree.column("Barcode", width=200) self.tree.column("Barcode", width=200)
self.tree.column("Type", width=100) self.tree.column("Type", width=100)
self.tree.column("Deposit", width=100) self.tree.column("Deposit", width=100)
self.tree.pack(fill="both", expand=True) self.tree.pack(fill="both", expand=True)
scrollbar = ttk.Scrollbar(self.info_frame, orient="vertical", command=self.tree.yview) scrollbar = ttk.Scrollbar(self.info_frame, orient="vertical", command=self.tree.yview)
scrollbar.pack(side="right", fill="y") scrollbar.pack(side="right", fill="y")
self.tree.configure(yscrollcommand=scrollbar.set) self.tree.configure(yscrollcommand=scrollbar.set)
def init_camera(self):
self.cap = cv2.VideoCapture(0) 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_WIDTH, 1280)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0) self.toggle_autofocus()
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()
def load_json(self): def load_json(self):
if os.path.exists(self.data_file): if os.path.exists(self.data_file):
@@ -106,13 +160,14 @@ class PfandScanner:
json.dump(self.quantities, f, indent=4) json.dump(self.quantities, f, indent=4)
def toggle_autofocus(self): def toggle_autofocus(self):
if self.autofocus_var.get(): if self.cap:
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1) if self.autofocus_var.get():
self.focus_slider.state(['disabled']) self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1)
else: self.focus_slider.state(['disabled'])
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0) else:
self.focus_slider.state(['!disabled']) self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0)
self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get()) self.focus_slider.state(['!disabled'])
self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get())
def adjust_image(self, frame): def adjust_image(self, frame):
brightness = self.brightness_slider.get() / 50.0 - 1.0 brightness = self.brightness_slider.get() / 50.0 - 1.0
@@ -120,27 +175,24 @@ class PfandScanner:
adjusted = cv2.convertScaleAbs(frame, alpha=contrast, beta=brightness * 127) adjusted = cv2.convertScaleAbs(frame, alpha=contrast, beta=brightness * 127)
gray = cv2.cvtColor(adjusted, cv2.COLOR_BGR2GRAY) gray = cv2.cvtColor(adjusted, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0) blurred = cv2.GaussianBlur(gray, (5, 5), 0)
binary = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2) cv2.THRESH_BINARY, 11, 2)
return binary
def process_video(self): def update_preview(self):
try: try:
ret, frame = self.cap.read() ret, frame = self.cap.read()
if ret: if ret:
if not self.autofocus_var.get(): if not self.autofocus_var.get():
self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get()) self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get())
processed_frame = self.adjust_image(frame) current_time = time.time()
barcodes = decode(processed_frame) or decode(frame) if current_time - self.last_process_time >= self.process_interval:
processed_frame = self.adjust_image(frame)
for barcode in barcodes: barcodes = decode(processed_frame) or decode(frame)
points = barcode.polygon for barcode in barcodes:
if len(points) == 4: barcode_data = barcode.data.decode('utf-8')
pts = [(p.x, p.y) for p in points] self.queue.put(barcode_data)
cv2.polylines(frame, [cv2.convexHull(cv2.UMat(cv2.Mat(pts))).get()], True, (0, 0, 255), 2) self.last_process_time = current_time
barcode_data = barcode.data.decode('utf-8')
self.queue.put(barcode_data)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image) img = Image.fromarray(cv2image)
@@ -148,9 +200,9 @@ class PfandScanner:
self.camera_label.imgtk = imgtk self.camera_label.imgtk = imgtk
self.camera_label.configure(image=imgtk) self.camera_label.configure(image=imgtk)
except Exception as e: 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): def show_product_selection(self, barcode_data):
if hasattr(self, 'product_win') and self.product_win.winfo_exists(): if hasattr(self, 'product_win') and self.product_win.winfo_exists():
@@ -168,7 +220,7 @@ class PfandScanner:
def confirm(): def confirm():
prod = selected_product.get() prod = selected_product.get()
if prod: if prod:
self.quantities[prod] += 1 self.quantities[prod] = self.quantities.get(prod, 0) + 1
self.save_json() self.save_json()
self.product_win.destroy() self.product_win.destroy()
else: else:
@@ -179,27 +231,19 @@ class PfandScanner:
def process_queue(self): def process_queue(self):
try: try:
barcode_data = self.queue.get(timeout=0.1) barcode_data = self.queue.get(timeout=0.1)
now = datetime.now() now = datetime.now()
if barcode_data in self.barcode_times: timestamps = self.barcode_times.get(barcode_data, [])
timestamps = self.barcode_times[barcode_data] timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)]
timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)] if len(timestamps) >= 3:
if len(timestamps) >= 3: return
return timestamps.append(now)
timestamps.append(now) self.barcode_times[barcode_data] = timestamps
self.barcode_times[barcode_data] = timestamps
else:
self.barcode_times[barcode_data] = [now]
current_time = now.strftime("%Y-%m-%d %H:%M:%S") current_time = now.strftime("%Y-%m-%d %H:%M:%S")
if len(barcode_data) == 13: pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE"
pfand_type = "EINWEG"
elif len(barcode_data) == 8:
pfand_type = "MEHRWEG"
else:
pfand_type = "DOSE"
deposit = self.pfand_values.get(pfand_type, 0.00) deposit = self.pfand_values.get(pfand_type, 0.00)
self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"{deposit:.2f}")) self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"{deposit:.2f}"))
if barcode_data not in self.prompted_barcodes: if barcode_data not in self.prompted_barcodes:
@@ -211,14 +255,14 @@ class PfandScanner:
except Exception as e: except Exception as e:
print(f"Error in queue processing: {e}") print(f"Error in queue processing: {e}")
finally: finally:
self.window.after(100, self.process_queue) # freez delay self.window.after(100, self.process_queue)
def on_closing(self): def on_closing(self):
if self.cap.isOpened(): if self.cap and self.cap.isOpened():
self.cap.release() self.cap.release()
self.window.destroy() self.window.destroy()
if __name__ != "__main__": if __name__ != "__main__":
def launch_pfand_scanner(): def launch_pfand_scanner():
scanner_window = tk.Toplevel() scanner_window = tk.Toplevel()
PfandScanner(scanner_window, "µScan V2.1.0") PfandScanner(scanner_window, "µScan V2.2.2")