跳转到内容

fglib5#

库 fglib5 用于管理计算机中采集卡的初始化和控制。

要使用该库,应将包含文件 basler_fg.h 添加到源代码中。

#include <basler_fg.h>

此外, fglib5.lib 应添加到您的 Microsoft Visual Studio 项目中,或者 libfglib5.so 添加到您的 Linux 项目中。如果您使用 CMake,则包名为 FgLib5。CMake 会将包含目录存储在变量中 ${FgLib5_INCLUDE_DIR} 并将库存储在 ${FgLib5_LIBRARIES}。请参阅 前提条件 了解有关项目以及如何使用 CMake 的更多详细信息。

采集卡库中的错误处理#

const char * Fg_getErrorDescription(
    Fg_Struct * fg,
    int result);

int Fg_getLastErrorNumber(
    Fg_Struct * fg);

API 的大多数函数都会返回一个 int 结果代码。如果函数调用成功执行,则返回值将为 FG_OK。在大多数情况下,负值表示出现错误状况。Error Codes 定义在头文件中 basler_fg.h.

函数 Fg_getErrorDescription() 可用于获取特定结果代码的字符串表示形式。此函数的第一个参数(即采集卡的句柄)未被使用,仅为了 API 向后兼容性而包含在内。当最后调用的函数未接收到采集卡句柄时,您应始终传递 nullptr.

函数 Fg_getLastErrorNumber() 将始终返回在同一线程上下文中调用的最后一个函数的结果代码。这在函数本身不返回结果代码的情况下最有用。该函数接受一个采集卡句柄作为参数,该参数可以是 nullptr 。若要请求需要采集卡句柄的函数的结果代码,必须将相同的句柄传递给 Fg_getLastErrorNumber().

信息

Error Codes 存储在线程本地存储中。这意味着通过调用 Fg_getLastErrorNumber() 应用程序无法从一个线程请求在另一个线程中发生的错误代码。

以下代码将打印发生在一个并非特定于采集卡句柄的上下文中的最后一个错误及其描述:

int result = Fg_getLastErrorNumber(nullptr);
if (result != FG_OK) {
    const char * description = Fg_getErrorDescription(nullptr, result);

    std::cout << "Error " << result
              << ": " << description
              << std::endl;
}

本文档其余部分的代码示例将不包含错误处理,因为这是针对应用程序特定需求的。不过,在可行的情况下,将对返回代码进行检查,以确保函数成功执行。

采集卡库初始化#

int Fg_InitLibraries(
    const char *);

void Fg_FreeLibraries();

在使用 fglib5 库之前,必须通过调用函数进行初始化 Fg_InitLibraries(). 该函数接受一个仅用于内部目的的参数,应将其设置为 nullptr。

当应用程序不再使用该库后,调用函数 Fg_FreeLibraries() 以释放先前初始化所分配的任何资源。

int result = Fg_InitLibraries(nullptr);
if (result != FG_OK) {
    // handle error ...
}

// use frame grabber ...

Fg_FreeLibraries();

系统信息#

在初始化采集卡之前,可以请求有关所使用的 Framegrabber API 版本、计算机中安装的采集卡的数量和类型以及其他信息。

Framegrabber API 版本#

const char * Fg_getSWVersion();

可以使用以下方法请求 Framegrabber API 版本的字符串表示形式 Fg_getSWVersion().

const char * rtVersion = Fg_getSWVersion();
std::cout << "Runtime SDK version: " << rtVersion
          << std::endl;

常规系统信息#

int Fg_getIntSystemInformationGlobal(
    Fg_Info_Selector information,
    FgProperty propertyId,
    int * value);

信息

函数 Fg_getIntSystemInformationGlobal() 已在 Framegrabber API 的 5.9 版本中添加。请参阅章节 Plain C 中的系统信息 了解旧接口。

可以通过函数请求以下信息 Fg_getIntSystemInformationGlobal() 使用 Property PROP_ID_VALUE:

信息 描述 键入
INFO_NR_OF_BOARDS 计算机中找到的板卡数量 int32_t
INFO_MAX_NR_OF_BOARDS 支持的最大板卡数量 int32_t
INFO_SERVICE_ISRUNNING 0:服务未运行;1:服务正在运行 int32_t

例如,可以请求计算机中安装的采集卡数量,如下所示:

int numBoards = 0;
int result = Fg_getIntSystemInformationGlobal(INFO_NR_OF_BOARDS, PROP_ID_VALUE, &numBoards);
if (result == FG_OK) {
    std::cout << "Number of boards: " << numBoards
              << std::endl;
}

单板特定系统信息#

int Fg_getIntSystemInformationForBoardIndex(
    unsigned int board,
    Fg_Info_Selector information,
     FgProperty propertyId,
     int * value);

int Fg_getInt64SystemInformationForBoardIndex(
    unsigned int board,
    Fg_Info_Selector information,
    FgProperty propertyId,
    int64_t * value);

int Fg_getStringSystemInformationForBoardIndex(
    unsigned int board,
    Fg_Info_Selector information,
    FgProperty propertyId,
    std::string & value,
    const std::string & arg = "");

int Fg_getIntSystemInformationForFgHandle(
    Fg_Struct * fg,
    Fg_Info_Selector information,
    FgProperty propertyId,
    int * value);

int Fg_getInt64SystemInformationForFgHandle(
    Fg_Struct * fg,
    Fg_Info_Selector information,
    FgProperty propertyId,
    int64_t * value);

int Fg_getStringSystemInformationForFgHandle(
    Fg_Struct * fg,
    Fg_Info_Selector information,
    FgProperty propertyId,
    std::string & value,
    const std::string & arg = "");

信息

本章中记录的函数是在 Framegrabber API 的 5.9 版本中添加的。有关旧接口,请参阅 System Information in Plain C 节。

对于任何给定的采集卡,可以通过以下函数请求大多数其他信息 Fg_getIntSystemInformationForBoardIndex(), Fg_getInt64SystemInformationForBoardIndex() 和 Fg_getStringSystemInformationForBoardIndex() 通过使用板卡索引或采集卡句柄以及 Property PROP_ID_VALUE:

信息 描述 键入
INFO_TIMESTAMP_FREQUENCY 用于图像 Timestamp 的 Timestamp 频率 int64_t
INFO_BOARDNAME 板卡名称,例如显示在 microDiagnostics 中 string
INFO_BOARDTYPE sisoboards.h 中定义的板卡类型 int32_t
INFO_BOARDSERIALNO 板卡序列号 int32_t
INFO_FIRMWAREVERSION 板卡的固件版本 string
INFO_HARDWAREVERSION 板卡的硬件版本 string
INFO_CAMERA_INTERFACE 板卡提供的相机接口('CameraLink' 或 'CXP') string
INFO_DRIVERVERSION 板卡所使用的驱动程序版本 string
INFO_DRIVERARCH 板卡所使用的驱动程序架构 string
INFO_DRIVERFULLVERSION 板卡所使用的完整驱动程序版本(包含架构) string
INFO_DRIVERGROUPAFFINITY 驱动程序 IRQ 组亲和性(参见 Support for Non-Uniform Memory Access) int32_t
INFO_DRIVERAFFINITYMASK 驱动程序 IRQ 处理器亲和性掩码(参见 Support for Non-Uniform Memory Access) int64_t
INFO_LICENSE_GROUP_CODE 采集卡许可证组代码(必须是小程序许可证组代码的超集a) int32_t
INFO_LICENSE_USER_CODE 采集卡许可证用户代码(必须与小程序许可证用户代码相匹配) int32_t
INFO_IS_POCL 0:板卡不支持 PoCL;1:板卡支持 PoCL int32_t
INFO_NR_OF_CXP_PORTS 具有“CXP”接口的板卡上的端口数量 int32_t
INFO_NR_OF_CL_PORTS 具有“CameraLink”接口的板卡上的端口数量 int32_t
INFO_NR_OF_PORTS 具有“CameraLinkHS”接口的板卡上的端口数量 int32_t
INFO_NR_OF_GIGE_PORTS 具有“GigE”接口的板卡上的端口数量 int32_t
INFO_APPLET_DESIGN_ID Applet 的 HAP ID(必须在参数 arg 中传入 applet 路径) string
INFO_APPLET_BITSTREAM_ID Applet 的 bit stream ID(必须在参数 arg 中传入 applet 路径) string
INFO_STATUS_PCI_LINK_WIDTH 帧采集卡使用的 PCIe 通道数 int32_t
INFO_STATUS_PCI_EXPECTED_LINK_WIDTH 帧采集卡支持的 PCIe 通道数 int32_t
INFO_STATUS_PCI_LINK_SPEED 帧采集卡使用的 PCIe 代数 int32_t
INFO_STATUS_PCI_EXPECTED_LINK_WIDTH 帧采集卡支持的 PCIe 代数 int32_t
INFO_STATUS_PCI_PAYLOAD_SIZE 帧采集卡使用的 PCIe 有效负载大小 int32_t

上面的列表并不完整,仅包含对应用程序有用的信息 Property。有关该 API 的更多用例,请参见 Framegrabber API reference。

例如,以下代码请求板卡类型和名称。

const int boardIndex = 0;

int boardType = 0;
int result =
    Fg_getIntSystemInformationForBoardIndex(boardIndex, INFO_BOARDTYPE,
                                            PROP_ID_VALUE, &boardType);

std::string boardName;
if (result == FG_OK) {
    result =
        Fg_getStringSystemInformationForBoardIndex(boardIndex, INFO_BOARDNAME,
                                                   PROP_ID_VALUE, boardName);
}

if (result == FG_OK) {
    std::cout << "Board #" << boardIndex
              << " is a " << boardName
              << " (type " << std::hex << boardType
              << std::dec << ")" << std::endl;
}

初始化后一旦获得采集卡句柄,即可使用以下函数 Fg_getIntSystemInformationForFgHandle(), Fg_getInt64SystemInformationForFgHandle() 和 Fg_getStringSystemInformationForFgHandle() 可用于根据上述列表获取板卡特定的信息。(参见章节 采集卡初始化。)可以使用帧采集卡句柄和 Property 请求以下信息 PROP_ID_VALUE:

信息 描述 键入
INFO_APPLET_CAPABILITY_TAGS 描述 applet 功能的键值对列表 string
INFO_OWN_BOARDINDEX 帧采集卡的板卡索引 int32_t
INFO_FPGA_BITSTREAM_ID FPGA 中激活的 applet 的 bit stream ID string
INFO_APPLET_FULL_PATH Applet 完整路径 string
INFO_APPLET_FILE_NAME Applet 文件名 string
INFO_APPLET_TYPE 0:HAP 文件;1:DLL/SO 文件(参见 Applets) int32_t

上面的列表并不完整,仅包含应用程序所需信息的 Property。有关该 API 的更多用例,请参阅 Framegrabber API 参考手册。

使用单板索引#

通过使用板卡索引来选择采集卡。如果您知道系统中存在的板卡数量,在默认情况下,第一块板卡由索引 0 标识,第二块板卡由索引 1 标识,依此类推。

Framegrabber API 允许用户使用可以从 microDiagnostics 内部生成的配置文件为每个板卡分配一个唯一的索引。在这种情况下,用户应了解其分配情况以及相应的板卡索引。

Applet#

要使用采集卡,需要一个 Applet。Applet 是多个内容的集合,通常包括:

  • 采集卡上 FPGA 的设计,实现从接收来自相机设备的图像到将处理后的图像发送到计算机 Memory 中的图像处理功能
  • 包含设计中使用的 VisualApplets 算子的软件设计描述,以及将参数映射到 FPGA 寄存器的信息
  • 一组软件接口库,用作类实例的生成器,用于处理算子参数值与 FPGA 寄存器内容之间的转换
  • 一个顶层库 VAS,它负责处理所有算子类的实例化、初始化以及与 Applet 的接口交互

如果小程序是使用 VisualApplets 设计的,则它们以 HAP 文件的形式提供;如果是由 Framegrabber SDK 预安装的,则以包装的小程序库文件形式提供。HAP 文件通常存储在以下位置的板卡专用子目录中 Hardware Applets 目录位于 Framegrabber SDK 安装目录下,而封装的 Applet 库文件则安装在 dll 目录。在本文档中,这些子目录被称为对应类型小程序的标准位置。

每个 Applet 提供了一组参数来配置所提供的功能。有关 Applet 提供的功能和参数的详细描述,请查阅所用特定 Applet 的文档。

选择正确的小程序#

可随 Framegrabber SDK 一起安装的小程序通常以包装库文件的形式提供。这些文件的命名约定遵循以下规则:

  • 文件名通常以 Acq_。(这些 Applet 被称为 Advanced Acquisition Applet,上一代被称为 Standard Applet 并且具有不同的命名约定。)
  • 接下来,以以下形式给出小程序支持的相机数量 Single, Dual 或 Quad.
  • 然后,给出支持的相机接口。例如, CXP12 表示 CoaXPress 标准版本 2.0 中规定的、最高可达 12 Gbit/s 的 CoaXPress 相机接口。
  • 在较旧的采集卡平台上,最大 Camera Link 连接数以 x1, x2 或 x4。(这已从命名约定中删除,因为 x4 包含对 x2 和 x1, x2 的形式给出,支持 x1 并且支持的最大链路数通常应根据支持的相机数量和采集卡提供的物理端口数量来明确。)
  • 然后,给出支持的传感器类型。这要么是 Area 或 Line。对于同时支持面阵和线阵类型传感器的程序,将省略此项。
  • 名称的最后一部分是 applet 支持的数据格式,例如 Gray8, Bayer16, RGB24。对于支持多种数据格式的小程序,将省略此项。

例如,applet Acq_SingleCXP12Area 是一个高级 Acquisition Applet,支持一个最高 12 Gbit/s 的 CXP 相机和一个面阵传感器。该小程序支持多种数据格式。要了解支持的数据格式和传感器尺寸,必须查阅小程序文档。

如果应用程序例如需要两个带 12 Gbit/s 的 CXP 相机、一个带 10 位灰度输出的线扫描传感器,请查找名为 Acq_DualCXP12Line, Acq_DualCXP12LineGray10 或 Acq_DualCXP12LineGray16 并请阅读随附的文档。如果相机仅通过单个物理链路连接,则 Quad 也可以考虑 applet 的变体。

启动行为#

当 Framegrabber SDK 开始初始化 fglib5 库时,在加载 Applet 之前就需要访问采集卡,如 Frame Grabber Initialization 中所述。例如,这对于按序列号对采集卡进行排序,甚至查看所有可供加载的 Applet 是必需的。

用于此初始化的 Applet 取决于您使用的采集卡。您可以使用以下各节中的信息将采集卡配置为以预定义的 Applet 启动。当使用与预定义 Applet 不同的 Applet 调用 Fg_Init 会导致启动时间延长。

在启动时,Framegrabber SDK 默认会加载 Fg_Init.

您可以通过设置系统环境变量来停用此行为 FGSDK_LOAD_LAST_APPLET_ON_INIT=Off.

microEnable 5 marathon Frame Grabbers 的启动行为#

在 microEnable 5 marathon 采集卡上,您必须将 applet 烧录到板载可用分区之一中。Framegrabber SDK 的默认行为是将 Fg_Init 中使用的 applet 设置为启动分区。在系统通电时,启动分区中的 applet 用于初始化板卡。然后,该 applet 也用于 Framegrabber SDK 的第一次初始化阶段。

microEnable 6 Frame Grabbers 的启动行为#

