Pylon C++ Advanced Topics

paw5zx Lv4

Architecture of pylon

本节将对pylon C++ API的最重要概念做简短的介绍

Transport Layers

传输层一词用作物理接口(如USB、GigE或Camera Link)的抽象。对于这些接口中的任意一个,都有对应的驱动程序提供对相机设备的访问。pylon目前包括几种不同的传输层:

  • BaslerGigE               :用于使用GigE Vision协议的千兆以太网相机
  • BaslerUsb                :用于符合USB3 Vision的相机
  • BaslerGenTlCxp     :用于符合CoaXPress 2.0的相机(在Linux ARM上不可用)
  • BaslerCamEmu      :用于相机仿真支持
  • BaslerCameraLink:用于使用CL串行接口的Camera Link相机(仅限于相机配置,仅在Windows上可用)

传输层对象设备工厂,用于:

  • 发现设备(此过程也称为设备枚举)
  • 创建用于访问相机设备的pylon设备
  • 销毁pylon设备
  • 访问特定于传输层的参数

Transport Layer Factory

应用程序不直接访问传输层实现。传输层工厂用于创建传输层对象,每个传输层对象代表一个传输层。此外,传输层工厂还可以用作设备工厂,为所有传输层创建和销毁pylon设备。

Paw5zx注:

每个传输层对象通常是针对一种相机接口设计的,例如程序中可以通过BaslerGigE传输层对象创建GigE相机设备对象。


这可以加入框架的设计思路。

GenApi Node Maps

为了配置相机和访问其他参数,pylon API使用由欧洲机器视觉协会(EMVA)主持的GenICam标准定义的技术。GenICam规范 定义了相机描述文件的格式。这些文件描述了符合GenICam标准的相机的配置接口。描述文件采用XML编写,描述了相机寄存器、它们之间的相互依赖关系,以及通过低级寄存器读写操作访问高级特征(如增益、曝光时间或图像格式)所需的所有其他信息。

相机描述文件中的元素被表示为称为节点的软件对象。例如,一个节点可以代表一个单独的相机寄存器,一个像Gain的相机参数,一组可用的参数值等。每个节点都实现了GenApi::INode接口。

这些节点通过GenApi标准文档 中解释的不同关系相互连接。所有节点的完整集合存储在一个称为节点映射(node map)的数据结构中。在运行时,节点映射是从一个XML描述中实例化的。

在pylon中,节点映射不仅用于表示相机设备参数。其他pylon对象(如传输层对象或图像格式转换器)的参数也通过GenApi节点映射暴露。

示例:

Low Level API

所有传输层都实现了Low Level API接口。这意味着对于所有传输层,事件可以用相同的方式处理。Low Level API部分列出了所有Low Level API类。

Low Level API pylon Devices

在pylon中,物理相机设备由pylon Devices表示。

Stream Grabbers

pylon架构允许相机对象提供一个或多个图像数据流。要从流中抓取图像,需要一个流抓取器对象。流抓取器对象不能直接由应用程序创建。它们由相机对象管理。

Event Grabbers

Basler GigE Vision和USB3 Vision相机可以发送事件消息。事件抓取器对象用于接收事件消息。

Chunk Parsers

如果所谓的数据块模式被激活,Basler相机可以发送附加到图像数据上的额外信息。在数据块模式下,相机发送一个扩展的数据流,该数据流由图像数据和额外信息(如帧号或时间戳)组成。扩展数据流是自描述的。pylon数据块解析器对象用于解析扩展数据流并提供对添加的信息的访问。

Instant Camera Classes

即时相机为相机设备提供便利的访问,同时具有高度的可定制性。它允许通过几行代码抓取图像,提供对从相机设备抓取的图像的即时访问。其内部使用了一个pylon设备。需要创建一个pylon设备并将其绑定到即时相机对象上以进行操作。额外的CBaslerUniversalInstantCamera类为相机的参数提供了更便捷的访问方式。此外,Instant Camera Array classes类简化了从多个相机设备抓取图像的编程工作。

Image Handling Support

除了用于抓取图像的即时相机类之外,pylon还提供了额外的图像处理支持,用于处理抓取到的图像。这包括一个图像类、一个图像格式转换器、图像的加载和保存、Windows位图图像支持、一个图像窗口、一个AVI写入器、一个视频写入器和一个图像解压器。

Enumerating and Creating pylon Devices

pylon提供了两种方法来枚举和创建pylon设备。第一种方法使用传输层工厂来枚举多个传输层的相机。第二种方法让一个传输层对象为特定的传输层枚举和创建 pylon设备。在描述不同的枚举方案之前,首先介绍“设备类”和“设备信息对象”的概念。

Device Classes

每个传输层可以创建特定类型的pylon设备。例如,PylonGigE传输层将创建代表GigE Vision相机的pylon设备。每种类型的设备都与一个称为设备类的唯一标识符字符串相关联。设备类标识符可以在DeviceClass.h头文件中找到。

Device Info Objects

设备枚举程序返回设备信息对象列表。设备信息对象的基类是Pylon::CDeviceInfo。设备信息对象唯一地描述了一个相机设备。设备信息对象被传输层和传输层工厂用来创建对应设备的相机对象。

Pylon::CDeviceInfo对象存储了一组字符串属性。值的数据类型为Pylon::String_t。以下属性适用于所有设备信息对象:

名称 描述
FriendlyName 设备的人类可读名称(例如相机的型号名称)。友好名称不是唯一的。
FullName 设备的唯一标识符。没有两个设备的全名会相同。
VendorName 制造商名称。
DeviceClass 每个传输层可以创建特定类型(或类)的相机设备(例如USB或GigE Vision设备)。设备类型由Device Class属性标识。
SerialNumber 设备的序列号。在枚举过程中,设备序列号的可用性不能保证,因此序列号属性可能未定义。
UserDefinedName 对于某些设备类别,可以为相机设备分配用户定义的名称。此属性的值可能不是唯一的。
DeviceFactory 传输层对象的唯一全名。

此外,特定的传输层将需要额外的属性。这些属性可以通过使用Pylon::IProperties接口以通用方式访问。

Using the Transport Layer Factory for Enumerating Cameras

Pylon::CTlFactory::EnumerateDevices()方法用于检索所有可用设备的列表,不论使用哪种传输层来访问设备。该列表包含要创建相机对象必需的设备信息对象。

返回的列表是Pylon::DeviceInfoList_t类型的,使用方式类似于C++标准库的std::vector类。

以下示例打印出所有已连接设备的唯一名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <pylon/PylonIncludes.h>
#include <ostream>
using namespace Pylon;
using namespace std;

int main()
{
PylonAutoInitTerm autoInitTerm;

CTlFactory& TlFactory = CTlFactory::GetInstance();
DeviceInfoList_t lstDevices;
TlFactory.EnumerateDevices( lstDevices );
if ( ! lstDevices.empty() ) {
DeviceInfoList_t::const_iterator it;
for ( it = lstDevices.begin(); it != lstDevices.end(); ++it )
cout << it->GetFullName();
}
else
cerr << "No devices found!" << endl;

return 0;
}

传输层工厂提供设备信息对象,并可用于创建相机对象。以下示例说明如何为设备列表中的第一个元素创建一个相机对象:

1
CInstantCamera camera( TlFactory.CreateDevice( lstDevices[0] ) );

注意:

永远不要对传输层工厂创建的Pylon::IPylonDevice指针调用freedelete。而是要用Pylon::CTlFactory::DestroyDevice()方法来删除一个IPylonDevice指针。

Using the Transport Layer Factory to Create a Transport Layer

