ITK 中的 ImageIO 图像输入输出结构框架

ITK 中进行图像读写时使用的接口是完全一样的,针对不同的图像文件格式,输入输出系统自动选择使用对应的子系统进行处理。而且,用户还可以自定义图像格式,扩展 ITK 的图像输入输出处理库。  

ITK 中的图像输入输出系统框架有如下特点(VTK 中是一样的):

1.可移植:ITK 的图像输入输出库能够在不同的计算机体系结构上工作,使用 C++ 编写,没有调用任何操作系统相关的函数,平台独立。

2.模块性:该图像输入输出库是“自包含”的,与 ITK 的其它子系统相互独立,并且可以在其它系统中重用(VTK 中的图像输入输出系统应该是同一个系统,但我并不懂 VTK,只是猜测)。为了达到这个目的,图像以两种形式存在:1)在磁盘中以文件存在;2.在内存中以数据结构存在。该 IO 库只负责图像的加载和保存,没有任何与特定图像应用相关的功能。这要求一个内部类能够对内存中的图像数据格式与存储与磁盘上的图像文件进行转换。这个功能由下图的 FilterIoToImage 类完成。

3.可扩展性:该 IO 库支持新的图像文件格式,而不需要修改任何已存在的代码:"Open to extension, but closed to modification”。这个目标是通过一个组合的设计模式达到的,即 Pluggable object factory(可插入式对象工厂模式),它通过定义一个具有标准接口的抽象超类达到这一目的。Pluggable object factory 允许在运行时增加新的子类,或称为 "plugged”。

4.透明性:理想情况下,用户只需要传递一个文件名便可操作图像的数据,而不必担心图像文件格式的实现细节。这一目标通过两个机制实现:1)所有图像文件格式的共同点被抽取出来,作为一个超类。具体的图像文件格式从此类进行继承。这样,用户可以通过一个共同的接口操作 IO 库所支持的所有图像文件。2)pluggable object factory 能够自动实例化处理给定类型图像文件的子类对象。

有两点必须熟记于心:1)图像在磁盘中与内存中是以不同形式存在的,这们是两个完全不再的概念。应用程序处理在内存中处理图像,我们使用一个内部类 FilterIoToImage 进行转换。2)我们使用了 pluggable object factory 模式,动态地实例化处理特定格式图像文件的子类对象。当我们需要支持一种新的文件格式时,需要从 ImageIO 继承一个新的子类处理此种图像格式的细节。另外,能够创建该新子类对象实例的工厂也被实例化,并且在主字典(a master dictionary)中注册。该主字典被用来在运行时、动态地选择合适的 ImageIO 子类进行实例化,以处理特定的图像格式。

类结构图如下所示:

 

             类层次结构,其中,斜体表示抽象类;箭头表示继承关系,即 "is-a” 关系;三角表示聚合关系,即 "has-a” 关系。

主要类代码如下:

1. ImageIO:IO 库的核心,抽象类。用于处理储存于磁盘上的图像文件,并且定义程序员与 IO 库交互的接口。提供加载和保存图像文件的方法,以及读取任何相关的头文件信息(如,维数、颜色深度等)。另外,我们这里定义了一个特化的方法,以允许我们将 2D 图像文件装配成一个 3D 体文件,并且从 3D 图像中抽取单一的 2D 切片。

   1:   
   2:  //ImageIO
   3:  class ImageIO 
   4:  {
   5:  public:
   6:      virtual void Load() = 0;
   7:      virtual void Save() = 0;
   8:      virtual void Save2DSlice(int sliceNumber) = 0;
   9:      virtual void Assemble3DVolume() = 0;
  10:   
  11:      //返回该类可以处理的文件类型的扩展名
  12:      virtual string GetSupportedFileExtensions() const = 0; 
  13:   
  14:  private:
  15:      void* imageData;
  16:  };
  17:   
  18:  class ImageIOJpeg 
  19:  {
  20:  public:
  21:      string GetSupportedFileExtensions() const 
  22:      {
  23:          return ".jpg"; 
  24:      }
  25:  };
       其中,数据成员 imageData 被定义为指向 void 的指针,这样它便可以储存任何类型的图像数据,Load 和 Save 方法将该指针转换(typecast)成合适的数据类型。
 