在 CXP-12 Interface Card、imaWorx CXP-12 Quad、imaFlex CXP-12 Quad 或 imaFlex CXP-12 Penta 采集卡上,Framegrabber SDK 会尝试打开上次加载的 Applet。上次加载的 Applet 定义在 LastApplet 部分中的键 FG 在名为的配置文件中 me6_<n>_init.config,其中 <n> 是板卡的驱动程序索引。驱动程序索引与章节中描述的板卡索引非常相似 使用单板索引,但它是特定于驱动程序的,不会受到重新排序板卡的影响。在 Windows 上, driver index 0 指驱动程序找到的第一块 CXP-12 Interface Card、imaWorx CXP-12 或 imaFlex CXP-12 采集卡, driver index 1 指第二块板卡,依此类推。在所有其他操作系统中,驱动程序索引与板卡索引相同,不会进行任何重新排序。

在 Windows 系统上,配置文件通常位于目录 %APPDATA%\basler。在所有其他操作系统中,配置文件位于 $HOME/.config/basler 目录中。如果未找到配置文件,则 Framegrabber SDK 会在 Framegrabber SDK 安装的根文件夹中查找。

如果没有找到采集卡的配置文件或初始化失败,则会使用默认 Applet。默认 Applet 通常是支持每个物理端口连接一个面阵相机的 Applet。

如果默认小程序初始化也失败,则Framegrabber SDK 打开任何可用的小程序。

枚举 Applet#

int Fg_getAppletIterator(
    int board,
    FgAppletIteratorSource source,
    Fg_AppletIteratorType * iter,
    int flags);

Fg_AppletIteratorItem Fg_getAppletIteratorItem(
    Fg_AppletIteratorType iter,
    int index);

int64_t Fg_getAppletIntProperty(
    Fg_AppletIteratorItem item,
    FgAppletIntProperty property);

const char * Fg_getAppletStringProperty(
    Fg_AppletIteratorItem item,
    FgAppletIntProperty property);

int Fg_freeAppletIterator(
    Fg_AppletIteratorType iter);

通常,应用程序会使用一个或几个特定的 applet,并且其功能将通过 applet 文档广为人知,以下函数对大多数应用程序而言并非必需。在应用程序设计得更加动态的情况下,Framegrabber API 提供了可通过其枚举 applet 并请求有关 applet 的各种信息的函数。

若要枚举 Applet,函数 Fg_getAppletIterator() 可以被调用。如果要枚举 Framegrabber SDK 安装中可用的 Applet,应使用源码 FG_AIS_FILESYSTEM 。若要仅枚举可在给定板卡上加载的 Applet,请传递 FG_AF_IS_LOADABLE 用于这些标志。这确保了 API 返回的任何 applet 都可以被后续调用以初始化采集卡。该函数返回迭代器中的项数。

可以通过调用获取 Applet 迭代器的各项 Fg_getAppletIteratorItem().

可以通过调用以下项来请求有关某项的以下信息: Fg_getAppletIntProperty() 或 Fg_getAppletStringProperty():

Property 描述 键入
FG_AP_INT_FLAGS 适用于 Applet 的标志(FG_AF_… 常量) int32_t
FG_AP_INT_INFO 适用于 Applet 的标签(FG_AI_… 常量) int32_t
FG_AP_INT_PARTITION 闪存 Applet 的分区(仅限 mE5) int32_t
FG_AP_INT_NR_OF_DMA Applet 提供的最大 DMA 通道数 int32_t
FG_AP_INT_NR_OF_CAMS 使用 Applet 时可访问的最大相机数量 int32_t
FG_AP_INT_GROUP_CODE Applet 许可证组代码(必须是采集卡许可证组代码的子集a) int32_t
FG_AP_INT_USER_CODE Applet 许可证用户代码(必须与采集卡许可证用户代码相匹配) int32_t
FG_AP_INT_DESIGN_VERSION FPGA 设计的主版本 int32_t
FG_AP_INT_DESIGN_REVISION FPGA 设计的版本修订号 int32_t
FG_AP_STRING_APPLET_UID 用于标识 Applet 文件的 UID string
FG_AP_STRING_BITSTREAM_UID 用于标识 FPGA 设计的 UID string
FG_AP_STRING_DESIGN_NAME FPGA 设计的名称 string
FG_AP_STRING_APPLET_NAME Applet 的名称 string
FG_AP_STRING_DESCRIPTION Applet 的描述 string
FG_AP_STRING_CATEGORY Applet 的类别 string
FG_AP_STRING_APPLET_PATH Applet 的完整路径 string
FG_AP_STRING_SUPPORTED_PLATFORMS 支持的平台的逗号分隔列表 string
FG_AP_STRING_TAGS 以逗号分隔的标签列表 string
FG_AP_STRING_VERSION Applet 版本 string
FG_AP_STRING_APPLET_FILE Applet 文件名 string
FG_AP_STRING_RUNTIME_VERSION 所需的 Framegrabber SDK 版本 string

上面的列表并不完整,仅包含应用程序所需信息的 Property。有关该 API 的更多用例,请参阅 Framegrabber API 参考手册。

使用完 Applet 迭代器后,应通过调用将其释放 Fg_freeAppletIterator().

只要名称唯一且 applet 位于标准位置之一,applet 名称就足以用于后续的采集卡初始化调用。如果多个 applet 使用相同的名称,或者 applet 位于标准位置之外,则应使用完整路径。以下示例展示了如何从标准位置中找到的所有 applet 中提取 applet 名称:

const in boardIndex = 0;

Fg_AppletIteratorType iter = 0;
int numItems =
    Fg_getAppletIterator(boardIndex, FG_AIS_FILESYSTEM,
                         &iter, FG_AF_IS_LOADABLE);

if (numItems >= 0) {
    std::cout << "Found " << numItems << " applets";

    for (int i = 0; i < numItems; ++i) {
        auto item = Fg_getAppletIteratorItem(iter, i);

        const char * appletName =
            Fg_getAppletStringProperty(item, FG_AP_STRING_APPLET_NAME);

        std::cout << " " << appletName;
    }

    std::cout << std::endl;
    Fg_freeAppletIterator(iter);
}

采集卡初始化#

Fg_Struct * Fg_Init(
    const char * applet,
    unsigned int board);

int Fg_FreeGrabber(
    Fg_Struct * fg);

要使用采集卡,必须首先对其进行初始化。这可以通过使用函数加载 Applet 来完成 Fg_Init()。用指定的 applet 初始化采集卡后,该函数返回给定板卡的采集卡句柄。该句柄用于后续章节中描述的大多数 API 函数。

应用程序使用完采集卡后,应通过调用将其释放 Fg_FreeGrabber().

例如,以下代码使用以下 applet 初始化 CXP-12 采集卡 Acq_SingleCXP12Area:

const int boardIndex = 0;
const char * applet = "Acq_SingleCXP12Area";

Fg_Struct * fg = Fg_Init(applet, boardIndex);
if (fg != nullptr) {
    // use frame grabber ...

    Fg_FreeGrabber(fg);
}

配置文件#

Fg_Struct * Fg_InitConfig(
    const char * config,
    unsigned int board);

int Fg_loadConfig(
    Fg_Struct * fg,
    const char * config);

int Fg_saveConfig(
    Fg_Struct * fg,
    const char * config);

如果您使用 microDisplay X 对采集卡进行初始配置并测试您的设置,则可以直接从程序内部保存配置。主配置文件将具有扩展名 .mcf 以及带有扩展名的第二个文件 .mfs 将同时创建。有了这两个文件,应用程序可以直接使用这两个文件初始化采集卡,而无需通过 API 调用来复制配置。

在此上下文中,配置是指初始化采集卡所使用的 Applet 以及该 Applet 的参数设置。有关详细信息,请参阅使用 Applet 参数一章。

信息

只要扩展名之前的文件名相同, .mcf 用于函数的参数中。带有扩展名的文件 .mfs 就将自动使用带有扩展名的主配置文件。

若要从配置文件初始化采集卡,函数 Fg_InitConfig() 可以代替调用 Fg_Init()。根据指定的的主配置文件初始化采集卡后,该函数返回给定板卡的采集卡句柄。

应用程序使用完采集卡后,应通过调用将其释放 Fg_FreeGrabber().

如果应用程序需要不同的配置集,可以通过调用将其应用到采集卡 Fg_loadConfig() 在调用之后的任何时间 Fg_Init() 或 Fg_InitConfig().

可以通过调用将当前配置写入配置文件 Fg_saveConfig() 在调用之后的任何时间 Fg_Init() 或 Fg_InitConfig().

以下示例展示了如何保存和恢复采集卡的配置:

const char * config = "SavedState.mcf";

int result = Fg_saveConfig(fg, config);
if (result != FG_OK) {
    // handle error ...
}

// change frame grabber configuration ...

result = Fg_loadConfig(fg, config);
if (result != FG_OK) {
    // handle error ...
}

处理 Applet 参数#

一旦使用 Applet 初始化了采集卡,就可以读取或操纵 Applet 的参数。例如,为了从相机采集图像,必须将 Applet 配置为根据相机发送的图像数据使用正确的图像尺寸和图像格式。另一个例子是配置 Applet 提供的触发模块以满足应用程序的需求。

参数标识符和参数名称#

int Fg_getParameterIdByName(
    Fg_Struct * fg,
    const char * name);

const char * Fg_getParameterNameById(
    Fg_Struct * fg,
    unsigned int id,
    unsigned int dma);

FgParamTypes Fg_getParameterTypeById(
    Fg_Struct * fg,
    unsigned int id,
    unsigned int dma);

信息

函数 Fg_getParameterTypeById() 于 Framegrabber SDK 5.9 版本中添加。

Applet 的每个参数都通过其名称进行标识,但 API 使用数字标识符来访问参数值或属性。许多参数具有头文件中指定的固定数字标识符 basler_fg.h但是,除了最常用的参数外,不建议对所有参数都使用此方法。要获取参数的数值标识符,请使用以下函数。 Fg_getParameterIdByName() 已使用。如果将名称转换为标识符时发生错误,该函数将返回 0 或负错误代码。

要获取已知其数字标识符的参数名称,可以使用函数 Fg_getParameterNameById() 可以使用。

要获取已知其数字标识符的参数类型,可以使用函数 Fg_getParameterTypeById() 可以使用。

参数类型#

参数是有类型的,要获取或设置参数的值,必须知道参数的类型,这可以从 applet 的文档中得知,或者内部设计 applet 会隐式地告知。 VisualApplets或者通过使用函数请求类型 Fg_getParameterTypeById().

参数类型 C/C++ 类型
FG_PARAM_TYPE_INT32_T int32_t
FG_PARAM_TYPE_UINT32_T uint32_t
FG_PARAM_TYPE_INT64_T int64_t
FG_PARAM_TYPE_UINT64_T uint64_t
FG_PARAM_TYPE_DOUBLE double
FG_PARAM_TYPE_CHAR_PTR string
FG_PARAM_TYPE_SIZE_T size_t
FG_PARAM_TYPE_STRUCT_FIELDPARAMINT FieldParameterAccess
FG_PARAM_TYPE_STRUCT_FIELDPARAMINT64 FieldParameterAccess
FG_PARAM_TYPE_STRUCT_FIELDPARAMDOUBLE FieldParameterAccess

具有其中一种字段参数类型的参数应使用以下内容处理: FG_PARAM_TYPE_STRUCT_FIELDPARAMACCESS 小节中描述的 API 访问场参数 章节的 使用 Plain C 时的注意事项.

访问参数值#

int Fg_getParameterWithType(
    Fg_Struct * fg,
    int id,
    int32_t * value,
    unsigned int dma);

// Fg_getParameterWithType() is overloaded for:
//     int32_t * value
//     uint32_t * value
//     int64_t * value
//     uint64_t * value
//     float * value
//     double * value
//     std::string & value
int Fg_setParameterWithType(
    Fg_Struct * fg,
    int id,
    int32_t value,
    unsigned int dma);

// Fg_setParameterWithType() is overloaded for:
//     int32_t value
//     uint32_t value
//     int64_t value
//     uint64_t value
//     float value
//     double value
//     const std::string & value

要获取或设置参数的值,重载函数 Fg_getParameterWithType() 和 Fg_setParameterWithType() 应该使用。

例如,给定一个帧采集卡手柄 fg 来自先前对以下内容的调用: Fg_Init()以下代码将小程序的第一个 DMA 通道的宽度设置为 1024。

const int dma = 0;
const int width = 1024;
int result = FG_INVALID_PARAMETER;

int paramId = Fg_getParameterIdByName(fg, "FG_WIDTH");
if (paramId > 0) {
    result = Fg_setParameterWithType(fg, paramId, width, dma);
}

传递给函数的最后一个参数 Fg_getParameterWithType() 和 Fg_setParameterWithType() 在大多数情况下指的是DMA通道。参数如 FG_WIDTH, FG_HEIGHT 对于每个 DMA 通道可以不同,并且可以通过将 DMA 通道的编号传递给这些函数来请求或更改这些参数的每个实例。

在某些情况下,最后一个参数可以引用其他逻辑索引。例如,参数 FG_NR_OF_DMAS 或 FG_NR_OF_CAMS 在 applet 的每个进程中存在多个实例,这是 VisualApplets 中定义的作用域。

在 applet 的上下文中,其他参数是全局的,函数中的最后一个参数会被忽略。其中一个这样的参数是: FG_NR_OF_PROCESSES. 但更显著的是,如果应用程序开发人员使用 VisualApplets 设计自己的 applet,则所有控制 applet 的参数都可以视为全局参数,因为它们由唯一的名称标识,并且函数中的最后一个参数将被忽略。

某些参数可能会因采集的每幅图像而异,并且需要 DMA 通道以及帧编号或缓冲区编号。这适用于任何图像元数据,例如图像传输到 computer memory 时的时间戳、实际图像数据的长度以及类似信息。这些参数无法通过上述函数处理,有关此主题的更多详细信息,将在图像采集章节中讨论。

访问参数 Property#

int Fg_getParameterPropertyWithType(
    Fg_Struct * fg,
    int id,
    FgProperty propertyId,
    int32_t * value);

// Fg_getParameterPropertyWithType() is overloaded for:
//     int32_t * value
//     uint32_t * value
//     int64_t * value
//     uint64_t * value
//     float * value
//     double * value
//     std::string & value
int Fg_getParameterPropertyWithTypeEx(
    Fg_Struct * fg,
    int id,
    FgProperty propertyId,
    int32_t * value,
    unsigned int dma);

// Fg_getParameterPropertyWithTypeEx() is overloaded for:
//     int32_t * value
//     uint32_t * value
//     int64_t * value
//     uint64_t * value
//     float * value
//     double * value
//     std::string & value

信息

本章中记录的函数是在 Framegrabber SDK 的 5.9 版本中添加的。有关旧接口,请参阅在纯 C 中访问参数 Property 一节。

除了当前值之外,参数还具有各种属性。使用重载函数 Fg_getParameterPropertyWithType() 不推荐使用,因为函数调用隐式使用 DMA 通道 0,但不同通道的参数属性可能有所不同。使用重载函数 Fg_getParameterPropertyWithTypeEx() 可以请求以下属性:

Property 描述 键入
PROP_ID_VALUE 当前参数值 与参数相同
PROP_ID_DATATYPE 参数类型
(根据 FgParamTypes 的枚举值)
int32_t
PROP_ID_NAME 描述性名称 string
PROP_ID_PARAMETERNAME 参数名称 string
PROP_ID_VALUELLEN 将值编码为字符串所需的长度 int32_t
PROP_ID_ACCESS 参数的访问标志 int32_t
PROP_ID_MIN 最小参数值b 与参数相同
PROP_ID_MAX 最大参数值b 与参数相同
PROP_ID_STEP 参数的步长b 与参数相同
PROP_ID_IS_ENUM 0:不是枚举参数
n:PROP_ID_ENUM_VALUES 所需的缓冲区大小
int32_t
PROP_ID_ENUM_VALUES 枚举值(请参阅访问枚举值参数 Property) FgPropertyEnumValues[]
PROP_ID_FIELD_SIZE 场参数中的元素数量 int32_t