可以通过调用[Pylon::CTlFactory::EnumerateTls()]方法获取所有可用传输层的列表。列表条目为传输层信息对象(Pylon::CTlInfo)。这些数据结构与设备信息对象非常相似。传输层信息对象被用作Pylon::CTlFactory::CreateTl()的参数,该方法创建传输层对象并返回一个Pylon::ITransportLayer类型的指针。

注意:

永远不要对传输层工厂创建的[ITransportLayer]指针调用freedelete。而是应使用Pylon::CTlFactory::ReleaseTl()方法来释放传输层对象。

Using a Transport Layer Object for Enumerating Cameras

传输层对象可用于枚举所有可通过相应传输层访问的设备。传输层对象由传输层工厂创建。以下示例演示了为PylonGigE传输层创建传输层对象的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <pylon/PylonIncludes.h>

using namespace Pylon;

int main()
{
PylonAutoInitTerm autoInitTerm;

CTlFactory& TlFactory = CTlFactory::GetInstance();
ITransportLayer* pTl = TlFactory.CreateTl( BaslerGigEDeviceClass );

return 0;
}

如上所述,传输层对象也可以通过传入一个传输层信息对象来创建。

现在使用传输层对象来枚举它可以访问的所有设备:

1
2
3
4
5
6
DeviceInfoList_t lstDevices;
pTl->EnumerateDevices( lstDevices );
if ( lstDevices.empty() ) {
cerr << "No devices found" << endl;
exit(1);
}

Pylon::ITransportLayer::EnumerateDevices将发现的设备添加到设备信息列表(lstDevices)中。

现在使用传输层对象来创建一个相机对象。在以下示例中,为第一个枚举出的相机设备创建了一个相机对象:

1
CInstantCamera camera( pTl->CreateDevice( lstDevices[0] ));

注意:

永远不要对由传输层工厂创建的Pylon::IPylonDevice指针调用freedelete。而是应使用Pylon::CTlFactory::DestroyDevice()方法来删除IPylonDevice指针。

Applying a Filter when Enumerating Cameras

要枚举具有特定属性的一系列设备,可以使用EnumerateDevices方法。要定义属性,可以传递一个带有设备信息对象的过滤器列表。如果相机具有过滤器列表中至少一个设备信息对象的属性,则会被枚举。以下示例枚举了过滤器列表中符合model name的所有相机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <pylon/PylonIncludes.h>
#include <ostream>
using namespace Pylon;
using namespace std;

int main()
{
PylonAutoInitTerm autoInitTerm;

CTlFactory& TlFactory = CTlFactory::GetInstance();

DeviceInfoList_t filter;
filter.push_back( CDeviceInfo().SetModelName( "acA1920-40uc" ));
filter.push_back( CDeviceInfo().SetModelName( "acA2500-14gm" ));

DeviceInfoList_t lstDevices;
TlFactory.EnumerateDevices( lstDevices, filter );
if ( ! lstDevices.empty() ) {
DeviceInfoList_t::const_iterator it;
for ( it = lstDevices.begin(); it != lstDevices.end(); ++it )
cout << it->GetFullName();
}
else
cerr << "No devices found!" << endl;

return 0;
}

Creating Specific Cameras

为了创建一个具体的特定设备,必须使用这个设备的属性设置一个信息对象。在下面的示例中,使用序列号和设备类别来识别相机。指定设备类别可以将搜索限制在正确的传输层上。这将节省在使用传输层工厂时的计算时间。

1
2
3
4
5
6
CTlFactory& TlFactory = CTlFactory::GetInstance();

CDeviceInfo di;
di.SetSerialNumber( "20399956" );
di.SetDeviceClass( BaslerUsbDeviceClass );
CInstantCamera camera( TlFactory.CreateDevice( di ) );

上述示例也等价于:

1
CInstantCamera camera( CTlFactory::GetInstance().CreateDevice( CDeviceInfo().SetDeviceClass( BaslerUsbDeviceClass ).SetSerialNumber( "20399956" )) );

当多个设备符合提供的属性时,CreateDevice方法将失败。如果需要创建多个设备中的任何一个,则可以使用CreateFirstDevice方法。

以下示例展示了如何为具有特定IP地址的GigE相机创建一个设备对象:

1
2
3
4
5
6
7
8
#include <pylon/PylonIncludes.h>

//....

CTlFactory& TlFactory = CTlFactory::GetInstance();
CDeviceInfo di;
di.SetIpAddress( "192.168.0.101");
CInstantCamera camera( TlFactory.CreateDevice( di ) );

Paw5zx注:

通过设备信息对象CDeviceInfo传入实例化相机对象所需的信息,而不是仅传入单一(如仅传入序列号字符串),可能有以下考虑:

  • 扩展性:

这可以加入框架的设计思路。

Grab Strategies

以下抓取策略涉及到相机设备的触发。根据相机设备的配置,支持以下触发模式:

  • 外部触发(external trigger):例如通过数字I/O触发。
  • 软件触发(software trigger):软件命令触发。
  • 内部触发(internal trigger):所谓的自由运行模式。

有关此话题的更多信息可以在代码示例Grab_Strategies和参数文档Instant Camera中找到。

One by One Grab Strategy

使用One By One抓取策略时,图像按照它们被采集的顺序进行处理。

  • 即时相机抓取引擎从空缓冲区队列中取出缓冲区,并将空缓冲区入队Low Level API流抓取器(1);
  • 相机设备被触发(2)。一个图像被相机设备获取,并被传输到计算机,然后被抓取到一个空缓冲区中;
  • 即时相机抓取引擎线程收到通知:有已填充的缓冲区可用。已填充的缓冲区由抓取引擎线程检索(3)并放入输出队列;
  • 等待(阻塞)在RetrieveResult()的应用程序线程得到通知,它停止等待抓取结果,并检索已填充的缓冲区(4)作为抓取结果数据对象的一部分;
  • 抓取结果数据对象由抓取结果智能指针持有。应用程序处理完图像数据后,已填充的缓冲区被返回到空缓冲区队列(5)。这是通过抓取结果智能指针的析构函数或当抓取结果数据对象被显式释放时完成的。返回的缓冲区将再次用于抓取。

Paw5zx注:

  • 对于(1)的描述,原文是:

    The Instant Camera grab engine unqueues buffers from the Empty Buffer Queue and queues the empty buffers at the Low Level API stream grabber (1).

    个人认为这里的queues the empty buffers不是指Low Level API stream grabber具有队列结构,而是指empty buffers被按序分配给stream grabber。

  • 对于(3),是抓取引擎线程检索

  • 对于(4),是应用程序检索

上述描述对理解后面的抓取策略比较有用,若我理解错了,麻烦指出。

Latest Image Only Grab Strategy

Latest Image Only抓取策略与One By One抓取策略的不同之处在于输出队列的大小。Latest Image Only抓取策略的输出队列的大小只有一个缓冲区。每当一个新的缓冲区(称为A)被抓取后(当新的图像数据被相机捕获并被写入到Low Level API Stream Grabber内部的缓冲区中后,这个缓冲区被认为是被抓取的),pylon会检查输出队列,如果输出队列已经有一个缓冲区(称为B)在等待(被应用程序检索),那么这个缓冲区B将会自动移回到空缓冲区队列(4.1).然后,缓冲区A被放入输出队列。这确保了始终提供的是最新抓取的图像。自动移回到空缓冲区队列的图像被称为skipped images

Latest Images Strategy

Latest Images抓取策略扩展了Latest Image Only抓取策略。它允许用户通过设置CInstantCamera::OutputQueueSize来调整输出队列的大小。如果一个新的缓冲区被抓取且输出队列已满,输出队列中的第一个缓冲区将自动返回到空缓冲区队列(4.1)。然后,新填充的缓冲区被放置进输出队列末尾。这确保应用程序总是能获得最新抓取的图像(图像都是最新抓取的,但是由于存在输出队列,相较于应用程序检索时,可能没有那么新)。自动返回到空缓冲区队列的图像被称为skipped images

  • 当将输出队列大小设置为1时,此策略等价于Latest Image Only抓取策略。
  • 当将输出队列大小设置为[Pylon::CInstantCamera::MaxNumBuffer]时,此策略等价于One By One抓取策略。

