如何从另一类文件调用Tkinter类并实现相机帧异步处理与显示
解决你的Andor相机采集与GUI实时显示问题
结合你的需求,我们需要拆分帧采集逻辑到独立线程、实现跨文件的GUI回调、并通过帧缓存实现"处理下一帧时显示当前帧"的低延迟效果。下面是具体的分步实现方案:
一、重构AndorCameraSDK:将帧存储逻辑移至线程
首先修改你的SDK类,把原LiveAcquisition中的循环提取为独立线程函数,并添加回调机制通知GUI更新。这样采集逻辑在后台线程运行,不会阻塞GUI主线程。
# 文件1: AndorCameraSDK.pyx import threading import numpy as np cimport numpy as np from cpython.mem cimport calloc, free class AndorCameraSDK: def __init__(self): self.count = 0 # 添加线程控制标志,方便终止采集 self._stop_event = threading.Event() # 初始化相机参数(请根据实际硬件补充width/height/stride值) self.width = 640 self.height = 480 self.stride = self.width * 2 # 示例值,根据相机实际 stride 调整 def LiveAcquisition(self, nframes, np.ndarray[np.uint16_t, ndim=3] image, frame_callback=None): """ 启动帧采集线程 :param nframes: 总采集帧数 :param image: 存储所有帧的3D数组 :param frame_callback: 帧处理完成后的回调函数(用于通知GUI更新) """ # 重置停止标志 self._stop_event.clear() # 启动采集线程(设置为守护线程,程序退出时自动销毁) acquisition_thread = threading.Thread( target=self._acquisition_loop, args=(nframes, image, frame_callback), daemon=True ) acquisition_thread.start() cdef _acquisition_loop(self, int nframes, np.ndarray[np.uint16_t, ndim=3] image, frame_callback): cdef unsigned char *pBuf cdef unsigned short *pus_image cdef int i, j, k cdef size_t sizeinBytes = self.width * self.height * sizeof(unsigned short) # 缓存上一帧,实现"处理i+1帧时显示i帧"的逻辑 prev_frame = None try: # 相机初始化逻辑(保留你原有的参数初始化代码) # .......Initialisation of parameters..............# for i in range(nframes): if self._stop_event.is_set(): break # 响应停止命令,终止采集 # 分配内存存储单帧数据 pBuf = <unsigned char*>calloc(sizeinBytes, sizeof(unsigned char)) if not pBuf: raise MemoryError("Failed to allocate frame buffer") # 将pBuf中的数据写入3D数组(保留你原有的数据拷贝逻辑) pus_image = <unsigned short*>pBuf for j in range(self.width): for k in range(self.height): image[i][j][k] = pus_image[0] pus_image += 1 # 核心逻辑:处理第i帧时,将上一帧(i-1)传给GUI显示 if i > 0 and frame_callback is not None: # 传递帧副本,避免线程间数据竞争 frame_callback(np.copy(prev_frame)) # 更新缓存的上一帧为当前帧 prev_frame = image[i].copy() # 释放当前帧的内存 free(pBuf) # 采集结束后,显示最后一帧 if prev_frame is not None and frame_callback is not None: frame_callback(prev_frame) finally: # 相机资源清理逻辑(保留你原有的收尾代码) # .......Cleanup code..............# pass def stop_acquisition(self): """停止采集线程""" self._stop_event.set() def function1(self): # 保留你原有的function1逻辑 pass
二、修改AndorCameraGUI:实现线程安全的GUI更新
Tkinter的UI操作必须在主线程执行,所以我们不能直接在子线程调用LivePlot,需要用after()方法将更新任务调度到主线程。同时,我们将GUI的绘图函数作为回调传给SDK的采集线程。
# 文件2: AndorCameraGUI.py import tkinter as tk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt import numpy as np import AndorCameraSDK as andorcamera class AndorCameraGUI: def __init__(self, root): self.root = root # 创建GUI组件布局 self._setup_gui() # 初始化相机实例 self.camera = andorcamera.AndorCameraSDK() # 初始化绘图对象 self.fig, self.ax = plt.subplots(figsize=(6,4), dpi=100) self.canvas = FigureCanvasTkAgg(self.fig, master=self.display_frame) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) def _setup_gui(self): # 基础GUI布局示例,可根据你的需求调整 self.root.title("Andor Camera Live View") self.root.geometry("700x550") # 控制按钮区 control_frame = tk.Frame(self.root) control_frame.pack(pady=10) self.live_btn = tk.Button(control_frame, text="Start Live", command=self.LiveImageGUI, width=12) self.live_btn.grid(row=0, column=0, padx=5) self.stop_btn = tk.Button(control_frame, text="Stop Live", command=self.stop_live, width=12, state=tk.DISABLED) self.stop_btn.grid(row=0, column=1, padx=5) # 图像显示区 self.display_frame = tk.Frame(self.root, bg="#f0f0f0") self.display_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) def LiveImageGUI(self): # 更新按钮状态,防止重复点击 self.live_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) # 定义总采集帧数和3D存储数组(尺寸匹配相机分辨率) nframes = 100 self.image_array = np.zeros((nframes, self.camera.width, self.camera.height), dtype=np.uint16) # 启动采集,传入帧回调函数 self.camera.LiveAcquisition(nframes, self.image_array, frame_callback=self._on_frame_ready) def stop_live(self): self.camera.stop_acquisition() self.live_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) def _on_frame_ready(self, frame): """帧就绪回调,调度到主线程更新UI""" # 使用after(0)确保绘图操作在Tkinter主线程执行 self.root.after(0, self.LivePlot, frame) def LivePlot(self, frame): """在GUI中实时显示帧""" # 清除原有绘图内容 self.ax.clear() # 显示灰度图像(可根据需求调整colormap) self.ax.imshow(frame, cmap="gray", vmin=0, vmax=65535) self.ax.axis("off") # 隐藏坐标轴 # 更新画布 self.canvas.draw() # 启动GUI程序 if __name__ == "__main__": root = tk.Tk() app = AndorCameraGUI(root) root.mainloop()
三、关键逻辑说明
- 线程分离:采集循环在后台守护线程运行,不会阻塞GUI主线程,彻底解决界面卡顿问题。
- 帧缓存机制:通过
prev_frame缓存上一帧,当处理第i帧时,将第i-1帧传给GUI显示,完美实现"处理i+1帧时显示i帧"的低延迟效果。 - 线程安全的GUI更新:使用Tkinter的
after()方法将绘图任务调度到主线程,避免子线程直接操作UI导致的程序崩溃。 - 可控制的采集流程:添加
stop_acquisition()方法和_stop_event标志,支持随时终止采集,保证相机资源安全释放。
内容的提问来源于stack exchange,提问作者aishwarya selvaraj
相关产品推荐
相关产品推荐

