From bcc971d528cf578f6a2ec4d014944eb33cb2ff6b Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 29 Oct 2025 15:27:50 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=87=E9=9B=86=E5=8D=A1bug=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 11 ++- gui_config.py | 89 ++++++++++++++++++++++-- preview.py | 188 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 236 insertions(+), 52 deletions(-) diff --git a/config.py b/config.py index eeb719d..fc6f58e 100644 --- a/config.py +++ b/config.py @@ -48,11 +48,20 @@ class ConfigManager: def save_config(self): """保存配置文件""" try: + import os + # 确保目录存在 + config_dir = os.path.dirname(os.path.abspath(self.config_file)) + if config_dir and not os.path.exists(config_dir): + os.makedirs(config_dir, exist_ok=True) + with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(self.config, f, ensure_ascii=False, indent=4) + print(f"✅ 配置文件已保存到: {os.path.abspath(self.config_file)}") return True except Exception as e: - print(f"保存配置文件失败: {e}") + print(f"❌ 保存配置文件失败: {e}") + import traceback + traceback.print_exc() return False def add_group(self, name=None): diff --git a/gui_config.py b/gui_config.py index 315d643..1df64be 100644 --- a/gui_config.py +++ b/gui_config.py @@ -127,6 +127,12 @@ class ConfigGUI: # 加载配置组列表 self.update_group_list() + # 如果有配置组,默认选择第一个 + if len(config_manager.config['groups']) > 0: + self.selected_index = 0 + self.group_listbox.selection_set(0) + self.load_group_config(0) + # 加载预览配置 display = config_manager.config.get('display', {}) for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']: @@ -260,16 +266,30 @@ class ConfigGUI: def save_config(self): """保存当前编辑的配置""" + # 检查索引有效性 + if self.selected_index >= len(config_manager.config['groups']): + messagebox.showerror("错误", f"配置组索引 {self.selected_index} 无效") + return False + # 保存当前组的配置 group = config_manager.get_group_by_index(self.selected_index) if group: for key, var in self.config_vars.items(): if not key.startswith('display_'): try: - value = int(var.get()) if var.get().isdigit() else var.get() + value_str = var.get().strip() + # 特殊处理:某些字段需要转换为整数 + if key in ['camera_index', 'camera_width', 'camera_height', 'serial_baudrate', 'move_velocity']: + try: + value = int(value_str) if value_str else group.get(key, 0) + except: + value = group.get(key, 0) + else: + value = value_str if value_str else group.get(key, '') group[key] = value - except: - group[key] = var.get() + except Exception as e: + print(f"保存字段 {key} 时出错: {e}") + pass # 保存预览配置 display = config_manager.config.get('display', {}) @@ -282,15 +302,25 @@ class ConfigGUI: pass config_manager.config['display'] = display + # 保存到文件 if config_manager.save_config(): + # 更新左侧列表显示 + self.update_group_list() messagebox.showinfo("成功", "配置已保存") + return True else: messagebox.showerror("错误", "配置保存失败") + return False def start_preview(self): """启动预览窗口""" - # 保存配置 - self.save_config() + # 保存配置(不显示消息框,静默保存) + if not self.save_config_silent(): + messagebox.showerror("错误", "配置保存失败,无法启动预览") + return + + # 重新加载配置(从文件读取最新配置) + config_manager.load_config() # 启动预览窗口 from preview import PreviewWindow @@ -298,10 +328,57 @@ class ConfigGUI: preview_thread.daemon = True preview_thread.start() + def save_config_silent(self): + """静默保存配置(不显示消息框)""" + # 保存当前组的配置 + if self.selected_index >= len(config_manager.config['groups']): + print(f"⚠️ 选中索引 {self.selected_index} 超出范围") + return False + + group = config_manager.get_group_by_index(self.selected_index) + if group: + for key, var in self.config_vars.items(): + if not key.startswith('display_'): + try: + value_str = var.get().strip() + # 特殊处理:某些字段需要转换为整数 + if key in ['camera_index', 'camera_width', 'camera_height', 'serial_baudrate', 'move_velocity']: + try: + value = int(value_str) if value_str else group.get(key, 0) + except: + value = group.get(key, 0) + else: + value = value_str if value_str else group.get(key, '') + group[key] = value + except Exception as e: + print(f"保存字段 {key} 时出错: {e}") + pass + + # 保存预览配置 + display = config_manager.config.get('display', {}) + for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']: + var = self.config_vars.get(f"display_{key}") + if var: + try: + display[key] = int(var.get()) + except: + pass + config_manager.config['display'] = display + + # 保存到文件 + result = config_manager.save_config() + + # 更新左侧列表显示 + if result: + self.update_group_list() + + return result + def start_program(self): """启动主程序""" # 保存配置 - self.save_config() + if not self.save_config(): + return messagebox.showinfo("提示", "配置已保存,请运行主程序") diff --git a/preview.py b/preview.py index bb6fdef..c3694f4 100644 --- a/preview.py +++ b/preview.py @@ -17,37 +17,88 @@ class PreviewWindow: 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']) + print("🔧 开始初始化采集卡...") + loaded_count = 0 + + # 如果没有活动配置,加载所有配置 + active_groups = [g for g in self.config['groups'] if g.get('active', True)] + if not active_groups: + print("⚠️ 没有活动的配置组,将尝试加载所有配置组") + active_groups = self.config['groups'] + + for i, group in enumerate(active_groups): + try: + cam_idx = group['camera_index'] + print(f" 尝试打开采集卡 {cam_idx} ({group['name']})...") + + cap = cv2.VideoCapture(int(cam_idx), cv2.CAP_DSHOW) + if not cap.isOpened(): + print(f" DSHOW模式失败,尝试默认模式...") + cap = cv2.VideoCapture(int(cam_idx)) + + if cap.isOpened(): + cap.set(cv2.CAP_PROP_FRAME_WIDTH, group['camera_width']) + cap.set(cv2.CAP_PROP_FRAME_HEIGHT, group['camera_height']) + # 测试读取一帧 + ret, test_frame = cap.read() + if ret: self.caps[i] = { 'cap': cap, 'group': group, 'name': group['name'] } - except Exception as e: - print(f"无法打开相机 {group['camera_index']}: {e}") + loaded_count += 1 + print(f" ✅ 采集卡 {cam_idx} 初始化成功") + else: + cap.release() + print(f" ❌ 采集卡 {cam_idx} 无法读取帧") + else: + print(f" ❌ 采集卡 {cam_idx} 无法打开") + except Exception as e: + print(f" ❌ 采集卡 {group.get('camera_index', '?')} 初始化失败: {e}") + + if loaded_count == 0: + print("⚠️ 警告:没有成功加载任何采集卡!") + print("请检查:") + print("1. 采集卡是否正确连接") + print("2. 采集卡索引是否正确") + print("3. 是否有活动的配置组") + else: + print(f"✅ 成功加载 {loaded_count} 个采集卡") 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 + try: + ret, frame = data['cap'].read() + if ret and frame is not None: + # 裁剪到配置的区域 + height, width = frame.shape[:2] + crop_top = 30 + crop_bottom = min(crop_top + 720, height) + crop_width = min(1280, width) + + # 确保裁剪范围有效 + if crop_bottom > crop_top and crop_width > 0: + frame = frame[crop_top:crop_bottom, 0:crop_width] + + # 转换颜色空间 + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + self.frames[idx] = frame_rgb + else: + print(f"⚠️ 采集卡 {idx} 裁剪参数无效") + else: + # 读取失败,清除旧帧 + if idx in self.frames: + self.frames[idx] = None + except Exception as e: + print(f"捕获帧 {idx} 错误: {e}") + import traceback + traceback.print_exc() + + import time + time.sleep(0.01) # 避免CPU占用过高 def create_grid_window(self): """创建网格窗口""" @@ -75,6 +126,19 @@ class PreviewWindow: try: canvas.delete("all") + # 如果没有加载任何采集卡,显示提示 + if not self.caps: + canvas.create_text( + root.winfo_width() // 2, + root.winfo_height() // 2, + text="未找到可用的采集卡\n\n请检查:\n1. 采集卡是否正确连接\n2. 配置组是否有活动的采集卡\n3. 采集卡索引是否正确", + fill='yellow', + font=('Arial', 14), + justify=tk.CENTER + ) + root.after(33, update_frames_once) + return + # 计算每个预览窗口的位置和大小 window_width = root.winfo_width() if root.winfo_width() > 1 else 1000 window_height = root.winfo_height() if root.winfo_height() > 1 else 700 @@ -84,7 +148,7 @@ class PreviewWindow: frame_idx = 0 for idx in self.caps.keys(): - if idx in self.frames: + if idx in self.frames and self.frames[idx] is not None: row = frame_idx // columns col = frame_idx % columns @@ -92,32 +156,48 @@ class PreviewWindow: 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) + try: + 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)) + 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 + # 转换为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"处理帧 {idx} 错误: {e}") + else: + # 显示等待提示 + row = frame_idx // columns + col = frame_idx % columns + x = col * cell_width + y = row * cell_height 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')) - + canvas.create_text(center_x, center_y, text=f"{name}\n等待画面...", fill='gray', font=('Arial', 12)) frame_idx += 1 except Exception as e: print(f"更新帧错误: {e}") + import traceback + traceback.print_exc() # 约30fps root.after(33, update_frames_once) @@ -170,16 +250,17 @@ class PreviewWindow: if not self.running or not self.large_window.winfo_exists(): return try: - if idx in self.frames: + # 获取窗口大小 + 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 + + if idx in self.frames and self.frames[idx] is not None: 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) @@ -190,8 +271,20 @@ class PreviewWindow: photo_obj['img'] = photo canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center') + else: + # 显示等待提示 + canvas.delete("all") + canvas.create_text( + window_width // 2, + window_height // 2, + text="等待画面...", + fill='gray', + font=('Arial', 16) + ) except Exception as e: print(f"更新大窗口错误: {e}") + import traceback + traceback.print_exc() self.large_window.after(33, update_large_once) self.large_window.after(33, update_large_once) @@ -200,9 +293,14 @@ class PreviewWindow: """运行预览""" self.init_cameras() + if not self.caps: + # 如果没有加载任何采集卡,仍然显示窗口并显示错误信息 + print("⚠️ 没有可用的采集卡,将显示错误提示窗口") + # 启动捕获线程 - capture_thread = threading.Thread(target=self.capture_frames, daemon=True) - capture_thread.start() + if self.caps: + capture_thread = threading.Thread(target=self.capture_frames, daemon=True) + capture_thread.start() # 创建并显示网格窗口 import time