Upcoming Image Grab Strategy

Upcoming Image抓取策略可用于确保获取的是在调用RetrieveResult()之后抓取的图像。

  • 在调用RetrieveResult()之前,Low Level API流抓取器不会接收空缓冲区。当应用程序调用RetrieveResult()(1)时,一个空缓冲区会从空缓冲区队列中出队,然后该空缓冲区被传递给Low Level API流抓取器(2)。
  • 相机设备被触发(3)。相机设备采集图像,图像数据被传输到计算机并存储到空缓冲区中。
  • 现在已填充的缓冲区作为由抓取结果智能指针持有的抓取结果数据对象的一部分返回(4)(1)。
  • 应用程序处理完图像数据后,填充的缓冲区被返回到空缓冲区队列(5)。这一操作是通过以下两种情况中任意一个完成的:①抓取结果智能指针的析构②抓取结果数据对象被显式释放。如果RetrieveResult()超时,则空缓冲区也会被返回到空缓冲区队列。

注意:

Upcoming Image抓取策略不能与USB相机设备一起使用。有关更多信息,请参阅Differences in Image Transport和以下部分。

Getting Informed About Camera Device Removal

要获取关于相机设备移除的通知,可以查询IsCameraDeviceRemoved()方法或注册一个配置事件处理程序。如果相机设备被移除,虚函数OnCameraDeviceRemoved()将被调用。设备移除只有在即时相机及其绑定的pylon设备打开时才能检测到。设备移除后需要销毁附加的pylon设备。这可以通过使用DestroyDevice()方法来完成。

以下是一个处理相机设备移除的配置事件处理程序的示例:

1
2
3
4
5
6
7
8
9
10
//Example of a configuration event handler that handles device removal events.
class CSampleConfigurationEventHandler : public Pylon::CConfigurationEventHandler
{
public:
// This method is called from a different thread when the camera device removal has been detected.
void OnCameraDeviceRemoved( CInstantCamera& /*camera*/ )
{
cout << "CSampleConfigurationEventHandler::OnCameraDeviceRemoved called." << std::endl;
}
};

以下示例显示了如何在循环中访问相机以检测设备移除。IsCameraDeviceRemoved()方法可以用来是否是在访问相机设备时因设备移除而引起了异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Declare a local counter used for waiting.
int loopCount = 0;

// Get the transport layer factory.
CTlFactory& tlFactory = CTlFactory::GetInstance();

// Create an instant camera object with the camera device found first.
CInstantCamera camera( tlFactory.CreateFirstDevice() );

// Print the camera information.
cout << "Using device " << camera.GetDeviceInfo().GetModelName() << endl;
cout << "Friendly Name: " << camera.GetDeviceInfo().GetFriendlyName() << endl;
cout << "Full Name : " << camera.GetDeviceInfo().GetFullName() << endl;
cout << "SerialNumber : " << camera.GetDeviceInfo().GetSerialNumber() << endl;
cout << endl;

// For demonstration purposes only, register another configuration event handler that handles device removal.
camera.RegisterConfiguration( new CSampleConfigurationEventHandler, RegistrationMode_Append, Cleanup_Delete );

// For demonstration purposes only, add a sample configuration event handler to print out information
// about camera use.
camera.RegisterConfiguration( new CConfigurationEventPrinter, RegistrationMode_Append, Cleanup_Delete );

// Open the camera. Camera device removal is only detected while the camera is open.
camera.Open();

// Now, try to detect that the camera has been removed:

// Ask the user to disconnect a device
loopCount = c_loopCounterInitialValue;
cout << endl << "Please disconnect the device (timeout " << loopCount / 4 << "s) " << endl;

try
{
// Get a camera parameter using generic parameter access.
CIntegerParameter width( camera.GetNodeMap(), "Width" );

// The following loop accesses the camera. It could also be a loop that is
// grabbing images. The device removal is handled in the exception handler.
while (loopCount > 0)
{
// Print a "." every few seconds to tell the user we're waiting for the callback.
if (--loopCount % 4 == 0)
{
cout << ".";
cout.flush();
}
WaitObject::Sleep( 250 );

// Change the width value in the camera depending on the loop counter.
// Any access to the camera like setting parameters or grabbing images
// will fail throwing an exception if the camera has been disconnected.
width.SetValue( width.GetMax() - (width.GetInc() * (loopCount % 2)) );
}

}
catch (const GenericException& e)
{
// An exception occurred. Is it because the camera device has been physically removed?

// Known issue: Wait until the system safely detects a possible removal.
WaitObject::Sleep( 1000 );

if (camera.IsCameraDeviceRemoved())
{
// The camera device has been removed. This caused the exception.
cout << endl;
cout << "The camera has been removed from the computer." << endl;
cout << "The camera device removal triggered an expected exception:" << endl
<< e.GetDescription() << endl;
}
else
{
// An unexpected error has occurred.

// In this example it is handled by exiting the program.
throw;
}
}

if (!camera.IsCameraDeviceRemoved())
cout << endl << "Timeout expired" << endl;

// Destroy the Pylon Device representing the detached camera device.
// It can't be used anymore.
camera.DestroyDevice();

上述代码片段可以在示例DeviceRemovalHandling中找到。

注意:

OnCameraDeviceRemoved()调用是从另一个单独的线程中进行的。

Paw5zx注:

对于IsCameraDeviceRemoved(),本节给出的描述是:

The IsCameraDeviceRemoved() method can be used to check whether the removal of the camera device has caused an exception while accessing the camera device, e.g. for grabbing.

可能是尝试说明其内部的判断逻辑,但是在IsCameraDeviceRemoved()函数介绍文档中仅说明了:

True if the camera device removal from the PC has been detected.

并没有强调其内部判断相机设备移除的逻辑是否与异常等相关。作为学习和使用者,我就暂时不考虑了。

Accessing Chunk Features

Basler相机可以发送附加到图像数据的额外信息,例如帧计数器、时间戳和CRC校验和。如果块模式被激活,这些数据块会被即时相机类自动解析。以下示例展示了如何使用CBaslerUniversalInstantCamera类来实现这一功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Enable chunks in general.
if (!camera.ChunkModeActive.TrySetValue( true ))
{
throw RUNTIME_EXCEPTION( "The camera doesn't support chunk features" );
}

// Enable time stamp chunks.
camera.ChunkSelector.SetValue( ChunkSelector_Timestamp );
camera.ChunkEnable.SetValue( true );

// Enable frame counter chunks?
if (camera.ChunkSelector.TrySetValue( ChunkSelector_Framecounter ))
{
// USB camera devices provide generic counters.
// An explicit FrameCounter value is not provided by USB camera devices.
// Enable frame counter chunks.
camera.ChunkEnable.SetValue( true );
}

// Enable CRC checksum chunks.
camera.ChunkSelector.SetValue( ChunkSelector_PayloadCRC16 );
camera.ChunkEnable.SetValue( true );

数据块数据可以通过CBaslerUniversalGrabResultPtr数据类的参数成员或使用提供的数据块数据节点图(未展示)访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
        // Camera.StopGrabbing() is called automatically by the RetrieveResult() method