上面的列表并不完整,仅包含未被视为已过时的信息属性。有关该 API 的更多用例,请参阅 Framegrabber API 参考。

以下示例展示了如何获取参数的最小值,假设 paramId 是类型为 的参数。 FG_PARAM_TYPE_INT32_T:

int minVal = 0;
int result =
    Fg_getParameterPropertyWithTypeEx(fg, paramId, PROP_ID_MIN,
                                      &minVal, dma);
if (result == FG_OK) {
    // work with the property ...
}

内存管理#

要从相机采集图像,计算机中需要 Memory,以便将这些图像传输到其中并在程序中访问图像数据。Framegrabber API 使用循环缓冲区 Memory 模型。Memory 缓冲区由至少两项组成(通常称为帧缓冲区或子缓冲区),这些项将被重复用于从相机采集的后续帧。应使用多少个帧缓冲区取决于各种因素,其中最重要的是在处理一帧的同时可以到达多少个后续帧。这将在图像采集章节的采集模型一节中进行更详细的讨论。

每个帧缓冲区的大小必须相同,并且必须足够大,以便存储应用程序要求的最大图像,通常由宽度定义。 FG_WIDTH,高度 FG_HEIGHT 像素格式 FG_FORMAT。(对于在 VisualApplets 中设计的小程序,宽度、高度和 Pixel Format 的设置通常会更为复杂。)

循环缓冲区可以是虚拟地址空间中连续的一大块内存,并被细分为大小相等的帧缓冲区。或者,它也可以由单独分配在内存中并添加到管理结构中的帧缓冲区组成。 dma_mem 供 API 使用。

void * Fg_AllocMem(
    Fg_Struct * fg,
    size_t totalSize,
    frameindex_t numFrames,
    unsigned int dma);

int Fg_FreeMem(
    Fg_Struct * fg,
    unsigned int dma);

有三组不同的函数可用于 Memory 管理。但是,使用这些函数 Fg_AllocMem() 和 Fg_FreeMem() 并不推荐,因为它仅限于使用标准采集模型(ACQ_STANDARD)。这里将更详细地讨论其余两组函数,因为它们可用于所有三种采集模型(ACQ_STANDARD、ACQ_BLOCK 和 ACQ_SELECT),既适用于简单的使用场景,也适用于应用程序更具体的要求。

高级内存管理#

dma_mem * Fg_AllocMemEx(
    Fg_Struct * fg,
    size_t totalSize,
    frameindex_t numFrames);

int Fg_FreeMemEx(
    Fg_Struct * fg,
    dma_mem * mem);

函数 Fg_AllocMemEx() 可用于分配一个占用连续内存的大型内存缓冲区,其大小为 totalSize 该缓冲区被细分为 numFrames 大小相等的帧缓冲区。该函数返回指向 Memory 管理结构的句柄,如果出错则返回 nullptr。

返回的指针并非指向 Memory 缓冲区本身的指针,切勿直接使用!不再需要 Memory 后,应使用以下工具将其释放: Fg_FreeMemEx().

在以下示例中,代码将为 16 个帧缓冲区分配内存缓冲区,这些帧缓冲区能够容纳尺寸为 1024 x 1024 的 24 位 RGB 图像:

const int width = 1024, height = 1024;
const int bytesPerPixel = 3;
const frameindex_t numFrames = 16;
const size_t frameSize = static_cast<size_t>(width) * height * bytesPerPixel;
const size_t totalSize = frameSize * numFrames;

dma_mem * mem = Fg_AllocMemEx(fg, totalSize, numFrames);
if (mem != nullptr) {
    // use memory, acquire and process images ...

    Fg_FreeMem(fg, mem);
}

即: static_cast<size_t>(width) 在计算 frameSize。这对于确保能够正确计算超大图像的尺寸是必要的。

灵活内存管理#

dma_mem * Fg_AllocMemHead(
    Fg_Struct * fg,
    size_t totalSize,
    frameindex_t numFrames);

int Fg_AddMem(
    Fg_Struct * fg,
    void * frameBuffer,
    size_t size,
    frameindex_t index,
    dma_mem * mem);

int Fg_DelMem(
    Fg_Struct * fg,
    dma_mem * mem,
    frameindex_t index);

int Fg_FreeMemHead(
    Fg_Struct * fg,
    dma_mem * mem);

如果应用程序对内存分配有特定要求,而不是让 Framegrabber API 来处理,则函数 Fg_AllocMemHead() 可用于准备 Memory 管理结构。该函数期望传入与以下相同的参数: Fg_AllocMemEx(),但它不会分配任何 Memory。在应用程序中分配 Memory 后,需要使用以下函数将每个帧缓冲区逐个添加: Fg_AddMem()。如果应用程序要求在采集运行期间动态更改用于内存缓冲区的帧缓冲区,则函数 Fg_DelMem() 可用于从 Memory 缓冲区中移除帧缓冲区。不再需要 Memory 缓冲区后,应首先通过调用以下函数来释放管理结构: Fg_FreeMemHead() 在释放帧缓冲区内存之前。

以下代码展示了函数“Fg_AllocMemEx()”如何作为更灵活的 Memory 管理 API 的便捷包装器:

int Fg_AllocMemEx(Fg_Struct * fg, size_t totalSize, frameindex_t numFrames)
{
    const size_t frameSize = totalSize / numFrames;
    char * buf = nullptr;

    dma_mem * mem = Fg_AllocMemHead(fg, totalSize, numFrames);
    if (mem != nullptr) {
        try {
            buf = new char[totalSize];
            for (frameindex_t frame = 0; frame < numFrames; ++frame) {
                const int result =
                    Fg_AddMem(fg, buf + frameSize*frame,
                              frameSize, frame, mem);
                if (result != FG_OK) {
                    throw std::runtime_error("Failed to add frame buffer");
                }
            }
        }
        catch (...) {
            for (frameindex_t frame = 0; frame < numFrames; ++frame) {
                Fg_DelMem(fg, mem, frame);
            }
            Fg_FreeMemHead(fg, mem);
            delete[] buf;
            return nullptr;
        }
    }
    return mem;
}

图像采集#

Framegrabber API 提供了两种图像数据传输模式,这两种模式可以与三种不同的采集模型相结合。

图像数据可以同步或异步模式交付。在同步模式下,必须由应用程序提供采集循环。在异步模式下,可以注册一个回调函数,每当有新的图像数据可用时就会调用该函数。对于异步模式,Framegrabber API 将在单独的线程中提供图像采集循环。对于某些 GUI 框架,将 Framegrabber API 线程上下文同步回 GUI 线程上下文可能会比较复杂,而在该框架提供的线程上下文中运行采集循环可能会更明智。

为异步模式注册回调函数#

typedef int (* Fg_ApcFunc_t)(
    frameindex_t frame,
    void * data);

int Fg_registerApcHandlerEx(
    Fg_Struct * fg,
    unsigned int dma,
    Fg_ApcFunc_t func,
    void * data,
    unsigned int timeout,
    unsigned int flags);

int Fg_unregisterApcHandler(
    Fg_Struct * fg,
    unsigned int dma);

信息

在 Framegrabber API 5.9 版本中,函数 Fg_registerApcHandlerEx() 和 Fg_unregisterApcHandler() 已添加,以及使用阻塞采集模型时的行为 ACQ_BLOCK 已更改。请参阅章节 在 Plain C 中为异步模式注册回调函数 了解旧接口。

函数 Fg_registerApcHandlerEx() 可用于在开始采集之前设置异步模式。

使用 Fg_registerApcHandlerEx(),一个类型为 Fg_ApcFunc_t 可以注册在接收到图像数据时被调用的函数。该函数将针对给定的采集卡和 DMA 通道进行注册,并且在调用时会传递两个参数。回调函数的第一个参数是所接收图像的帧号(位于 ACQ_STANDARD 和 ACQ_SELECT)或缓冲区编号(在 ACQ_BLOCK中)。第二个参数是在调用时提供的指针 Fg_registerApcHandlerEx() 且可用作指向上下文结构或类的指针,例如 this 实现图像处理的类实例的指针。

对于任何给定的采集卡上的每个 DMA 通道,只能注册一个回调函数。

回调函数将从 Framegrabber API 提供的采集循环中调用。这意味着回调函数是在采集循环的线程上下文中调用的。这也意味着回调函数返回所需的时间会增加到采集循环的一般管理开销中,并且在此期间可能会接收到多幅图像。如果在该期间接收到任何图像,回调函数将在返回后立即再次被调用。

图像采集的处理可以通过以下方式进行控制: timeout 和 flags 调用中的参数 Fg_registerApcHandlerEx()。超时时间以秒为单位,采集循环应等待新图像到达。对于参数 flags,以下值及其与运算符的组合 | (按位或)可用于:

标志 描述
FG_APC_DEFAULTS 采集 loop 中的默认处理
FG_APC_BATCH_FRAMES 如果接收到多个图像,在对回调函数的单次调用中,可能仅交付最新图像
FG_APC_DELIVER_ERRORS 将错误作为负的 frameindex_t 值传递给回调函数
FG_APC_IGNORE_TIMEOUTS 忽略图像超时并继续处理
FG_APC_IGNORE_APCFUNC_RETURN 忽略回调返回值并继续处理
FG_APC_IGNORE_STOP 忽略采集停止状态并继续处理
FG_APC_HIGH_PRIORITY 提高实现采集循环的线程优先级。要使用此值,需要操作系统的提升权限。
FG_APC_OLD_ACQ_BLOCK_BEHAVIOR 使用 Fg_getLastPicNumberBlockingEx() 在 ACQ_BLOCK 中c

采集循环的默认处理方式是:

  • 每一幅单独的图像都会被传送到回调函数
  • 错误值不会传递给回调函数
  • 图像超时会结束采集循环
  • 回调函数的返回值不为零时,将结束采集循环
  • 停止采集会结束采集循环
  • 采集循环运行在具有默认优先级的线程中
  • 函数 Fg_getLastPicNumberBlockingEx() 被调用,并且结果会传送到回调函数

信息

结束采集循环将自动取消注册回调函数。虽然在停止采集时总是结束采集 loop 似乎很合逻辑,但这并非必然如此。例如,考虑这样一种情况:应用程序需要通过调用具有有限采集帧数的 Fg_AcquireEx 来以特定数量的图像突发运行采集。在采集了指定数量的帧后,驱动程序会自动停止采集。保持采集 loop 运行并从单独的线程启动采集突发可能会很方便。

信息

设置 FG_APC_HIGH_PRIORITY 只有当进程具备更改线程优先级的必要权限时才可能实现。请查阅所用操作系统的权限和权限管理文档。对于使用 Microsoft Windows 的应用程序,函数 SetThreadPriority 被使用。在 Linux 上,该函数 pthread_setschedparam 将被使用。

以下示例展示了如何使用包含处理图像采集所需信息的简单结构体来注册回调函数:

struct ApcUserCallbackData
{
    Fg_Struct * fg;
    dma_mem * mem;
    unsigned int dma;
    unsigned int timeoutInSeconds;
    int mode;
};

int ApcUserCallback(frameindex_t frame, void * data)
{
    auto context = reinterpret_cast<ApcUserCallbackData *>(data);

    if (frame > 0) {
        // process new image ...
    } else {
        // handle error ...
    }

    return 0;
}

void SetupApcUserCallback(ApcUserCallbackData * context)
{
    // register callback function
    int result =
        Fg_registerApcHandlerEx(
            context->fg,
            context->dma,
            &ApcUserCallback,
            context,
            context->timeoutInSeconds,
            FG_APC_DELIVER_ERRORS | FG_APC_IGNORE_TIMEOUTS);
    if (result != FG_OK) {
        throw std::runtime_error("Failed to register callback function");
    }
}

上下文字结构的分配和管理未在示例中显示。只要回调函数保持注册状态,该指针就必须有效。一种解决方案是将所有内容保存在一个 C++ 类中。为了在 C++ 类上下文中使用回调函数,可以使用静态函数来注册回调处理器,并且 this 指针应用作上下文数据指针,该指针可以转换回类指针并作相应使用。

在不再需要回调函数之后,可以通过调用将其注销 Fg_unregisterApcHandler() 使用相同的采集卡句柄和 DMA 通道:

Fg_unregisterApcHandler(context->fg, context->dma);

开始采集#

要启用将图像数据从采集卡传输到应用程序 Memory 的功能,必须启动 applet 中的采集。这将通知采集卡图像数据应传输到的帧缓冲区,并在成功传输新图像的数据时使驱动程序能够发送中断。在大多数情况下,在采集卡端启动采集后,您还必须在相机端启动采集并开始触发相机。

有关控制相机的更多信息,请参阅相机控制库 siso_genicam 或Camera Link 串口接口库 clsersis。虽然触发相机超出了本文档的范围,但 Applet 的文档将提供有关应用程序通过软件或硬件信号触发帧 grabber 的选项的更多信息。

int Fg_Acquire(
    Fg_Struct * fg,
    unsigned int dma,
    frameindex_t frames);

int Fg_stopAcquire(
    Fg_Struct * fg,
    unsigned int dma);

这些函数 Fg_Acquire() 和 Fg_stopAcquire() 只能与内存管理函数结合使用 Fg_AllocMem() 和 Fg_FreeMem() 并且限于使用标准采集模型 ACQ_STANDARD。不建议使用这些函数,且本文档不予介绍。而是使用这些函数 Fg_AcquireEx() 和 Fg_stopAcquireEx() 应该使用。

使用高级或灵活的 Memory 管理进行图像采集#
int Fg_AcquireEx(
    Fg_Struct * fg,
    unsigned int dma,
    frameindex_t frames,
    int flags,
    dma_mem * mem);

int Fg_stopAcquireEx(
    Fg_Struct * fg,
    unsigned int dma,
    dma_mem * mem,
    int flags);

函数 Fg_AcquireEx() 可以调用以启动采集卡上单个 DMA 通道上的图像采集。此调用将绑定一个 DMA 通道 dma 指向内存句柄 mem 从而使用为接收图像数据而分配的 Memory。

采集将持续进行,直到通过参数指定的帧数 frames 已达到。如果 GRAB_INFINITE 传递给 frames,采集将无限期运行。通过参数 flags,可以选定采集模型。请参阅 采集模型 以获取更多信息。

型号 描述
ACQ_STANDARD 连续采集,且不提供帧缓冲区保护
ACQ_BLOCK 连续采集,并采用帧缓冲区锁定机制
ACQ_SELECT 完全由应用程序控制的采集,并带有手动帧缓冲区处理功能

当在调用中指定要采集的帧数时 Fg_AcquireEx()中,当 Framegrabber SDK 遇到预期帧数时,采集将自动停止。在某些情况下,这可能会在处理最后一帧时导致意外行为。为避免这种情况,除采集模型外, ACQ_NO_AUTOSTOP 可以在参数中指定 flags。当 ACQ_NO_AUTOSTOP 被使用时,当达到请求的帧数,驱动程序将停止采集帧,但 Framegrabber SDK 的其余部分以及图像采集卡将保持采集模式,直到 Fg_stopAcquireEx() 被调用。

要停止采集并重置所用 applet 的运行状态,函数 Fg_stopAcquireEx() 应该被调用。通过参数 flags,可以选择停止模式:

