initial commit
this is a copy ;3
This commit is contained in:
218
PfandApplication/pfand_scanner.py
Normal file
218
PfandApplication/pfand_scanner.py
Normal file
@@ -0,0 +1,218 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user