// when c_countOfImagesToGrab images have been retrieved.
while (camera.IsGrabbing())
{
// Wait for an image and then retrieve it. A timeout of 5000 ms is used.
// RetrieveResult calls the image event handler's OnImageGrabbed method.
camera.RetrieveResult( 5000, ptrGrabResult, TimeoutHandling_ThrowException );
cout << "GrabSucceeded: " << ptrGrabResult->GrabSucceeded() << endl;

// Image grabbed successfully?
if (ptrGrabResult->GrabSucceeded())
{
#ifdef PYLON_WIN_BUILD
// Display the image
Pylon::DisplayImage( 1, ptrGrabResult );
#endif

// The result data is automatically filled with received chunk data.
// (Note: This is not the case when using the low-level API)
cout << "SizeX: " << ptrGrabResult->GetWidth() << endl;
cout << "SizeY: " << ptrGrabResult->GetHeight() << endl;
const uint8_t* pImageBuffer = (uint8_t*) ptrGrabResult->GetBuffer();
cout << "Gray value of first pixel: " << (uint32_t) pImageBuffer[0] << endl;

// Check to see if a buffer containing chunk data has been received.
if (PayloadType_ChunkData != ptrGrabResult->GetPayloadType())
{
throw RUNTIME_EXCEPTION( "Unexpected payload type received." );
}

// Since we have activated the CRC Checksum feature, we can check
// the integrity of the buffer first.
// Note: Enabling the CRC Checksum feature is not a prerequisite for using
// chunks. Chunks can also be handled when the CRC Checksum feature is deactivated.
if (ptrGrabResult->HasCRC() && ptrGrabResult->CheckCRC() == false)
{
throw RUNTIME_EXCEPTION( "Image was damaged!" );
}

// Access the chunk data attached to the result.
// Before accessing the chunk data, you should check to see
// if the chunk is readable. When it is readable, the buffer
// contains the requested chunk data.
if (ptrGrabResult->ChunkTimestamp.IsReadable())
{
cout << "TimeStamp (Result): " << ptrGrabResult->ChunkTimestamp.GetValue() << endl;
}

// USB camera devices provide generic counters. An explicit FrameCounter value is not provided by USB camera devices.
if (ptrGrabResult->ChunkFramecounter.IsReadable())
{
cout << "FrameCounter (Result): " << ptrGrabResult->ChunkFramecounter.GetValue() << endl;
}

cout << endl;
}
else
{
cout << "Error: " << std::hex << ptrGrabResult->GetErrorCode() << std::dec << " " << ptrGrabResult->GetErrorDescription() << endl;
}
}

以上代码片段可以在示例Grab_ChunkImage中找到。

Handling Camera Events

Basler GigE Vision和USB3 Vision相机可以发送事件消息。例如,当传感器曝光结束时,相机可以向计算机发送曝光结束事件。这个事件可以在完成曝光的图像数据完全传输之前被计算机接收到。这种机制有很多作用,例如:通过事件消息得知曝光的结束,然后可以更早地开始后续的动作或处理步骤,比如移动被拍摄的物体到下一个位置,从而优化整个成像流程的效率。

事件消息由即时相机类自动检索和处理。事件消息携带的信息以节点的形式暴露在相机节点图中,并可以像普通的相机参数一样被访问。当接收到相机事件时,这些节点会更新。您可以注册相机事件处理器对象,它将在当接收到事件数据时触发。

以下是一个相机事件处理的示例,用于在屏幕上打印事件数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Example handler for camera events.
class CSampleCameraEventHandler : public CBaslerUniversalCameraEventHandler
{
public:
// Only very short processing tasks should be performed by this method. Otherwise, the event notification will block the
// processing of images.
virtual void OnCameraEvent( CBaslerUniversalInstantCamera& camera, intptr_t userProvidedId, GenApi::INode* /* pNode */ )
{
std::cout << std::endl;
switch (userProvidedId)
{
case eMyExposureEndEvent: // Exposure End event
if (camera.EventExposureEndFrameID.IsReadable()) // Applies to cameras based on SFNC 2.0 or later, e.g, USB cameras
{
cout << "Exposure End event. FrameID: " << camera.EventExposureEndFrameID.GetValue() << " Timestamp: " << camera.EventExposureEndTimestamp.GetValue() << std::endl << std::endl;
}
else
{
cout << "Exposure End event. FrameID: " << camera.ExposureEndEventFrameID.GetValue() << " Timestamp: " << camera.ExposureEndEventTimestamp.GetValue() << std::endl << std::endl;
}
break;
case eMyEventOverrunEvent: // Event Overrun event
cout << "Event Overrun event. FrameID: " << camera.EventOverrunEventFrameID.GetValue() << " Timestamp: " << camera.EventOverrunEventTimestamp.GetValue() << std::endl << std::endl;
break;
}
}
};

默认情况下,处理相机事件功能被禁用,需要首先激活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 用于区分不同事件的枚举。
enum MyEvents
{
eMyExposureEndEvent = 100,
eMyEventOverrunEvent = 200
// 这里可以添加更多事件。
};

...

// 基于 SFNC 2.0 或更高版本的相机,例如 USB 相机
if (camera.GetSfncVersion() >= Sfnc_2_0_0)
{
// 为曝光结束事件注册一个事件处理程序
// 对于每种事件类型,都有一个代表事件的“数据”节点
// 事件携带的实际数据由数据节点的子节点持有。
// 在曝光结束事件的情况下,子节点包括EventExposureEndFrameID和EventExposureEndTimestamp。
// CSampleCameraEventHandler展示了如何在触发父数据节点的回调中访问这些子节点
// 用户提供的ID eMyExposureEndEvent可用于区分多个事件(未展示)。
camera.RegisterCameraEventHandler( pHandler1, "EventExposureEndData", eMyExposureEndEvent, RegistrationMode_ReplaceAll, Cleanup_None );
// 处理程序同时注册了 EventExposureEndFrameID 和 EventExposureEndTimestamp 节点
// 这些节点代表了曝光结束事件携带的数据
// 对于收到的每一个曝光结束事件,处理程序将被调用两次,一次是针对帧 ID,另一次是针对时间戳。
camera.RegisterCameraEventHandler( pHandler2, "EventExposureEndFrameID", eMyExposureEndEvent, RegistrationMode_Append, Cleanup_None );
camera.RegisterCameraEventHandler( pHandler2, "EventExposureEndTimestamp", eMyExposureEndEvent, RegistrationMode_Append, Cleanup_None );
}
else
{
// 为曝光结束事件注册一个事件处理程序
// 对于每种事件类型,都有一个代表事件的“数据”节点
// 事件携带的实际数据由数据节点的子节点持有
// 在曝光结束事件的情况下,子节点包括ExposureEndEventFrameID、ExposureEndEventTimestamp和ExposureEndEventStreamChannelIndex
// CSampleCameraEventHandler展示了如何在为父数据节点触发的回调中访问这些子节点。
camera.RegisterCameraEventHandler( pHandler1, "ExposureEndEventData", eMyExposureEndEvent, RegistrationMode_ReplaceAll, Cleanup_None );

// 为第二个事件注册相同的处理程序
// 用户提供的 ID 可用于区分事件
camera.RegisterCameraEventHandler( pHandler1, "EventOverrunEventData", eMyEventOverrunEvent, RegistrationMode_Append, Cleanup_None );

// 为ExposureEndEventFrameID和ExposureEndEventTimestamp节点注册处理程序。这些节点代表曝光结束事件携带的数据。
// 每收到一个曝光结束事件,处理程序将被调用两次,一次用于帧ID,一次用于时间戳。
camera.RegisterCameraEventHandler( pHandler2, "ExposureEndEventFrameID", eMyExposureEndEvent, RegistrationMode_Append, Cleanup_None );
camera.RegisterCameraEventHandler( pHandler2, "ExposureEndEventTimestamp", eMyExposureEndEvent, RegistrationMode_Append, Cleanup_None );
}

