Тематическое исследование: полиморфизм для обработки изображений - PullRequest
0 голосов
/ 29 октября 2011

Я изучаю цифровую обработку изображений самостоятельно и был бы очень признателен, если бы кто-то мог прокомментировать, следует ли применять polymorphism для этого случая или если есть лучший дизайн класса.

По сути, 2D-фильтр / ядро ​​может быть либо: non-separable, либо separable. Важной операцией ядра является convolution, и способ ее вычисления зависит от типа фильтра.

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;
 };

Обратите внимание, что даже у класса CSeparableKernel2D может быть два закрытых члена: CKernel1D<T> m_X, m_Y. Класс CKernel1D<T> может иметь свой собственный метод myConvolution, т.е. myConvolutionRows, myConvolutionCols.

Также, как правило, мы хотели бы применить набор filters (отделимый / неразделимый) к данному изображению, то есть сверить входное изображение с данным filter. Поэтому, в зависимости от filter type, должен вызываться соответствующий метод myConvolution.

(1) Какой должен быть самый чистый способ сделать что-либо? как?

 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.

Мне казалось, что единственный способ сделать это - использовать полиморфизм, верно?

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

(2) Теперь, предположив, что наша filterbank уже заполнена ядрами обоих типов, чтобы применить свертку к входному изображению, достаточно сделать:

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

(3) А теперь представьте, что я хотел бы иметь функцию друга convolution со следующим прототипом:

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

еще раз, я бы хотел, чтобы вызывался правильный метод myConvolution в зависимости от типа kernel. Как я мог добиться такой операции? Я читаю по поводу Virtual Friend Function Idiom, как вы думаете, имеет ли смысл применять эту идиому для такого случая?

Все комментарии и предложения действительно приветствуются ;-) Мне бы очень хотелось услышать, что вы думаете об этом дизайне? Есть ли лучший способ разработать эти функции?

1 Ответ

0 голосов
/ 29 октября 2011

Поскольку анализ изображений требует больших вычислительных мощностей, важными являются хорошие характеристики. Все знают, что полиморфизм - отличная вещь, но, конечно, он добавляет уровень абстракции во время выполнения, поэтому он медленнее, чем статически связанный код.

Поскольку вы уже используете шаблоны, почему вы не используете слой абстракции времени компиляции, используя шаблоны? Как и в STL, вы можете обернуть свои алгоритмы в классы и передать их через параметры шаблона.

Я публикую здесь простой пример, чтобы показать принцип.

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;
}

Компиляторы нового поколения могут довольно хорошо оптимизировать этот код :) издержки, если вы используете хороший компилятор и компилируете в режиме выпуска, будут равны нулю.

Конечно, это имеет смысл, если вам нужно вызывать внутренний функтор тысячу раз, если вы должны вызывать его только один раз, когда вы можете просто использовать полиморфизм. Вы также можете комбинировать оба метода для достижения высокой производительности во внутренних циклах и в то же время для простоты использования на более высоком уровне.

...