系统概述

本章的目的是向您概述 ORFEO Toolbox 系统。我们建议您阅读本章以了解OTB的应用范围和领域。在本章中,我们将参考 OTB featuresITK features 没有区别。请记住,OTB使用ITK作为其核心要素,因此OTB的所有基本要素都来自ITK。OTB将ITK的功能扩展到遥感图像处理领域。我们受益于为ITK选择的开源开发方法,它允许我们以比封闭源代码世界中的情况少得多的工作量提供一组令人印象深刻的功能!

系统组织

Orfeo工具箱由几个子系统组成:

基本系统概念
像任何软件系统一样,OTB是围绕一些核心设计概念构建的。OTB使用ITK的那些。一些更重要的概念包括泛型编程、用于内存管理的智能指针、用于自适应对象实例化的对象工厂、使用命令/观察器设计范例的事件管理以及多线程支持。
数学系
OTB,因为ITK使用VxL的VNL数值库。这些是Netlib Fortran数值分析例程(http://www.netlib.org).)中易于使用的C++包装器
数据表示和访问
两个主要类用于表示数据: otb::Imageitk::Mesh 上课。此外,在ITK中使用了各种类型的迭代器和容器来保存和遍历数据。其他重要但不太受欢迎的类也用于表示直方图等数据。
ITK数据处理流水线
数据表示类(称为 data objects )由以下人员操作 filters 其继而可以被组织成数据流 pipelines 。这些管道保持状态,因此仅在必要时执行。它们还支持多线程,并且支持流(即,可以对数据片段进行操作,以最大限度地减少内存占用)。
IO框架
与数据处理流水线相关联的是 sources 、启动管道的筛选器,以及 mappers 、终止管道的筛选器。源和映射器的标准示例如下 readerswriters 分别进行了分析。读取器输入数据(通常来自文件),写入器从管道输出数据。 Viewers 是映射器的另一个例子。
空间对象
几何形状使用ITK空间对象层次结构在OTB中表示。这些类旨在支持ITK中的解剖结构建模。OTB使用它们来为制图元素建模。使用公共的基本接口,空间对象能够以各种不同的方式表示空间区域。例如:网格结构、图像遮罩和隐式方程可以用作底层表示方案。空间对象是一种自然的数据结构,用于交流分割方法的结果,并在分割和配准方法中引入几何先验。
ITK的注册框架
灵活的配准框架支持四种不同类型的配准:图像配准、多分辨率配准、基于PDE的配准和有限元配准。
有限元框架
ITK包括一个子系统,用于解决一般的有限元问题,特别是非刚性配准。有限元程序包包括网格定义(节点和元素)、载荷和边界条件。
水平集框架
Level Set框架是一组类,用于创建过滤器以使用迭代的有限差分更新方案来求解图像上的偏微分方程组。Level Set框架由有限差分解算器组成,包括稀疏水平集解算器、通用水平集分割过滤器和几个特定的子类,包括基于阈值、Canny和Laplace的方法。
包装
ITK使用一个独特的、功能强大的系统来生成TCL和Python等解释性语言的接口(即“包装器”)。GCC工具用于生成任意复杂C++代码的XML描述;然后使用CSWIG将该XML描述转换为使用 SWIG 包裹。OTB目前不使用该系统。

基本系统概念

本节介绍了ITK中的一些核心概念和实施特点,因此也描述了OTB中的一些核心概念和实施特征。

泛型编程

泛型编程是一种组织由泛型或可重用软件组件组成的库的方法。我们的想法是让软件能够以一种高效、适应性强的方式“插接在一起”。泛型编程的基本思想是 containers 为了保存数据, iterators 以访问数据,以及 generic algorithms 使用容器和迭代器来创建高效的基本算法,如排序。泛型编程是用C++实现的 template 编程机制和STL标准模板库的使用。

C++模板是一种编程技术,允许用户根据一个或多个未知类型编写软件 T 。要创建可执行代码,软件的用户必须指定所有类型 T (称为 template instantiation ),并用编译器成功地处理代码。这个 T 可以是本机类型,如 floatint ,或 T 可以是用户定义的类型(例如, class )。在编译时,编译器确保模板化的类型与实例化的代码兼容,并且必要的方法和运算符支持这些类型。

ITK在其实现中使用了泛型编程技术。这种方法的优点是,只需定义适当的模板类型,就可以支持几乎无限种类的数据类型。例如,在OTB中,可以创建由几乎任何类型的像素组成的图像。此外,类型解析是在编译时执行的,因此编译器可以优化代码以提供最高性能。泛型编程的缺点是,许多编译器仍然不支持这些高级概念,无法编译OTB。即使它们这样做了,它们也可能会因为最简单的语法错误而产生完全无法识别的错误消息。

包括文件和类定义

在ITK和OTB中,类最多由两个文件定义:一个头文件 .h 文件和实现文件- .cxx 如果是非模板化类,并且 .hxx 如果是模板化类。头文件包含类声明和格式化注释,DOOXO文档系统使用这些声明和格式化注释自动生成HTML手册页。

除了类头文件,还有一些其他重要的ITK头文件。

itkMacro.h
定义标准的系统范围宏(如 Set/Get 、常量和其他参数)。
itkNumericTraits.h
定义本机类型的数字特征,如其最大和最小可能值。
itkWin32Header.h
用于定义操作系统参数以控制编译过程。

对象工厂

OTB中的大多数类都是通过 object factory 机制。也就是说,不是使用标准的C++类构造函数和析构函数,而是使用静态类创建OTB类的实例 New() 方法。事实上,构造函数和析构函数是 protected: 因此,通常不可能在堆上构造OTB实例。(注意:此行为与派生自 itk::LightObject 。在某些情况下,出于提高速度或减少内存占用的需要,类不能从LightObject派生,在这种情况下,可以在堆上创建实例。这样的类的一个例子是 itk::EventObject 。)

对象工厂使用户能够通过将一个或多个工厂注册到 itk::ObjectFactoryBase 。这些注册的工厂支持该方法 CreateInstance(classname) 它接受要创建的类的名称作为输入。工厂可以根据包括计算机系统配置和环境变量在内的多个因素来选择创建类。例如,在特定应用中,OTB用户可能希望部署使用专用图像处理硬件实现的他们自己的类(即,以实现性能增益)。通过使用对象工厂机制,可以在运行时将特定OTB筛选器的创建替换为此类自定义类。(当然,该类必须提供与它要替换的API完全相同的API。)为此,用户编译他们的类(使用相同的编译器、构建选项等)并将目标代码插入共享库或DLL中。然后,该库被放置在 OTB_AUTOLOAD_PATH 环境变量。在实例化时,对象工厂将定位库,确定它可以使用工厂创建具有特定名称的类,并使用工厂创建实例。(注:如果 CreateInstance() 方法找不到可以创建命名类的工厂,则类的实例化将返回到通常的构造函数。)

在实践中,对象工厂主要(通常是透明地)由OTB输入/输出(IO)类使用。对于大多数用户来说,最大的影响是对 New() 方法来创建类。一般而言, New() 方法是通过宏声明和实现的 itkNewMacro() 发现于 Modules/Core/Common/include/itkMacro.h

智能指针和内存管理

从本质上讲,面向对象的系统通过各种对象类型或类来表示和操作数据。当一个特定的类被实例化以产生该类的一个实例时,就会发生内存分配,以便该实例可以存储数据属性值和方法指针(即vtable)。然后,在程序的正常操作期间,该对象可以被其他类或数据结构引用。通常,在程序执行期间,对该实例的所有引用都可能消失,此时必须删除该实例以恢复内存资源。然而,知道何时删除实例是困难的。太快删除实例会导致程序崩溃;删除太晚会导致内存泄漏(或过度内存消耗)。这种分配和释放内存的过程称为内存管理。

在ITK中,内存管理是通过引用计数来实现的。这与包括Java在内的许多系统使用的另一种流行的方法--垃圾收集--形成了鲜明对比。在引用计数中,保留对每个实例的引用数量的计数。当引用变为零时,对象会自行销毁。在垃圾回收中,后台进程会清理系统,识别系统中不再引用的实例并将其删除。垃圾收集的问题在于删除内存的实际时间点是可变的。当对象大小可能很大(考虑大小为千兆字节的大型3D体积)时,这是不可接受的。引用计数会立即删除内存(一旦对某个对象的所有引用消失)。

引用计数是通过 Register()/Delete() 成员函数接口。OTB对象的所有实例都有一个 Register() 由引用它们的任何其他对象对它们调用的方法。这个 Register() 方法递增实例的引用计数。当对实例的引用消失时, Delete() 方法在递减引用计数的实例上调用--这等效于 UnRegister() 方法。当引用计数返回零时,实例被销毁。

通过使用一个名为 itk::SmartPointer 。智能指针的作用类似于常规指针(例如支持运算符 ->* ),但会自动执行 Register() 在引用实例时,以及 UnRegister() 当它不再指向实例时。与OTB中的大多数其他实例不同,智能指针可以在程序堆栈上分配,并在创建智能指针的作用域关闭时自动删除。因此,您应该 rarely if ever call Register() or Delete() 在OTB。例如:

void MyRegistrationFunction()
{ // Start of scope
  // here an interpolator is created and associated to the
  // SmartPointer "interp".
  InterpolatorType::Pointer interp = InterpolatorType::New();
} // End of scope

在此示例中,创建了引用计数的对象(使用 New() 方法),引用计数为1。对智能指针的赋值 interp 不会更改引用计数。在范围的尽头, interp 被销毁,则为实际内插器对象的引用计数(由 interp )递减,并且如果它达到零,则插值器也被破坏。

请注意,在ITK中,智能指针始终用于引用派生自 itk::LightObject 。方法调用和函数调用通常返回指向实例的“真实”指针,但它们会立即分配给SmartPointer.当速度和/或内存需求需要更小、更快的类时,原始指针用于非LightObject类。

数据表示

otb::Image 表示一个 n -对数据进行多维、定期抽样。采样方向与每个坐标轴平行,并且可以指定采样的原点、像素间距和每个方向上的样本数(即,图像尺寸)。OTB中的样本或像素类型是任意的-模板参数 TPixel 指定模板实例化时的类型。(实例化图像类时还必须指定图像的维度。)关键是,如果要在所有情况下编译代码(例如,要由使用这些操作的特定过滤器处理),则像素类型必须支持某些操作(例如,加法或差)。实际上,OTB用户将使用C++简单类型(例如, intfloat )或预定义的像素类型,并且很少创建新类型的像素类。

关于图像的一个重要的ITK概念是,图像的矩形、连续的部分称为 regions 。区域用于指定要处理图像的哪一部分,例如在多线程中,或将哪部分保存在内存中。在ITK中,有三种常见的区域类型:

  1. LargestPossibleRegion -完整的形象。
  2. BufferedRegion -保留在内存中的图像部分。
  3. RequestedRegion -滤镜或其他类在操作图像时所请求的区域部分。

这个 otb::Image 类扩展了 itk::Image 以便考虑到特定的遥感特征,如地理预测等。

数据处理管道

虽然使用数据对象(例如,图像)来表示数据, process objects 是对数据对象进行操作并可能产生新数据对象的类。流程对象被分类为 sourcesfilter objects ,或 mappers 。源(如读取器)生成数据,筛选器对象接收数据并对其进行处理以生成新数据,映射器接受数据以输出到文件或其他系统。有时,术语 filter 被广泛地用来指所有这三种类型。

数据处理流水线将数据对象(例如,图像)和处理对象捆绑在一起。管道支持自动更新机制,当且仅当筛选器的输入或内部状态更改时,该机制才会使筛选器执行。此外,数据流水线支持 streaming ,能够自动将数据拆分成更小的片段,逐个处理这些片段,并将处理后的数据重新组合成最终结果。

通常,数据对象和过程对象使用 SetInput()GetOutput() 方法如下:

typedef otb::Image<float,2> FloatImage2DType;

itk::RandomImageSource<FloatImage2DType>::Pointer random;
random = itk::RandomImageSource<FloatImage2DType>::New();
random->SetMin(0.0);
random->SetMax(1.0);

itk::ShrinkImageFilter<FloatImage2DType,FloatImage2DType>::Pointer shrink;
shrink = itk::ShrinkImageFilter<FloatImage2DType,FloatImage2DType>::New();
shrink->SetInput(random->GetOutput());
shrink->SetShrinkFactors(2);

otb::ImageFileWriter::Pointer<FloatImage2DType> writer;
writer = otb::ImageFileWriter::Pointer<FloatImage2DType>::New();
writer->SetInput (shrink->GetOutput());
writer->SetFileName("test.raw");
writer->Update();

在本例中,源对象 itk::RandomImageSource 连接到 itk::ShrinkImageFilter ,并且收缩滤镜连接到映射器 otb::ImageFileWriter 。当 Update() 方法时,数据处理管道会按顺序执行每个筛选器,最终将最终数据写入磁盘上的一个文件。