模式 描述
STOP_ASYNC 立即停止采集
STOP_SYNC_TO_APC 在异步模式下,将停止采集同步到回调函数
等待回调完成的时间(以毫秒为单位)可通过参数 FG_APC_STOP_TIMEOUT 进行设置
STOP_SYNC 将停止采集与驱动程序进行同步
等待下一幅图像传输完成的时间(以秒为单位)可通过参数 FG_STOP_TIMEOUT 进行设置
STOP_ASYNC_FALLBACK 可与 STOP_SYNC 结合使用
如果无法完成同步停止,则回退至 STOP_ASYNC

为同步模式编写采集循环#

在同步模式下,应用程序必须处理新图像的接收。这通常通过采集循环来实现,该循环会调用 Framegrabber API 提供的函数之一,以等待新图像在特定的 DMA 通道上传输。要了解应使用哪个函数,请参阅 Acquisition Models 节以获取更多信息。针对每个模型,对应的子节中都提供了一个示例采集循环。

frameindex_t Fg_getLastPicNumberBlocking(
    Fg_Struct * fg,
    frameindex_t frame,
    unsigned int dma,
    int timeout);

函数 Fg_getLastPicNumberBlocking() 只能与函数结合使用 Fg_Acquire() 并且受限于标准采集模型 ACQ_STANDARD。不建议使用此函数,因此不予文档化。取而代之的是函数 Fg_getLastPicNumberBlockingEx() 或 Fg_getImageEx() 应该使用。

使用高级或灵活的 Memory 管理等待图像#
frameindex_t Fg_getLastPicNumberBlockingEx(
    Fg_Struct * fg,
    frameindex_t frame,
    unsigned int dma,
    int timeout,
    dma_mem * mem);

frameindex_t Fg_getImageEx(
    Fg_Struct * fg,
    int strategy,
    frameindex_t frame,
    unsigned int dma,
    unsigned int timeout,
    dma_mem * mem);

函数 Fg_getLastPicNumberBlockingEx() 等待参数中请求的帧 frame 在参数指定的秒数内到达 timeout 在参数中指定的给定 DMA 通道上 dma。第一帧的编号为 1,而不是 0。如果成功,该函数将返回大于 0 的帧编号;如果失败,则返回负错误代码。

信息

返回的帧号可以大于请求的帧号,因为该函数将始终返回可用的最新帧号。这意味着在对该函数的两次调用之间,可能已经到达了一个以上的帧,应用程序需要决定是仅处理最新帧还是处理所有帧。

以下示例展示了一个简单的采集循环,适用于 ACQ_STANDARD:

const int timeoutInSeconds = 10;

frameindex_t nextFrame = 1;
while (true) {
    // get new image
    const frameindex_t newestFrame =
        Fg_getLastPicNumberBlockingEx(fg, nextFrame, dma,
                                      timeoutInSeconds, mem);

    if (newestFrame > 0) {
        // process new images ...

        nextFrame = (newestFrame < FRAMEINDEX_MAX) ? newestFrame + 1 : 1;
    } else {
        // handle error ...
    }
}

函数 Fg_getImageEx() 要复杂得多。函数行为以及成功时返回代码的含义取决于参数 strategy 和 timeout 以及所使用的采集模型。如果发生错误,结果将始终是一个负错误代码。下面将对不同的策略进行简要说明,但通常该函数的使用应局限于 阻塞式采集模型中描述的使用场景。不鼓励将该函数用于任何其他用途,本文档中也不再对其进行详细探讨。

SEL_NEW_IMAGE:该函数要求指定超时,并等待至少一个新图像到达。在 ACQ_STANDARD 和 ACQ_SELECT 将返回最新图像的帧编号。在 ACQ_BLOCK中,该函数会为主图像阻塞帧缓冲区,解除阻塞任何附加图像的帧缓冲区,并返回被阻塞图像的缓冲区编号。

SEL_NEXT_IMAGE中:在 ACQ_STANDARD,若未指定超时时间,该函数将返回接收到的最新图像的缓冲区编号。若指定了超时时间,该函数将等待自前一次函数调用中接收到的最后一帧之后的下一帧,若此前未调用过该函数,则等待帧编号 1。该函数将返回接收到的最新图像的帧编号。在 ACQ_BLOCK中,该函数会阻塞首个尚未被阻塞或解除阻塞的已接收图像的帧缓冲区,并返回被阻塞图像的缓冲区编号。若没有更多可阻塞的图像且指定了超时时间,该函数将等待至少一个新图像到达,然后再执行阻塞操作。

SEL_ACT_IMAGE中:在 ACQ_STANDARD中,该函数的行为与 SEL_NEXT_IMAGE 指定时的行为相同。在 ACQ_BLOCK中,该函数会阻塞最新图像的帧缓冲区,对任何其他图像的帧缓冲区解除阻塞,并返回被阻塞图像的缓冲区编号。若没有更多可阻塞的图像且指定了超时时间,该函数将等待至少一个新图像到达,然后再执行阻塞操作。

SEL_LAST_IMAGE中:该函数返回由该函数或由 Fg_getLastPicNumberBlockingEx()。这取决于上一次调用该函数的方式,可能是最后一帧的帧编号,也可能是缓冲区编号。

SEL_NUMBER:该函数的行为类似于 Fg_getLastPicNumberBlockingEx().

以下示例展示了一个简单的采集循环,适用于 ACQ_BLOCK 返回的最后一个值 SEL_NEXT_IMAGE (采集到的每一幅图像都会被处理):

const int timeoutInSeconds = 10;

while (true) {
    // get new image
    const frameindex_t buffer =
        Fg_getImageEx(fg, SEL_NEXT_IMAGE, 0, dma, timeoutInSeconds, mem);

    if (buffer > 0) {
        // process new image ...
    } else {
        // handle error ...
    }
}

驱动程序图像采集超时 FG_TIMEOUT#

除了在调用函数时指定等待图像的时间外, Fg_getImageEx() 或 Fg_getLastPicNumberBlockingEx(),或者在为异步模式设置回调函数时,驱动程序还会在处理图像传输中断的上下文中跟踪图像采集。如果在参数 FG_TIMEOUT。如果 FG_TIMEOUT 设为 FG_TIMEOUT_INFINITE ( FG_TIMEOUT_INFINITE 的值为 INT_MAX - 1) 期间未接收到任何图像数据,驱动程序将停止图像采集。

参数 FG_TIMEOUT )会在采集启动时转发给驱动程序。在采集启动后对该参数进行的任何更改,在停止并重新启动采集之前都不会被驱动程序采用。

在驱动程序停止采集后,任何仍在等待图像的函数调用都将返回 FG_TIMEOUT_ERR 的值以及在采集停止后调用以等待图像的任何函数都将返回 FG_TRANSFER_NOT_ACTIVE.

信息

参数的默认值为 FG_TIMEOUT 的值为 不 FG_TIMEOUT_INFINITE,但在大多数情况下设置为 1000000 秒(相当于大约 277 小时 46 分钟)。建议显式设置参数 FG_TIMEOUT 更改为 FG_TIMEOUT_INFINITE 启动采集之前进行设置。

以下示例展示了如何禁用驱动程序图像采集超时:

Fg_setParameterWithType(fg, FG_TIMEOUT, FG_TIMEOUT_INFINITE, dma);

图像和采集信息#

int Fg_getParameterEx(
    Fg_Struct * fg,
    int param,
    void * value,
    unsigned int dma,
    dma_mem * mem,
    frameindex_t frame);

void * Fg_getImagePtrEx(
    Fg_Struct * fg,
    frameindex_t frame,
    unsigned int dma,
    dma_mem * mem);

frameindex_t Fg_getStatusEx(
    Fg_Struct * fg,
    int status,
    frameindex_t frame,
    unsigned int dma,
    dma_mem * mem);

对于接收到的每一帧,可以通过调用函数请求以下信息 Fg_getParameterEx():

参数 描述 键入
FG_TRANSFER_LEN 实际传输的字节数 size_t
FG_TIMESTAMP_LONG 图像的高分辨率 Timestamp uint64_t
FG_TIMESTAMP_LONG_FREQUENCY 高分辨率 Timestamp 频率 uint64_t
FG_TIMESTAMP 以毫秒为单位的图像 Timestamp uint32_t
FG_IMAGE_TAG 图像标签 uint32_t
FG_IMAGE_NUMBER 帧号 uint64_t

信息

帧编号信息是在 Framegrabber SDK 的 5.9 版本中添加的。

每帧的信息并非永久可用,仅在相应的缓冲区未排队等待后续传输时有效。根据所使用的采集模型,该函数需要帧号或缓冲区号。有关更多详细信息,请参阅采集模型一节。

以下示例展示了如何请求给定帧的 Timestamp:

// get time stamp frequency
uint64_t frequency = 0;
Fg_getParameterEx(fg, FG_TIMESTAMP_LONG_FREQUENCY, &frequency, 0, nullptr, 0);

// ...

// get frame time stamp
uint64_t timestamp = 0
int result =
    Fg_getParameterEx(fg, FG_TIMESTAMP_LONG, &timestamp, dma, mem, frame);
if (result == FG_OK) {
    // this will probably be 'seconds since booting the computer' ...
    // it makes more sense when you calculate the difference
    // between timestamps of two frames
    double seconds =
        static_cast<double>(timestamp)/static_cast<double>(frequency);

    // ...
}

要获取指向帧缓冲区的指针,可以使用以下函数 Fg_getImagePtrEx() 可以调用。每帧的指针并非永久可用,仅在相应的缓冲区未排队等待后续传输时有效。根据所使用的采集模型,该函数需要帧号或缓冲区号。请参阅 采集模型 一节了解更多详情。

以下示例展示了如何请求给定帧的帧缓冲区指针:

// get a pointer to the frame buffer for the newest image
void * buffer = Fg_getImagePtrEx(fg, frame, dma, mem);

// get the actual number of bytes transferred
size_t length = 0;
int result =
    Fg_getParameterEx(fg, FG_TRANSFER_LEN, &length, dma, mem, frame);

if ((buffer != nullptr) && (result == FG_OK) && (length > 0)) {
        // process image data ...
}

函数 Fg_getStatusEx() 可以被调用以请求以下通用状态信息,参数 frame 以下情况将被忽略:

状态 描述
NUMBER_OF_GRABBED_IMAGES 传输的总帧数
NUMBER_OF_LAST_IMAGE 报告的最后一帧帧号 Fg_getLastPicNumberBlockingEx()
NUMBER_OF_NEXT_IMAGE 报告的最后一帧之后的下一帧帧号
GRAB_ACTIVE 0:DMA 通道处于未激活状态
1:DMA 通道处于激活状态

以下示例展示了如何请求 DMA 通道的 Acquisition Status:

frameindex_t active =
    Fg_getStatusEx(fg, GRAB_ACTIVE, 0, dma, mem);
if (active == 1) {
    // the DMA channel is active ...
} else if (active == 0) {
    // the DMA channel is inactive ...
} else {
    // handle error ...
}

采集模型#

Framegrabber API 提供了三种不同的采集模型,可以在调用 Fg_AcquireEx():标准、阻塞和选择性。这三种采集模型均基于 内存管理 一章中所述的 Memory 模型,该模型使用至少两个图像缓冲区来采集图像。

对于本节中的示例,将使用由虚拟内存中四个连续帧缓冲区组成的内存缓冲区。

Memory Buffer Model

帧编号与缓冲区编号#

整个Framegrabber API使用帧号和缓冲区号,两者均为类型 frameindex_t.

虽然帧号是严格单调递增的自然数,范围在 [1; FRAMEINDEX_MAX]指的是自采集开始以来采集的图像数量,类型 frameindex_t 用于帧号的数据类型是有符号类型,在 API 中有时既用于返回负的 Error Codes,也用于返回帧号。特别是在 32 位应用程序中,必须注意正确处理帧号溢出:一旦 FRAMEINDEX_MAX 图像计数已达到上限,图像计数将回绕至 1!(在 64 位应用程序中,即使帧速率极高,图像计数器溢出也需要数十万年才会发生。)

相比之下,缓冲区号指存放图像数据的帧缓冲区,对于 N 个缓冲区,其范围为 [1; N]在这些示例中,缓冲区编号介于 1 到 4 之间,指的是 FB0 … FB3 相应地。

标准采集模型 (ACQ_STANDARD)#
frameindex_t Fg_getLastPicNumberBlockingEx(
    Fg_Struct * fg,
    frameindex_t frame,
    unsigned int dma,
    int timeout,
    dma_mem * mem);
int Fg_getParameterEx(
    Fg_Struct * fg,
    int param,
    void * value,
    unsigned int dma,
    dma_mem * mem,
    frameindex_t frame);

void * Fg_getImagePtrEx(
    Fg_Struct * fg,
    frameindex_t frame,
    unsigned int dma,
    dma_mem * mem);

标准采集模式通过传递以下参数来选择: ACQ_STANDARD 更改为 flags 在调用 Fg_AcquireEx()时。所有帧缓冲区始终都可以由用于采集图像数据的驱动程序以及用于处理图像数据的应用程序访问。

当开始采集时,驱动程序会在采集卡中为图像数据传输排队至少一个帧缓冲区,并从第一个帧缓冲区开始 FB0。一旦图像完全传输到计算机内存中,软件就会收到传输完成的通知,并且驱动程序将排队多一个帧缓冲区。采集卡将继续为下一张图像将数据传输到队列中的下一个缓冲区,在我们的示例中 FB1.

(排队的帧缓冲区数量取决于各种因素,例如所使用的采集卡、驱动程序版本以及 Windows 注册表中的设置或 Linux 驱动程序的参数。但是,在标准采集模式下,排队将始终以线性方式进行,并且将从 FB0 在最后一个帧缓冲区排队后再次进行。虽然排队的缓冲区数量可能会对计算机中可达到的最大帧速率产生影响,但这种影响只有在每秒超过 10,000 帧时才能测得。)

由于驱动程序以连贯的循环方式使用帧缓冲区,因此可以使用关系式将帧号简单地转换为缓冲区号 bufferNumber = 1 + ((frameNumber - 1) % numberOfBuffers)开始。函数 Fg_getImagePtrEx() 可用于获取指向帧缓冲区的指针,该缓冲区对应于帧号或缓冲区号。

在标准采集模式下,应用程序全权负责确保图像采集不会“超越”图像处理。只有在应用程序结合图像处理来控制图像源的触发,或者帧缓冲区数量经过精心调整以匹配所使用计算机系统的帧率和性能,或者能够跳过图像的情况下,才能确保这一点。

在这样一种场景下:当上一张图像仍在处理时,应用程序只触发单张图像,此时两个缓冲区就足够了——一个用于正在处理的图像数据,另一个用于传输图像数据。(如果应用程序确保仅在图像完全处理完毕后才生成触发器,实际上一个缓冲区就足以进行处理。然而,Framegrabber API 不支持这一点,在所有情况下应用程序都必须至少分配两个缓冲区。)

在没有来自应用程序端的控制、图像被连续生成的场景中,图像源和驱动程序中的缓冲区处理都可以被视为自由运行。在以下示例中, FB0 和 FB1 包含有效的图像数据。 FB0 正由应用程序进行处理, FB1 正在等待处理。驱动程序已至少排队 FB2 且当前正在传输图像数据。这种情况仍然是安全的,因为 FB3 尚未使用,尽管可能已经排队。

安全内存缓冲区模型

调用函数 Fg_getLastPicNumberBlockingEx() 用于处理后的下一个帧编号 FB0 完成将返回单个新帧(传输至 FB1)在这种情况下,如果传输到 FB2 在此期间尚未完成。

如果图像处理速度慢于图像采集,应用程序可能已经转去处理 FB1。然而,在下面的示例中,在此期间传输了另外两幅图像, FB2 和 FB3,并且驱动程序已重新从 FB0。当图像传输完成时,驱动程序将使用 FB1开始,因此采集卡可能会覆盖当前正在处理的数据。只有当应用程序能够确保在 FB1 仍在处理中。

