图片前的版本

This commit is contained in:
Ray
2025-10-29 18:15:12 +08:00
parent 94fa69043b
commit 2399f87d57
3 changed files with 167 additions and 65 deletions

2
.idea/huojv.iml generated
View File

@@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="dnf" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

2
.idea/misc.xml generated
View File

@@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="D:\CONDA\anaconda3" /> <option name="sdkName" value="D:\CONDA\anaconda3" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="dnf" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
</project> </project>

View File

@@ -126,8 +126,17 @@ class PreviewWindow:
print(f"✅ 成功加载 {loaded_count} 个采集卡") print(f"✅ 成功加载 {loaded_count} 个采集卡")
def capture_frames(self): def capture_frames(self):
"""捕获帧""" """每5秒截取一张图"""
import time
first_capture = True # 第一次立即截取
while self.running: while self.running:
if first_capture:
first_capture = False
# 第一次立即截取,不等待
pass
else:
# 之后每5秒截取一次
time.sleep(5.0)
for idx, data in self.caps.items(): for idx, data in self.caps.items():
try: try:
ret, frame = data['cap'].read() ret, frame = data['cap'].read()
@@ -145,19 +154,18 @@ class PreviewWindow:
# 转换颜色空间 # 转换颜色空间
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.frames[idx] = frame_rgb self.frames[idx] = frame_rgb
print(f"📸 采集卡 {idx} 截图已更新")
else: else:
print(f"⚠️ 采集卡 {idx} 裁剪参数无效") print(f"⚠️ 采集卡 {idx} 裁剪参数无效")
else: else:
# 读取失败,清除旧帧 # 读取失败,清除旧帧
if idx in self.frames: if idx in self.frames:
self.frames[idx] = None self.frames[idx] = None
print(f"⚠️ 采集卡 {idx} 读取失败")
except Exception as e: except Exception as e:
print(f"捕获帧 {idx} 错误: {e}") print(f"捕获帧 {idx} 错误: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
import time
time.sleep(0.01) # 避免CPU占用过高
def create_grid_window(self): def create_grid_window(self):
"""创建网格窗口""" """创建网格窗口"""
@@ -187,7 +195,7 @@ class PreviewWindow:
print(f"📐 预览窗口配置: {preview_width}x{preview_height}, 网格: {columns}x{rows}") print(f"📐 预览窗口配置: {preview_width}x{preview_height}, 网格: {columns}x{rows}")
root = tk.Tk() root = tk.Tk()
root.title("采集卡预览 - 点击放大") root.title("采集卡预览每5秒更新- 点击放大")
root.geometry(f"{preview_width}x{preview_height}") root.geometry(f"{preview_width}x{preview_height}")
root.update_idletasks() # 立即更新窗口尺寸 root.update_idletasks() # 立即更新窗口尺寸
@@ -197,6 +205,7 @@ class PreviewWindow:
# 存储图像对象使用列表保存所有PhotoImage引用防止GC # 存储图像对象使用列表保存所有PhotoImage引用防止GC
self.photo_objects_list = [] self.photo_objects_list = []
self.photo_objects = {} # 按索引映射 self.photo_objects = {} # 按索引映射
self.canvas_image_items = {} # 保存canvas中的图像项ID用于更新而不是删除重建
# 用于控制调试输出(只打印前几次) # 用于控制调试输出(只打印前几次)
self.debug_count = 0 self.debug_count = 0
@@ -344,23 +353,31 @@ class PreviewWindow:
texts_to_draw.append((center_x, center_y, f"{name}\n等待画面...", 'gray')) texts_to_draw.append((center_x, center_y, f"{name}\n等待画面...", 'gray'))
frame_idx += 1 frame_idx += 1
# 清空画布保留photo引用只清空画布内容 # 先收集所有photo对象到列表中确保引用不丢失
canvas.delete("all") photos_current_frame = []
# 先绘制所有图像(底层) # 先绘制所有图像(底层)
if self.debug_count <= 3: if self.debug_count <= 3:
print(f"📊 绘制状态: {len(images_to_draw)} 个图像, {len(texts_to_draw)} 个文本, 画布={canvas_width}x{canvas_height}") 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: if images_to_draw:
# 先收集所有photo对象到列表中确保引用不丢失 # 删除旧的文本和分割线但保留图像项这样PhotoImage引用不会被释放
photos_current_frame = [] # 只删除文本项和线条项
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): for i, item in enumerate(images_to_draw):
if len(item) == 4: if len(item) == 4:
photo, x, y, idx = item photo, x, y, idx = item
else: else:
# 兼容旧格式(如果还有的话)
photo, x, y = item[:3] photo, x, y = item[:3]
idx = None idx = None
@@ -370,45 +387,58 @@ class PreviewWindow:
# 先保存引用到列表防止GC # 先保存引用到列表防止GC
photos_current_frame.append(photo) photos_current_frame.append(photo)
# 然后绘制 # 更新或创建图像项
canvas.create_image(x, y, image=photo, anchor='center') if idx in self.canvas_image_items:
# 更新现有图像项
if self.debug_count <= 3 and i == 0: try:
print(f" 绘制图像 #{i} (idx={idx}) 到位置 ({x}, {y})") 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: else:
if self.debug_count <= 3: if self.debug_count <= 3:
print(f" ⚠️ 图像 #{i} 位置 ({x}, {y}) 超出画布范围") print(f" ⚠️ 图像 #{i} 位置 ({x}, {y}) 超出画布范围")
except Exception as e: except Exception as e:
error_msg = str(e).lower() error_msg = str(e).lower()
print(f" ❌ 绘制图像 #{i} 时出错: {type(e).__name__}: {e}") print(f" ❌ 绘制图像 #{i} 时出错: {type(e).__name__}: {e}")
import traceback
if "pyimage" in error_msg: traceback.print_exc()
# 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()
# 保存当前帧的所有photo引用替换旧的列表只保留当前帧 # 删除不再存在的图像项
# 这样确保正在显示的photo对象不会被GC current_indices = set(idx for item in images_to_draw if len(item) >= 4 for idx in [item[3]])
self.photo_objects_list = photos_current_frame[:] # 复制列表 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: else:
# 如果没有图像,至少显示一些提示 # 如果没有图像,清空所有并显示提示
canvas.delete("all")
self.canvas_image_items.clear()
if self.debug_count <= 3: if self.debug_count <= 3:
print(" ⚠️ 没有图像可绘制") print(" ⚠️ 没有图像可绘制")
# 在画布中心显示提示
canvas.create_text( canvas.create_text(
canvas_width // 2, canvas_width // 2,
canvas_height // 2, canvas_height // 2,
@@ -429,6 +459,20 @@ class PreviewWindow:
except Exception as e: except Exception as e:
print(f"绘制文本错误: {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() canvas.update_idletasks()
@@ -462,8 +506,8 @@ class PreviewWindow:
print(f"更新帧错误: {e}") print(f"更新帧错误: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
# 约30fps # 每1秒检查一次是否有新截图截图在另一个线程每5秒更新
root.after(33, update_frames_once) root.after(1000, update_frames_once)
def on_canvas_click(event): def on_canvas_click(event):
"""点击画布事件""" """点击画布事件"""
@@ -516,52 +560,109 @@ class PreviewWindow:
root.mainloop() root.mainloop()
def show_large_window(self, idx): def show_large_window(self, idx):
"""显示大窗口""" """显示大窗口(实时显示指定采集卡)"""
if self.large_window is not None and self.large_window.winfo_exists(): if self.large_window is not None and self.large_window.winfo_exists():
self.large_window.destroy() self.large_window.destroy()
self.large_window = tk.Toplevel() 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") self.large_window.geometry("1280x720")
canvas = Canvas(self.large_window, bg='black') canvas = Canvas(self.large_window, bg='black')
canvas.pack(fill=tk.BOTH, expand=True) canvas.pack(fill=tk.BOTH, expand=True)
photo_obj = {} photo_obj = {}
canvas_image_item = None # 保存canvas中的图像项ID
def update_large_once(): def update_large_once():
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 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_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 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: if ret and frame is not None:
# 先处理图像,再清空画布 # 裁剪到配置的区域
frame = self.frames[idx] height, width = frame.shape[:2]
h, w = 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) 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)
resized_frame = cv2.resize(frame, (new_w, new_h)) resized_frame = cv2.resize(frame_rgb, (new_w, new_h))
# 确保颜色通道正确BGR -> RGB pil_image = Image.fromarray(resized_frame)
if len(resized_frame.shape) == 3 and resized_frame.shape[2] == 3: photo = ImageTk.PhotoImage(image=pil_image)
resized_frame = cv2.cvtColor(resized_frame, cv2.COLOR_BGR2RGB) # 保存引用到字典确保不被GC
pil_image = Image.fromarray(resized_frame) photo_obj['img'] = photo
photo = ImageTk.PhotoImage(image=pil_image)
# 保存引用到字典确保不被GC
photo_obj['img'] = photo
# 清空并绘制新图像 # 更新或创建图像
canvas.delete("all") if canvas_image_item is not None:
canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center') 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: else:
# 显示等待提示 # 显示等待提示
canvas.delete("all") canvas.delete("all")
canvas_image_item = None
canvas.create_text( canvas.create_text(
window_width // 2, window_width // 2,
window_height // 2, window_height // 2,
@@ -572,6 +673,7 @@ class PreviewWindow:
except Exception as e: except Exception as e:
if "pyimage" not in str(e).lower(): # 忽略pyimage错误避免刷屏 if "pyimage" not in str(e).lower(): # 忽略pyimage错误避免刷屏
print(f"更新大窗口错误: {e}") print(f"更新大窗口错误: {e}")
# 约30fps实时显示
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)