采集卡bug修复
This commit is contained in:
11
config.py
11
config.py
@@ -48,11 +48,20 @@ class ConfigManager:
|
|||||||
def save_config(self):
|
def save_config(self):
|
||||||
"""保存配置文件"""
|
"""保存配置文件"""
|
||||||
try:
|
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:
|
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||||
json.dump(self.config, f, ensure_ascii=False, indent=4)
|
json.dump(self.config, f, ensure_ascii=False, indent=4)
|
||||||
|
print(f"✅ 配置文件已保存到: {os.path.abspath(self.config_file)}")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"保存配置文件失败: {e}")
|
print(f"❌ 保存配置文件失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def add_group(self, name=None):
|
def add_group(self, name=None):
|
||||||
|
|||||||
@@ -127,6 +127,12 @@ class ConfigGUI:
|
|||||||
# 加载配置组列表
|
# 加载配置组列表
|
||||||
self.update_group_list()
|
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', {})
|
display = config_manager.config.get('display', {})
|
||||||
for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']:
|
for key in ['preview_width', 'preview_height', 'preview_columns', 'preview_rows']:
|
||||||
@@ -260,16 +266,30 @@ class ConfigGUI:
|
|||||||
|
|
||||||
def save_config(self):
|
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)
|
group = config_manager.get_group_by_index(self.selected_index)
|
||||||
if group:
|
if group:
|
||||||
for key, var in self.config_vars.items():
|
for key, var in self.config_vars.items():
|
||||||
if not key.startswith('display_'):
|
if not key.startswith('display_'):
|
||||||
try:
|
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
|
group[key] = value
|
||||||
except:
|
except Exception as e:
|
||||||
group[key] = var.get()
|
print(f"保存字段 {key} 时出错: {e}")
|
||||||
|
pass
|
||||||
|
|
||||||
# 保存预览配置
|
# 保存预览配置
|
||||||
display = config_manager.config.get('display', {})
|
display = config_manager.config.get('display', {})
|
||||||
@@ -282,15 +302,25 @@ class ConfigGUI:
|
|||||||
pass
|
pass
|
||||||
config_manager.config['display'] = display
|
config_manager.config['display'] = display
|
||||||
|
|
||||||
|
# 保存到文件
|
||||||
if config_manager.save_config():
|
if config_manager.save_config():
|
||||||
|
# 更新左侧列表显示
|
||||||
|
self.update_group_list()
|
||||||
messagebox.showinfo("成功", "配置已保存")
|
messagebox.showinfo("成功", "配置已保存")
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
messagebox.showerror("错误", "配置保存失败")
|
messagebox.showerror("错误", "配置保存失败")
|
||||||
|
return False
|
||||||
|
|
||||||
def start_preview(self):
|
def start_preview(self):
|
||||||
"""启动预览窗口"""
|
"""启动预览窗口"""
|
||||||
# 保存配置
|
# 保存配置(不显示消息框,静默保存)
|
||||||
self.save_config()
|
if not self.save_config_silent():
|
||||||
|
messagebox.showerror("错误", "配置保存失败,无法启动预览")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 重新加载配置(从文件读取最新配置)
|
||||||
|
config_manager.load_config()
|
||||||
|
|
||||||
# 启动预览窗口
|
# 启动预览窗口
|
||||||
from preview import PreviewWindow
|
from preview import PreviewWindow
|
||||||
@@ -298,10 +328,57 @@ class ConfigGUI:
|
|||||||
preview_thread.daemon = True
|
preview_thread.daemon = True
|
||||||
preview_thread.start()
|
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):
|
def start_program(self):
|
||||||
"""启动主程序"""
|
"""启动主程序"""
|
||||||
# 保存配置
|
# 保存配置
|
||||||
self.save_config()
|
if not self.save_config():
|
||||||
|
return
|
||||||
|
|
||||||
messagebox.showinfo("提示", "配置已保存,请运行主程序")
|
messagebox.showinfo("提示", "配置已保存,请运行主程序")
|
||||||
|
|
||||||
|
|||||||
186
preview.py
186
preview.py
@@ -17,37 +17,88 @@ class PreviewWindow:
|
|||||||
|
|
||||||
def init_cameras(self):
|
def init_cameras(self):
|
||||||
"""初始化所有相机"""
|
"""初始化所有相机"""
|
||||||
for i, group in enumerate(self.config['groups']):
|
print("🔧 开始初始化采集卡...")
|
||||||
if group.get('active', True): # 只加载活动的配置
|
loaded_count = 0
|
||||||
try:
|
|
||||||
cap = cv2.VideoCapture(group['camera_index'], cv2.CAP_DSHOW)
|
# 如果没有活动配置,加载所有配置
|
||||||
if cap.isOpened():
|
active_groups = [g for g in self.config['groups'] if g.get('active', True)]
|
||||||
cap.set(cv2.CAP_PROP_FRAME_WIDTH, group['camera_width'])
|
if not active_groups:
|
||||||
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, group['camera_height'])
|
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] = {
|
self.caps[i] = {
|
||||||
'cap': cap,
|
'cap': cap,
|
||||||
'group': group,
|
'group': group,
|
||||||
'name': group['name']
|
'name': group['name']
|
||||||
}
|
}
|
||||||
except Exception as e:
|
loaded_count += 1
|
||||||
print(f"无法打开相机 {group['camera_index']}: {e}")
|
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):
|
def capture_frames(self):
|
||||||
"""捕获帧"""
|
"""捕获帧"""
|
||||||
while self.running:
|
while self.running:
|
||||||
for idx, data in self.caps.items():
|
for idx, data in self.caps.items():
|
||||||
ret, frame = data['cap'].read()
|
try:
|
||||||
if ret:
|
ret, frame = data['cap'].read()
|
||||||
# 裁剪到配置的区域
|
if ret and frame is not None:
|
||||||
height, width = frame.shape[:2]
|
# 裁剪到配置的区域
|
||||||
crop_top = 30
|
height, width = frame.shape[:2]
|
||||||
crop_bottom = min(crop_top + 720, height)
|
crop_top = 30
|
||||||
crop_width = min(1280, width)
|
crop_bottom = min(crop_top + 720, height)
|
||||||
frame = frame[crop_top:crop_bottom, 0:crop_width]
|
crop_width = min(1280, width)
|
||||||
|
|
||||||
# 转换颜色空间
|
# 确保裁剪范围有效
|
||||||
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
if crop_bottom > crop_top and crop_width > 0:
|
||||||
self.frames[idx] = frame_rgb
|
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):
|
def create_grid_window(self):
|
||||||
"""创建网格窗口"""
|
"""创建网格窗口"""
|
||||||
@@ -75,6 +126,19 @@ class PreviewWindow:
|
|||||||
try:
|
try:
|
||||||
canvas.delete("all")
|
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_width = root.winfo_width() if root.winfo_width() > 1 else 1000
|
||||||
window_height = root.winfo_height() if root.winfo_height() > 1 else 700
|
window_height = root.winfo_height() if root.winfo_height() > 1 else 700
|
||||||
@@ -84,7 +148,7 @@ class PreviewWindow:
|
|||||||
|
|
||||||
frame_idx = 0
|
frame_idx = 0
|
||||||
for idx in self.caps.keys():
|
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
|
row = frame_idx // columns
|
||||||
col = frame_idx % columns
|
col = frame_idx % columns
|
||||||
|
|
||||||
@@ -92,32 +156,48 @@ class PreviewWindow:
|
|||||||
y = row * cell_height
|
y = row * cell_height
|
||||||
|
|
||||||
# 调整图像大小
|
# 调整图像大小
|
||||||
frame = self.frames[idx]
|
try:
|
||||||
h, w = frame.shape[:2]
|
frame = self.frames[idx]
|
||||||
scale = min(cell_width / w, cell_height / h) * 0.9
|
h, w = frame.shape[:2]
|
||||||
new_w = int(w * scale)
|
scale = min(cell_width / w, cell_height / h) * 0.9
|
||||||
new_h = int(h * scale)
|
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图像
|
||||||
pil_image = Image.fromarray(resized_frame)
|
pil_image = Image.fromarray(resized_frame)
|
||||||
photo = ImageTk.PhotoImage(image=pil_image)
|
photo = ImageTk.PhotoImage(image=pil_image)
|
||||||
# 保持引用,避免被GC
|
# 保持引用,避免被GC
|
||||||
self.photo_objects[idx] = photo
|
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_x = x + cell_width // 2
|
||||||
center_y = y + cell_height // 2
|
center_y = y + cell_height // 2
|
||||||
canvas.create_image(center_x, center_y, image=photo, anchor='center')
|
|
||||||
|
|
||||||
# 绘制标签
|
|
||||||
name = self.caps[idx]['name']
|
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
|
frame_idx += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"更新帧错误: {e}")
|
print(f"更新帧错误: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
# 约30fps
|
# 约30fps
|
||||||
root.after(33, update_frames_once)
|
root.after(33, update_frames_once)
|
||||||
|
|
||||||
@@ -170,16 +250,17 @@ class PreviewWindow:
|
|||||||
if not self.running or not self.large_window.winfo_exists():
|
if not self.running or not self.large_window.winfo_exists():
|
||||||
return
|
return
|
||||||
try:
|
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")
|
canvas.delete("all")
|
||||||
|
|
||||||
frame = self.frames[idx]
|
frame = self.frames[idx]
|
||||||
h, w = frame.shape[:2]
|
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)
|
scale = min(window_width / w, window_height / h)
|
||||||
new_w = int(w * scale)
|
new_w = int(w * scale)
|
||||||
new_h = int(h * scale)
|
new_h = int(h * scale)
|
||||||
@@ -190,8 +271,20 @@ class PreviewWindow:
|
|||||||
photo_obj['img'] = photo
|
photo_obj['img'] = photo
|
||||||
|
|
||||||
canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center')
|
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:
|
except Exception as e:
|
||||||
print(f"更新大窗口错误: {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)
|
||||||
|
|
||||||
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()
|
self.init_cameras()
|
||||||
|
|
||||||
|
if not self.caps:
|
||||||
|
# 如果没有加载任何采集卡,仍然显示窗口并显示错误信息
|
||||||
|
print("⚠️ 没有可用的采集卡,将显示错误提示窗口")
|
||||||
|
|
||||||
# 启动捕获线程
|
# 启动捕获线程
|
||||||
capture_thread = threading.Thread(target=self.capture_frames, daemon=True)
|
if self.caps:
|
||||||
capture_thread.start()
|
capture_thread = threading.Thread(target=self.capture_frames, daemon=True)
|
||||||
|
capture_thread.start()
|
||||||
|
|
||||||
# 创建并显示网格窗口
|
# 创建并显示网格窗口
|
||||||
import time
|
import time
|
||||||
|
|||||||
Reference in New Issue
Block a user