不安全内存缓冲区模型

调用函数 Fg_getLastPicNumberBlockingEx() 用于处理后的下一个帧编号 FB0 完成时,将返回两个新帧(传输到 FB3)在这种情况下,如果传输到 FB0 在此期间尚未完成。

以下示例扩展了编写采集循环一节中给出的采集循环结构,以处理所有帧:

const int timeoutInSeconds = 10;

frameindex_t nextFrame = 1;
while (true) {
    // get new image
    const frameindex_t newestFrame =
        Fg_getLastPicNumberBlockingEx(fg, nextFrame, dma,
                                      timeoutInSeconds, mem);

    if (newestFrame > 0) {
        for (frameindex_t frame = nextFrame; frame <= newestFrame; ++frame) {
                // get a pointer to the frame buffer for the newest image
                void * ptr =
                    Fg_getImagePtrEx(fg, frame, dma, mem);

            // get the actual number of bytes transferred
            size_t length = 0;
            int result =
                Fg_getParameterEx(fg, FG_TRANSFER_LEN, &length,
                                  dma, mem, frame);

            if ((ptr != nullptr) && (result == FG_OK) && (length > 0)) {
                    // process image data ...
            }
        }

        nextFrame = (newestFrame < FRAMEINDEX_MAX) ? newestFrame + 1 : 1;
    } else {
        // handle error ...
    }
}

如果应用程序可以跳过图像并且仅处理接收到的最新帧,则可以从采集循环中删除内部 for 循环,转而仅处理 newestFrame.

函数 Fg_getImageEx() 不应与标准采集模型结合使用。

在异步模式下,Framegrabber API 将调用 Fg_getLastPicNumberBlockingEx() 以等待新图像,并且如果标志 FG_APC_BATCH_FRAMES (如果已设置),或者如果不止一次设置则会调用多次,针对接收到的每个图像调用一次。回调将接收图像的帧编号。

若要在异步模式下处理帧,内部循环的内容可用于回调函数(ApcCallbackData 是为异步模式注册回调函数一节中所述的结构):

int ApcUserCallback(frameindex_t frame, void * data)
{
    auto context = reinterpret_cast<ApcUserCallbackData *>(data);

    if (frame > 0) {
        // get a pointer to the frame buffer for the newest image
        void * ptr =
            Fg_getImagePtrEx(context->fg, frame, context->dma, context->mem);

        // get the actual number of bytes transferred
        size_t length = 0;
        int result =
            Fg_getParameterEx(context->fg, FG_TRANSFER_LEN, &length,
                              context->dma, context->mem, frame);

        if ((ptr != nullptr) && (result == FG_OK) && (length > 0)) {
            // process image data ...
        }
    } else {
        // handle error ...
    }

    return 0;
}

通过这种方式,回调函数既可用于仅处理最新图像(当 FG_APC_BATCH_FRAMES 已传递 flags 更改为 Fg_registerApcHandlerEx()时),也可用于处理所有图像。当 FG_APC_BATCH_FRAMES 未传递。

阻塞采集模型 (ACQ_BLOCK)#
frameindex_t Fg_getImageEx(
    Fg_Struct * fg,
    int strategy,
    frameindex_t frame,
    unsigned int dma,
    unsigned int timeout,
    dma_mem * mem);
int Fg_getParameterEx(
    Fg_Struct * fg,
    int param,
    void * value,
    unsigned int dma,
    dma_mem * mem,
    frameindex_t buffer);

void * Fg_getImagePtrEx(
    Fg_Struct * fg,
    frameindex_t buffer,
    unsigned int dma,
    dma_mem * mem);

frameindex_t Fg_getStatusEx(
    Fg_Struct * fg,
    int status,
    frameindex_t buffer,
    unsigned int dma,
    dma_mem * mem);
int Fg_setStatusEx(
    Fg_Struct * fg,
    int status,
    frameindex_t buffer,
    unsigned int dma,
    dma_mem * mem);

信息

时,回调函数将被调用多次,针对每个新图像调用一次。 ACQ_BLOCK 已在 Framegrabber API 的 5.9 版本中更改。

阻塞采集模式是通过传递以下内容来选择的: ACQ_BLOCK 更改为 flags 在调用 Fg_AcquireEx()。每个帧缓冲区要么由驱动程序独占访问以采集图像数据,要么处于排队状态或被阻止以供应用程序处理图像数据。只要帧缓冲区被阻止,使用它们就是安全的;但当驱动程序遇到没有更多未阻止的缓冲区可用于图像数据传输的情况时,可能会丢失图像。

当开始采集时,驱动程序会在采集卡中为图像数据传输排队至少一个帧缓冲区,并从第一个帧缓冲区开始 FB0。一旦图像完全传输到计算机 Memory 中,软件就会收到传输完成的通知,帧缓冲区将排队等待应用程序处理,并且在应用程序显式或隐式对其进行解除阻塞之前,驱动程序不会使用它。只要还有一个或多个未阻塞的帧缓冲区可用,驱动程序就会在图像采集卡中多排队一个帧缓冲区。如果没有更多未开发的帧缓冲区可用,则称为 虚拟缓冲区的特殊帧缓冲区 将在图像采集卡中排队,以保持图像采集持续运行。图像采集卡将继续把下一幅图像的数据传输到队列中的下一个缓冲区,在我们的示例中,这将是 FB1.

(如标准采集模型小节所述,图像采集卡中排队的帧缓冲区数量取决于各种因素。)

虚拟帧缓冲区可以被视为无限大,并且可以吸收任意大小的数据,但这对于处理图像来说并没有意义。因此,虚拟缓冲区对应用程序不可访问,传输到虚拟缓冲区的所有图像都将丢失。由于每次传输帧时自动进行下一个缓冲区的排队,这意味着如果没有未解除阻塞的帧缓冲区可供驱动程序排队,则当没有可用缓冲区时,至少会自动丢失一个图像。即使数据源停止并在帧缓冲区再次可用后才重新启动,情况也是如此!

在同步模式下,函数 Fg_getImageEx() 应调用以请求并阻塞一个用于新采集图像的帧缓冲区。该函数将返回被阻塞帧缓冲区的缓冲区编号。以下策略与阻塞采集模式相关:

策略 描述
SEL_NEXT_IMAGE 返回下一帧的缓冲区编号
此策略用于按照传输顺序逐个处理每个单帧
SEL_ACT_IMAGE 返回最新帧的缓冲区编号
当图像处理速度慢于 Acquisition Frame Rate 时,此策略用于跳过帧

上述列表并不完整,仅包含与阻塞采集模型相关的策略。

如果 SEL_NEXT_IMAGE 被使用时,只会阻止下一个帧缓冲区,所有其他缓冲区将继续排队以供应用程序在稍后请求。如果 SEL_ACT_IMAGE 被使用时,只有最新的帧缓冲区会被锁定,所有其他排队等待应用程序的帧缓冲区将从队列中移除并隐式解锁。

当调用函数 Fg_getImageEx() 使用此模型时,应始终将 0 传递给 frame。该函数不能用于等待特定的帧编号,也不能用于将帧编号转换为缓冲区编号。若要获取帧缓冲区的帧编号,可调用函数 Fg_getParameterEx() 可以通过传递 FG_IMAGE_NUMBER 在参数 param,一个指向类型为 frameindex_t 用于参数中的帧号和缓冲区号 buffer.

函数 Fg_getStatusEx() 中,可以调用以请求与阻塞采集模型相关的以下状态信息:

状态 描述
NUMBER_OF_LOST_IMAGES 丢失的帧数
NUMBER_OF_BLOCKED_IMAGES 当前阻塞的帧数
NUMBER_OF_IMAGES_IN_PROGRESS 可通过以下方式获取的可用帧数 Fg_getImageEx()
BUFFER_STATUS 0:帧缓冲区未阻塞
1:帧缓冲区已阻塞

函数 Fg_setStatusEx() 可以调用该函数来解锁在先前调用中被请求和锁定的帧缓冲区 Fg_getImageEx():

状态 描述
FG_UNBLOCK 解除阻塞单个帧缓存
FG_UNBLOCK_ALL 解除阻塞当前所有已阻塞的缓存

FG_UNBLOCK_ALL 也将移除任何仍排队等待应用程序但尚未被请求和阻止的缓冲区。

当使用函数 Fg_AcquireEx() 来指定要采集的图像数量时,丢失的图像和成功交付的图像都会被计算在内。这可能会导致意外的 FG_TIMEOUT_ERR 或 FG_TRANSFER_NOT_ACTIVE 调用时产生的结果 Fg_getImageEx() 并且在采集循环中只计算成功交付的图像。

在这样一种场景下:当上一幅图像仍在处理时,应用程序仅触发单幅图像,此时两个缓冲区就足够了,一个用于正在处理的图像数据,另一个用于传输图像数据。(由于会发生缓冲区锁定,即使应用程序能确保只有在图像完全处理完毕后才生成触发器,但在所有情况下至少仍需要两个缓冲区。)

在应用程序不进行控制而连续生成图像的场景中,图像源可以被视为自由运行。然而,只要仍有未阻止的可用缓冲区,驱动程序就只能向应用程序交付图像。在以下示例中, FB0 和 FB1 包含有效的图像数据。 FB0 正由应用程序进行处理, FB1 正在等待处理。驱动程序已至少排队 FB2 且当前正在传输图像数据。这种情况仍然是安全的,因为 FB3 尚未使用,尽管可能已经排队。

安全内存缓冲区模型

如果图像处理速度慢于图像采集速度,应用程序可能已经完成了处理 FB0,将其解除阻止并继续处理 FB1。然而,在下面的示例中,在此期间传输了另外两幅图像, FB2 和 FB3,并且驱动程序已重新从 FB0。当图像传输完成时,如果应用程序尚未完成处理 FB1 并将其解锁后,驱动程序将没有更多可用于排队的空闲缓冲区。虚拟缓冲区将被排队,采集过程中将丢失至少一个图像,但尚未解除阻止的帧缓冲区的完整性将得以保持。

不安全内存缓冲区模型

以下示例扩展了编写采集循环一节中给出的采集循环结构,以处理所有帧:

const int timeoutInSeconds = 10;
while (true) {
    // get new image
    frameindex_t buffer =
        Fg_getImageEx(fg, SEL_NEXT_IMAGE, 0, dma, timeoutInSeconds, mem);

    if (buffer > 0)
        // get the frame number (if needed)
        uint64_t frame = 0;
        Fg_getParameterEx(fg, FG_IMAGE_NUMBER, &frame, dma, mem, buffer);

        // get a pointer to the frame buffer for the newest image
        void * ptr = Fg_getImagePtrEx(fg, buffer, dma, mem);

        // get the actual number of bytes transferred
        size_t length = 0;
        int result =
            Fg_getParameterEx(fg, FG_TRANSFER_LEN, &length, dma, mem, buffer);

        if ((ptr != nullptr) && (result == FG_OK) && (length > 0)) {
            // process image data ...
        }

        // unblock frame buffer
        Fg_setStatusEx(fg, FG_UNBLOCK, buffer, dma, mem);
    } else {
        // handle error ...
    }
}

函数 Fg_getLastPicNumberBlockingEx() 不应与阻塞采集模型结合使用。

在异步模式下,Framegrabber API 将调用 Fg_getImageEx() 返回的最后一个值 SEL_ACT_IMAGE 如果标志 FG_APC_BATCH_FRAMES 已设置,或者使用 SEL_NEXT_IMAGE 如果未设置。回调函数将收到图像的缓冲区编号。c

要在异步模式下处理帧,可以在回调函数中使用 if 条件语句(ApcUserCallbackData 是 注册异步模式回调函数 节中所述的结构体):

int ApcUserCallback(frameindex_t buffer, void * data)
{
    auto context = reinterpret_cast<ApcUserCallbackData *>(data);

    if (buffer > 0) {
        // get the frame number (if needed)
        uint64_t frame = 0;
        Fg_getParameterEx(fg, FG_IMAGE_NUMBER, &frame, dma, mem, buffer);

        // get a pointer to the frame buffer for the newest image
        void * ptr =
            Fg_getImagePtrEx(context->fg, buffer, context->dma, context->mem);

        // get the actual number of bytes transferred
        size_t length = 0;
        int result =
            Fg_getParameterEx(context->fg, FG_TRANSFER_LEN, &length,
                              context->dma, context->mem, buffer);

        if ((ptr != nullptr) && (result == FG_OK) && (length > 0)) {
            // process image data ...
        }

        // unblock frame buffer
        Fg_setStatusEx(fg, FG_UNBLOCK, buffer, dma, mem);
    } else {
        // handle error ...
    }

    return 0;
}

通过这种方式,回调函数既可用于仅处理最新图像(当 FG_APC_BATCH_FRAMES 已传递 flags 更改为 Fg_registerApcHandlerEx()时),也可用于处理所有图像。当 FG_APC_BATCH_FRAMES 未传递。

选择性采集模型 ACQ_SELECT#
frameindex_t Fg_getLastPicNumberBlockingEx(
    Fg_Struct * fg,
    frameindex_t frame,
    unsigned int dma,
    int timeout,
    dma_mem * mem);
int Fg_getParameterEx(
    Fg_Struct * fg,
    int param,
    void * value,
    unsigned int dma,
    dma_mem * mem,
    frameindex_t frame);

void * Fg_getImagePtrEx(
    Fg_Struct * fg,
    frameindex_t buffer,
    unsigned int dma,
    dma_mem * mem);
int Fg_setStatusEx(
    Fg_Struct * fg,
    int status,
    frameindex_t buffer,
    unsigned int dma,
    dma_mem * mem);

信息

选择性采集模型 ACQ_SELECT 于 Framegrabber SDK 5.9 版本中添加。

选择性采集模型的选择方法是传递 ACQ_SELECT 更改为 flags 在调用 Fg_AcquireEx()。应用程序拥有对驱动程序使用帧缓冲区的完全控制权。

必须在图像采集卡中显式选择用于排队的帧缓冲区,驱动程序将严格按照选择的顺序对帧缓冲区进行排队。当开始采集时,如果选择了任何帧缓冲区,驱动程序就会将帧缓冲区排队到图像采集卡中以进行图像数据传输,并从第一个被选择的帧缓冲区开始。一旦图像完全传输到计算机 Memory 中,软件就会收到传输完成的通知,该帧缓冲区将不再被视为已被选择,并且在再次被显式选择之前,驱动程序将不会使用它。如果有可用的帧缓冲区,驱动程序将再对一个或多个帧缓冲区进行排队。如果没有可用的帧缓冲区,则不会有帧缓冲区排队。图像采集卡将继续把下一幅图像的数据传输到队列中的下一个缓冲区中。如果图像采集卡中的队列变空,这可能会导致内部缓冲区溢出,并可能丢失图像。

(如标准采集模型小节所述,图像采集卡中排队的帧缓冲区数量取决于各种因素。)

由于应用程序完全控制帧缓冲区的的使用,跟踪缓冲区编号也是应用程序的责任。除非始终按相同的顺序选择帧缓冲区,否则没有简单的方法可以将帧编号转换为缓冲区编号。此外,也没有用于执行该转换的 API 函数。

函数 Fg_setStatusEx() 可以通过使用以下方式来选择帧缓冲区 FG_SELECT_BUFFER 并传递缓冲区编号。