必须在相机中启用感兴趣的事件。然后在等待图像时通过RetrieveResult()调用处理事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Enable sending of Exposure End events.
// Select the event to receive.
camera.EventSelector.SetValue( EventSelector_ExposureEnd );

// Enable it.
if (!camera.EventNotification.TrySetValue( EventNotification_On ))
{
// scout-f, scout-g, and aviator GigE cameras use a different value
camera.EventNotification.SetValue( EventNotification_GenICamEvent );
}


// Enable event notification for the EventOverrun event, if available
if (camera.EventSelector.TrySetValue( EventSelector_EventOverrun ))
{
// Enable it.
if (!camera.EventNotification.TrySetValue( EventNotification_On ))
{
// scout-f, scout-g, and aviator GigE cameras use a different value
camera.EventNotification.SetValue( EventNotification_GenICamEvent );
}
}


// Start the grabbing of c_countOfImagesToGrab images.
camera.StartGrabbing( c_countOfImagesToGrab );

// This smart pointer will receive the grab result data.
CGrabResultPtr ptrGrabResult;

// Camera.StopGrabbing() is called automatically by the RetrieveResult() method
// when c_countOfImagesToGrab images have been retrieved.
while (camera.IsGrabbing())
{
// Execute the software trigger. Wait up to 1000 ms for the camera to be ready for trigger.
if (camera.WaitForFrameTriggerReady( 1000, TimeoutHandling_ThrowException ))
{
camera.ExecuteSoftwareTrigger();
}

// Retrieve grab results and notify the camera event and image event handlers.
camera.RetrieveResult( 5000, ptrGrabResult, TimeoutHandling_ThrowException );
// Nothing to do here with the grab result, the grab results are handled by the registered event handler.
}

以上代码片段可以在示例Grab_CameraEvents中找到。

Getting Informed About Parameter Changes

GenICam API提供了注册回调函数的功能,当参数的值或状态(例如访问模式或值范围)发生变化时,这些回调函数将被调用。可以注册一个C函数或一个C++类成员函数作为回调。

每个回调都为特定参数安装。如果该参数本身被修改了,或者其他可能影响该参数状态的另一个参数发生了变化,回调将被触发。

以下示例演示如何为Width参数注册回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <pylon/PylonIncludes.h>
#include <pylon/gige/BaslerGigEInstantcamera.h>
#include <ostream>
using namespace Pylon;
using namespace std;

// C callback function
void staticcallback(GenApi::INode* pNode )
{
cout << "Perhaps the value or state of " << pNode->GetName() << "has changed." << endl;
if ( GenApi::IsReadable( pNode ) ) {
GenApi::CValuePtr ptrValue( pNode );
cout << "The current value is " << ptrValue->ToString() << endl;
}
}

class C
{
public:
// Member function as callback function
void membercallback(GenApi::INode* pNode )
{
cout << "Perhaps the value or state of " << pNode->GetName() << "has changed." << endl;
if ( GenApi::IsReadable( pNode ) ) {
GenApi::CValuePtr ptrValue( pNode );
cout << "The current value is " << ptrValue->ToString() << endl;
}
}
};

int main()
{
PylonAutoInitTerm autoInitTerm;

C cb; // c.membercallback() will be installed as callback

// Only look for cameras supported by Camera_t.
CDeviceInfo info;
info.SetDeviceClass( Camera_t::DeviceClass());

// Create an Instant Camera object with the first found camera device matching the specified device class.
CBaslerGigEInstantCamera_t camera( CTlFactory::GetInstance().CreateFirstDevice( info));
camera.Open();

// Install the C-function as callback
GenApi::CallbackHandleType h1 =
GenApi::Register( camera.Width.GetNode(), &staticcallback );

// Install a member function as callback
GenApi::CallbackHandleType h2 =
GenApi::Register( camera.Width.GetNode(), cb, &C::membercallback );

// This will trigger the callback functions
camera.Width.SetValue( 128 );

// Uninstall the callback functions
camera.Width.GetNode()->DeregisterCallback(h2);
camera.Width.GetNode()->DeregisterCallback(h1);

// Close the camera object
camera.Close();
}

注意:

对于node map中的节点,可以选择使用相机事件处理程序来得到参数变化的通知。这是因为当注册相机事件处理程序时,会为由节点名称标识的节点内部注册一个GenApi节点回调。这种回调触发对CCameraEventHandler::OnCameraEvent()方法的调用。使用相机事件处理程序可能更方便。有关如何注册相机事件处理程序的更多信息,请参见示例Grab_CameraEvents

Instant Camera Class and User Provided Buffers

可以将缓冲区工厂绑定到即时相机对象上,以使用用户提供的缓冲区。使用缓冲区工厂是可选的,仅适用于高级用例。缓冲区工厂类必须派生自Pylon::IBufferFactory。可以通过调用SetBufferFactory()将缓冲区工厂对象绑定到即时相机类的实例上。当调用StartGrabbing时,将分配缓冲区。在缓冲区工厂绑定到相机对象上时,不能删除缓冲区工厂;在最后一个缓冲区被释放之前,不能删除缓冲区工厂。要释放所有缓冲区,需要停止抓取且所有抓取结果都要被释放或销毁。示例Grab_UsingBufferFactory展示了缓冲区工厂的使用。

Initialization/Uninitialization of the pylon Runtime Library in MFC Applications

GigE Multicast/Broadcast: Grab Images of One Camera on Multiple Computers

Basler GigE相机可以配置为发送图像数据流到多个目的地。可以使用IP组播或IP广播。

The Controlling Application and the Monitoring Application

当不同计算机上的多个应用程序期望从同一台相机接收数据流时,其中一个应用程序负责配置相机以及开始和停止数据采集。这个应用程序被称为控制应用程序。其他也期望接收数据流的应用程序被称为监控应用程序。这些应用程序必须以只读模式连接到相机,可以读取所有相机参数,但不能更改它们。

设备枚举和设备创建过程对于控制应用程序和监控应用程序是相同的。每种应用程序类型都必须为其将要接收数据的相机设备创建一个相机对象。组播设备的创建方式与单播设置相同(见前面的解释)。

配置即时相机作为监控者的示例:

1
camera.MonitorModeActive = true;

在使用Low Level API时,传递给相机对象的Pylon::CBaslerGigECamera::Open()方法的参数(设备访问模式)决定了应用程序是作为控制应用程序还是监控应用程序。以下代码片段展示了监控应用程序如何调用Pylon::CBaslerGigECamera::Open()方法:

1
2
3
4
// Low Level-API only
// Open the camera in stream mode to receive multicast packets (monitoring mode)
// In this mode the camera must be controlled by another application that must be in controlling mode
camera.Open(Stream);

在使用Low Level API时,控制应用程序可以调用Pylon::CBaslerGigECamera::Open()方法而不传入任何参数(Pylon::CBaslerGigECamera::Open()方法的默认参数确保了设备将以控制和流模式打开),或者可以明确指定Pylon::CBaslerGigECamera::Open()方法的访问模式为控制和流模式:

1
2
// Open the camera in controlling mode but without setting the Exclusive flag for the access mode
camera.Open(Stream | Control);

重要的是,控制应用程序不应设置访问模式的Exclusive标志。使用Exclusive标志将阻止监控应用程序完全访问相机。当控制应用程序希望接收相机事件时,必须将Events标志添加到访问模式参数中。

控制应用程序和监控应用程序必须以与单播设置相同的方式创建流抓取对象。配置流抓取器用于组播或广播将在接下来的部分中解释。

Setting Up the Controlling Application for Enabling Multicast and Broadcast

GigE流抓取类的TransmissionType参数可以用来配置相机是向单一目的地发送数据流还是向多个目的地发送。

