2. pylon C++ API核心对象解析

在 pylon C++ API 中,真正决定“程序如何发现相机、如何连接相机、如何取流”的并不是某一个函数,而是一组职责清晰的核心对象。理解它们之间的分工,比死记 API 名称更重要。本节围绕四个关键概念展开:DeviceInfo(设备描述)、TransportLayer(传输层)、InstantCamera(即时相机对象)以及相机枚举机制。它们共同构成了从“物理相机存在”到“C++ 对象可操作”的完整链路。

一、四个核心对象的职责边界

可以把 pylon 的相机访问过程理解为一条“发现—描述—创建—控制”的链路。物理相机通过 GenICam 传输层被枚举出来,枚举结果以 DeviceInfo 的形式呈现;开发者根据 DeviceInfo 选择目标相机,再通过相机工厂创建 InstantCamera 实例,最终由该实例完成参数配置、取流与事件处理。

DeviceInfo 是设备的“身份证”。它并不代表相机本身,而是对一台已发现设备的描述信息,典型字段包括:

  • GetFriendlyName():用户可读名称,例如 acA1920-40gc;
  • GetDeviceClass():设备类别,常见为 BaslerGigE 或 BaslerUsb;
  • GetSerialNumber():序列号,用于唯一标识;
  • GetUserDefinedName():用户自定义名称,便于多相机场景区分;
  • GetModelName():型号名称;
  • GetInterfaceID():所属接口标识,用于判断相机挂在哪个网卡或 USB 控制器下。

TransportLayer 是“发现通道”。pylon 通过不同的传输层实现与不同物理接口的通信,例如 GigE 传输层、USB 传输层、Camera Link 传输层等。传输层负责在底层网络上广播发现请求、接收相机响应,并把响应封装成 DeviceInfo 列表。没有传输层,枚举就无从谈起。

InstantCamera 是“操作手柄”。它继承自 Pylon::CPylonInstantCamera 或通过 Pylon::CTlFactory 创建,封装了打开设备、配置节点、启动取流、抓取结果、注册事件回调等能力。开发者日常写的 StartGrabbing()、RetrieveResult()、StopGrabbing() 都发生在这一层。

四者关系可以用下图表示:

物理相机 GigE / USB3 硬件设备 TransportLayer GigE / USB / CL 广播发现 · 封装响应 提供设备列表 DeviceInfo FriendlyName SerialNumber DeviceClass / InterfaceID InstantCamera Open / Close StartGrabbing RetrieveResult 存在 发现 描述 控制 相机枚举机制:CTlFactory::EnumerateDevices() 遍历所有已注册传输层 → 汇总 DeviceInfo 列表 → 供上层筛选

二、相机枚举机制的实际执行过程

枚举是访问相机的第一步。pylon 的枚举入口通常是 CTlFactory::GetInstance().EnumerateDevices(),它返回一个 DeviceInfoList_t。其内部逻辑可以概括为:

  1. 获取传输层工厂单例,遍历当前已注册的所有传输层;
  2. 对每个传输层调用其设备发现接口,例如 GigE 传输层会向本地网段发送发现广播;
  3. 收集各传输层返回的设备描述,统一转换为 DeviceInfo 对象;
  4. 将结果汇总为列表返回给调用者。

下面是一段典型的枚举与筛选代码,示例中假设环境中存在两台 GigE 相机和一台 USB 相机:

#include <pylon/PylonIncludes.h>
using namespace Pylon;

int main() {
    PylonInitialize();

    CTlFactory& tlFactory = CTlFactory::GetInstance();
    DeviceInfoList_t devices;
    tlFactory.EnumerateDevices(devices);

    std::cout << "发现设备数量: " << devices.size() << std::endl;

    for (const auto& di : devices) {
        std::cout << "名称: " << di.GetFriendlyName()
                  << " | 类别: " << di.GetDeviceClass()
                  << " | 序列号: " << di.GetSerialNumber()
                  << " | 接口: " << di.GetInterfaceID()
                  << std::endl;
    }

    PylonTerminate();
    return 0;
}

假设运行后输出如下(示例输出,非实测):

