mardi 29 avril 2014

c ++ - étude de cas : polymorphisme pour traitement de l'Image - débordement de pile


I'm studying Digital Image Processing by myself and would be really grateful if somebody could comment whether polymorphism should be applied for this case or if there's a better class design.


Basically, a 2D Filter/Kernel can be either: non-separable or separable. An important kernel operation is the convolution and the way to compute it, depends on the filter type.


template < typename T >
class CKernel2D{
public:
//....
virtual CMatrix<T> myConvolution(const CMatrix<T> & input) = 0;
//....
};

template < typename T >
class CNonSeparableKernel : public CKernel2D<T> {
public:
//....
CMatrix<T> myConvolution(const CMatrix<T> & input );
void initNonSeparableFilter1( double, int );
//....
private:
CMatrix<T> m_Kernel;
};

template < typename T >
class CSeparableKernel2D : public CKernel2D<T>{
public:
//....
CMatrix<T> myConvolution(const CMatrix<T> & input );
void initSeparableFilter1( double, double );
//....
private:
std::vector<T> m_KernelX;
std::vector<T> m_KernelY;
};

Note that even the CSeparableKernel2D class could have two private members: CKernel1D<T> m_X, m_Y. The CKernel1D<T> class could have its own myConvolution method, i.e. myConvolutionRows, myConvolutionCols.


Also, usually, we would like to apply a set of filters (separable/non-separable) onto a given image, i.e. convolve an input image is with a given filter. Therefore, depending on the filter type, the corresponding myConvolution method should be called.


(1) Which should be the cleanest way to be able to do sth. like?


 CNonSeparableKernel<float> myNonSepFilter1;
myNonSepFilter1.initNonSeparableFilter1(3.0, 1);

CNonSeparableKernel<float> mySepFilter1;
mySepFilter1.initSeparableFilter1(0.5, 0.5);

std::vector<CKernel2D<float> > m_vFilterbank;
m_vFilterbank.push_back(myNonSepFilter1); // Would like to assign a non-sep filter.
m_vFilterbank.push_back(mySepFilter1); // Would like to assign a sep filter.

It seemed to me that the only way to do that is by using polimorphism, right?


CKernel2D<float> * pKernel2d = NULL;
pKernel2d = &mySepFilter1; m_vFilterbank.push_back(*pKernel2d);
pKernel2d = &myNonSepFilter1; m_vFilterbank.push_back(*pKernel2d);

(2) Now assuming that our filterbank is already filled with both type of kernels, to apply the convolution on an input image, it would be enough to do:


outputSeparable1    = m_vFilterbank.at(0).myConvolution(input);
outputNonSeparable1 = m_vFilterbank.at(1).myConvolution(input);

(3) Now imagine, I would like to have a friend convolution function with following prototype:


friend CMatrix<T> convolution(const CKernel2D<T> &, const CImage<T> &);

again, I would like that the proper myConvolution method is called depending on the kernel type. How could I achieve such operation? I read sth. about the Virtual Friend Function Idiom, do you think, it would make sense to apply that idiom for such a case?


All comments & suggestions are really welcomed ;-) I would really love to hear what do you think about this design? Is there a better way to design those functionalities?




Since image analysis requires a lot of computational power, good performances are importants. Everyone know that polymorphism is a great stuff but of course it adds a runtime abstraction layer, so, it is slower than statically linked code.


Since you are already using templates why you don't use a compile time abstraction layer using templates? Like STL does, you can wrap your algorithms in classes and pass them through template parameters.


I post here a simple example to show the principle.


template <typename T, typename FUNCTOR>
class ArrayTransformer
{
public:

static void Transform(T* array, int count)
{
for (int i = 0; i < count; ++i)
FUNCTOR::Transform(array[i]);
}

template <int N>
static void Transform(T (&array)[N])
{
for (int i = 0; i < N; ++i)
FUNCTOR::Transform(array[i]);
}
};

template <typename T>
class NegateTransformer
{
public:

static void Transform(T& value)
{
value = -value;
}
};

int main()
{
int array[] = { 1, 2, 3, 4, 5, 6 };
ArrayTransformer<int, NegateTransformer<int> >::Transform(array);
....
return 0;
}

New generation compilers can optimize this code quite well :) the overhead, if you are using a good compiler and you compile in release mode, will be zero.


Of course this makes sense if you have to call the inner functor thousand of times, if you have to call it only once you can just use polymorphism. You can also mix both techniques to obtain high performance in inner loops and in the same time easy usability at higher level.



