
简介这套Python SDK资料面向需要二次开发大恒水星系列工业相机的开发者尤其适合使用MER-500-14GM等型号进行图像采集、视觉检测与自动化项目的人群。包内封装了gxipy核心接口、相机控制脚本及配套说明可帮助快速掌握软触发、单彩/单色图像抓取等典型操作。压缩包共15个文件以Python源码8个py和编译缓存5个pyc为主另有jpg示例图与txt说明文件整体约99KB体积轻量便于直接参考或嵌入现有工程。目前已有2539人学习下载内容聚焦实际调用流程从底层gxidef、gxwrapper到上层示例脚本逐层展开既能看到API封装方式也能通过README了解环境配置与运行要点。对于需要摆脱硬件触发限制、改用软件控制相机或希望在Python环境中快速实现图像采集与预处理的使用者来说这份SDK包提供了可运行的代码起点和清晰的二次开发路径。1. 大恒水星系列Python SDK从拿到相机到看到第一帧画面大恒图像的水星系列是工业视觉里常见的 USB3.0/GigE 面阵相机官方 SDK 自带 Python 绑定也就是标题里说的 Python SDK。越来越多的视觉项目只用 Python 写采集与处理不再为一张图维护 C 采集线程用 gxipy 枚举设备、打开相机、设置曝光和触发、把每一帧直接落成 numpy 数组后面接 OpenCV 或推理都很顺。这篇按拿到一台水星相机后的真实顺序讲先搞清楚 SDK 包怎么组织再跑通枚举、参数配置、软硬触发采图最后把帧率上不去和退不干净两个高频坑的排查路径列清楚。适合正在配 Python 环境的机器视觉工程师也适合刚准备接触工业相机的 Python 入门开发者。2. 环境准备Python SDK包结构与最小枚举脚本2.1 python安装与SDK包结构先对齐位数和解释器版本大恒的 SDK 发布物不是单独的 PyPI 包而是一个带完整目录的安装包。Windows 下安装之后目录里同时存在 Development、Runtime、Samples 等位置Python 绑定放在 Development 下的 Samples 里核心是一个叫 gxipy 的 Python 包目录。这个包就是水星系列相机 Python SDK 的主体它对外提供 DeviceManager、Camera、FrameData 这些对象底层调用的是 GxIAPI 动态库。写代码时不需要 pip 安装额外的大恒包直接把 gxipy 所在目录引入工程即可官方也一直是以这种方式分发 Python 示例。做 python 安装和环境配置时最容易忽略的是解释器位数。水星相机当前的 Windows 版 SDK 基本以 x64 为主如果误装了 32 位 Python导入 gxipy 大概率直接失败界面提示往往是找不到模块排查半天才发现是位数不匹配。建议统一用 64 位 Python 3.8 以上的版本同时把 numpy、opencv-python 先装好这两个是后面把帧数据转成可处理图像的标准依赖。SDK 里的 Python 示例工程结构可以直接作为项目骨架gxipy 包放在工程根目录业务代码单独建文件 import 它。拿到 SDK 后的第一件事是确认当前相机属于哪一类接口。USB3.0 的水星相机走 GxIAPI 的 USB 传输GigE 网口相机走同一套接口但需要网卡配合后面涉及时序参数时会有区别枚举和打开代码两边完全一致。也就是说Python SDK 这层把 USB3.0 和 GigE 的差异屏蔽掉了业务代码不用为接口类型分叉。2.2 gxipy导入链路动态库路径决定导入成败gxipy 导入失败是最常见的第一个卡点但多数情况不是代码问题而是动态库没被找到。gxipy 内部通过 ctypes 加载 GxIAPI 动态库Windows 上对应 GxIAPI.dllLinux 上对应 libGxIAPI.so。安装版 SDK 一般会把 Runtime 目录加入系统 PATH解压版就不会这时候需要手动把动态库所在目录加进来。Windows 下可以这样处理在运行 Python 脚本前把 Runtime 目录追加到 PATH或者用 os.add_dll_directory 在代码启动时加入。Linux 下对应的是 LD_LIBRARY_PATH 环境变量指向 SDK 解压后动态库所在目录。下面的处理方式比较通用适合 portable 部署场景。import os import sys sdk_python_dir rD:\GalaxySDK\Development\Samples\Python if sdk_python_dir not in sys.path: sys.path.append(sdk_python_dir) # Windows 解压版 SDK让 ctypes 能找到 GxIAPI.dll sdk_runtime_dir rD:\GalaxySDK\Runtime\Win64_x64 if os.name nt and os.path.isdir(sdk_runtime_dir): os.add_dll_directory(sdk_runtime_dir) import gxipy as gx逻辑说明sys.path 是为了让 import gxipy 能找到包目录os.add_dll_directory 是 Windows 10 之后推荐的动态库搜索路径注入方式比直接改全局 PATH 更干净也不影响其他程序。Linux 用户把 sdk_runtime_dir 对应路径写进 LD_LIBRARY_PATH 后重新启动解释器即可。判断标准很简单以上代码不报 ImportError环境就算通了。提示如果 gxipy 导入后提示找不到 GxIAPI 动态库先别动业务代码优先检查系统 PATH 和环境变量里有没有 SDK Runtime 目录。2.3 最小枚举脚本没有相机也能验证 SDK 装载环境配好之后先不急着插相机。gxipy 的 DeviceManager 在没有设备时也能正常创建update_device_info 返回的设备数是 0这本身就能验证 SDK 装载是否完整。插上水星相机后再跑一遍应该能枚举出相机信息列表。import gxipy as gx dev_manager gx.DeviceManager() dev_num, dev_info_list dev_manager.update_device_info() print(枚举到设备数:, dev_num) for index in range(dev_num): print(findex{index}) print(dev_info_list[index])逻辑说明DeviceManager 是 SDK 的顶层入口一个进程创建一个实例即可。update_device_info 返回两个值第一个是本次枚举到的设备数量第二个是设备信息列表列表元素包含索引、厂商、型号、序列号、接口类型等字段。打印整个元素比逐个猜字段名更快不同 SDK 版本字段名称略有差异但整体结构一致。这段脚本跑通之后SDK 的装载链路就完整了。实际开发中建议把枚举逻辑封装成一个函数返回设备索引和序列号的对应关系后面多相机选路时会频繁用到。此时可以做一张对照表把 DeviceManager 的常用方法记下来方便随时查。方法行为返回值常见使用场景DeviceManager()创建设备管理器实例管理器对象每个进程创建一次update_device_info()增量枚举当前设备(设备数, 设备信息列表)程序启动后第一次扫描update_all_device_info()强制全量重枚举(设备数, 设备信息列表)运行中插拔相机后重新扫描open_device_by_index(index)按枚举索引打开相机Camera 对象或 None单相机调试open_device_by_user_id(user_id)按用户自定义 ID 打开Camera 对象或 None多相机标定顺序无关open_device_by_sn(sn)按相机序列号打开Camera 对象或 None固定相机工位这张表解决的是“SDK 是什么、从哪里入手”的问题。拿到一个相机时先跑 2.3 的枚举脚本确认能看到信息列表再进入第 3 章打开相机和配参数不要直接跳到采图。3. 相机初始化的核心链路打开设备、配置三大参数3.1 打开设备按索引还是按 UserID打开水星相机的方法有三种按枚举索引、按序列号、按用户自定义 ID。按索引最简单但枚举顺序受 USB 端口和系统识别顺序影响多相机场景下不稳定。生产环境更推荐先给每台相机写一个 UserID再按 UserID 打开这样代码和设备物理位置无关。import gxipy as gx dev_manager gx.DeviceManager() dev_num, dev_info_list dev_manager.update_device_info() if dev_num 0: raise RuntimeError(未发现水星相机设备) # 调试阶段直接按索引打开 cam dev_manager.open_device_by_index(0) if cam is None: raise RuntimeError(打开相机失败) # 设置用户自定义ID方便多相机识别 cam.UserID.set(left-camera) print(序列号:, cam.DeviceSerialNumber.get()) print(当前UserID:, cam.UserID.get()) cam.close_device() # 重新全量枚举后按UserID打开 dev_manager.update_all_device_info() cam dev_manager.open_device_by_user_id(left-camera)逻辑说明UserID 写入相机后需要重新枚举才能在信息列表中生效所以代码里先 close_device 再 update_all_device_info。按 UserID 打开的好处是USB 插拔或者驱动重新枚举导致索引变化时业务逻辑不用改。注意 UserID 是相机掉电保存的参数调试时写入一次即可不需要每次启动都写。打开后立即读 DeviceSerialNumber 是个好习惯它能在日志里留下明确的设备身份。同一台机器同时接多个水星相机时串号和 UserID 的组合是唯一的建议作为所有日志和报错的关键字。3.2 曝光、增益、帧率水星相机三个必调参数怎么设水星相机的参数控制遵循 GenICam 特征码体系ExposureTime、Gain、AcquisitionFrameRate 这些名字在 USB3.0 和 GigE 型号上基本一致。关键点是先读范围再赋值工业相机的参数范围不是固定值和当前像素格式、数据位深、ROI 都有关系。# 曝光时间单位微秒 print(曝光范围:, cam.ExposureTime.get_range()) print(当前曝光:, cam.ExposureTime.get()) cam.ExposureTime.set(8000.0) # 8ms # 增益单位dB print(增益范围:, cam.Gain.get_range()) print(当前增益:, cam.Gain.get()) cam.Gain.set(6.0) # 帧率先使能再设目标值 cam.AcquisitionFrameRateEnable.set(True) cam.AcquisitionFrameRate.set(30.0) print(目标帧率:, cam.AcquisitionFrameRate.get()) print(实际理论帧率:, cam.ResultingFrameRate.get()) # 关闭自动曝光自动增益确保手动值生效 cam.AutoExposureTime.set(0) cam.AutoGain.set(0)逻辑说明ExposureTime 的单位是微秒8000 就是 8 毫秒暗场环境下常见范围是 500 到 30000。Gain 单位是 dB值越大噪声越明显优先加曝光、逼不得已才加增益。AcquisitionFrameRateEnable 必须先设为 True否则 AcquisitionFrameRate 写不进去。ResultingFrameRate 是 SDK 根据当前曝光、像素格式、带宽计算出的理论最大帧率如果曝光时间太长即使设了 30fps 也达不到这个值会直接反映出来。AutoExposureTime 和 AutoGain 的分支在不同 SDK 版本里命名有差异有的叫 AutoExposureTime有的叫 ExposureAuto设置方式都是把自动模式切到关闭。设置完这三个参数后建议把参数值打印出来做二次确认因为部分相机在切换像素格式时会自动重置曝光和增益。3.3 特征码控件类型看懂 get/set 背后的规则刚接触 gxipy 的人最容易困惑的一点是为什么有些参数用 set有些用 send_command有些又带 Enable 后缀。这背后是特征码控件类型的不同。水星相机的每个特征码都归属一种控件类型控件类型决定了操作方式。控件类型典型特征码操作方式IFloatControl 浮点控件ExposureTime、Gain、AcquisitionFrameRateget() / set() / get_range()IIntControl 整数控件AcquisitionBufferNumberget() / set()IBoolControl 布尔控件AcquisitionFrameRateEnableset(True) / set(False)IEnumControl 枚举控件TriggerMode、TriggerSource、PixelFormatset(枚举值) / get()ICommandControl 命令控件TriggerSoftwaresend_command()这套规则的直接推论是看到特征码名称时先判断类型。以 Trigger 为例TriggerMode 是枚举控件TriggerSoftware 是命令控件前者用 set 修改状态后者用 send_command 触发一次软件信号。混用会直接报错SDK 内部对控件类型检查得很严格。# 错误理解: TriggerSoftware 不是读取目标帧率的属性 # 正确做法: 它是命令控件只能触发不能赋值 cam.TriggerSoftware.send_command() # 错误做法: 给命令控件 set # cam.TriggerSoftware.set(1)这个经验在查报错时特别有用。见到“unsupported operation”一类的提示时第一反应应该是查这个特征码属于哪类控件而不是怀疑相机坏了。把这套控件类型记熟大恒 SDK 配其他品牌工业相机 SDK 时也能少走弯路因为 GenICam 体系内的控件分类是共通的。4. 图像采集实战连续采集、硬触发与图像格式转换4.1 连续采图的取图时序与 timeout 语义gxipy 的取图链路分三段stream_on 开始采集、data_stream 接收数据、get_image 取帧。stream_on 之后 SDK 内部开始搬运图像但取帧动作是业务代码主动调 get_image所以循环里必须设置合理的超时时间。import gxipy as gx cam dev_manager.open_device_by_index(0) # 连续采集模式关掉触发让相机自己出图 cam.TriggerMode.set(gx.GxSwitchEntry.OFF) cam.stream_on() for i in range(10): # timeout 单位是毫秒-1 表示无限等待 frame cam.data_stream[0].get_image(2000) if frame is None: print(f第{i}帧取图超时) continue numpy_image frame.get_image() print(f第{i}帧: {frame.width}x{frame.height}, shape{numpy_image.shape}) cam.stream_off() cam.close_device()逻辑说明get_image 的 timeout 参数单位是毫秒2000 是大多数水星相机在正常曝光下的安全阈值。timeout 设为 -1 时表示无限等待适合硬触发后不确定信号何时到达的场景但配套的采集线程要做好能随时被外部停止的准备。frame 对象持有 SDK 内部的内存引用get_image 返回的是 numpy 数组之后可以传给 OpenCV 或存盘。一个易错细节是 stream_on 之后没有立即出图。如果相机触发模式还开着连续采集模式下图像不会自动到 data_stream这就是 4.2 要处理的触发问题。4.2 软触发与注册回调式采图需要严格按指令采一张拍一张时用软触发。软触发的链路是开启 TriggerMode、把 TriggerSource 设为软件、调用 TriggerSoftware 发一次信号然后 get_image 收到一帧。# 配置软触发 cam.TriggerMode.set(gx.GxSwitchEntry.ON) cam.TriggerSource.set(gx.GxTriggerSourceEntry.SOFTWARE) cam.stream_on() for i in range(5): cam.TriggerSoftware.send_command() frame cam.data_stream[0].get_image(1000) if frame is not None: img frame.get_image() print(f软触发第{i}帧: {img.shape}) cam.stream_off()逻辑说明TriggerMode 设为 ON 之后相机进入等待触发状态不触发不出图。TriggerSoftware.send_command 每次调用产生一个内部信号和外部按下快门等效。软触发适合视觉工位里由 PLC 逻辑决定拍照时机的场景Python 侧只需要保证调用顺序正确。如果不想循环调 get_image用注册回调的方式更接近事件驱动。gxipy 允许注册一个采集回调SDK 每收一帧自动调用一次。def on_frame(camera, frame): if frame is None: return numpy_image frame.get_image() print(回调收到图像:, numpy_image.shape) cam.register_capture_callback(on_frame) cam.TriggerMode.set(gx.GxSwitchEntry.OFF) cam.stream_on() # 模拟主线程业务保持运行 time.sleep(3) cam.unregister_capture_callback() cam.stream_off() cam.close_device()逻辑说明回调函数签名固定为两个参数第一个是相机对象第二个是 FrameData 对象。回调由 SDK 内部采集线程触发不是主线程所以不要在回调里做窗口刷新、长时间保存图片这类操作典型做法是回调里只做格式转换然后放进 queue另一个工作线程去消费。注意回调函数由 SDK 内部采集线程触发回调里不要做耗时操作否则帧率跑不满还会拖垮整个采集链路。4.3 像素格式转换Mono、RGB 与 Bayer 的落地路径水星相机输出的像素格式由 PixelFormat 特征码控制。单色相机通常是 Mono8彩色相机常见 RGB8 或 Bayer 格式。frame.get_image 返回的 numpy 数组格式直接跟随相机的 PixelFormat所以不做转换时数组的含义一定要和像素格式对应清楚。# 直接拿原格式 numpy_image frame.get_image() # 转成RGB后再取 rgb_frame frame.convert(RGB) rgb_image rgb_frame.get_image()逻辑说明convert 是 FrameData 提供的转换方法参数既支持字符串也支持像素格式枚举。将 Bayer 数据交给 OpenCV 自己转也可以但要注意 Bayer 排列匹配问题错一个字母颜色就完全不对。更稳妥的做法是把 Bayer 转换交给 SDK 的 convertSDK 内部知道当前 PixelFormat 的真实排列转出来的 RGB 顺序可控。相机 PixelFormatnumpy 返回推荐用法MONO8(H, W) uint8直接作为灰度图使用RGB8_8_8_RGB(H, W, 3) uint8OpenCV 按 RGB 顺序注意转 BGRRGB8_8_8_BGR(H, W, 3) uint8OpenCV 可直接当 BGR 图像BAYERRG / BAYERGB / BAYERGR / BAYERBG 8(H, W) uint8用 frame.convert(RGB) 最稳实际项目中保存图片最常见的操作是先 convert 成 RGB再交给 OpenCV 的 imwrite。imwrite 默认按 BGR 解释输入数组所以如果直接拿 RGB8_8_8_RGB 的数组去保存颜色会偏。这个转换顺序写错会浪费半天排查时间建议在采集侧固定好项目内部统一用的通道顺序。5. 采图性能、丢帧排查与安全退出5.1 帧率上不去时先查这三个参数帧率只有标称值一半甚至更低时按顺序检查曝光、触发、缓冲三个位置。曝光时间是最容易被忽略的瓶颈8ms 曝光理论上限就是 1000/8 约 120fps但因为 CMOS 读出时间的存在实际一定更低。先用 ResultingFrameRate 读一遍这个值低于目标帧率时只能减小曝光或降低像素格式位深。第二个要查的是触发状态。TriggerMode 还开着但代码没发触发信号get_image 就会一直超时表现像是帧率变成 0。第三个是缓冲队列长时间丢帧时把 AcquisitionBufferNumber 调大给 SDL 更多缓冲空间。# 调大相机内部缓冲 cam.AcquisitionBufferNumber.set(10) print(当前缓冲数:, cam.AcquisitionBufferNumber.get())5.2 两个高频报错的排查方向打开相机时报“设备被占用”或返回空对象绝大多数是上一次会话没有正确关闭。Windows 下进程若被强制结束句柄不一定立刻释放重开项目前最好把相机拔插一次或在 SDK 的设备管理器里确认状态。更规范的做法是把关闭动作固定在 finally 里执行close_device 前先 stream_off顺序反过来容易让驱动的状态机卡住。get_image 返回 None 时先看触发配置再看出图间隔。超时时间设得太短也会误伤比如曝光是 20mstimeout 只有 10ms那永远等不到图像。把 timeout 调到曝光时间的十倍以上再做判断是一个简单有效的经验值。5.3 退出与重连的规范写法提交到生产环境的 Python 采图代码建议把设备枚举、参数设置和采图放进一个类里__del__里只做一件事顺着互斥锁把 stream_off 和 close_device 按顺序执行完。这样下一轮启动时相机能干净打开不需要靠拔线恢复。import threading _lock threading.Lock() def safe_close(cam): with _lock: if cam is not None: try: cam.stream_off() finally: cam.close_device()这个锁的意义在于回调线程和主线程可能同时访问相机对象退出时如果不加锁stream_off 触发的状态切换会与回调里的 get_image 交错偶发崩溃很难定位。把 safe_close 设计成同一个相机只被调用一次配合上层的异常捕获水星相机在整个 Python 进程生命周期里的开关动作就完全可预期了。本文还有配套的精品资源点击获取