Files
huojv/preview.py
2025-10-29 14:36:45 +08:00

216 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import cv2
import tkinter as tk
from tkinter import Canvas
from PIL import Image, ImageTk
import threading
import numpy as np
from config import config_manager
class PreviewWindow:
"""采集卡预览窗口"""
def __init__(self):
self.config = config_manager.config
self.caps = {}
self.frames = {}
self.large_window = None
self.running = True
def init_cameras(self):
"""初始化所有相机"""
for i, group in enumerate(self.config['groups']):
if group.get('active', True): # 只加载活动的配置
try:
cap = cv2.VideoCapture(group['camera_index'], cv2.CAP_DSHOW)
if cap.isOpened():
cap.set(cv2.CAP_PROP_FRAME_WIDTH, group['camera_width'])
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, group['camera_height'])
self.caps[i] = {
'cap': cap,
'group': group,
'name': group['name']
}
except Exception as e:
print(f"无法打开相机 {group['camera_index']}: {e}")
def capture_frames(self):
"""捕获帧"""
while self.running:
for idx, data in self.caps.items():
ret, frame = data['cap'].read()
if ret:
# 裁剪到配置的区域
height, width = frame.shape[:2]
crop_top = 30
crop_bottom = min(crop_top + 720, height)
crop_width = min(1280, width)
frame = frame[crop_top:crop_bottom, 0:crop_width]
# 转换颜色空间
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.frames[idx] = frame_rgb
def create_grid_window(self):
"""创建网格窗口"""
root = tk.Tk()
root.title("采集卡预览 - 点击放大")
root.geometry("1000x700")
# 获取显示配置
display = self.config.get('display', {})
preview_width = display.get('preview_width', 640)
preview_height = display.get('preview_height', 360)
columns = display.get('preview_columns', 2)
rows = display.get('preview_rows', 2)
canvas = Canvas(root, bg='black')
canvas.pack(fill=tk.BOTH, expand=True)
# 存储图像对象
self.photo_objects = {}
def update_frames_once():
"""在主线程中更新一帧使用after循环"""
if not self.running:
return
try:
canvas.delete("all")
# 计算每个预览窗口的位置和大小
window_width = root.winfo_width() if root.winfo_width() > 1 else 1000
window_height = root.winfo_height() if root.winfo_height() > 1 else 700
cell_width = window_width // columns
cell_height = window_height // rows
frame_idx = 0
for idx in self.caps.keys():
if idx in self.frames:
row = frame_idx // columns
col = frame_idx % columns
x = col * cell_width
y = row * cell_height
# 调整图像大小
frame = self.frames[idx]
h, w = frame.shape[:2]
scale = min(cell_width / w, cell_height / h) * 0.9
new_w = int(w * scale)
new_h = int(h * scale)
resized_frame = cv2.resize(frame, (new_w, new_h))
# 转换为PIL图像
pil_image = Image.fromarray(resized_frame)
photo = ImageTk.PhotoImage(image=pil_image)
# 保持引用避免被GC
self.photo_objects[idx] = photo
# 绘制图像
center_x = x + cell_width // 2
center_y = y + cell_height // 2
canvas.create_image(center_x, center_y, image=photo, anchor='center')
# 绘制标签
name = self.caps[idx]['name']
canvas.create_text(center_x, y + 20, text=name, fill='white', font=('Arial', 12, 'bold'))
frame_idx += 1
except Exception as e:
print(f"更新帧错误: {e}")
# 约30fps
root.after(33, update_frames_once)
def on_canvas_click(event):
"""点击画布事件"""
window_width = root.winfo_width()
window_height = root.winfo_height()
cell_width = window_width // columns
cell_height = window_height // rows
col = int(event.x // cell_width)
row = int(event.y // cell_height)
index = row * columns + col
# 找到对应的配置
if index < len(self.caps):
idx = list(self.caps.keys())[index]
self.show_large_window(idx)
canvas.bind('<Button-1>', on_canvas_click)
# 使用after在主线程中循环刷新
root.after(33, update_frames_once)
def on_closing():
"""关闭窗口"""
self.running = False
for data in self.caps.values():
data['cap'].release()
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
def show_large_window(self, idx):
"""显示大窗口"""
if self.large_window is not None and self.large_window.winfo_exists():
self.large_window.destroy()
self.large_window = tk.Toplevel()
self.large_window.title(f"放大视图 - {self.caps[idx]['name']}")
self.large_window.geometry("1280x720")
canvas = Canvas(self.large_window, bg='black')
canvas.pack(fill=tk.BOTH, expand=True)
photo_obj = {}
def update_large_once():
if not self.running or not self.large_window.winfo_exists():
return
try:
if idx in self.frames:
canvas.delete("all")
frame = self.frames[idx]
h, w = frame.shape[:2]
# 调整到窗口大小
window_width = self.large_window.winfo_width() if self.large_window.winfo_width() > 1 else 1280
window_height = self.large_window.winfo_height() if self.large_window.winfo_height() > 1 else 720
scale = min(window_width / w, window_height / h)
new_w = int(w * scale)
new_h = int(h * scale)
resized_frame = cv2.resize(frame, (new_w, new_h))
pil_image = Image.fromarray(resized_frame)
photo = ImageTk.PhotoImage(image=pil_image)
photo_obj['img'] = photo
canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center')
except Exception as e:
print(f"更新大窗口错误: {e}")
self.large_window.after(33, update_large_once)
self.large_window.after(33, update_large_once)
def run(self):
"""运行预览"""
self.init_cameras()
# 启动捕获线程
capture_thread = threading.Thread(target=self.capture_frames, daemon=True)
capture_thread.start()
# 创建并显示网格窗口
import time
time.sleep(0.5) # 等待几帧
self.create_grid_window()
if __name__ == "__main__":
preview = PreviewWindow()
preview.run()