更好的模板创建方式?

6

我正在尝试为此设计一个更好的方案!

考虑到我们有一个继承自模板矩阵库(在这种情况下是Eigen,但它可以是任何东西)的模板Image类。

template <typename T>
class Image : public Eigen::Matrix <T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>

现在考虑一个情况,我们希望编写一个函数来处理从文件中读取图像。当然,图像可以是不同的类型,如无符号字符、uint16_t、浮点数,甚至具有不同的通道,如灰度、RGB或RGBA。
因此,我们可以使用模板类来轻松处理这些。比如:
Image<RGB<unisgned char>> or Image<RGBA<float>>

当我们知道图像的类型,比如单色8位,处理起来就变得简单了。

Image<unisgned char> image = ReadImage(const char* const filename);

或者它甚至可以是
Image<unisgned char> image;
bool b = ReadImage(const char* const filename, Image<unisgned char>& image)

然而,当读取图像文件时,我们无法事先知道其类型。例如,Tiff和png都支持8位和16位,而tiff甚至支持float。在这种情况下,使用上述任何函数都是不可能的。然而,我们可以使用一个模板工厂类来解决这个问题。为此,我们首先需要引入一个BaseImage类。
class BaseImage
{
public:
    inline BaseImage() {};
    virtual inline ~BaseImage() {};

    virtual inline int Width() const = 0;
    virtual inline int Height() const = 0;
    virtual inline int Depth() const = 0;
    etc...
};

template <typename T>
class Image : public BaseImage, public Eigen::Matrix <T, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>

然后我们可以有一个工厂类(factory class),在其中传递我们的unsigned char*,float*等数据类型,并让它处理创建过程。

class ImageFactory
{
  typename <T>
  static BaseImage* createImage(const T* const src, const int& width, const int& height, const int& depth)
  {
    if (depth == 1)
    {
      return new Image<T>();
    }
    else if (depth == 3)
    {
      return new Image<RGB<T>>();
    }
    etc...
  }
}

当然,这迫使我使用动态分配和继承。

我想我可以通过将其隐藏在一个负责处理它的类中来避免动态分配。 在构造函数中,工厂类将被调用。

class Image2
{
public:
    Image2(const char* const path)
    {
        // where ReadImage and external function that will call ImageFactory
        pBaseImage = ReadImage(path);
    }
    ~Image2();

private:
    BaseImage* pBaseImage;
};

无论哪种情况,我的 BaseImage 类都必须公开我需要从矩阵库中使用的所有功能,这有点违背了继承的目的。
问题是,是否存在更好的设计方案,因为现在变得相当麻烦。

为什么在这里要使用模板?我宁愿在单个类“Image”内使用简单的联合(或variant)。此外,您能展示一个“float类型”的图像示例吗?您是指点将由浮点值颜色表示吗? - Nelfeal
@Nelxiost 这会非常慢。float 适用于单色图像。 - polkovnikov.ph
为什么要使用矩阵来表示图像?虽然两者都是二维有序数据,但其中一个基本上是线性变换,而另一个则不是。 - Yakk - Adam Nevraumont
@Nexiost确实,联合是我考虑过的一种方法,但除了速度慢之外,对于我只使用无符号char*的情况,它还需要更多的内存。 - xerion
1
另一种选择是只使用原始缓冲区(其类型在读取图像头之前我不会知道)。原始缓冲区可以很好地包装在一个类周围,以隐藏void(或unisnged char)的丑陋,并且我必须有某种类型来区分它。然后,我想我可以使用type_traits,在需要时创建我的Image<T>模板。这是我另一个想法,但我不喜欢两阶段构建。 - xerion
显示剩余5条评论
1个回答

1
如果可以在评论中展示代码,我会把这个作为评论发布... 对我而言,这似乎很明显,你可能也想过。为什么这不是你要找的东西呢?
class Image
{
public:
  Image (int bytesPerPixelIn = 1) { data_ = NULL; bytesPerPixel=bytesPerPixelIn;};

  void readImage (int width, int height);
private:
  int       bytesPerPixel_;
  void*     data_;
};

void Image::read (int width, int height, ...)
{
   data_ = new unsigned char [width*height*pixelSize(imageType_)];
   //...
}

//And you can inherit from this if need be to add special functions 
//like interleaving in BMP files

编辑:好的,我明白了。这个更好吗?
template <typename PixelType>
class Image
{
public:
  Image () { data_ = NULL; };

  virtual void allocateImage (int width, int height)
  {
    data_ = new PixelType [width*height]; 
  }
  virtual void readImage (int width, int height) = 0;
private:
  PixelType*     data_;
};

template <typename PixelType>
class ImageTIFF: public Image <PixelType> ...

或者这个:
template <typename PixelType, int WIDTH, int HEIGHT>
class Image
{
public:
  Image () { }

  virtual void readImage () = 0;
private:
  PixelType*     data_ [WIDTH][HEIGHT];
};

template <typename PixelType, int WIDTH, int HEIGHT>
class ImageTIFF: public Image <PixelType, WIDTH, HEIGHT> ...

后者消除了动态内存...但是没有动态内存,你就不能有调整大小的函数。我会使用动态内存。
肯定需要继承不同的文件格式,因为它们不能以相同的方式读取图像。

--

好的。这个怎么样?这里的BaseImage就像Java中的“接口”,它将确保派生类具有它们所需的函数。

class BaseImage 
{
public:
  virtual void allocateImage (int width, int height) = 0;
  virtual void readImage     (int width, int height) = 0;
};

template <typename PixelType>
class Image: public BaseImage ... /* and so on */

BaseImage* myTIFFImage = readTIFFImage (/* whatever goes here */);

并不是真正的通用,也不是类型安全的。你需要为图像可以持有的每种类型编写代码。 此外,它无法帮助编译器进行优化,如向量化(这一点可能错误)。 - xerion
当然,动态内存选项更好,但我想主要问题是从readTiffImage(const char*)函数中返回什么?Tiff可以保存各种类型,唯一的解决方案是拥有一个基类,你可以返回它,这也是我所考虑的。唯一的区别是我们如何构建基类,我依赖Eigen进行优化。 - xerion
由于您需要readTIFFImage返回一个不指定任何内容的类,包括像素类型,那么这听起来像是一个基类,它指定了一些必要的函数,特别是readImage,但没有其他内容。我在上面放置了代码,看看您认为如何解决。 - Topological Sort

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接