Example: polymorphism for image processing

I study digital image processing on my own and will be very grateful if anyone can comment on whether polymorphism should be applied for this case or if there is a better class design.

Basically, a 2D filter / core can be: non-separable or separable . An important kernel operation is convolution and the way it is calculated depends on the type of filter.

 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 can have two private members: CKernel1D<T> m_X, m_Y . The class CKernel1D<T> may have its own method myConvolution , i.e. myConvolutionRows , myConvolutionCols .

In addition, as a rule, we would like to apply a set of filters (shared / inseparable) to a given image, that is, convolution of the input image with a given filter . Therefore, depending on the filter type should call the corresponding myConvolution method.

(1) What should be the cleanest way to do smth. as?

  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 this was to use polymorphism, 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 already filled with nuclear types of both types, to apply convolution on the input image, this would be enough:

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

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

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

again, I would like the correct myConvolution method to be called in kernel . How can I achieve such an operation? I am reading. About Virtual Friend Function Idiom , do you think it would be wise to apply this idiom for such a case?

All comments and suggestions are really welcome ;-) I would really like to hear what you think about this design? Is there a better way to develop these features?

0
source share
1 answer

Since image processing requires a lot of processing power, good performance is important. Everyone knows that polymorphism is great stuff, but of course it adds a level of runtime abstraction, so it's slower than statically linked code.

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

I am posting a simple example here 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 very well :) The overhead, if you use a good compiler and compile it in release mode, will be zero.

Of course, this makes sense if you have to call the inner functor thousands of times, if you need to call it only once, when you can just use polymorphism. You can also mix both methods for high performance in internal loops and at the same time simple use at a higher level.

0
source

Source: https://habr.com/ru/post/1380398/


All Articles