为了简化说明,以下示例假设在开始采集后所有四个缓冲区都已完成采集。 FB0 … FB3 按自然顺序被选择。此外,图像传输后会被处理,并且缓冲区会按顺序再次被选择。通过这种方式,缓冲区的顺序将始终保持不变,并且可以通过关系式将帧号直观地转换为缓冲区号 bufferNumber = 1 + ((frameNumber - 1) % numberOfBuffers).

在应用程序不进行控制而连续生成图像的场景中,图像源可以视为自由运行。然而,只要仍有选定的缓冲区可用,驱动程序就只能将图像传递给应用程序。在以下示例中, FB0 和 FB1 包含有效的图像数据且未被选择。 FB0 正由应用程序进行处理, FB1 正在等待处理。驱动程序已至少排队 FB2 且当前正在传输图像数据。这种情况仍然是安全的,因为 FB3 尚未使用,尽管可能已经排队。

安全内存缓冲区模型

调用函数 Fg_getLastPicNumberBlockingEx() 用于处理后的下一个帧编号 FB0 完成将返回单个新帧(传输至 FB1)在这种情况下,如果传输到 FB2 在此期间尚未完成。

如果图像处理速度慢于图像采集速度,应用程序可能已经完成了处理 FB0将其选中并继续进行处理 FB1。然而,在下面的示例中,在此期间传输了另外两幅图像, FB2 和 FB3,并且驱动程序已重新从 FB0。当图像传输完成时,如果应用程序尚未完成处理 FB1 并对其进行了选择,驱动程序将没有更多可用于排队的空闲缓冲区。只要没有进一步的图像到达,这种情况就不会像在以下情况中那样导致隐式的图像丢失 ACQ_BLOCK。但一旦接收到另一幅图像,采集卡中的内部内存缓冲区就可能会发生溢出状况,从而导致数据丢失。

不安全内存缓冲区模型

以下示例扩展了编写采集循环一节中给出的采集循环结构,以处理所有帧:

const frameindex_t numberOfBuffers = 4;
const int timeoutInSeconds = 10;

frameindex_t nextFrame = 1;
while (true) {
    // get new image
    const frameindex_t newestFrame =
        Fg_getLastPicNumberBlockingEx(fg, nextFrame, dma,
                                      timeoutInSeconds, mem);

    if (newestFrame > 0) {
        for (frameindex_t frame = nextFrame; frame <= newestFrame; ++frame) {
            // get the buffer number
            frameindex_t buffer = 1 + ((frame - 1) % numberOfBuffers);

            // get a pointer to the frame buffer for the newest image
            void * ptr = Fg_getImagePtrEx(fg, buffer, dma, mem);

            // get the actual number of bytes transferred
            size_t length = 0;
            int result =
                Fg_getParameterEx(fg, FG_TRANSFER_LEN, &length,
                                  dma, mem, buffer);

            if ((ptr != nullptr) && (result == FG_OK) && (length > 0)) {
                // process image data ...
            }

            // select frame buffer
            Fg_setStatusEx(fg, FG_SELECT_BUFFER, buffer, dma, mem);
        }

        nextFrame = (newestFrame < FRAMEINDEX_MAX) ? newestFrame + 1 : 1;
    } else {
        // handle error ...
    }
}

如果应用程序可以跳过图像并且只需处理接收到的最新帧, Fg_setStatusEx() 必须与 FG_SELECT_BUFFER 来选择被跳过的缓冲区。否则,应用程序将留下未使用的缓冲区。

函数 Fg_getImageEx() 不应与选择性采集模型结合使用。

在异步模式下,Framegrabber API 将调用 Fg_getLastPicNumberBlockingEx() 以等待新图像,并且如果标志 FG_APC_BATCH_FRAMES (如果已设置),或者如果不止一次设置则会调用多次,针对接收到的每个图像调用一次。回调将接收图像的帧编号。

要在异步模式下处理帧,可以在回调函数中使用内部循环的内容(ApcUserCallbackData 是 注册异步模式回调函数 节中所述的结构体):

int ApcUserCallback(frameindex_t frame, void * data)
{
    auto context = reinterpret_cast<ApcUserCallbackData *>(data);

    if (frame > 0) {
        // get the buffer number
        frameindex_t buffer = 1 + ((frame - 1) % numberOfBuffers);

        // get a pointer to the frame buffer for the newest image
        void * ptr = Fg_getImagePtrEx(fg, buffer, dma, mem);

        // get the actual number of bytes transferred
        size_t length = 0;
        int result =
            Fg_getParameterEx(fg, FG_TRANSFER_LEN, &length, dma, mem, buffer);

        if ((ptr != nullptr) && (result == FG_OK) && (length > 0)) {
            // process image data ...
        }

        // select frame buffer
        Fg_setStatusEx(fg, FG_SELECT_BUFFER, buffer, dma, mem);
    } else {
        // handle error ...
    }

    return 0;
}

通过这种方式,回调函数既可用于仅处理最新图像(当 FG_APC_BATCH_FRAMES 已传递 flags 更改为 Fg_registerApcHandlerEx()时),也可用于处理所有图像。当 FG_APC_BATCH_FRAMES 未传递。

将数据传输到图像采集卡#

局限性

本节介绍了供使用 VisualApplets 算子的应用程序使用的新 API DmaFromPC.该 API 具有以下限制:

  • 这是一个初步的功能预览。这意味着函数名称和功能可能会在 Framegrabber SDK 的未来版本中更改。
  • 它仅适用于包含 VisualApplets 算子的 applet DmaFromPC。请勿将该 API 用于使用 DmaToPC 或是随 Framegrabber SDK 一起提供的 Advanced Acquisition Applets。该算子 DmaFromPC 的常规 DMA 通道。
  • 目前该功能仅在 Windows 上实现。

使用 VisualApplets 算子 DmaFromPC,数据可以从 PC 传输到采集卡。应用场景包括但不限于:协同处理图像数据,或者为采集卡上的复杂图像处理提供额外的参数。

分配 Memory#

用于传输数据的缓存必须使用 高级内存管理 中记录的函数进行分配,例如:

const size_t bufferSize = 1024 * 1024;
const size_t numBuffers = 16;
const size_t totalSize = bufferSize * numBuffers;

dma_mem * mem = Fg_AllocMemEx(fg, totalSize, numBuffers);
if (mem != nullptr) {
    // use memory, send buffers ...

    Fg_FreeMem(fg, mem);
}

或者,您可以使用 灵活内存管理 中记录的函数,例如:

dma_mem * mem = Fg_AllocMemHead(fg, totalSize, numBuffers);
for (int i = 0; i < numBuffers; i++) {
    auto buffer = new uint8_t[bufferSize];
    Fg_AddMem(fg, buffer, bufferSize, i, mem);
}

虽然所有缓冲区的大小都相同,但每次单独传输的字节数是分别指定的。数据传输的大小必须是算子 Parallelism 的整数倍 DmaFromPc,其下限为 32,上限为缓冲区的大小。

启动数据传输#

 int Fg_startBufferQueue(
    Fg_Struct * fg,
    uint32_t dma,
    dma_mem * mem);

int Fg_stopBufferQueue(
    Fg_Struct * fg,
    uint32_t dma,
    int32_t flags);

要启动向采集卡的数据传输,请调用函数 Fg_startBufferQueue()。此调用会将 DMA 通道 dma 指向内存句柄 mem 从而将使用为数据传输分配的内存。

参数 dma 绑定到 VisualApplets 设计中存在的函数 Fg_startBufferQueue() 由使用 VisualApplets 算子向 PC 传输图像的常规 DMA 通道数量决定 DmaToPC 。用于 VisualApplets 算子的 DMA 通道 DmaFromPC 始终是最后一个 DMA 通道。

在传输完所有数据后,调用 Fg_stopBufferQueue() 停止缓冲区队列并切断 DMA 通道与所用内存句柄之间的联系。

将缓冲区排队以进行数据传输#

int Fg_queueBuffer(
    Fg_Struct * fg,
    frameindex_t buffer,
    uint64_t numBytesToTransfer,
    uint32_t dma,
    dma_mem * mem);

一旦要传输到采集卡的数据已写入缓冲区且该缓冲区准备好传输,您就可以通过调用将缓冲区放入缓冲区队列中 Fg_queueBuffer()。对于放入队列中的每个缓冲区,您必须指定要传输的字节数。

您最多可以在缓冲区队列中放入 16 个缓冲区。驱动程序会自动将队列中的下一个缓冲区传输到采集卡。这样,在必要时可以确保向采集卡持续传输数据。

参数 mem 仅在启动缓冲区队列之前将缓冲区排队时才需要。在此之后 Fg_startBufferQueue() 被调用时,参数 mem 是可选的,可以 NULL.

等待缓冲区传输完成#

int Fg_waitForBuffers(
    Fg_Struct * fg,
    uint32_t dma,
    uint64_t timeout,
    void * /* reserved */,
    size_t /* reserved */);

函数 Fg_waitForBuffers() 会等待至少一个缓冲区在参数中给定的秒数内完成传输 timeout 在参数中指定的给定 DMA 通道上 dma。如果成功,该函数返回完全传输的缓冲区数量;如果失败,则返回负错误代码。

您只能重复使用已通过以下方式排队的缓冲区: Fg_queueBuffer() 并在缓冲区完全传输后用新数据进行 Fill。

VisualApplets 算子的数据传输模型 DmaFromPC 非常类似于 选择性采集模型。这意味着由于应用程序完全掌控缓存的使用,跟踪缓存编号也属于应用程序的责任。除非始终按相同的顺序选择缓存,否则没有简单的方法将传输 Counter 转换为缓存编号。此外,也没有用于执行该转换的 API 函数。

Applet 事件#

包含在 Framegrabber SDK 安装中的 applet 可以将与采集卡中的图像采集或处理相关的各种事件通知应用程序。此类事件的示例包括:帧开始事件、帧结束事件(每当从相机接收到图像的第一个或最后一个像素时生成)、触发输入上升沿事件或触发输入下降沿事件(每当检测到触发输入信号边缘时生成)。VisualApplets 用户可以在其设计中使用事件算子来根据其应用需求生成事件。

每个事件源都有一个唯一名称,并通过无符号 64 位整数(称为事件掩码)的单个位进行标识。通过这种方式,可以通过在事件掩码中设置多个位(例如使用按位或运算符)将多个事件源分组在一起 | 适用于多个事件源的多个事件掩码。然而,这也意味着任何 applet 最多只能支持 64 个事件源。

事件可以以同步或异步模式传递。在同步模式下,应用程序必须提供一个事件循环。在异步模式下,可以注册一个回调函数,每当发生一个或多个事件时就会调用该函数。这两种方法不应在应用程序中混合使用,因为这可能会导致资源冲突。

由于事件源可能会在计算机系统上产生较高的中断负载,因此必须显式启用每个事件源,并且应仅在需要时激活。

对于事件的每次发生,都会记录接收到事件源中断的时间。除了时间戳之外,某些事件源还可以在每次事件时生成额外的数据,称为事件负载。具有负载的事件不能通过为多个事件源设置多个位来组合在一起。请查阅您使用的任何 applet 的文档,以了解 applet 中的事件源是否生成额外数据以及如何解释这些数据。

事件信息#

uint64_t Fg_getEventMask(
    Fg_Struct * fg,
    const char * name);

int Fg_getEventPayload(
    Fg_Struct * fg,
    uint64_t mask);

int Fg_getEventCount(
    Fg_Struct * fg);

const char * Fg_getEventName(
    Fg_Struct * fg,
    uint64_t mask);

如果事件源的名称已知,则可以通过调用函数来请求相应的位 Fg_getEventMask()。如果未识别出事件名称,该函数将返回 0。

可以通过调用函数来查询任意给定事件的有效载荷大小 Fg_getEventPayload()。如果事件掩码对应于单个有效的事件源,该函数将返回大于或等于零的值,否则返回负错误代码。

要遍历所有事件源,可以使用函数 Fg_getEventCount() 来请求 applet 支持的事件源数量,并使用函数 Fg_getEventName() 来获取事件源的名称。以下示例展示了这一点:

int numEvents = Fg_getEventCount(fg);
for (int event = 0; event < numEvents; ++event) {
    // get the event mask for each event
    const uint64_t eventMask = (1 << event);

    const char * eventName = Fg_getEventName(fg, eventMask);
    if (eventName != nullptr) {
        std::cout << "Event " << event << " is " << eventName << std::endl;
    }
}

为异步事件处理注册回调函数#

typedef int (* Fg_EventFunc_t)(
    uint64_t events,
    void * data,
    const struct fg_event_info * info);

int Fg_registerEventCallback(
    Fg_Struct * fg,
    uint64_t mask,
    Fg_EventFunc_t handler,
    void * data,
    unsigned int flags,
    struct fg_event_info * info);

int Fg_unregisterEventCallback(
    Fg_Struct * fg,
    uint64_t mask);

信息

在 Framegrabber SDK 5.9 版本中,类型 struct fg_event_data 已被移除,并添加了函数 Fg_unregisterEventCallback() 已添加。请参阅 在 Plain C 中取消注册用于异步事件处理的回调函数 了解旧接口。

使用 Fg_registerEventCallback(),一个类型为 Fg_EventFunc_t 可以注册回调函数,以便在接收到来自一组事件源的一个或多个事件时被调用。该函数将针对给定的采集卡和事件掩码进行注册,并在被调用时传入三个参数。回调函数的第一个参数是接收到事件的所有事件源的事件 mask。第二个参数是在调用时提供的指针 Fg_registerEventCallback() 且可用作指向上下文结构或类的指针,例如 this 实现了图像处理的类实例指针。第三个参数是指向 struct fg_event_info 的指针,该指针必须由应用程序分配并在调用 Fg_registerEventCallback() 时传入,用于存储有关事件的信息。

对于任何给定的采集卡上的同一组事件,只能注册一个回调函数。

回调函数将从 Framegrabber API 提供的事件循环中被调用。这意味着回调函数是在事件循环的线程上下文中调用的。这也意味着回调函数返回前所花费的时间会增加事件循环的一般管理开销,并且在此期间可能会接收到多个事件。如果在该期间接收到了任何事件,回调函数将在返回后立即再次被调用。

通过参数 flags 在调用 Fg_registerEventCallback(),应用程序可以选择每次调用回调函数时仅传递单个事件,还是将所有挂起事件组合在一起。传递时的默认行为 FG_EVENT_DEFAULT_FLAGS 传递给参数 flags 是每次调用回调函数时传递一个事件。通过传递 FG_EVENT_BATCHED ,所有挂起的事件将被组合在一起。

在不再需要回调函数之后,可以通过调用将其注销 Fg_unregisterEventCallback().

以下示例展示了如何使用包含处理图像采集所需信息的简单结构体来注册回调函数:

struct EventUserCallbackData
{
    Fg_Struct * fg;
    fg_event_info info;
    uint64_t mask;
};

int EventUserCallback(uint64_t events, void * data,
                      const struct fg_event_info * info)
{
    auto context = reinterpret_cast<EventUserCallbackData *>(data);

    // process events

    return 0;
}

void SetupEventUserCallback(EventUserCallbackData * context)
{
    // register callback function
    int result = Fg_registerEventCallback(
        context->fg, context->mask, &EventUserCallback, context,
        FG_EVENT_DEFAULT_FLAGS, &context->info);
    if (result != FG_OK) {
        throw std::runtime_error("Failed to register callback function");
    }
}

上下文字结构的分配和管理未在示例中显示。只要回调函数保持注册状态,该指针就必须有效。一种解决方案是将所有内容保存在一个 C++ 类中。为了在 C++ 类上下文中使用回调函数,可以使用静态函数来注册回调处理器,并且 this 指针应用作上下文数据指针,该指针可以转换回类指针并作相应使用。

同步等待事件#

