diff --git a/.idea/huojv.iml b/.idea/huojv.iml
index 8b74e97..8388dbc 100644
--- a/.idea/huojv.iml
+++ b/.idea/huojv.iml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index b02dc17..5f01a00 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,5 +3,5 @@
-
+
\ No newline at end of file
diff --git a/preview.py b/preview.py
index 179be11..835c5fe 100644
--- a/preview.py
+++ b/preview.py
@@ -126,8 +126,17 @@ class PreviewWindow:
print(f"✅ 成功加载 {loaded_count} 个采集卡")
def capture_frames(self):
- """捕获帧"""
+ """每5秒截取一张图"""
+ import time
+ first_capture = True # 第一次立即截取
while self.running:
+ if first_capture:
+ first_capture = False
+ # 第一次立即截取,不等待
+ pass
+ else:
+ # 之后每5秒截取一次
+ time.sleep(5.0)
for idx, data in self.caps.items():
try:
ret, frame = data['cap'].read()
@@ -145,19 +154,18 @@ class PreviewWindow:
# 转换颜色空间
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.frames[idx] = frame_rgb
+ print(f"📸 采集卡 {idx} 截图已更新")
else:
print(f"⚠️ 采集卡 {idx} 裁剪参数无效")
else:
# 读取失败,清除旧帧
if idx in self.frames:
self.frames[idx] = None
+ print(f"⚠️ 采集卡 {idx} 读取失败")
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):
"""创建网格窗口"""
@@ -187,7 +195,7 @@ class PreviewWindow:
print(f"📐 预览窗口配置: {preview_width}x{preview_height}, 网格: {columns}x{rows}")
root = tk.Tk()
- root.title("采集卡预览 - 点击放大")
+ root.title("采集卡预览(每5秒更新)- 点击放大")
root.geometry(f"{preview_width}x{preview_height}")
root.update_idletasks() # 立即更新窗口尺寸
@@ -197,6 +205,7 @@ class PreviewWindow:
# 存储图像对象(使用列表保存所有PhotoImage引用,防止GC)
self.photo_objects_list = []
self.photo_objects = {} # 按索引映射
+ self.canvas_image_items = {} # 保存canvas中的图像项ID,用于更新而不是删除重建
# 用于控制调试输出(只打印前几次)
self.debug_count = 0
@@ -344,23 +353,31 @@ class PreviewWindow:
texts_to_draw.append((center_x, center_y, f"{name}\n等待画面...", 'gray'))
frame_idx += 1
- # 清空画布(保留photo引用,只清空画布内容)
- canvas.delete("all")
+ # 先收集所有photo对象到列表中,确保引用不丢失
+ photos_current_frame = []
# 先绘制所有图像(底层)
if self.debug_count <= 3:
print(f"📊 绘制状态: {len(images_to_draw)} 个图像, {len(texts_to_draw)} 个文本, 画布={canvas_width}x{canvas_height}")
- print(f" 当前保存的照片对象数量: {len(self.photo_objects_list)}")
if images_to_draw:
- # 先收集所有photo对象到列表中,确保引用不丢失
- photos_current_frame = []
+ # 删除旧的文本和分割线,但保留图像项(这样PhotoImage引用不会被释放)
+ # 只删除文本项和线条项
+ for item_id in list(canvas.find_all()):
+ item_tags = canvas.gettags(item_id)
+ item_type = canvas.type(item_id)
+ # 删除文本和线条,保留图像
+ if item_type in ['text', 'line']:
+ try:
+ canvas.delete(item_id)
+ except:
+ pass
+ # 现在更新或创建图像项
for i, item in enumerate(images_to_draw):
if len(item) == 4:
photo, x, y, idx = item
else:
- # 兼容旧格式(如果还有的话)
photo, x, y = item[:3]
idx = None
@@ -370,45 +387,58 @@ class PreviewWindow:
# 先保存引用到列表(防止GC)
photos_current_frame.append(photo)
- # 然后绘制
- canvas.create_image(x, y, image=photo, anchor='center')
-
- if self.debug_count <= 3 and i == 0:
- print(f" 绘制图像 #{i} (idx={idx}) 到位置 ({x}, {y})")
+ # 更新或创建图像项
+ if idx in self.canvas_image_items:
+ # 更新现有图像项
+ try:
+ item_id = self.canvas_image_items[idx]
+ canvas.coords(item_id, x, y)
+ canvas.itemconfig(item_id, image=photo)
+ if self.debug_count <= 3 and i == 0:
+ print(f" 更新图像 #{i} (idx={idx}) 到位置 ({x}, {y})")
+ except:
+ # 如果更新失败,删除旧的并创建新的
+ try:
+ canvas.delete(self.canvas_image_items[idx])
+ except:
+ pass
+ item_id = canvas.create_image(x, y, image=photo, anchor='center')
+ self.canvas_image_items[idx] = item_id
+ if self.debug_count <= 3 and i == 0:
+ print(f" 重新创建图像 #{i} (idx={idx}) 到位置 ({x}, {y})")
+ else:
+ # 创建新图像项
+ item_id = canvas.create_image(x, y, image=photo, anchor='center')
+ self.canvas_image_items[idx] = item_id
+ if self.debug_count <= 3 and i == 0:
+ print(f" 创建图像 #{i} (idx={idx}) 到位置 ({x}, {y})")
else:
if self.debug_count <= 3:
print(f" ⚠️ 图像 #{i} 位置 ({x}, {y}) 超出画布范围")
except Exception as e:
error_msg = str(e).lower()
print(f" ❌ 绘制图像 #{i} 时出错: {type(e).__name__}: {e}")
-
- if "pyimage" in error_msg:
- # PhotoImage 对象被 GC 了,尝试恢复
- if self.debug_count <= 5:
- print(f" ⚠️ PhotoImage 对象丢失,尝试从字典恢复 (idx={idx})")
- if idx is not None and idx in self.photo_objects:
- try:
- photo = self.photo_objects[idx]
- photos_current_frame.append(photo)
- canvas.create_image(x, y, image=photo, anchor='center')
- if self.debug_count <= 5:
- print(f" ✅ 已从字典恢复并重新绘制")
- except Exception as e2:
- if self.debug_count <= 5:
- print(f" ❌ 恢复失败: {e2}")
- else:
- # 其他类型的错误,打印完整堆栈
- import traceback
- traceback.print_exc()
+ import traceback
+ traceback.print_exc()
- # 保存当前帧的所有photo引用(替换旧的列表,只保留当前帧)
- # 这样确保正在显示的photo对象不会被GC
- self.photo_objects_list = photos_current_frame[:] # 复制列表
+ # 删除不再存在的图像项
+ current_indices = set(idx for item in images_to_draw if len(item) >= 4 for idx in [item[3]])
+ for idx in list(self.canvas_image_items.keys()):
+ if idx not in current_indices:
+ try:
+ canvas.delete(self.canvas_image_items[idx])
+ del self.canvas_image_items[idx]
+ except:
+ pass
+
+ # 保存当前帧的所有photo引用
+ self.photo_objects_list = photos_current_frame[:]
else:
- # 如果没有图像,至少显示一些提示
+ # 如果没有图像,清空所有并显示提示
+ canvas.delete("all")
+ self.canvas_image_items.clear()
if self.debug_count <= 3:
print(" ⚠️ 没有图像可绘制")
- # 在画布中心显示提示
canvas.create_text(
canvas_width // 2,
canvas_height // 2,
@@ -429,6 +459,20 @@ class PreviewWindow:
except Exception as e:
print(f"绘制文本错误: {e}")
+ # 绘制更新时间提示(右上角)
+ try:
+ import datetime
+ update_time = datetime.datetime.now().strftime("%H:%M:%S")
+ canvas.create_text(
+ canvas_width - 10, 15,
+ text=f"最后更新: {update_time}",
+ fill='gray',
+ font=('Arial', 8),
+ anchor='ne'
+ )
+ except:
+ pass
+
# 强制更新画布显示
canvas.update_idletasks()
@@ -462,8 +506,8 @@ class PreviewWindow:
print(f"更新帧错误: {e}")
import traceback
traceback.print_exc()
- # 约30fps
- root.after(33, update_frames_once)
+ # 每1秒检查一次是否有新截图(截图在另一个线程每5秒更新)
+ root.after(1000, update_frames_once)
def on_canvas_click(event):
"""点击画布事件"""
@@ -516,52 +560,109 @@ class PreviewWindow:
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.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 = {}
+ canvas_image_item = None # 保存canvas中的图像项ID
def update_large_once():
if not self.running or not self.large_window.winfo_exists():
return
try:
+ # 从采集卡实时读取帧(不依赖截图)
+ if idx not in self.caps:
+ canvas.delete("all")
+ canvas.create_text(
+ 640, 360,
+ text="采集卡已断开",
+ fill='red',
+ font=('Arial', 16)
+ )
+ self.large_window.after(1000, update_large_once)
+ return
+
+ cap = self.caps[idx]['cap']
+ ret, frame = cap.read()
+
# 获取窗口大小
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:
- # 先处理图像,再清空画布
- frame = self.frames[idx]
- h, w = frame.shape[:2]
+ 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)
+ h, w = frame_rgb.shape[:2]
- # 调整到窗口大小
- scale = min(window_width / w, window_height / h)
- new_w = int(w * scale)
- new_h = int(h * scale)
+ # 调整到窗口大小
+ 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))
- # 确保颜色通道正确(BGR -> RGB)
- if len(resized_frame.shape) == 3 and resized_frame.shape[2] == 3:
- resized_frame = cv2.cvtColor(resized_frame, cv2.COLOR_BGR2RGB)
- pil_image = Image.fromarray(resized_frame)
- photo = ImageTk.PhotoImage(image=pil_image)
- # 保存引用到字典,确保不被GC
- photo_obj['img'] = photo
+ resized_frame = cv2.resize(frame_rgb, (new_w, new_h))
+ pil_image = Image.fromarray(resized_frame)
+ photo = ImageTk.PhotoImage(image=pil_image)
+ # 保存引用到字典,确保不被GC
+ photo_obj['img'] = photo
- # 清空并绘制新图像
- canvas.delete("all")
- canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center')
+ # 更新或创建图像项
+ if canvas_image_item is not None:
+ try:
+ # 更新现有图像项
+ canvas.coords(canvas_image_item, window_width // 2, window_height // 2)
+ canvas.itemconfig(canvas_image_item, image=photo)
+ except:
+ # 如果更新失败,删除旧的并创建新的
+ try:
+ canvas.delete(canvas_image_item)
+ except:
+ pass
+ canvas_image_item = canvas.create_image(
+ window_width // 2,
+ window_height // 2,
+ image=photo,
+ anchor='center'
+ )
+ else:
+ # 创建新图像项
+ canvas.delete("all") # 首次创建时清空
+ canvas_image_item = 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='red',
+ font=('Arial', 16)
+ )
else:
# 显示等待提示
canvas.delete("all")
+ canvas_image_item = None
canvas.create_text(
window_width // 2,
window_height // 2,
@@ -572,6 +673,7 @@ class PreviewWindow:
except Exception as e:
if "pyimage" not in str(e).lower(): # 忽略pyimage错误,避免刷屏
print(f"更新大窗口错误: {e}")
+ # 约30fps,实时显示
self.large_window.after(33, update_large_once)
self.large_window.after(33, update_large_once)