2. pylon SDK与相机配置基础
pylon 是 Basler 相机的官方 SDK,它把 GigE、USB3 Vision、CoaXPress 等不同接口的相机统一到同一套 API 之下。无论你后续用 C++、C# 还是 Python 开发,调优与排障的起点都是同一件事:让 pylon 能稳定地找到相机、打开相机、把参数写进去、再把图像取出来。这一节围绕这条最小可用链路展开,给出可复现的操作与代码骨架。
一、安装 pylon:选对组件,装完先验证
从 Basler 官网下载 pylon 时,注意区分 pylon Camera Software Suite(完整套件)与单独的驱动包。完整套件包含运行时(Runtime)、pylon Viewer 图形化工具、SDK 头文件与示例代码,建议开发机直接装完整版。安装过程中有两个选项值得留意:
- pylon IP Configurator:GigE 相机必备,用于查看和修改相机 IP、子网掩码、网关,以及启用巨帧(Jumbo Frame)。
- USB 驱动与过滤器驱动:USB3 Vision 相机需要安装 pylon USB 驱动,否则系统可能以普通 UVC 设备识别,导致无法访问 GenICam 功能。
安装完成后不要急着写代码,先用 pylon Viewer 验证链路。打开 Viewer,左侧设备树应能列出已连接的相机;双击打开,点击连续采集(Continuous Grab),若能看到实时画面,说明驱动、网络/总线、供电都正常。这一步把“环境问题”和“代码问题”提前分离开,是后续排障的重要基线。
二、枚举设备:从设备信息到唯一标识
pylon 的枚举接口会返回一个设备信息列表,每个条目包含厂商名、型号、序列号、用户自定义名、设备类(BaslerGigE / BaslerUsb 等)以及当前 IP。实际工程中不要用“列表第 0 个”来选相机,而应使用序列号或用户自定义名作为唯一标识,否则多相机场景下顺序变化会导致取错设备。
枚举的典型流程是:创建传输层工厂(TlFactory)→ 获取传输层 → 枚举设备 → 遍历并打印信息 → 按序列号匹配目标相机。下面给出 Python 版(pylon 通过 pypylon 提供)的核心代码:
from pypylon import pylon
tl_factory = pylon.TlFactory.GetInstance()
devices = tl_factory.EnumerateDevices()
for dev in devices:
print("Model:", dev.GetModelName())
print("Serial:", dev.GetSerialNumber())
print("Class :", dev.GetDeviceClass())
print("User :", dev.GetUserDefinedName())
# 按序列号精确匹配
target = None
for dev in devices:
if dev.GetSerialNumber() == "40123456":
target = dev
break
if target is None:
raise RuntimeError("未找到目标相机")
camera = pylon.InstantiateCamera(target)
camera.Open()
若使用 C++,对应的是 CTlFactory::GetInstance().EnumerateDevices(list) 与 InstantiateCamera,逻辑完全一致。枚举阶段还应记录设备类,因为 GigE 与 USB 在后续的包大小、带宽参数上处理方式不同。
三、配置相机参数:先读后写,注意节点可用性
pylon 的参数访问基于 GenICam 节点树。每个参数(如 Width、ExposureTime、Gain、PixelFormat)都有可读、可写、最小值、最大值、增量等属性。直接赋值前应先判断节点是否可写,否则会抛出逻辑错误。一个稳健的配置顺序是:
- 设置
PixelFormat(如 Mono8 / BayerRG8),它会影响后续 Width 的对齐约束。 - 设置
Width/Height,注意增量(Inc)通常为 2 或 4 的倍数。 - 设置
OffsetX/OffsetY,用于 ROI 裁剪。 - 设置曝光与增益:
ExposureAuto = Off后再写ExposureTime,否则手动值会被自动算法覆盖。 - 若需外部触发,设置
TriggerMode = On、TriggerSource(Line1 / Software)。
camera.PixelFormat.Value = "Mono8"
camera.Width.Value = 1280
camera.Height.Value = 1024
camera.OffsetX.Value = 0
camera.OffsetY.Value = 0
camera.ExposureAuto.Value = "Off"
camera.ExposureTime.Value = 8000.0 # 单位 us
camera.GainAuto.Value = "Off"
camera.Gain.Value = 6.0 # 单位 dB
camera.TriggerMode.Value = "Off" # 自由运行
这里 8000 μs 与 6 dB 是教学示例值,实际应根据光源亮度、运动速度与目标灰度动态调整。判断配置是否生效,最直接的方式是回读参数并打印,或在 pylon Viewer 中同步观察。若写入被拒绝,先检查 IsWritable,再检查是否与当前采集状态冲突——部分参数在采集进行中不可修改。
四、采集图像:从 GrabResult 到可用数据
pylon 提供两种取图方式:连续采集(StartGrabbing + RetrieveResult 循环)和单帧采集(GrabOne)。连续采集适合产线持续检测,单帧适合标定或调试。核心对象是 GrabResult,它携带图像缓冲、时间戳、帧号以及状态码。必须检查 GrabSucceeded(),失败时读取 GetErrorCode() 与 GetErrorDescription(),这是排障的第一手信息。
camera.StartGrabbing(pylon.GrabStrategy_LatestImageOnly)
while camera.IsGrabbing():
result = camera.RetrieveResult(5000, pylon.TimeoutHandling_ThrowException)
if result.GrabSucceeded():
img = result.Array # numpy 数组
print("Frame:", result.ImageNumber,
"W x H:", result.Width, result.Height,
"Mean:", img.mean())
else:
print("Error:", result.ErrorCode, result.ErrorDescription)
result.Release()
camera.StopGrabbing()
camera.Close()
GrabStrategy_LatestImageOnly 只保留最新帧,适合实时性优先的场景;若需逐帧不丢,应改用 GrabStrategy_OneByOne 并配合足够的缓冲数量。RetrieveResult 的超时时间(示例 5000 ms)应根据帧率设置,过短会频繁抛超时,过长会掩盖真实的丢帧问题。
五、把链路串起来:一个可复用的最小程序
把上述四步合并,就得到一个可复用的最小采集程序。它的价值在于:任何调优或排障都可以在这个骨架上加日志、加参数回读、加异常分支。建议在工程中固定以下习惯:
- 枚举后打印全部设备信息,便于现场核对序列号。
- 打开相机后立即回读关键参数,确认写入生效。
- 采集循环中记录帧号与时间戳,用于判断是否丢帧。
- 退出时按
StopGrabbing → Close → Destroy顺序释放,避免相机被占用。
ExposureAuto 仍为 Continuous 时手动写 ExposureTime,参数会被自动算法覆盖,表现为“设了没用”。正确做法是先把自动模式关掉,再写手动值。完成这一节后,你已经具备用 pylon 完成“找到相机—配置相机—取到图像”的完整能力。后续的曝光调优、带宽优化、触发同步与故障诊断,都是在这条最小链路上逐段加深:参数怎么调、数据怎么流、异常怎么定位。把这条链路跑通并留下日志,是后面所有工作的基础。