uint64_t Fg_eventWait(
    Fg_Struct * fg,
    uint64_t mask,
    unsigned int timeout,
    unsigned int flags,
    struct fg_event_info * info);

应用程序可以通过调用同步等待来自一组事件源的事件 Fg_eventWait()。参数 flags 允许选择每次调用该函数时是仅返回单个事件,还是将所有挂起事件组合在一起。传递时的默认行为 FG_EVENT_DEFAULT_FLAGS 传递给参数 flags 是每次调用该函数时最多返回一个事件。通过传递 FG_EVENT_BATCHED ,所有挂起的事件将被组合在一起。该函数将一直等待,直到在参数中指定的任一事件源上接收到事件 mask,或参数中指定的秒数 timeout 已过。如果在超时发生之前未收到任何事件,该函数将返回 0,否则将返回包含所接收事件或事件组的掩码。

激活事件#

int Fg_activateEvents(
    Fg_Struct * fg,
    uint64_t mask,
    int enable);

在事件源能够发送事件之前,必须先将其启用。通过调用 Fg_activateEvents() 可以启用或禁用一组事件源。如果向参数传递 1 enable,则参数中传递的事件源组 mask 将被启用。如果向参数传递 0 enable,则它们会被禁用。一旦应用程序不再使用所有事件源,就应将其全部禁用。

由于事件源在启用后随时可能触发事件,因此建议仅在满足以下条件之一时才激活事件源:已使用以下内容为其注册了回调 Fg_registerEventCallback(),或者已准备好单独的线程并使用以下内容等待事件 Fg_eventWait().

支持从多个进程使用图像采集卡#

某些应用程序可能需要从不同的进程访问单个 frame grabber。一个简单的例子是监控另一个进程活动的进程,例如采集进程。另一个例子可能是分别使用 frame grabber(最多连接四个相机)中各一个相机的独立采集进程。

Framegrabber API 通过主/从模式对这类场景提供有限的支持。在使用主/从进程时,了解其局限性非常重要。同样重要的是,要了解当两个或多个进程必须同步时,应用程序开发人员所面临的普遍挑战。虽然 Framegrabber API 支持从多个进程使用 frame grabber,但它不支持超出极基础功能的进程间同步(这些基础功能仅用于在初始化 frame grabber 时同步进程的启动)。对于通用的进程间通信和同步,必须使用其他操作系统功能或支持库。

主/从模式的一个特定局限性在于:每当两个进程试图控制采集卡上的同一个 Property 时,后一个进程会“胜出”并覆盖前一个进程的操作。前一个进程甚至可能不会察觉到这种覆盖,从而对采集卡的状态做出错误的假设。(存在一个实验性功能,可以通过进程间通信来同步各进程之间的状态,但这种方法有其自身的局限性,将在 参数和采集同步的实验性支持 一节中讨论。)

总体建议是设计进程时使其承担独特的任务(这些任务在 applet 参数和 Property 方面是不相交的),并且无需了解其他进程的存在。唯一的例外是监控进程,它仅访问 applet 的只读 Property,或通过进程间通信手段从其他进程收集信息。

另一个局限性是,虽然只有主进程会完全初始化 frame grabber,但每个进程中仍必须进行基础初始化。这种基础初始化可能会干扰较早启动的进程(如果它们已经开始了图像采集或相机发现)。

在初始化期间同步进程#

int Fg_InitLibrariesEx(
    const char * path,
    unsigned int flags,
    const char * id,
    unsigned int timeout);

void Fg_AbortInitLibraries();

void Fg_InitLibrariesStartNextSlave();
Fg_Struct * Fg_InitEx(
    const char * applet,
    unsigned int board,
    int flags);

Fg_Struct * Fg_InitConfigEx(
    const char * config,
    unsigned int board,
    int flags);

当同步多个进程时,Framegrabber API 提供的同步点是对 Fg_InitLibrariesEx()的调用。与 Fg_InitLibraries()一样,对该调用的第一个参数不会被使用,应用程序应始终传递 nullptr.

要同步的进程会形成一个组,并且同步将仅考虑组内的进程。应进行同步的进程组通过参数进行标识 id。通过这种方式,复杂的应用程序可以拥有多个独立的同步组。标识该组的字符串不应为空,且应仅包含所使用操作系统中文件名允许的字符。不建议以 siso- 开头来命名应用程序使用的标识符,前缀 siso- 应被视为留给 Framegrabber API 专用的保留项。

可以通过参数配置同步行为 flags。以下值和宏可用于参数 flags:

标志 描述
FG_INIT_LIBRARIES_SINGLE 默认初始化,无同步
FG_INIT_LIBRARIES_MASTER 主设备初始化,准备从设备同步
FG_INIT_LIBRARIES_SLAVE 从设备初始化,等待主设备
FG_INIT_LIBRARIES_SEQUENTIAL 严格按顺序启动
FG_INIT_LIBRARIES_AUTOSTART_ON_INIT 在采集卡初始化时启动下一个从设备
FG_INIT_LIBRARIES_SET_SLAVE_PRIORITY(n) 设置从设备优先级
FG_INIT_LIBRARIES_SET_NUMBER_OF_SLAVES(n) 设置从属设备数量

使用 FG_INIT_LIBRARIES_SINGLE 等同于调用 Fg_InitLibraries().

使用 FG_INIT_LIBRARIES_SEQUENTIAL 将仅从主进程启动具有最高优先级的从进程。具有第二高优先级的从进程必须从具有最高优先级的从进程启动,依此类推。这确保了严格的进程初始化顺序。如果 FG_INIT_LIBRARIES_SEQUENTIAL 用于同步组中时,必须在所有进程(包括主进程)中指定。宏 FG_INIT_LIBRARIES_SET_SLAVE_PRIORITY() 在使用 FG_INIT_LIBRARIES_SEQUENTIAL 时,必须在所有从进程中使用,并且它定义了从进程的优先级。最高优先级为 1,最低优先级为 63。组中的每个进程必须具有唯一的优先级,并且分配优先级时不能留有间隙。(如果进程 2 想要启动进程 4,但没有进程 3,那么将无人启动进程 4。)宏 FG_INIT_LIBRARIES_SET_NUMBER_OF_SLAVES() 在使用 FG_INIT_LIBRARIES_SEQUENTIAL 时,必须在主进程中使用,并且它定义了从进程的数量。允许的最小值为 1,最大值为 63。

调用 Fg_InitLibrariesEx() 中的最后一个参数指定了在向应用程序报告错误之前,等待进程被调度的毫秒数。

该组的第一个进程(通常称为主进程)在调用 Fg_InitLibrariesEx() 时建立一个组。所有后续进程(通常称为从进程)可以选择在各自的 Fg_InitLibrariesEx() 调用中等待被调度。随后,启动一个或多个从进程是由主进程中的 Fg_InitLibrariesStartNextSlave() 调用触发的。函数 Fg_InitLibrariesStartNextSlave() 可以在进程通过调用 Fg_Init(), Fg_InitEx(), Fg_InitConfig() 或 Fg_InitConfigEx().

初始化采集卡时自动调用。 Fg_InitLibraries() 函数 Fg_InitLibrariesEx() 在正常情况下绝不应该失败,但在使用同步时

则并非如此。请合理指定等待时间并相应地处理错误。 Fg_InitLibrariesEx() 在多线程应用程序中,可以通过从另一个线程调用 Fg_AbortInitLibraries()来中止对

的调用等待。这会导致被中止的调用报告错误。虽然在主进程中可以使用对 Fg_Init() 或 Fg_InitConfig() 的调用来初始化采集卡,但所有从进程都必须调用 Fg_InitEx() 或 Fg_InitConfigEx() 并传递 FG_INIT_FLAG_SLAVE 在参数 flags。调用 Fg_InitEx() 或 Fg_InitConfigEx() 并分别带有 FG_INIT_FLAG_DEFAULT 等同于调用 Fg_Init() 或 Fg_InitConfig()。

以下示例展示了主进程如何为严格的顺序调度使用两个从进程来设置同步组。主进程使用对 Fg_InitLibrariesStartNextSlave() 在进行一些后续初始化之后启动第一个从属进程:

const char * syncGroupId = "example";

int result =
    Fg_InitLibrariesEx(
        nullptr,
        FG_INIT_LIBRARIES_MASTER
            | FG_INIT_LIBRARIES_SEQUENTIAL
            | FG_INIT_LIBRARIES_SET_NUMBER_OF_SLAVES(2),
        syncGroupId,
        0);

if (result == FG_OK) {
    const char * applet = "Acq_SingleCXP12Area";

    Fg_Struct * fg = Fg_Init(applet, 0);
    if (fg != nullptr) {
        // further initialization ...

        Fg_InitLibrariesStartNextSlave();

        // use frame grabber ...
    }

    Fg_FreeLibraries();
}

示例中的从属进程是相同的,只是在调用以下内容时指定的优先级不同: Fg_InitLibrariesEx()此处仅显示第一个从属进程。从属进程使用 FG_INIT_LIBRARIES_AUTOSTART_ON_INIT 以便在调用以下内容时隐式启动下一个从属进程: Fg_InitEx() :

const char * syncGroupId = "example";

int result =
    Fg_InitLibrariesEx(
        nullptr,
        FG_INIT_LIBRARIES_SLAVE
            | FG_INIT_LIBRARIES_SEQUENTIAL
            | FG_INIT_LIBRARIES_AUTOSTART_ON_INIT
            | FG_INIT_LIBRARIES_SET_SLAVE_PRIORITY(1),
        syncGroupId,
        10000);

if (result == FG_OK) {
    const char * applet = "Acq_SingleCXP12Area";

    Fg_Struct * fg = Fg_InitEx(applet, 0, FG_INIT_FLAG_SLAVE);
    if (fg != nullptr) {
        // use frame grabber ...
    }

    Fg_FreeLibraries();
}

如果从属进程在主进程之前启动,它将在对以下的调用中等待 Fg_InitLibrariesEx() ,直到主进程启动。一旦主进程调用 Fg_InitLibrariesStartNextSlave() ,就会调度从属进程运行,并且对以下的调用将 Fg_InitLibrariesEx() 返回 FG_OK。如果未启动主进程,或者在从属进程的等待时间用完之前未完成初始化,则无法调度从属进程运行,并且对以下的调用 Fg_InitLibrariesEx() 将返回超时错误。

随 Framegrabber SDK 一起安装的 microDisplay X 工具支持主/从同步,并且从属进程可以使用群组 ID 同步到 microDisplay X siso-microdisplay-master。标志 FG_INIT_LIBRARIES_SEQUENTIAL 不受 microDisplay X 支持。

对参数和采集同步的实验性支持#

信息

参数和采集同步的实验性支持是在 Framegrabber SDK 5.9 版本中添加的。

Framegrabber API 提供实验性支持,用于跨进程同步组同步所有参数更改和采集状态。要启用参数和采集同步, FG_INIT_FLAG_PARAM_SYNC 必须添加到参数 flags 在调用 Fg_InitEx() 或 Fg_InitConfigEx() 中,适用于组中的所有进程。

但是,在使用参数和采集同步时,有几点需要考虑。

首先,Framegrabber API 提供的同步使用操作系统提供的进程间通信机制。由于获取和设置参数不再在进程中本地完成,而是必须在多个进程之间进行同步,因此这些操作的延迟和抖动都会增加。对于大多数应用程序来说,这些缺点应该没有影响。但是,如果应用程序使用实时操作系统,并且获取或设置一个或多个参数被视为实时关键型操作,则不应启用参数和采集同步。如果 FG_INIT_FLAG_PARAM_SYNC 未被显式使用,则不会启用参数和采集同步,并且与早期版本的 Framegrabber API 相比,此实验性功能对性能没有影响。

其次,在使用同步时,主进程或从属进程可以控制采集,但绝不能两者同时控制。不能将主进程中的采集与同步组内的一个或多个从属进程中的采集混合使用。在大多数情况下,采集应在一个或多个从属进程中完成,并且 FG_INIT_FLAG_ACQUISITION_SLAVE 应添加到参数 flags 在调用 Fg_InitEx() 或 Fg_InitConfigEx() 中,适用于主进程。

支持非统一内存访问#

int Fg_NumaPinThread(
    Fg_Struct * fg);

void * Fg_NumaAllocDmaBuffer(
    Fg_Struct * fg,
    size_t size);

int Fg_NumaFreeDmaBuffer(
    Fg_Struct * fg,
    void * ptr);

在使用非统一内存访问 (NUMA) 的多处理器计算机中,每个物理处理器都有自己的 Memory 和外设总线。只要进程只访问其正在运行的同一物理处理器上的资源,其访问速度几乎与单处理器计算机一样快,并且明显快于使用对称多处理 (SMP) 的多处理器计算机。但是,如果在一个物理处理器上运行的进程想要访问属于另一个物理处理器的资源,则必须在两个物理处理器之间移动数据,从而降低访问速度。有关详细信息,请参阅维基百科上关于 非统一内存访问 的文章。

为了支持在 NUMA 计算机上运行的应用程序,访问采集卡的所有线程都应固定(pinned)到设备本地连接的物理处理器。这可以通过调用以下内容来实现: Fg_NumaPinThread() 在访问采集卡上的参数或控制采集的每个线程的上下文中。

此外,内存也应在同一个物理处理器上本地分配。如果应用程序使用 Framegrabber API 提供的分配函数,则会自动完成此操作。但是,如果应用程序使用 Fg_AllocMemHead() 和 Fg_AddMem(),则应使用支持 NUMA 感知的内存分配来分配内存。Framegrabber API 提供了以下函数: Fg_NumaAllocDmaBuffer() 和 Fg_NumaFreeDmaBuffer() 用于在采集卡本地连接的同一个物理处理器上分配和释放内存。

如果应用程序使用其他支持 NUMA 感知的函数来分配内存,则可以使用函数 Fg_getIntSystemInformationForBoardIndex() 通过调用 INFO_DRIVERGROUPAFFINITY 来提供驱动程序 IRQ 组亲和性,并使用函数 Fg_getInt64SystemInformationForBoardIndex() 通过调用 INFO_DRIVERAFFINITYMASK 来为设备提供驱动程序 IRQ 处理器亲和性掩码。

某些 NUMA 计算机上可能还存在其他限制。例如,某些 NUMA 计算机的设计旨在平衡对外设总线的访问,而不允许单个设备使用全部甚至大部分带宽。虽然这对大多数服务器应用程序有利,但对于图像采集和处理应用程序,此策略可能会限制可用于采集卡设备的带宽。

使用 Plain C 时的注意事项#

当应用程序仅限于使用纯 C 编译器时,将无法使用 Framegrabber API 为提供便利或类型安全性而提供的一些 C++ 包装器。本章将针对每个主题解释如何使用 C++ 包装器所使用的底层功能。

Plain C 中的系统信息#

int Fg_getSystemInformation(
    Fg_Struct * fg,
    enum Fg_Info_Selector info,
    enum FgProperty property,
    int arg,
    void * buffer,
    unsigned int * size);

在小节 常规系统信息 和 单板特定系统信息 章节的 系统信息 中,记录了一组用于请求关于系统概况或特定采集卡的各种信息的函数。这两个章节中的函数全都是用于该函数的 C++ 封装 Fg_getSystemInformation().

函数 Fg_getSystemInformation() 将始终以字符串形式返回请求的信息,该字符串存储在传递给函数的缓冲区中。为了分配足够大小的缓冲区,可以通过将用于参数的变量初始化来请求缓冲区的大小 size 设置为 0 并在调用中传递 NULL 传递给参数 buffer 。如果请求的信息是数字类型,则必须在成功请求信息后转换该字符串。

