Files
pfand_PKG/PfandApplication/pfand_scanner.py
rattatwinko 6fa8b4bfc8 modded
2025-04-15 18:52:08 +02:00

221 lines
8.5 KiB
Python

# µScan
import tkinter as tk
from tkinter import ttk, simpledialog, messagebox
import cv2
from PIL import Image, ImageTk
from pyzbar.pyzbar import decode
from datetime import datetime, timedelta
import threading
import queue
import json
import os
class PfandScanner:
def __init__(self, window, window_title):
self.window = window
self.window.title(window_title)
self.data_file = "quantities.json"
self.load_json()
self.barcode_times = {}
self.prompted_barcodes = set()
self.camera_frame = ttk.Frame(window)
self.camera_frame.pack(side="left", padx=10, pady=5)
self.control_frame = ttk.Frame(window)
self.control_frame.pack(side="left", padx=10, pady=5, fill="y")
self.info_frame = ttk.Frame(window)
self.info_frame.pack(side="right", padx=10, pady=5, fill="both", expand=True)
self.camera_label = ttk.Label(self.camera_frame)
self.camera_label.pack()
focus_frame = ttk.LabelFrame(self.control_frame, text="Camera Controls")
focus_frame.pack(pady=5, padx=5, fill="x")
ttk.Label(focus_frame, text="Focus:").pack(pady=2)
self.focus_slider = ttk.Scale(focus_frame, from_=0, to=255, orient="horizontal")
self.focus_slider.set(0)
self.focus_slider.pack(pady=2, padx=5, fill="x")
self.autofocus_var = tk.BooleanVar(value=True)
self.autofocus_check = ttk.Checkbutton(
focus_frame, text="Autofocus", variable=self.autofocus_var, command=self.toggle_autofocus)
self.autofocus_check.pack(pady=2)
process_frame = ttk.LabelFrame(self.control_frame, text="Image Processing")
process_frame.pack(pady=5, padx=5, fill="x")
ttk.Label(process_frame, text="Brightness:").pack(pady=2)
self.brightness_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal")
self.brightness_slider.set(50)
self.brightness_slider.pack(pady=2, padx=5, fill="x")
ttk.Label(process_frame, text="Contrast:").pack(pady=2)
self.contrast_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal")
self.contrast_slider.set(50)
self.contrast_slider.pack(pady=2, padx=5, fill="x")
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 (€)")
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)
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()
def load_json(self):
if os.path.exists(self.data_file):
with open(self.data_file, 'r') as f:
self.quantities = json.load(f)
else:
self.quantities = {}
def save_json(self):
with open(self.data_file, 'w') as f:
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())
def adjust_image(self, frame):
brightness = self.brightness_slider.get() / 50.0 - 1.0
contrast = self.contrast_slider.get() / 50.0
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
def process_video(self):
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, 255, 0), 2)
barcode_data = barcode.data.decode('utf-8')
self.queue.put(barcode_data)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
self.camera_label.imgtk = imgtk
self.camera_label.configure(image=imgtk)
self.window.after(10, self.process_video)
def show_product_selection(self, barcode_data):
if hasattr(self, 'product_win') and self.product_win.winfo_exists():
return # prevent multiple dialogs
self.product_win = tk.Toplevel(self.window)
self.product_win.title("Produktwahl")
ttk.Label(self.product_win, text=f"Welches Produkt soll dem Barcode '{barcode_data}' zugeordnet werden?").pack(pady=5)
selected_product = tk.StringVar()
for prod in self.quantities:
ttk.Radiobutton(self.product_win, text=prod, variable=selected_product, value=prod).pack(anchor='w')
def confirm():
prod = selected_product.get()
if prod:
self.quantities[prod] += 1
self.save_json()
self.product_win.destroy()
else:
messagebox.showwarning("Keine Auswahl", "Bitte ein Produkt auswählen.")
ttk.Button(self.product_win, text="Bestätigen", command=confirm).pack(pady=5)
def process_queue(self):
try:
while True:
barcode_data = self.queue.get_nowait()
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=20)]
if len(timestamps) >= 10:
continue
timestamps.append(now)
self.barcode_times[barcode_data] = timestamps
else:
self.barcode_times[barcode_data] = [now]
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"
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:
self.prompted_barcodes.add(barcode_data)
self.window.after(0, self.show_product_selection, barcode_data)
except queue.Empty:
pass
finally:
self.window.after(100, self.process_queue)
def on_closing(self):
if self.cap.isOpened():
self.cap.release()
self.window.destroy()
if __name__ != "__main__":
def launch_pfand_scanner():
scanner_window = tk.Toplevel()
PfandScanner(scanner_window, "µScan V1.1.0")