163 lines
5.5 KiB
Python
163 lines
5.5 KiB
Python
import os
|
||
import tkinter as tk
|
||
from tkinter import filedialog, messagebox
|
||
from typing import Optional
|
||
|
||
import cv2
|
||
from PIL import Image, ImageTk
|
||
|
||
from ..config_loader import load_config
|
||
from ..io_utils import read_image_bgr
|
||
from ..pipeline import EAN13Recognizer
|
||
|
||
|
||
class TkEAN13App:
|
||
def __init__(self) -> None:
|
||
self.config = load_config()
|
||
self.root = tk.Tk()
|
||
self.root.title(self.config["app"]["ui"]["window_title"])
|
||
|
||
self.image_panel = tk.Label(self.root)
|
||
self.image_panel.pack(side=tk.TOP, padx=10, pady=10)
|
||
|
||
btn_frame = tk.Frame(self.root)
|
||
btn_frame.pack(side=tk.TOP, pady=5)
|
||
|
||
self.btn_open = tk.Button(btn_frame, text="选择图片", command=self.on_open)
|
||
self.btn_open.pack(side=tk.LEFT, padx=5)
|
||
|
||
self.btn_recognize = tk.Button(btn_frame, text="识别条码", command=self.on_recognize)
|
||
self.btn_recognize.pack(side=tk.LEFT, padx=5)
|
||
|
||
self.btn_camera = tk.Button(btn_frame, text="摄像头识别", command=self.on_camera)
|
||
self.btn_camera.pack(side=tk.LEFT, padx=5)
|
||
|
||
self.result_var = tk.StringVar(value="结果:-")
|
||
self.result_label = tk.Label(self.root, textvariable=self.result_var)
|
||
self.result_label.pack(side=tk.TOP, pady=5)
|
||
|
||
self.current_image_path: Optional[str] = None
|
||
self.recognizer = EAN13Recognizer()
|
||
self.cap = None
|
||
self.cam_running = False
|
||
|
||
def on_open(self) -> None:
|
||
path = filedialog.askopenfilename(
|
||
title="选择条码图片",
|
||
filetypes=[("Image Files", "*.png;*.jpg;*.jpeg;*.bmp;*.tif;*.tiff")],
|
||
)
|
||
if not path:
|
||
return
|
||
self.current_image_path = path
|
||
img_bgr = read_image_bgr(path)
|
||
if img_bgr is None:
|
||
messagebox.showerror("错误", "无法读取图片")
|
||
return
|
||
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
||
# 限制显示尺寸
|
||
max_w = 800
|
||
h, w = img_rgb.shape[:2]
|
||
if w > max_w:
|
||
scale = max_w / float(w)
|
||
img_rgb = cv2.resize(img_rgb, (max_w, int(h * scale)), interpolation=cv2.INTER_AREA)
|
||
im = Image.fromarray(img_rgb)
|
||
self.tk_img = ImageTk.PhotoImage(im)
|
||
self.image_panel.configure(image=self.tk_img)
|
||
self.result_var.set("结果:-")
|
||
|
||
def on_recognize(self) -> None:
|
||
if not self.current_image_path:
|
||
messagebox.showinfo("提示", "请先选择图片")
|
||
return
|
||
result = self.recognizer.recognize_any_from_path(self.current_image_path)
|
||
ean13 = result.get("ean13", "")
|
||
if ean13:
|
||
self.result_var.set(f"结果:EAN-13 {ean13}")
|
||
return
|
||
others = result.get("others", [])
|
||
if others:
|
||
first = others[0]
|
||
self.result_var.set(f"结果:{first.get('type')} {first.get('code')}")
|
||
return
|
||
self.result_var.set("结果:未识别")
|
||
|
||
def on_camera(self) -> None:
|
||
if self.cam_running:
|
||
# 若已在运行,视为停止
|
||
self.stop_camera()
|
||
return
|
||
cam_cfg = self.config.get("camera", {})
|
||
index = int(cam_cfg.get("index", 0))
|
||
self.cap = cv2.VideoCapture(index, cv2.CAP_DSHOW)
|
||
if not self.cap.isOpened():
|
||
messagebox.showerror("错误", f"无法打开摄像头 index={index}")
|
||
self.cap.release()
|
||
self.cap = None
|
||
return
|
||
# 设置分辨率(尽力设置)
|
||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, float(cam_cfg.get("width", 1280)))
|
||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, float(cam_cfg.get("height", 720)))
|
||
self.cam_running = True
|
||
self.result_var.set("结果:摄像头开启,正在识别...")
|
||
self.btn_camera.configure(text="停止摄像头")
|
||
self._camera_loop()
|
||
|
||
def stop_camera(self) -> None:
|
||
self.cam_running = False
|
||
try:
|
||
if self.cap is not None:
|
||
self.cap.release()
|
||
finally:
|
||
self.cap = None
|
||
self.btn_camera.configure(text="摄像头识别")
|
||
|
||
def _camera_loop(self) -> None:
|
||
if not self.cam_running or self.cap is None:
|
||
return
|
||
ret, frame = self.cap.read()
|
||
if not ret:
|
||
# 读取失败,稍后重试
|
||
self.root.after(int(self.config.get("camera", {}).get("interval_ms", 80)), self._camera_loop)
|
||
return
|
||
# 显示到面板
|
||
show = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||
max_w = 800
|
||
h, w = show.shape[:2]
|
||
if w > max_w:
|
||
scale = max_w / float(w)
|
||
show = cv2.resize(show, (max_w, int(h * scale)), interpolation=cv2.INTER_AREA)
|
||
im = Image.fromarray(show)
|
||
self.tk_img = ImageTk.PhotoImage(im)
|
||
self.image_panel.configure(image=self.tk_img)
|
||
|
||
# 识别
|
||
result = self.recognizer.recognize_any_from_image(frame)
|
||
ean13 = result.get("ean13", "")
|
||
if ean13:
|
||
self.result_var.set(f"结果:EAN-13 {ean13}")
|
||
self.stop_camera()
|
||
return
|
||
others = result.get("others", [])
|
||
if others:
|
||
first = others[0]
|
||
self.result_var.set(f"结果:{first.get('type')} {first.get('code')}")
|
||
self.stop_camera()
|
||
return
|
||
|
||
# 未识别,继续下一帧
|
||
self.root.after(int(self.config.get("camera", {}).get("interval_ms", 80)), self._camera_loop)
|
||
|
||
def run(self) -> None:
|
||
self.root.mainloop()
|
||
|
||
|
||
def main() -> None:
|
||
app = TkEAN13App()
|
||
app.run()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
|
||
|