I'm studying Digital Image Processing by myself and would be really grateful if somebody could comment whether polymorphism should be applied for this case or if there's a better class design.


Basically, a 2D Filter/Kernel can be either: non-separable or separable. An important kernel operation is the convolution and the way to compute it, depends on the filter type.


template < typename T >
class CKernel2D{
public:
//....
virtual CMatrix<T> myConvolution(const CMatrix<T> & input) = 0;
//....
};

template < typename T >
class CNonSeparableKernel : public CKernel2D<T> {
public:
//....
CMatrix<T> myConvolution(const CMatrix<T> & input );
void initNonSeparableFilter1( double, int );
//....
private:
CMatrix<T> m_Kernel;
};

template < typename T >
class CSeparableKernel2D : public CKernel2D<T>{
public:
//....
CMatrix<T> myConvolution(const CMatrix<T> & input );
void initSeparableFilter1( double, double );
//....
private:
std::vector<T> m_KernelX;
std::vector<T> m_KernelY;
};

Note that even the CSeparableKernel2D class could have two private members: CKernel1D<T> m_X, m_Y. The CKernel1D<T> class could have its own myConvolution method, i.e. myConvolutionRows, myConvolutionCols.


Also, usually, we would like to apply a set of filters (separable/non-separable) onto a given image, i.e. convolve an input image is with a given filter. Therefore, depending on the filter type, the corresponding myConvolution method should be called.


(1) Which should be the cleanest way to be able to do sth. like?


 CNonSeparableKernel<float> myNonSepFilter1;
myNonSepFilter1.initNonSeparableFilter1(3.0, 1);

CNonSeparableKernel<float> mySepFilter1;
mySepFilter1.initSeparableFilter1(0.5, 0.5);

std::vector<CKernel2D<float> > m_vFilterbank;
m_vFilterbank.push_back(myNonSepFilter1); // Would like to assign a non-sep filter.
m_vFilterbank.push_back(mySepFilter1); // Would like to assign a sep filter.

It seemed to me that the only way to do that is by using polimorphism, right?


CKernel2D<float> * pKernel2d = NULL;
pKernel2d = &mySepFilter1; m_vFilterbank.push_back(*pKernel2d);
pKernel2d = &myNonSepFilter1; m_vFilterbank.push_back(*pKernel2d);

(2) Now assuming that our filterbank is already filled with both type of kernels, to apply the convolution on an input image, it would be enough to do:


outputSeparable1    = m_vFilterbank.at(0).myConvolution(input);
outputNonSeparable1 = m_vFilterbank.at(1).myConvolution(input);

(3) Now imagine, I would like to have a friend convolution function with following prototype:


friend CMatrix<T> convolution(const CKernel2D<T> &, const CImage<T> &);

again, I would like that the proper myConvolution method is called depending on the kernel type. How could I achieve such operation? I read sth. about the Virtual Friend Function Idiom, do you think, it would make sense to apply that idiom for such a case?


All comments & suggestions are really welcomed ;-) I would really love to hear what do you think about this design? Is there a better way to design those functionalities?



Since image analysis requires a lot of computational power, good performances are importants. Everyone know that polymorphism is a great stuff but of course it adds a runtime abstraction layer, so, it is slower than statically linked code.


Since you are already using templates why you don't use a compile time abstraction layer using templates? Like STL does, you can wrap your algorithms in classes and pass them through template parameters.


I post here a simple example to show the principle.


template <typename T, typename FUNCTOR>
class ArrayTransformer
{
public:

static void Transform(T* array, int count)
{
for (int i = 0; i < count; ++i)
FUNCTOR::Transform(array[i]);
}

template <int N>
static void Transform(T (&array)[N])
{
for (int i = 0; i < N; ++i)
FUNCTOR::Transform(array[i]);
}
};

template <typename T>
class NegateTransformer
{
public:

static void Transform(T& value)
{
value = -value;
}
};

int main()
{
int array[] = { 1, 2, 3, 4, 5, 6 };
ArrayTransformer<int, NegateTransformer<int> >::Transform(array);
....
return 0;
}

New generation compilers can optimize this code quite well :) the overhead, if you are using a good compiler and you compile in release mode, will be zero.


Of course this makes sense if you have to call the inner functor thousand of times, if you have to call it only once you can just use polymorphism. You can also mix both techniques to obtain high performance in inner loops and in the same time easy usability at higher level.


0 commentaires:

Enregistrer un commentaire