图片前的版本
This commit is contained in:
2
.idea/huojv.iml
generated
2
.idea/huojv.iml
generated
@@ -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
2
.idea/misc.xml
generated
@@ -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>
|
||||||
204
preview.py
204
preview.py
@@ -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,20 +154,19 @@ 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:
|
||||||
|
# 更新现有图像项
|
||||||
|
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:
|
if self.debug_count <= 3 and i == 0:
|
||||||
print(f" 绘制图像 #{i} (idx={idx}) 到位置 ({x}, {y})")
|
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}")
|
||||||
|
|
||||||
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
|
import traceback
|
||||||
traceback.print_exc()
|
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)
|
|
||||||
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)
|
pil_image = Image.fromarray(resized_frame)
|
||||||
photo = ImageTk.PhotoImage(image=pil_image)
|
photo = ImageTk.PhotoImage(image=pil_image)
|
||||||
# 保存引用到字典,确保不被GC
|
# 保存引用到字典,确保不被GC
|
||||||
photo_obj['img'] = photo
|
photo_obj['img'] = photo
|
||||||
|
|
||||||
# 清空并绘制新图像
|
# 更新或创建图像项
|
||||||
|
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.delete("all")
|
||||||
canvas.create_image(window_width // 2, window_height // 2, image=photo, anchor='center')
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user