当相机使用有限广播(limited broadcasts)发送图像数据时,相机向地址255.255.255.255发送数据,数据会发送到本地网络中的所有设备。“有限”意味着数据不会被发送到路由器后面的目的地,例如互联网中的计算机。要启用有限广播,控制应用程序必须将TransmissionType参数设置为TransmissionType_LimitedBroadcast。相机将数据发送到特定端口。关于设置接收相机数据的目的地端口,请参见PortSelection部分。

当相机使用子网定向广播(subnet directed broadcasts)发送图像数据时,相机向与相机处于同一子网的所有设备发送数据。要启用子网定向广播,将TransmissionType参数设置为TransmissionType_SubnetDirectedBroadcast。关于设置接收相机数据的目的地端口的信息,请参见PortSelection部分。

使用广播的缺点是相机将数据发送给网络中的所有接收者,无论这些设备是否需要数据。网络流量会造成一定的CPU负载,并消耗不需要流数据的设备的网络带宽。

当相机使用组播(multicasts)发送图像数据时,数据仅发送给期望接收数据流的设备。设备通过加入所谓的组播组(multicast group)来声明其接收数据的意愿。组播组由组播地址范围(224.0.0.0239.255.255.255)中的一个IP地址定义。特定组播组的成员只接收为该组准备的数据。其他组的数据不会被接收。通常,网络适配器和网络交换机能够在硬件级别有效地过滤网络包,防止由于网络中那些不属于组播组的设备的组播网络流量而导致的CPU负载。

当为pylon启用组播时,pylon会自动处理加入和离开由目的地IP地址定义的组播组。请注意,组播地址范围中的一些地址是为一般用途保留的。由RFC 2365 指定的地址范围从239.255.0.0239.255.255.255是由本地管理的地址空间。如果您不确定,请使用此范围内的地址。

要启用组播流,控制应用程序必须将TransmissionType参数设置为TransmissionType_Multicast并将DestinationAddr参数设置为有效的组播IP地址。除了地址外,还必须指定端口。关于设置接收相机数据的目的地端口,请参见Selecting a Destination Port

使用CBaslerUniversalInstantCamera类的示例:

1
2
camera.GetStreamGrabberParams().DestinationAddr = "239.0.0.1";
camera.GetStreamGrabberParams().DestinationPort = 49154;

示例(Low Level):

1
2
StreamGrabber.DestinationAddr = "239.0.0.1";
StreamGrabber.DestinationPort = 49154;

在协议层面上,组播涉及所谓的IGMP消息(IGMP = Internet Group Management Protocol)。要从组播中受益,应使用管理型网络交换机。这些管理型网络交换机支持IGMP协议,只有当连接了加入相应组播组的设备时,才转发组播包。如果交换机不支持IGMP协议,组播等同于广播。

当多个相机在同一网络中进行组播时,每个相机应该向不同的组播组进行流媒体传输。如果使用的网络交换机支持IGMP协议,向不同的组播组流媒体传输可以减少CPU负载并节省网络带宽。

Setting Up the Monitoring Application for Receiving Multicast and Broadcast Streams

必须区分两种情况:

  • 监控应用程序在控制应用程序已经为广播或组播设置了流抓取器后打开流抓取器。
  • 监控应用程序在控制应用程序打开其流抓取器之前打开流抓取器。

对于第一种情况,为监控应用程序设置流抓取器相对简单。由于控制应用程序已经配置了相机(即目的地地址和端口已由控制应用程序设置),这些设置可以很容易地从相机中读取。为了让监控应用程序的流抓取器从相机读取设置,监控应用程序必须将流抓取器的TransmissionType参数设置为TransmissionType_UseCameraConfig,然后调用流抓取器的Open()方法。

使用CBaslerUniversalInstantCamera类的示例:

1
2
3
4
5
6
// Select transmission type. If the camera is already controlled by another application
// and configured for multicast or broadcast, the active camera configuration can be used
// (IP Address and Port will be auto set).
camera.GetStreamGrabberParams().TransmissionType = TransmissionType_UseCameraConfig;

// Start grabbing...

Low Level示例:

1
2
3
4
5
6
7
// Select transmission type. If the camera is already controlled by another application
// and configured for multicast or broadcast, the active camera configuration can be used
// (IP Address and Port will be auto set).
StreamGrabber.TransmissionType = TransmissionType_UseCameraConfig;

// Open the stream grabber
StreamGrabber.Open();

对于第二种情况,当监控应用程序在控制应用程序打开其流抓取器之前打开流抓取器时,不能使用TransmissionType_UseCameraConfig。相反,控制应用程序和所有监控应用程序必须对以下与IP目的地相关的参数使用相同的设置:

请注意,当使用广播时,DestinationAddr参数是只读的。Pylon将配置相机以使用正确的广播地址。

当控制应用程序和监控应用程序显式设置与目的地相关的参数时,哪个应用程序首先打开流抓取器并不重要。

Selecting a Destination Port

相机数据的目的地由目的地IP地址和目的地IP端口指定。对于组播,监控应用程序和控制应用程序必须为相同的组播IP地址配置流抓取器。相应地,对于广播,监控应用程序和控制应用程序必须使用由pylon自动设置的相同的广播IP地址。

在这两种情况下,控制应用程序和监控应用程序都必须指定相同的目的地端口。所有应用程序必须使用一个在接收数据流的所有计算机上未被占用的端口。目的地端口通过使用流抓取器的DestinationPort参数来设置。

当监控应用程序将TransmissionType参数设置为TransmissionType_UseCameraConfig时,它会自动使用控制应用程序已经写入相应相机寄存器的端口。在这种情况下,控制应用程序必须使用一个在运行监控应用程序的所有计算机上都未被使用的端口。Basler不建议使用这种自动选择机制来选择广播或组播端口。

DestinationPort参数设置为0时,pylon会自动选择一个未使用的端口。这对于只使用单播流的应用程序非常方便。在组播或广播的情况下,只有当监控应用程序对TransmissionType参数使用TransmissionType_UseCameraConfig值时,控制应用程序才可以使用参数值0。因为控制应用程序自动选择的端口可能已经在运行监控应用程序的计算机上被使用,所以我们不推荐使用这种自动选择机制来为广播或组播选择端口。

Paw5zx注:

这里应该是想表达,在配置端口时要尽量避免使用自动端口选择机制,特别是在组播和广播设置中。自动选择可能导致在不同接收设备之间出现端口冲突。


关于网络的知识还要学习…

Receiving Image Data

对于广播或组播,抓取图像的方式与单播设置相同。控制和监控应用程序必须为抓取分配内存,将缓冲区注册到流抓取器,缓冲区入队并从流抓取器中检索它们。监控应用程序和控制应用程序之间唯一的区别是,只有控制应用程序负责启动和停止相机中的图像采集。

Sample Program

pylon SDK包含一个名为Grab_MultiCast的简单示例程序。此示例演示了如何为组播设置控制应用程序和监控应用程序。

GigE Action Commands

动作命令(action command)特征允许您使用单个广播协议消息(无需额外布线)同时或在定义的时间点(计划操作命令)触发多个GigE设备(例如相机)中的动作。动作命令的使用方式与数字输入线等相同。

在为操作命令设置所需的相机参数后,可以使用Pylon::IGigETransportLayer::IssueActionCommandPylon::IGigETransportLayer::IssueScheduledActionCommand方法来触发动作命令。这在示例 Grab_UsingActionCommand中有所展示。示例中使用Pylon::CActionTriggerConfiguration来设置所需的相机参数。CActionTriggerConfiguration以头文件形式提供。这样可以看到相机的哪些参数被更改。可以复制并修改代码以创建自己的配置类。

Saving and Restoring Camera Features to/from Files

本节描述了如何将那些可读写的相机特征的当前值写入文件。还展示了如何将已保存的特征值写回设备。使用Pylon::CFeaturePersistence类来执行保存和恢复相机特征的操作。