发现设备数量: 3
名称: acA1920-40gc | 类别: BaslerGigE | 序列号: 22001234 | 接口: 192.168.1.100
名称: acA1920-40gc | 类别: BaslerGigE | 序列号: 22005678 | 接口: 192.168.1.100
名称: acA1300-60um | 类别: BaslerUsb | 序列号: 23009876 | 接口: USB:0

从输出可以读出几个关键信息:两台 GigE 相机共享同一接口 ID,说明它们接在同一网卡下;USB 相机的接口 ID 以 USB: 开头,说明其传输层不同。这些信息在后续选择相机时非常关键。

三、从 DeviceInfo 到 InstantCamera 的创建

拿到 DeviceInfo 列表后,通常不会直接操作列表,而是根据业务规则筛选目标相机。常见筛选策略包括:按序列号精确匹配、按用户自定义名称匹配、按设备类别过滤、按接口 ID 限定网卡。筛选完成后,通过 CTlFactory::CreateDevice() 或 InstantCamera 构造函数创建设备对象。

DeviceInfoList_t filtered;
for (const auto& di : devices) {
    if (di.GetDeviceClass() == "BaslerGigE") {
        filtered.push_back(di);
    }
}

if (filtered.empty()) {
    std::cerr << "未找到 GigE 相机" << std::endl;
    return -1;
}

CInstantCamera camera(tlFactory.CreateDevice(filtered[0]));
camera.Open();
std::cout << "已打开: " << camera.GetDeviceInfo().GetFriendlyName() << std::endl;

这里有一个容易混淆的点:CreateDevice() 返回的是 IPylonDevice*,而 CInstantCamera 接收该指针并接管其生命周期。因此不要手动 delete 这个指针,否则会造成重复释放。正确做法是让 InstantCamera 对象在析构时自动管理。

实践提示:在多相机场景中,建议始终使用序列号或用户自定义名称进行筛选,而不是依赖列表顺序。因为枚举顺序可能因网络响应时间、USB 枚举顺序而变化,依赖索引会导致程序在不同机器上行为不一致。

四、InstantCamera 的关键能力与生命周期

InstantCamera 之所以称为“Instant”,是因为它把取流过程封装成了接近“即时可用”的接口。其核心生命周期包括:

  • 创建:通过 DeviceInfo 创建,此时尚未占用设备;
  • 打开:Open() 后设备被独占,参数节点可读写;
  • 配置:通过 GetNodeMap() 访问 GenICam 节点,设置曝光、增益、触发模式等;
  • 取流:StartGrabbing() 启动采集,RetrieveResult() 获取 CGrabResultPtr;
  • 停止与关闭:StopGrabbing()、Close(),释放设备。

需要特别注意的是,Open() 之后才能访问相机节点。如果在未打开状态下调用 GetNodeMap() 并尝试写入,通常会抛出 LogicalErrorException。同样,StartGrabbing() 之前必须确保触发模式与预期一致,否则可能出现“启动了但一帧都收不到”的情况。

五、传输层注册与枚举失败的排查方向

枚举不到相机时,问题往往不在代码,而在传输层或网络配置。可按以下顺序排查:

现象 可能原因 检查方式
枚举列表为空 传输层未注册或网卡未启用 检查 pylon 安装时是否勾选对应传输层;确认网卡已启用
只发现部分相机 跨网段、防火墙拦截广播 确认相机与主机在同一子网;临时关闭防火墙测试
DeviceInfo 存在但 Open 失败 设备被其他进程占用 关闭 pylon Viewer 或其他采集程序后重试
USB 相机枚举不稳定 USB 控制器带宽不足或供电不足 更换 USB 端口,避免使用无源 Hub
注意:GigE 相机的枚举依赖网络广播。如果主机配置了多个网卡,而相机接在非默认网卡上,枚举可能失败。此时应检查 GetInterfaceID() 是否与目标网卡一致,必要时在 pylon IP Configurator 中为相机分配同网段 IP。

六、小结与工程建议

DeviceInfo 负责“描述设备”,TransportLayer 负责“发现设备”,InstantCamera 负责“控制设备”,而枚举机制是把三者串起来的入口。实际工程中,建议把枚举与筛选封装成独立函数,返回目标 DeviceInfo;把 InstantCamera 的创建、打开、配置、取流封装成 RAII 风格的类,确保异常路径下也能正确释放设备。这样既能避免资源泄漏,也能让多相机程序的结构更清晰。