多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

【大恒相机GXIPY】Python封装类后相机句柄失效、出函数就报错终极解决

【大恒相机GXIPY】Python封装类后相机句柄失效、出函数就报错终极解决 【大恒相机GXIPY】Python封装类后相机句柄失效、出函数就报错终极解决前言很多小伙伴在用大恒工业相机 gxipy做Python开发时都会遇到一个极其诡异、且全网很少讲透的BUG✅在初始化函数内部self.cam 开采、取流完全正常❌一旦跳出初始化函数在其他成员函数调用 self.cam.stream_on() 直接报错、句柄失效、自动断流控制台会出现关键日志[DoCloseDataStream success]相机数据流被莫名关闭。最让人困惑的是self.cam 赋值完全没错、不是None、对象ID没变但底层设备就是失效。今天一文彻底讲透底层原理、错误写法、正确封装方案完美解决该问题。一、问题现象百分百复现1、相机初始化函数内调用采集、开流运行正常2、初始化函数执行完毕退出后在按钮点击、子线程、其他函数中调用相机接口直接报错3、SDK日志打印DoCloseDataStream success底层数据流被自动关闭4、Python层面self.cam正常存在不为空对象地址不变。二、核心根因99%开发者踩坑问题根本不是self.cam赋值错误而是大恒gxipy SDK的特殊生命周期机制相机设备的生命周期不归 cam 对象管只归 DeviceManager 管理如果DeviceManager是函数局部变量1、函数执行结束局部变量被Python GC垃圾回收销毁2、DeviceManager 析构时会强制关闭所有由它打开的相机设备、销毁底层句柄3、仅剩Python层的self.cam空壳对象底层设备已失效调用任何接口都会报错。简单总结cam 只是设备句柄副本DeviceManager 才是设备真正的“监护人”。三、高危错误写法大家几乎都这么写错误核心DeviceManager 写在函数内部为局部变量def init_camera(self): # 局部变量函数结束直接被GC回收 device_manager gx.DeviceManager() dev_num, dev_info_list device_manager.update_all_device_list() self.cam device_manager.open_device_by_index(1) # 本行执行正常 self.cam.stream_on() self.cam.stream_off() return True # 跳出本函数后self.cam 底层直接失效哪怕你全程用self.cam只要device_manager销毁相机底层直接断连。四、正确原理与解决方案想要相机跨函数、跨线程持续有效必须保证DeviceManager 全程常驻不被GC回收。解决方案将 DeviceManager 提升为类实例属性self托管全程持有引用禁止销毁。五、完整可运行类封装代码无BUG版基于官方Demo重构修复句柄失效问题、增加线程安全、资源自动释放可直接用于项目# version:1.0.2406.9261 import gxipy as gx from gxipy.gxidef import * import threading class DaHengCamera: def __init__(self): self.device_manager None self.cam None self.remote_device_feature None self._thread_state False self._capture_thread None def scan_devices(self): 扫描设备返回设备数量与设备信息列表 if self.device_manager is None: self.device_manager gx.DeviceManager() dev_num, dev_info_list self.device_manager.update_all_device_list() return dev_num, dev_info_list def open_by_index(self, index1): 按索引打开相机index从1开始 dev_num, dev_info_list self.scan_devices() if dev_num 0: print(Number of enumerated devices is 0) return False #print(------------------enter open_by_index--------------------------) # 打开相机存self.cam防止局部变量GC回收 self.cam self.device_manager.open_device_by_index(index) self.remote_device_feature self.cam.get_remote_device_feature_control() # 加载默认参数集 self.remote_device_feature.get_enum_feature(UserSetSelector).set(Default) self.remote_device_feature.get_command_feature(UserSetLoad).send_command() print(***********************************************) print(fVendor Name: {dev_info_list[index-1][vendor_name]}) print(fModel Name: {dev_info_list[index-1][model_name]}) print(***********************************************) return True def _capture_thread_func(self): 内部采集线程函数 self.cam.stream_on() while self._thread_state: try: image self.cam.data_stream[0].dq_buf(1000) if image.frame_data.status GxFrameStatusList.SUCCESS: print(fSuccessful acquisition: Width: {image.frame_data.width}, fHeight: {image.frame_data.height}, FrameID: {image.frame_data.frame_id}) else: print(Abnormal Acquisition) # 归还buffer必须执行 self.cam.data_stream[0].q_buf(image) except Exception as ex: print(fcapture error: {str(ex)}) self.cam.stream_off() def start_capture(self): 启动采集线程 if self.cam is None: print(camera not opened!) return if self._thread_state: print(already capturing) return self._thread_state True self._capture_thread threading.Thread(targetself._capture_thread_func, daemonTrue) self._capture_thread.start() def stop_capture(self): 停止采集等待线程退出 self._thread_state False if self._capture_thread is not None and self._capture_thread.is_alive(): self._capture_thread.join(timeout2.0) def close(self): 关闭相机释放资源 self.stop_capture() if self.cam is not None: self.cam.close_device() self.cam None self.remote_device_feature None if self.device_manager is not None: self.device_manager None def __del__(self): # 析构兜底释放 self.close() def main(): cam_obj DaHengCamera() ok cam_obj.open_by_index(1) print(Press [a] or [A] and then press [Enter] to start acquisition) print(Press [x] or [X] and then press [Enter] to Exit the Program) wait_start True while wait_start: user_input input() if user_input in (A, a): # 打开相机索引1 if ok: cam_obj.start_capture() wait_start False elif user_input in (X, x): print(App exit!) return try: # 主线程等待输入按x退出 while True: key input() if key in (X, x): break finally: cam_obj.close() print(camera closed) if __name__ __main__: print() print(-------------------------------------------------------------) print(Sample to show how to acquire image continuously and show dqbuf acquired image.) print(-------------------------------------------------------------) print() print(Initializing......) print() main()运行结果六、关键避坑总结必看1、生命周期核心规则❌ 错误DeviceManager 写函数局部变量出函数销毁相机直接失效✅ 正确DeviceManager 绑定 self全程常驻2、采集缓冲区硬性规则dq_buf取图后必须强制 q_buf 归还缓冲区否则缓冲区池耗尽后续采集卡死、超时。七、常见问题答疑Qself.cam对象ID没变为什么还是报错Python层对象只是壳底层C语言句柄由DeviceManager管理管理器销毁即底层失效和Python对象无关。Q为什么官方Demo局部变量可以运行官方Demo是单流程脚本函数不退出、不跨函数调用封装成类、多函数、多线程场景后生命周期问题会彻底暴露。结尾大恒相机Python开发90%的诡异句柄失效、自动断流、跨函数报错全部来自DeviceManager 局部变量GC回收这个坑。只要记住管理器常驻、相机不失效即可彻底解决所有同类问题代码可直接用于机器视觉、Qt界面、实时采集项目。欢迎点赞收藏解决大恒相机Python封装各种疑难杂症专栏归属本文收录于大恒相机・SDK 开发实战篇系列文章持续更新解决工业相机使用中的各种开发问题
返回列表