Writing the Camera Features to a File

使用静态方法Pylon::CFeaturePersistence::Save()来将相机当前特征的值保存至文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <pylon/PylonUtilityIncludes.h>
// ...

const char Filename[] = "NodeMap.pfs"; // Pylon Feature Stream

// ...

// Open the camera
camera.Open();

// Save the content of the camera's node map into the file
try
{
CFeaturePersistence::Save( Filename, &camera.GetNodeMap() );
}
catch (Pylon::GenericException &e)
{
// Error handling
cerr << "An exception occurred!" << endl << e.GetDescription() << endl;
}

Writing the Feature Values Back to the Camera

使用静态方法Pylon::CFeaturePersistence::Load()将相机特征值从文件中恢复。(使用配置文件配置相机特征参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <pylon/PylonUtilityIncludes.h>
// ...
const char Filename[] = "NodeMap.pfs"; // Pylon Feature Stream

// ...

// Open the camera
camera.Open();

// Read the content of the file back to the camera's node map with validation on
try
{
CFeaturePersistence::Load( Filename, &camera.GetNodeMap(), true );
}
catch (Pylon::GenericException &e)
{
// Error handling
cerr << "An exception occurred!" << endl << e.GetDescription() << endl;
}

代码截取自示例ParametrizeCamera_LoadAndSave

Transferring Shading Data to the Camera

本节描述了如何使用GenICam FileIO功能将增益校正数据(gain shading data)传输到相机。

支持增益校正功能的相机设备会将校正数据作为文件存储在相机的内部文件系统中。这些文件通过在GenApi/Filestream.h头文件中提供的GenICam Filestream类进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Include files to use the PYLON API
#include <pylon/PylonIncludes.h>
using namespace Pylon;

// for file upload
#include <GenApi/Filestream.h>

// ...

// Create the camera object of the first available camera
// The camera object is used to set and get all available
// camera features.
Camera_t Camera(pTl->CreateDevice(devices[ 0 ]));

// Open the camera
camera.Open();

// ...

GenICam定义了两个基于字符的流类,用于简化读写操作。

1
2
typedef ODevFileStreamBase<char, std::char_traits<char> > ODevFileStream;
typedef IDevFileStreamBase<char, std::char_traits<char> > IDevFileStream;

ODevFileStream类用于将数据上传到相机的文件系统。IDevFileStream类用于从相机的文件系统下载数据。

在内部,这些类使用GenApi::FileProtocolAdapter类。GenApi::FileProtocolAdapter类定义了像打开、关闭、读取和写入这样的基于文件的操作。

这些操作的一个常见参数是要在设备文件系统上使用的文件名。文件名必须对应设备文件系统中的现有文件。要检索已连接相机支持的有效文件名列表,请读取FileSelector枚举特征的条目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
GenApi::CEnumerationPtr ptrFileSelector = camera.GetNodeMap().GetNode("FileSelector");
if ( ptrFileSelector.IsValid() ) {
try
{
GenApi::NodeList_t entries;
ptrFileSelector->GetEntries( entries );
for ( GenApi::NodeList_t::iterator it = entries.begin(); it != entries.end(); ++it) {
if (GenApi::IsAvailable(*it)) {
GenApi::CEnumEntryPtr pEntry = (*it);
if ( NULL != pEntry ) {
GenApi::INode* pNode = pEntry->GetNode();
GenICam::gcstring strFilename = pEntry->GetSymbolic().c_str();

// Do with strFilename whatever you want (e.g. adding to a list)
// ...

} // if
} // if
} // for
}
catch (Pylon::GenericException &e)
{
// Handle error
// ...
}
} // if

Upload Shading Data to the Camera

相机设备将增益校正数据存储在名为“UserGainShading1”、“UserGainShading2”等的文件中。

要将增益校正数据上传到相机,请使用ODevFileStream类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Name of the file in the camera where shading data is stored
static const char CameraFilename[] = "UserGainShading1";

// ...

// Read data from local file into pBuf
char *pBuf = new char[Size];
size_t read = fread(pBuf, 1, Size, fp);
fclose(fp);

if (read != Size) {
RUNTIME_EXCEPTION("Failed to read from file '%s'\n", pLocalFilename);
}

// Transfer data to camera
ODevFileStream stream(&camera.GetNodeMap(), CameraFilename);
stream.write(pBuf, streamsize(Size));
if (stream.fail()) {
// Do some error handling
// ...
}

stream.close();
delete[] pBuf;

// ...

上述代码截取自示例ParametrizeCamera_Shading

Download Shading Data From the Camera

从相机下载增益校正数据到缓冲区上跟上传过程一样简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define FILEBUFFSIZE 1024   // size of receive buffer!
// Name of the file in the camera where shading data is stored
static const char CameraFilename[] = "UserGainShading1";

char *pBuffer = new char[FILEBUFFSIZE];

// ...

// Transfer data from camera
IDevFileStream stream(&camera.GetNodeMap(), CameraFilename);
if (stream.fail()) {
RUNTIME_EXCEPTION("Failed to open camerafile file '%s'\n", CameraFilename);
}
int nBytesRead = 0;
if (stream.is_open()) {
do {
stream.read(pBuffer, FILEBUFFSIZE); // read max. FILEBUFFSIZE number of bytes from camera
nBytesRead = stream.gcount(); // get number of bytes read
if (nBytesRead > 0) {
// Do something with the received bytes in pBuffer e.g. writing to disk
// file.write(pBuffer, nBytesRead);
// ...
}
} while (nBytesRead == FILEBUFFSIZE); // if nBytesRead == FILEBUFFSIZE maybe there are more data to receive
}

stream.close();
delete [] pBuffer;

Waiting for Multiple Events

Wait Objects

在应用程序中,一般会有一个单独的线程专门用于抓取图像。通常,这个抓取线程必须与应用程序的其他线程同步。例如,应用程序可能想要通知抓取线程终止。

可以使用等待对象(Wait Objects)来同步线程。等待对象的概念允许您获取关于事件的信息,例如抓取到的图像。

等待对象是操作系统特定对象的抽象,可以是有信号的或无信号的。等待对象提供了一种等待操作,该操作会阻塞直到等待对象被标记为有信号状态。

虽然pylon接口返回Pylon::WaitObject类型的对象,但pylon还提供了Pylon::WaitObjectEx类,用户应用程序需要实例化这个类。使用静态工厂方法WaitObjectEx::Create()来创建这些等待对象。

1
2
3
4
5
#include <pylon/PylonIncludes.h>
using namespace Pylon;
// ...

WaitObjectEx wo( WaitObjectEx::Create() );

WaitObjectEx::Signal()方法用于标记一个等待对象。WaitObjectEx::Reset()方法可以用来将等待对象置为无信号状态。

1
2
3
4
5
// Put w0 into the signaled state
w0.Signal();

// Put w0 into the non-signaled state
w0.Reset();

对于Windows操作系统,Pylon::WaitObjectPylon::WaitObjectEx类是对原生Win32对象的封装。可以创建这些类的实例来封装已存在的句柄:

1
2
3
4
5
6
7
8
using namespace Pylon;

// Create or retrieve a handle for a Win32 object that can be signaled and used
// to wait for, e.g., an event or mutex.
HANDLE h = CreateOrGetAHandle();

// Create a wait object from a given handle
WaitObjectEx wo(h); // When wo is destroyed, the handle remains valid!

默认情况下,封装的句柄会被复制。但也可以让WaitObjectEx接管句柄的所有权:

1
2
3
4
5
6
7
8
using namespace Pylon;

// Create or retrieve a handle for a Win32 object that can be signaled and used
// to wait for, e.g., an event or mutex.
HANDLE h = CreateOrGetAHandle();

// Create a wait object from a given handle
WaitObjectEx wo(h, true); // When wo is destroyed, the handle will be closed!

WaitObjectEx类适用于封装那些可以触发信号操作的对象的句柄。这不适用于线程句柄或可等待计时器。要封装这种对象(线程句柄或可等待计时器),使用WaitObject类代替。

1
2
3
4
5
6
7
8
using namespace Pylon;

// Create or retrieve a handle for a Win32 object whose handle can be used
// to wait for but can't be signaled, e.g. a thread, or waitable timer
HANDLE h = CreateOrGetAHandle();

// Create wait object that can't be signaled from a given handle
WaitObject wo(h); // When wo is destroyed, the handle remains valid!

Paw5zx注:

在Linux中,Pylon的WaitObject是基于文件描述符实现的,其等待操作是使用poll()实现的。

Container for Wait Objects

Pylon::WaitObjects类是一个用于存储等待对象的容器,提供了两种等待容器中等待对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Create a container and insert two wait objects
WaitObjects waitObjects;
waitObjects.Add(w0);
waitObjects.Add(w1);

// Wait for three seconds until any of the wait objects get signaled
unsigned int index;
if ( waitObjects.WaitForAny( 3000, &index) ) {
cout << "WaitObject w" << index << " has been signaled" << endl;
}
else {
cout << "Timeout occurred when waiting for wait objects" << endl;
}

// Wait for three seconds until all of the wait objects are signaled
if ( waitObjects.WaitForAll(3000) ) {
cout << "All wait objects are signaled" << endl;
} else {
cout << "Timeout occurred when waiting for wait objects" << endl;
}

Example

以下代码片段演示了如何在抓取线程中使用WaitForAny()方法同时等待缓冲区和终止请求。

准备抓取后,应用程序的主线程启动抓取线程并休眠5秒。

1
2
3
4
5
6
7
8
9
10
11
12
// 启动抓取线程。抓取线程开始图像采集并抓取图像
cout << "Going to start the grab thread" << endl;
StartThread();

// 让线程抓取图像5秒
#if defined(PYLON_WIN_BUILD)
Sleep(5000);
#elif defined(PYLON_UNIX_BUILD)
sleep(5);
#else
#error unsupported platform
#endif

抓取线程设置了一个等待对象容器,包括StreamGrabber的等待对象和一个Pylon::WaitObjectEx。后者被主线程用来请求终止抓取:

1
2
3
4
5
// 创建并准备等待对象容器
WaitObjects waitObjects;

waitObjects.Add(camera.GetGrabResultWaitObject()); // 获取抓取结果通知
waitObjects.Add(m_TerminationEvent); // 获取终止请求通知

然后抓取线程进入一个无限循环,开始等待任何一个等待对象:

1
2
3
4
5
6
7
8
CGrabResultPtr result;   // 抓取结果
bool terminate = false;
while (!terminate) {
if (!waitObjects.WaitForAny(INFINITE, &index)) {
// 发生超时,当使用INFINITE时永不发生
cerr << "Timeout occurred????" << endl;
break;
}

WaitForAny()方法返回true时,index的值用于判断是缓冲区已被抓取还是存在一个终止抓取的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch (index)
{
case 0: // 一个已抓取的缓冲区可用
if (m_Camera.RetrieveResult(0, result, TimeoutHandling_Return)) {
if (result->GrabSucceeded()) {
cout << "Successfully grabbed image " << ++nSucc << endl;
unsigned char* pPixel = (unsigned char*) result->GetBuffer();
// 处理缓冲区......
}
} else {
cerr << "Failed to retrieve result" << endl;
terminate = true;
}
break;

case 1: // 收到一个终止请求
terminate = true;
break;
} // switch

主线程通过调用WaitObjectExSignal()方法向抓取线程发出终止信号:

1
2
3
// 发出终止请求
cout << "Going to issue termination request" << endl;
m_TerminationEvent.Signal();

主线程等待直到抓取线程终止。如何从原生Win32线程句柄创建一个Pylon::WaitObject展示如下,该WaitObject用于等待。

1
2
3
4
// 与线程同步,即等待它终止
cout << "Going to join with the thread" << endl;
WaitObject woThread(m_hThread);
woThread.Wait(INFINITE); // 等待直到线程终止

Paw5zx注:

WaitObject是pylon中很有意思的设计,后续有时间可以深入了解一下。

Interruptible Wait Operation

在上节中展示了如何使用Pylon::WaitObjectEx来通知线程终止。

作为使用专用等待对象获取外部事件通知的替代方法,可以使用WaitObject::WaitEx()方法进行等待。这种等待操作可以被中断。对于pylon的Windows版本,WaitEx()可以通过排队的APC(异步过程调用)或I/O完成例程来中断。对于pylon的Linux和macOS版本,WaitEx()可以通过信号来中断。

对应于WaitObject::WaitEx()方法,Pylon::WaitObjects类提供了可中断的WaitForAnyEx()WaitForAllEx()方法。

Application Settings for High Performance

对于需要以恒定帧率和低抖动进行图像处理的应用,推荐以下设置:

  • 数据包大小应调整为网络适配器和网络设置支持的最高值,例如,数据包大小调整到8092字节。
  • 抓取循环线程应该将其优先级设置在实时优先级范围内。抓取循环线程是调用RetrieveResult()方法的线程。推荐的值为24或更高。线程优先级可以使用SetRTThreadPriority方法调整。由即时相机对象可选地提供的抓取循环线程的优先级可以通过GrabLoopThreadPriorityOverrideGrabLoopThreadPriority参数进行调整。
  • 内部即时相机抓取引擎线程的优先级应设置在实时优先级范围内。推荐的值为25或更高。默认优先级为25。抓取引擎线程的优先级必须高于抓取循环线程的优先级。抓取引擎线程的优先级可以通过InternalGrabEngineThreadPriorityOverrideInternalGrabEngineThreadPriority参数进行调整。

注意:

在使用实时线程优先级时,要非常小心,确保没有高优先级的线程会消耗所有可用的CPU时间。

Programming Using the pylon Low Level API

Instant Camera类使用Low Level API进行操作。这意味着之前的API,现在称为Low Level API,仍然是pylon C++ API的一部分,并将在未来继续存在。Low Level API可用于现有的应用程序以及那些无法通过使用Instant Camera类解决的罕见的高级用例。关于如何使用Low Level API编程的更多信息,可以在此处找到。

Paw5zx注:

虽然Instant Camera类提供了简化和抽象的接口,但若需要进行定制操作,可以通过Low Level API来实现更精细的控制。

Migrating Existing Code for Using SFNC 2.x-Based Camera Devices

Camera Emulator

Pylon提供了一个相机仿真传输层,与其他传输层类似。这个相机仿真传输层可以创建简单的相机仿真设备,允许您在没有将物理相机设备连接到计算机的情况下开发应用程序。虽然仿真器的功能有限,但它能够为不同的位深生成测试图像。

可以通过设置环境变量<⁠PYLON_CAMEMU>来控制可用的仿真器设备数量。

示例:

1
PYLON_CAMEMU=2

这将提供两个仿真器设备。这些设备可以通过pylon API和pylon Viewer程序访问。

当未设置<⁠PYLON_CAMEMU>时,不提供仿真器设备。

注意:

支持的最大仿真设备数量为256个。

Image Decompression

暂略

Multi-Component Grab Results

暂略

Static Defect Pixel Correction

暂略

  • 标题: Pylon C++ Advanced Topics
  • 作者: paw5zx
  • 创建于 : 2024-11-20 16:04:35
  • 更新于 : 2024-11-23 23:13:15
  • 链接: https://paw5zx.github.io/pylon-cpp-advanced-topics/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
Pylon C++ Advanced Topics