2. Factory : pluggable object factory 基类。其中,Pluggable object factory 是一个组合的设计模式,它主要由:抽象工厂(Abstract Factory),提供从一个类层次结构中实例化对象的机制;单件(Singleton),确保一特定的类实例对象只有一个,且是全局对象。原型模式(Prototype),我们使用它从抽象的 pluggable object factory 工厂中生成具体的工厂。工厂维护一个字典(a dictionary),它用于将 key 映射为对象实例。
   1:   
   2:  //Factory
   3:  template <class Key, class Object> 
   4:  class Factory
    
   
   5:  {
   6:  public:
   7:      static Object* Create (const Key k) 
   8:      {
   9:          Factory
   
    * f = (dictionary->find(k))->second;
   
  10:          if (!f) 
  11:              return NULL
  12:          else 
  13:              return f->MakeObject();
  14:      }
  15:  protected:
  16:      Factory(const Key k)
  17:      {
  18:          static bool dictionaryInitialized = false;
  19:          if (!dictionaryInitialized) 
  20:          {
  21:              dictionary = new Dictionary;
  22:              dictionaryInitialized = true;
  23:          }
  24:          dictionary->insert(std::make_pair(k, this);
  25:      }
  26:   
  27:      virtual Object* MakeObject() const
  28:      {
  29:          /* implemented by subclasses. return instance of object that subclass is responsible for */
  30:      }
  31:  private:
  32:   
  33:      typedef Factory* FactoryPtr;

  
  34:      typedef std::map
     
       Dictionary;
     
  35:      static Dictionary* dictionary;
  36:  };
  37:   

公有成员方法 Create() 是程序员唯一可见的方法,它唯一的参数是一个在内部字典中用来进行索引的 key。如果在字典中找到该 key,则使用与其相关联的对象工厂实例化一个对象。字典是一个 static 数据成员。因此,所有的对象工厂间共享一个字典实例。

Abstract Facotory 构造函数的参数是一个 key,该 key 被插入字典中,字典保持 key 与具体的(concrete)工厂子类之间的映射关系(子类的构造函数通过 insert(std::make_pair(k, this); 中的 this 实现该功能,因为 this 表示指向自身的指针)。现在来看一下具体的子类工厂注册自己的过程,注意在构造函数中使用了静态布尔变量 dictionaryInitialized,该变量用于确保字典只被实例化一次,且必须在第一个具体的子类工厂注册自己之前。因为不同的平台及编译器在编译多个源文件时,静态变量的初始化顺序并不相同,所以这里有必要进行检查以确保字典在具体的工厂注册自身时已经初始化。

3. FactoryIO : 继承自 Factory 类。因为每一种格式的图像文件都有不同的扩展名(如,bmp, jpg, png 等等),所以可以使用图像格式的扩展名作为 key。这样,便可从 FactoryIO 类继承得到处理不同格式图像的具体工厂,每一种工厂仅仅负责实例化与之相关联的 ImageIO 类。如下,类 FactoryIOJpeg 用来处理 JPEG 格式的图像文件:

   1:   
   2:   
   3:  //FactoryIOJpeg
   4:  class FactoryIOJpeg : public FactoryIO 
   5:  {
   6:  public:
   7:      FactoryIOJpeg() : FactoryIO(myObject.GetSupportedFileExtensions()) {}
   8:   
   9:      //每个具体的工厂类都包含一个自身的静态实例
  10:      static const ImageIOJpeg myObject; 
  11:      ImageIO* MakeObject() const 
  12:      { 
  13:          return new ImageIOJpeg;
  14:      }
  15:      static const FactoryIOJpeg registerMyself;
  16:  };
  17:   
  18:   
 

具体工厂类的实现有两点需要注意:1.每个具体的工厂类包含一个自身的静态实例,它唯一的目的便是在字典中注册自己。2.每个具体的工厂类都有一个静态的 ImageIO Object 对象实例,负责克隆,它的目的是用来在注册过程中决定 key 值。

4. Image : 该类保存在内存中,这里不关心它的设计与实现。我们只需要知道它的参数是应用程序中处理的图像的像素类型即可。PixelType 没有必要与磁盘中图像文件的数据类型相同。

5. FilterIOToImage : 最后一个重要的类,因为我们将图像文件在磁盘中与内存中的形式区分成两种不同的类型,而且我们还想要这两个类不知道对方的任何信息。因此我们必须使用一个内部过滤器类,即 FilterIOToImage 在这两种类型之间进行转换。

   1:   
   2:   
   3:  //FilterIOToImage
   4:  class FilterIOToImage
       
      
   5:  {
   6:  public:
   7:      FilterIOToImage(const char* fileName, Image
      
       * image);
      
   8:      FilterIOToImage(Image
      
       * image, 
       const 
       char* fileName);
      
   9:      virtual void CopyPixelsToIO();
  10:      virtual void CopyPixelsToImage;
  11:  protected:
  12:      ImageIO* myIO;
  13:      Image
      
       * myImage;
      
  14:      static const Factory
      
        myIOFactory;
      
  15:  };
  16:   
  17:   

类 FilterIOToImage 包含一个 ImageIO 对象以及 Image Object 对象的引用。因为 FilterIOToImage 类和 Image 类的参数都是像素类型,所以 FilterIOToImage 知道关于 Image 的模板参数。正如其名字暗示的一样,CopyPixelsToIO() 和 CopyPixelsToImage() 将图像数据从一种表示转换成另一种。通过继承 FilterIOToImage,该转换可以是任意复杂的形式。通过其构造函数可知,FilterIOToImage 通过分析文件名的扩展名实例化合适的 ImageIO Object,然后使用该对象加载或者保存图像数据。

使用时:

   1:   
   2:  //test
   3:  void main () 
   4:  {
   5:      FilterIOToImage<float>* loadFilter, saveFilter;
   6:      Image<float>* image = NULL;
   7:      loadFilter = new FilterIOToImage<float>("input.jpg", image);
   8:      // Start processing image
   9:      //
  10:      ...
  11:      // Done processing image. Save result 
  12:      saveFilter = new FilterIOToImage<float>(image, "output.bmp");
  13:   
  14:  }
  15:   
   

上面演示了如何从磁盘中加载一帧 jpg 格式的图像文件,以及如何将一幅图像以 bmp 格式保存到磁盘中,可以看到,其使用是非常简洁的。

常用的使用格式如下所示:

   1:   
   2:  //test
   3:  void main () 
   4:  {
   5:      FilterIOToImage<float>* loadFilter, saveFilter;
   6:      Image<float>* image;
   7:      const FactoryIO ioFactory;
   8:      ImageIO* loadIO, saveIO;
   9:      char inputFileName[] = "inputSlice*.jpg";
  10:      char outputFileName[] = "outputSlice.bmp";
  11:   
  12:      loadIO = ioFactory.Create(ParseFileExtension(inputFileName));
  13:      loadIO->SetFileName(inputFileName); 
  14:      loadIO->Assemble3DVolume();
  15:      loadFilter = new FilterIOToImage<float>(loadIO, image);
  16:      loadFilter->CopyPixelsToImage();
  17:      // Start processing image
  18:      //...
  19:   
  20:      // Done processing image. Save result 
  21:      saveIO = ioFactory.Create(ParseFileExtension(outputFileName));
  22:      saveIO->SetFileName(outputFileName); 
  23:      saveFilter = new FilterIOToImage<float>(saveIO, image);
  24:      saveFilter->CopyPixelsToIO();
  25:      saveIO->Save2DSlice(3);
  26:  }
  27:   
   
    如上演示了一些常用接口的使用方法,首先从一个 jpeg 格式图像的 2D 序列图像组装成一个 3D 图像文件。经过一定的处理后,又从该 3D 图像文件中提取出一个 2D 切片,并将其以 bmp 格式保存在磁盘中。上面还演示了磁盘图像文件与内存图像之间的转换。
    ITK 以及 VTK 都是出自同一公司,上面的框架就是其图像输入输出的核心部分。
0
0
猜你在找
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
快速回复 TOP
    个人资料
    ITK 中的 ImageIO 图像输入输出结构框架_第1张图片
    zhangcunli
    • 访问:80796次
    • 积分:1432
    • 等级:
    • 排名:第17778名
    • 原创:56篇
    • 转载:1篇
    • 译文:1篇
    • 评论:39条
    文章分类
  • C++(21)
  • DesignPattern(32)
  • ITK(18)
  • 其它(5)
  • 随感(4)
  • 霹雳相关(4)
  • linux(1)
  • Golang(0)
    文章存档
  • 2012年05月(1)
  • 2011年10月(1)
  • 2010年01月(5)
  • 2009年12月(19)
  • 2009年11月(7)
  • 2009年10月(11)
  • 2009年09月(12)
  • 2008年09月(2)
    阅读排行
  • ITK 编译安装(7696)
  • 初学者编译Linux内核,最简单ko模块(7352)
  • ITK 配准框架示例(6090)
  • ITK 编程步骤示例(3155)
  • ITK 数据源(3120)
  • 霹雳狂刀---回忆录(2458)
  • SVN 初步(2268)
  • ITK Viola-Wells 互信息测度配准示例(2215)
  • ITK 中的 ImageIO 图像输入输出结构框架(2043)
  • Multi-Resolution 多分辨策略示例(1798)
    评论排行
  • ITK 数据源(11)
  • ITK 配准框架示例(9)
  • ITK 编译安装(4)
  • Multi-Resolution 多分辨策略示例(3)
  • 使用 CL 编译器选项查看 C++ 类内存布局(3)
  • 霹雳狂刀---回忆录(3)
  • Command 命令模式(2)
  • Linux 下配置 ITK(1)
  • ITK Viola-Wells 互信息测度配准示例(1)
  • 初学者编译Linux内核,最简单ko模块(1)
    推荐文章
    • *Android自定义ViewGroup打造各种风格的SlidingMenu
    • * Android 6.0 运行时权限处理完全解析
    • * 数据库性能优化之SQL语句优化
    • *Animation动画详解(七)——ObjectAnimator基本使用
    • * Chromium网页URL加载过程分析
    • * 大数据三种典型云服务模式
    最新评论
  • 使用 CL 编译器选项查看 C++ 类内存布局

    jackap: 图片看不到了,可以修复下吗?

  • ITK 配准框架中的 Subject/Observer 模式及优化过程模拟演示-4

    aaaa_1111_: 楼主,您好!请问ITK中的三维配准有什么要求吗?DeformableRegistration8中优化...

  • ITK 数据源

    jamesLBJ: 楼主你好,请问ITK怎么读.mhd和.raw格式的?还是只能用vtk来读取?

  • Linux 下配置 ITK

    lj695242104: cmake 在ubuntu下gui?您说的按箭头操作根本就没出现啊!求解?

  • ITK 配准框架示例

    u014376282: ITKIO不存在啊

  • 霹雳布袋戏所有剧集

    u011683778: 怎么没有 霹雳震寰宇之龙战八荒?

  • 霹雳狂刀---回忆录

    u011472090: 我也是山西人,小时候没看完,现在网上的资源不太给力,模糊,没有国语的

  • ITK 编译安装

    TonyShengTan: 正在编译中,不过cmake'提示Performing Test CXX_HAS_WARNING-Wf...

  • 初学者编译Linux内核,最简单ko模块

    thfeathers: 不得在任何场合表达与公司有关的信息,给你个一级违规

  • ITK 配准框架示例

    gfhhgf: 这是一个itk自带的例子吧

你可能感兴趣的:(image,object,processing,Dictionary,磁盘,操作系统相关)