若要请求关于系统概况的信息(如小节所述), 常规系统信息中,函数 Fg_getSystemInformation() ,并将 NULL 传递给参数 fg ,将 0 传递给参数 arg.

例如,可以请求计算机中安装的采集卡数量,如下所示:

int numBoards = 0;
char buffer[256];
unsigned int size = sizeof(buffer);
int result =
    Fg_getSystemInformation(NULL, INFO_NR_OF_BOARDS, PROP_ID_VALUE,
                            0, buffer, &size);
if (result == FG_OK) {
    numBoards = atoi(buffer);
    std::cout << "Number of boards: " << numBoards << std::endl;
}

若要请求关于特定采集卡的信息(如小节所述), 单板特定系统信息中,函数 Fg_getSystemInformation() 需要将采集卡句柄传递给参数 fg,或者将板卡索引传递给参数 arg.

例如,以下代码请求板卡类型和名称。

const int boardIndex = 0;

char buffer[256];
unsigned int size = sizeof(buffer);

int boardType = 0;
std::string boardName;
int result =
    Fg_getSystemInformation(NULL, INFO_BOARDTYPE, PROP_ID_VALUE,
                            boardIndex, buffer, &size);
if (result == FG_OK) {
    boardType = atoi(buffer);

    size = sizeof(buffer);
    result =
        Fg_getSystemInformation(NULL, INFO_BOARDNAME, PROP_ID_VALUE,
                                boardIndex, buffer, &size);
}
if (result == FG_OK) {
    boardName = buffer;

    std::cout << "Board #" << boardIndex
              << " is a " << boardName
              << " (type " << std::hex << boardType
              << std::dec << ")" << std::endl;
}

。若要请求需要输入附加参数的信息,缓冲区将双向工作。附加参数必须在调用之前以字符串形式存储在缓冲区中。在调用期间,将使用并在缓冲区中传递的参数,然后将其覆盖,因为请求的信息将写入缓冲区。

在 Plain C 中访问参数值#

int Fg_getParameterWithType(
    Fg_Struct * fg,
    int id,
    void * value,
    unsigned int dma,
    enum FgParamTypes type);

int Fg_freeParameterStringWithType(
    Fg_Struct * fg,
    int id,
    void * value,
    unsigned int dma,
    enum FgParamTypes type);
int Fg_setParameterWithType(
    Fg_Struct * fg,
    int id,
    const void * value,
    unsigned int dma,
    enum FgParamTypes type);

在小节 访问参数值 章节的 处理 Applet 参数中,记录了用于请求最常见类型参数的 C++ 封装。若要从纯 C 访问参数,或访问没有相应 C++ 封装的类型的参数,请使用函数 Fg_getParameterWithType() 和 Fg_setParameterWithType() 可以使用。调用时需要一个匹配类型的变量,并且对应的 FgParamTypes 值必须在参数中传递 type 在调用中。类型值记录在小节 参数类型 中,应查阅小应用程序 (applet) 文档以了解特定参数的类型。

例如,以下代码将 applet 的第一个 DMA 通道的宽度设置为 1024。

const int dma = 0;
const int width = 1024;
int result = FG_INVALID_PARAMETER;
int paramId = Fg_getParameterIdByName(fg, "FG_WIDTH");
if (paramId > 0) {
    result =
        Fg_setParameterWithType(fg, paramId, &width, dma,
                                FG_PARAM_TYPE_INT32_T);
}

调用时的特殊情况 Fg_getParameterWithType() 是类型 FG_PARAM_TYPE_CHAR_PTR 它代表一个字符串参数。虽然可以通过调用来请求分配足够大小的缓冲区所需的字符串长度 Fg_getParameterPropertyEx(),但这并不是访问动态参数最安全的方法。更好的方法是请求具有以下类型的参数 FG_PARAM_TYPE_CHAR_PTR 返回的最后一个值 FG_PARAM_TYPE_CHAR_PTR_PTR 代替。此请求将分配正确大小的缓冲区,可以通过调用来释放该缓冲区 Fg_freeParameterStringWithType() 在不再需要该值之后。

以下示例展示了如何使用 FG_PARAM_TYPE_CHAR_PTR_PTR,假设 paramId 是类型的参数 FG_PARAM_TYPE_CHAR_PTR:

const char * val = NULL;
int result =
    Fg_getParameterWithType(fg, paramId, &val, dma,
                            FG_PARAM_TYPE_CHAR_PTR_PTR);
if (result == FG_OK) {
    // use the string value stored in val ...

    Fg_freeParameterStringWithType(fg, paramId, val, dma,
                                   FG_PARAM_TYPE_CHAR_PTR_PTR);
}

调用 Fg_freeParameterStringWithType() 接收一个 char *,实际上不是 char **.

访问场参数#
struct FieldParameterAccess {
    enum FgParamTypes vtype;
    unsigned int index;
    unsigned int count;
    union {
        int32_t * p_int32_t;
        uint32_t * p_uint32_t;
        int64_t * p_int64_t;
        uint64_t * p_uint64_t;
        double * p_double;
    };
};

Field 参数是指代表两个或多个同类型值数组的参数。典型的例子是可用于重新映射像素值的查找表。Field 参数的类型可以是 FG_PARAM_TYPE_STRUCT_FIELDPARAMINT, FG_PARAM_TYPE_STRUCT_FIELDPARAMINT64 或 FG_PARAM_TYPE_STRUCT_FIELDPARAMDOUBLE.

Field 参数的大小可以通过请求参数 Property 来确定 PROP_ID_FIELD_SIZE 如小节所述 访问参数 Property 章节的 处理 Applet 参数。如果需要,请同时查阅小节 在 Plain C 中访问参数 Property 。

若要请求或更改 Field 参数数组中的值,函数 Fg_getParameterWithType() 和 Fg_setParameterWithType() 应通过使用以下实例进行调用 struct FieldParameterAccess 和 FG_PARAM_TYPE_STRUCT_FIELDPARAMACCESS 应在参数中传递 type。成员 vtype 的 struct FieldParameterAccess 必须设置为 enum FgParamTypes 对应字段中单一值类型的数值,而不是字段参数类型本身。成员 index 和 count 指定了字段参数数组的偏移量以及在函数调用中请求或提供的项目数量。最后,联合体中对应于字段中单一值类型的指针必须初始化为一个预分配的缓冲区,如果调用 Fg_setParameterWithType() ,该缓冲区包含要在输入时更改的值;或者在调用 Fg_getParameterWithType() 后,包含当前存储在数组中的值。

以下示例请求查找表中的前 256 个值,假设 paramId 是类型的参数 FG_PARAM_TYPE_STRUCT_FIELDPARAMINT 且字段本身由至少 256 个类型为 FG_PARAM_TYPE_INT32_T:

int32_t field[256];
struct FieldParameterAccess fpa;
fpa.vtype = FG_PARAM_TYPE_INT32_T;
fpa.index = 0;
fpa.count = 256;
fpa.p_int32_t = &field;
int result =
    Fg_getParameterWithType(fg, paramId, &fpa, dma,
                            FG_PARAM_TYPE_STRUCT_FIELDPARAMACCESS);
if (result == FG_OK) {
    // work with the values in field ...
}

在 Plain C 中访问参数 Property#

int Fg_getParameterProperty(
    Fg_Struct * fg,
    int id,
    enum FgProperty property,
    void * buffer,
    int * size);

int Fg_getParameterPropertyEx(
    Fg_Struct * fg,
    int id,
    enum FgProperty property,
    int dma,
    void * buffer,
    int * size);

在小节 访问参数 Property 章节的 处理 Applet 参数中,记录了用于请求最常见类型参数 Property 的 C++ 封装。若要从纯 C 访问参数 Property,或访问没有相应 C++ 封装的类型的参数 Property,请使用函数 Fg_getParameterPropertyEx() 。使用函数 Fg_getParameterProperty() 是不推荐的,因为该函数调用隐式使用了 DMA 通道 0,而不同通道的参数 Property 可能会有所不同。

函数 Fg_getParameterPropertyEx() 除一种情况外,在所有其他情况下都会将请求的信息作为字符串返回,该字符串存储在传递给函数的缓冲区中。为了分配足够大小的缓冲区,可以通过初始化用于参数的变量来请求缓冲区的大小 size 设置为 0 并在调用中传递 NULL 传递给参数 buffer 。如果请求的信息是数字类型,则必须在成功请求信息后转换该字符串。

以下示例展示了如何获取参数的最小值,假设 paramId 是类型为 的参数。 FG_PARAM_TYPE_INT32_T:

char buffer[256];
int size = sizeof(buffer);

int32_t minVal = 0;
int result =
    Fg_getParameterPropertyEx(fg, paramId, PROP_ID_MIN, dma, buffer, &size);
if (result == FG_OK) {
    minVal = atoi(buffer);

    // work with the property ...
}
访问枚举值参数 Property#
struct FgPropertyEnumValues {
    int32_t value;
    char name[1];
};

#define FG_PROP_GET_NEXT_ENUM_VALUE(pev) ...

当请求 PROP_ID_ENUM_VALUES 这个 Fg_getParameterPropertyEx() 枚举参数的 Property 时,调用 struct FgPropertyEnumValues进行填充。宏 FG_PROP_GET_NEXT_ENUM_VALUE() 可用于遍历缓冲区中的元素。为了方便起见,Property PROP_ID_IS_ENUM 将返回枚举参数所需的缓冲区大小。

以下示例展示了如何获取并打印参数的枚举值 Property,假设 paramId 是一个枚举参数:

const int defBufferSize = 256;

int size = defBufferSize;
char * buffer = malloc(size);

int result =
    Fg_getParameterPropertyEx(fg, paramId, PROP_ID_IS_ENUM,
                              dma, buffer, &size);
if (result == FG_OK) {
    int newSize = atoi(buffer);
    if (newSize > 0) {
        free(buffer);
        size = newSize;
        buffer = malloc(size);

        result =
            Fg_getParameterPropertyEx(fg, paramId, PROP_ID_ENUM_VALUES,
                                      dma, buffer, &size);
    } else {
        result = FG_INVALID_TYPE;
    }
}
if (result == FG_OK) {
    struct FgPropertyEnumValues * pev;
    for (pev = (struct FgPropertyEnumValues *)buffer;
         pev != NULL;
         pev = FG_PROP_GET_NEXT_ENUM_VALUE(pev)) {
            printf("%s is %d\n", pev->name, pev->value);
    }
}
free(buffer);

在 Plain C 中为异步模式注册回调函数#

struct FgApcControl {
    unsigned int version;
    Fg_ApcFunc_t func;
    void *data;
    unsigned int timeout;
    unsigned int flags;
};

int Fg_registerApcHandler(
    Fg_Struct * fg,
    unsigned int dma,
    struct FgApcControl * control,
    enum FgApcControlFlags flags);

函数 Fg_registerApcHandler() 可用于在开始采集之前设置异步模式。

回调函数的工作方式与小节中记录的方式相同 为异步模式注册回调函数 章节的 图像采集。通过调用函数注册回调函数时 Fg_registerCallbackHandler() 来注册回调函数时,控制采集循环的参数作为 struct FgApcControl的一个实例传入,而在调用 C++ 封装器函数 Fg_registerApcHandlerEx() 时,这些参数是作为函数的参数传入的。

在注册回调函数时,传递标志的地方有两个。函数 flags 的参数 Fg_registerApcHandler() 不控制采集循环且不会被使用。应用程序应始终向该参数传递 0。相反,控制采集循环的标志是通过使用相同的图像采集卡句柄和 DMA 通道传入成员 flags 的 struct FgApcControl.

以下示例展示了如何使用包含处理图像采集所需信息的简单结构体来注册回调函数:

struct ApcUserCallbackData
{
    Fg_Struct * fg;
    dma_mem * mem;
    unsigned int dma;
    unsigned int timeoutInSeconds;
    int mode;
};

int ApcUserCallback(frameindex_t frame, void * data)
{
    auto context = reinterpret_cast<ApcUserCallbackData *>(data);

    if (frame > 0) {
        // process new image ...
    } else {
        // handle error ...
    }

    return 0;
}

void SetupApcUserCallback(ApcUserCallbackData * context)
{
    // register callback function
    FgApcControl control;
    control.version = 0;
    control.func = &ApcUserCallback;
    control.data = context;
    control.timeout = context->timeoutInSeconds;
    control.flags = FG_APC_DELIVER_ERRORS | FG_APC_IGNORE_TIMEOUTS;

    int result = Fg_registerApcHandler(
        context->fg, context->dma, &control, 0);
    if (result != FG_OK) {
        throw std::runtime_error("Failed to register callback function");
    }
}

上下文字结构的分配和管理未在示例中显示。只要回调函数保持注册状态,该指针就必须有效。一种解决方案是将所有内容保存在一个 C++ 类中。为了在 C++ 类上下文中使用回调函数,可以使用静态函数来注册回调处理器,并且 this 指针应用作上下文数据指针,该指针可以转换回类指针并作相应使用。

在不再需要回调函数之后,可以通过调用将其注销 Fg_registerApcHandler() 中的,但传递的是 NULL 传递给参数 func:

int result = Fg_registerApcHandler(context->fg, context->dma, NULL, 0);

在 Plain C 中取消注册用于异步事件处理的回调函数#

int Fg_registerEventCallback(
    Fg_Struct * fg,
    uint64_t mask,
    Fg_EventFunc_t handler,
    void * data,
    unsigned int flags,
    struct fg_event_info * info);

使用 Fg_registerEventCallback(),一个类型为 Fg_EventFunc_t 可以注册回调,以便在收到来自一组事件源的一个或多个事件时被调用,如小节所述 为异步事件处理注册回调函数 章节的 图像采集.

在不再需要回调函数之后,可以通过调用将其注销 Fg_registerEventCallback(),并为同一组事件传递掩码, FG_EVENT_DEFAULT_FLAGS 传递给参数 flags 和 NULL 传递给参数 handler, data 和 info:

// unregister event handler
Fg_registerEventCallback(fg, mask, NULL, NULL, FG_EVENT_DEFAULT_FLAGS, NULL);

  1. 组代码是一个位域,其中每个位对应许可证的一个单一功能。在此上下文中的超集意味着,对于小应用程序组代码中的每一位,采集卡组代码中必须置位该位。在此上下文中的子集意味着,对于小应用程序组代码中的每一位,采集卡组代码中必须置位该位。如果小应用程序使用了未通过采集卡上的许可证激活的功能,则 Framegrabber SDK 无法加载该小应用程序。 ↩↩

  2. 参数的值可以设置为 [PROP_ID_MIN; PROP_ID_MAX] 中的任意值,步长为 PROP_ID_STEP ,根据线性关系 PROP_ID_VALUE = PROP_ID_MIN + n*PROP_ID_STEP,其中 n 位于 [0; (PROP_ID_MAX-PROP_ID_MIN)/PROP_ID_STEP[. ↩↩↩

  3. 较旧版本的 Framegrabber API 在采集循环中使用 Fg_getLastPicNumberBlockingEx() ,无论使用何种采集模型。当使用 ACQ_BLOCK 时,新的默认行为是调用 Fg_getImageEx() 并分别带有 SEL_ACT_IMAGE 当 FG_APC_BATCH_IMAGES 已设置且 SEL_NEXT_IMAGE 时则不然。这意味着在 ACQ_BLOCK使用默认行为时,回调处理程序接收的是缓冲区编号,而不是帧编号。由于在使用 ACQ_BLOCK中,不建议从回调处理程序中使用 FG_APC_OLD_ACQ_BLOCK_BEHAVIOR 并调用时 Fg_getImageEx() 不建议从回调处理程序中调用,并且应仅在绝对有必要保证应用程序代码的向后兼容性时使用。 ↩↩