2. 相机发现与设备枚举
在上一节完成 pylon SDK 的安装与运行环境确认之后,接下来要解决的是“程序怎么知道现场有哪些相机”。这一步在 pylon 中称为设备枚举(Device Enumeration)。它并不是简单地扫描网口,而是由 pylon 的传输层(Transport Layer,简称 TL)把不同物理接口上的相机统一抽象成可查询的设备对象。理解枚举机制,是后续打开相机、配置参数、处理多相机场景的前提。
一、Transport Layer:pylon 与相机之间的“接口适配层”
Basler 相机可能通过 GigE、USB3 Vision、Camera Link、CoaXPress 等不同物理接口接入主机。pylon 不希望上层应用为每种接口写一套发现逻辑,于是引入了 Transport Layer 概念。每个 TL 是一个动态库(Windows 下为 pylon_TL_xxx.dll,Linux 下为 libpylon_TL_xxx.so),负责该接口的发现、连接与数据流管理。常见的有:
- pylon GigE TL:面向 GigE Vision 相机,通过 UDP 广播/单播发现设备,是工业现场最常用的 TL。
- pylon USB TL:面向 USB3 Vision 相机,通过 USB 枚举获取设备。
- pylon CL TL / CXP TL:分别面向 Camera Link 与 CoaXPress 采集卡。
- pylon GenTL Producer:第三方厂商按 GenICam GenTL 标准提供的 TL,pylon 也能加载。
枚举时,pylon 会遍历当前已加载的所有 TL,让每个 TL 报告自己“看得见”的设备,再汇总成一份统一的设备列表。因此,如果某台相机没被列出,问题往往不在相机本身,而在于对应 TL 是否加载、网络是否可达、驱动是否正常。
二、通过 IP 或序列号识别相机
枚举得到的每个设备都对应一个 CDeviceInfo 对象,它携带了识别相机所需的关键字段。最常用的两个是序列号(SerialNumber)和IP 地址(对于 GigE 相机)。
序列号是相机出厂时写入的全球唯一标识,不随网络配置变化,因此是最可靠的识别依据。在多相机系统中,推荐用序列号建立“物理相机—工位—程序实例”的映射关系。IP 地址则适合 GigE 相机,但要注意:相机 IP 可能被修改,也可能因 DHCP 变化,因此 IP 更适合作为连接参数,而不是长期身份标识。
pylon 提供两种典型用法:一是通过 CDeviceInfo 的 GetSerialNumber()、GetIpAddress() 等方法读取信息;二是用 CreateDevice(info) 直接按枚举到的信息创建设备对象。若已知序列号,也可以构造 CDeviceInfo 或使用 DeviceInfo 的查找接口定位特定相机。
pylon-gige-configurator 调整网卡与相机 IP,使其处于同一子网。三、多相机枚举与设备信息读取
多相机场景下,枚举结果是一个设备列表。工程上通常按以下顺序处理:
- 调用
CTlFactory::GetInstance().EnumerateDevices(list)获取全部设备。 - 遍历列表,读取每台相机的
GetModelName()、GetSerialNumber()、GetUserDefinedName()、GetDeviceClass()、GetInterfaceID()等字段。 - 根据序列号或用户自定义名称,把设备分配到对应的业务逻辑(如“上相机”“下相机”“侧相机”)。
- 对每台相机分别创建
CInstantCamera实例,再执行打开、参数配置与取流。
需要特别注意的是:枚举只负责“发现”,不负责“占用”。同一台相机可以被多次枚举到,但同一时刻通常只能被一个进程以独占方式打开。如果打开失败并提示设备被占用,应先检查是否有其他程序(包括 pylon Viewer)正在使用该相机。
四、编写控制台程序列出所有可用 Basler 相机
下面给出一个最小可运行的控制台示例,使用 C++ 与 pylon C++ API。它完成三件事:初始化 pylon、枚举设备、打印设备信息。
#include <pylon/PylonIncludes.h>
#include <iostream>
using namespace Pylon;
using namespace GenApi;
using namespace std;
int main()
{
// 1. 初始化 pylon 运行时
PylonInitialize();
try
{
// 2. 获取传输层工厂并枚举设备
CTlFactory& tlFactory = CTlFactory::GetInstance();
DeviceInfoList_t devices;
tlFactory.EnumerateDevices(devices);
cout << "发现设备数量: " << devices.size() << endl;
// 3. 遍历并打印每台设备的信息
for (size_t i = 0; i < devices.size(); ++i)
{
const CDeviceInfo& info = devices[i];
cout << "----------------------------------------" << endl;
cout << "索引 : " << i << endl;
cout << "型号 : " << info.GetModelName() << endl;
cout << "序列号 : " << info.GetSerialNumber() << endl;
cout << "用户自定义名: " << info.GetUserDefinedName() << endl;
cout << "设备类 : " << info.GetDeviceClass() << endl;
cout << "接口 ID : " << info.GetInterfaceID() << endl;
// GigE 相机可读取 IP
if (info.GetDeviceClass() == "BaslerGigE")
{
cout << "IP 地址 : " << info.GetIpAddress().c_str() << endl;
}
}
}
catch (const GenericException& e)
{
cerr << "pylon 异常: " << e.GetDescription() << endl;
}
// 4. 释放 pylon 运行时
PylonTerminate();
return 0;
}
编译时需链接 pylon 的库(如 pylonbase、pylonutility、GenApi 等),并确保运行时能找到对应 TL 动态库。运行后,控制台会输出类似下面的结果(以下为示例输出,实际内容取决于现场设备):
发现设备数量: 2
----------------------------------------
索引 : 0
型号 : acA1920-40gc
序列号 : 24001234
用户自定义名: TopCamera
设备类 : BaslerGigE
接口 ID : 192.168.1.100
IP 地址 : 192.168.1.10
----------------------------------------
索引 : 1
型号 : acA1300-60gm
序列号 : 24005678
用户自定义名: SideCamera
设备类 : BaslerGigE
接口 ID : 192.168.1.100
IP 地址 : 192.168.1.11
判断枚举是否成功,可以看三个信号:设备数量是否与现场一致、序列号是否与相机铭牌一致、GigE 相机的 IP 是否与预期网段一致。如果数量为 0,优先检查 TL 是否加载、网卡是否启用、防火墙是否拦截了 UDP 广播;如果数量正确但打开失败,则检查 IP 子网与设备占用情况。
掌握设备枚举之后,下一步就可以基于枚举结果选择目标相机,进入打开设备与参数配置阶段。枚举本身不复杂,但它是整个采集链路的入口,把 TL、设备信息与多相机管理这三件事在这里理清,后续的取流与异常处理会顺畅很多。