Реализация двумерной бикубической интерполяции в C ++

Это дополнительный вопрос для реализации двумерной бикубической интерполяции в C и шаблонной функции recursive_transform с уровнем разворачивания для реализации произвольной вложенной итерируемой произвольной формы в C ++. Помимо кода версии C, я пытаюсь создать функцию бикубической интерполяции версии C ++. bicubicInterpolation который может быть применен к двумерным вложенным векторам std::vector<std::vector<>> состав.

Пример матрицы ввода:

1       1       1
1       100     1
1       1       1

Выходная матрица (результат бикубической интерполяции из входной матрицы выше):

1       1       1       1       1       1       1       1       1       1       1       1
1       9       19      27      30      27      19      9       1       0       0       0
1       19      39      56      62      56      39      19      1       0       0       0
1       27      56      79      89      79      56      27      1       0       0       0
1       30      62      89      100     89      62      30      1       0       0       0
1       27      56      79      89      79      56      27      1       0       0       0
1       19      39      56      62      56      39      19      1       0       0       0
1       9       19      27      30      27      19      9       1       0       0       0
1       1       1       1       1       1       1       1       1       1       1       1
1       0       0       0       0       0       0       0       1       2       2       1
1       0       0       0       0       0       0       0       1       2       2       1
1       0       0       0       0       0       0       0       1       1       1       1

Экспериментальная реализация

  • пространство имен: TinyDIP

  • bicubicInterpolation реализация функции:

    constexpr auto bicubicInterpolation(const int& newSizeX, const int& newSizeY)
    {
        auto output = Image<ElementT>(newSizeX, newSizeY);
        auto ratiox = (float)this->getSizeX() / (float)newSizeX;
        auto ratioy = (float)this->getSizeY() / (float)newSizeY;
    
        for (size_t y = 0; y < newSizeY; y++)
        {
            for (size_t x = 0; x < newSizeX; x++)
            {
                float xMappingToOrigin = (float)x * ratiox;
                float yMappingToOrigin = (float)y * ratioy;
                float xMappingToOriginFloor = floor(xMappingToOrigin);
                float yMappingToOriginFloor = floor(yMappingToOrigin);
                float xMappingToOriginFrac = xMappingToOrigin - xMappingToOriginFloor;
                float yMappingToOriginFrac = yMappingToOrigin - yMappingToOriginFloor;
    
                ElementT ndata[4 * 4];
                for (int ndatay = -1; ndatay <= 2; ndatay++)
                {
                    for (int ndatax = -1; ndatax <= 2; ndatax++)
                    {
                        ndata[(ndatay + 1) * 4 + (ndatax + 1)] = this->get(
                            clip(xMappingToOriginFloor + ndatax, 0, this->getSizeX() - 1), 
                            clip(yMappingToOriginFloor + ndatay, 0, this->getSizeY() - 1));
                    }
    
                }
                output.set(x, y, bicubicPolate(ndata, xMappingToOriginFrac, yMappingToOriginFrac));
            }
        }
        return output;
    }
    
  • Вспомогательные функции для bicubicInterpolation функция:

    template<class InputT>
    constexpr auto bicubicPolate(const ElementT* const ndata, const InputT& fracx, const InputT& fracy)
    {
        auto x1 = cubicPolate( ndata[0], ndata[1], ndata[2], ndata[3], fracx );
        auto x2 = cubicPolate( ndata[4], ndata[5], ndata[6], ndata[7], fracx );
        auto x3 = cubicPolate( ndata[8], ndata[9], ndata[10], ndata[11], fracx );
        auto x4 = cubicPolate( ndata[12], ndata[13], ndata[14], ndata[15], fracx );
    
        return clip(cubicPolate( x1, x2, x3, x4, fracy ), 0.0, 255.0);
    }
    
    template<class InputT1, class InputT2>
    constexpr auto cubicPolate(const InputT1& v0, const InputT1& v1, const InputT1& v2, const InputT1& v3, const InputT2& frac)
    {
        auto A = (v3-v2)-(v0-v1);
        auto B = (v0-v1)-A;
        auto C = v2-v0;
        auto D = v1;
        return D + frac * (C + frac * (B + frac * A));
    }
    
    template<class InputT1, class InputT2, class InputT3>
    constexpr auto clip(const InputT1& input, const InputT2& lowerbound, const InputT3& upperbound)
    {
        if (input < lowerbound)
        {
            return static_cast<InputT1>(lowerbound);
        }
        if (input > upperbound)
        {
            return static_cast<InputT1>(upperbound);
        }
        return input;
    }
    
  • Image реализация класса шаблона (image.h):

    /* Develop by Jimmy Hu */
    
    #ifndef Image_H
    #define Image_H
    
    #include <algorithm>
    #include <array>
    #include <chrono>
    #include <complex>
    #include <concepts>
    #include <functional>
    #include <iostream>
    #include <iterator>
    #include <list>
    #include <numeric>
    #include <string>
    #include <type_traits>
    #include <variant>
    #include <vector>
    #include "basic_functions.h"
    
    namespace TinyDIP
    {
        template <typename ElementT>
        class Image
        {
        public:
            Image()
            {
            }
    
            Image(const int newWidth, const int newHeight)
            {
                this->image_data.resize(newHeight);
                for (size_t i = 0; i < newHeight; ++i) {
                    this->image_data[i].resize(newWidth);
                }
                this->image_data = recursive_transform<2>(this->image_data, [](ElementT element) { return ElementT{}; });
                return;
            }
    
            Image(const int newWidth, const int newHeight, const ElementT initVal)
            {
                this->image_data.resize(newHeight);
                for (size_t i = 0; i < newHeight; ++i) {
                    this->image_data[i].resize(newWidth);
                }
                this->image_data = recursive_transform<2>(this->image_data, [initVal](ElementT element) { return initVal; });
                return;
            }
    
            Image(const std::vector<std::vector<ElementT>>& input)
            {
                this->image_data = recursive_transform<2>(input, [](ElementT element) {return element; } ); //  Deep copy
                return;
            }
    
            template<class OutputT>
            constexpr auto cast()
            {
                return this->transform([](ElementT element) { return static_cast<OutputT>(element); });
            }
    
            constexpr auto get(const unsigned int locationx, const unsigned int locationy)
            {
                return this->image_data[locationy][locationx];
            }
    
            constexpr auto set(const unsigned int locationx, const unsigned int locationy, const ElementT& element)
            {
                this->image_data[locationy][locationx] = element;
                return *this;
            }
    
            template<class InputT>
            constexpr auto set(const unsigned int locationx, const unsigned int locationy, const InputT& element)
            {
                this->image_data[locationy][locationx] = static_cast<ElementT>(element);
                return *this;
            }
    
            constexpr auto getSizeX()
            {
                return this->image_data[0].size();
            }
    
            constexpr auto getSizeY()
            {
                return this->image_data.size();
            }
    
            constexpr auto getData()
            {
                return this->transform([](ElementT element) { return element; });   //  Deep copy
            }
    
            void print()
            {
                for (auto& row_element : this->toString())
                {
                    for (auto& element : row_element)
                    {
                        std::cout << element << "t";
                    }
                    std::cout << "n";
                }
                std::cout << "n";
                return;
            }
    
            constexpr auto toString()
            {
                return this->transform([](ElementT element) { return std::to_string(element); });
            }
    
            constexpr auto bicubicInterpolation(const int& newSizeX, const int& newSizeY)
            {
                auto output = Image<ElementT>(newSizeX, newSizeY);
                auto ratiox = (float)this->getSizeX() / (float)newSizeX;
                auto ratioy = (float)this->getSizeY() / (float)newSizeY;
    
                for (size_t y = 0; y < newSizeY; y++)
                {
                    for (size_t x = 0; x < newSizeX; x++)
                    {
                        float xMappingToOrigin = (float)x * ratiox;
                        float yMappingToOrigin = (float)y * ratioy;
                        float xMappingToOriginFloor = floor(xMappingToOrigin);
                        float yMappingToOriginFloor = floor(yMappingToOrigin);
                        float xMappingToOriginFrac = xMappingToOrigin - xMappingToOriginFloor;
                        float yMappingToOriginFrac = yMappingToOrigin - yMappingToOriginFloor;
    
                        ElementT ndata[4 * 4];
                        for (int ndatay = -1; ndatay <= 2; ndatay++)
                        {
                            for (int ndatax = -1; ndatax <= 2; ndatax++)
                            {
                                ndata[(ndatay + 1) * 4 + (ndatax + 1)] = this->get(
                                    clip(xMappingToOriginFloor + ndatax, 0, this->getSizeX() - 1), 
                                    clip(yMappingToOriginFloor + ndatay, 0, this->getSizeY() - 1));
                            }
    
                        }
                        output.set(x, y, bicubicPolate(ndata, xMappingToOriginFrac, yMappingToOriginFrac));
                    }
                }
                return output;
            }
    
            Image<ElementT>& operator=(Image<ElementT> const& input)  //  Copy Assign
            {
                this->image_data = input.getData();
                return *this;
            }
    
            Image<ElementT>& operator=(Image<ElementT>&& other)       //  Move Assign
            {
                this->image_data = std::move(other.image_data);
                std::cout << "move assignedn";
                return *this;
            }
    
            Image(const Image<ElementT> &input)                      //  Copy Constructor
            {
                this->image_data = input.getData();
            }
    
            /*    Move Constructor
             */
            Image(Image<ElementT> &&input) : image_data(std::move(input.image_data))
            {
            }
    
        private:
            std::vector<std::vector<ElementT>> image_data;
    
            template<class F>
            constexpr auto transform(const F& f)
            {
                return recursive_transform<2>(this->image_data, f);
            }
    
            template<class InputT>
            constexpr auto bicubicPolate(const ElementT* const ndata, const InputT& fracx, const InputT& fracy)
            {
                auto x1 = cubicPolate( ndata[0], ndata[1], ndata[2], ndata[3], fracx );
                auto x2 = cubicPolate( ndata[4], ndata[5], ndata[6], ndata[7], fracx );
                auto x3 = cubicPolate( ndata[8], ndata[9], ndata[10], ndata[11], fracx );
                auto x4 = cubicPolate( ndata[12], ndata[13], ndata[14], ndata[15], fracx );
    
                return clip(cubicPolate( x1, x2, x3, x4, fracy ), 0.0, 255.0);
            }
    
            template<class InputT1, class InputT2>
            constexpr auto cubicPolate(const InputT1& v0, const InputT1& v1, const InputT1& v2, const InputT1& v3, const InputT2& frac)
            {
                auto A = (v3-v2)-(v0-v1);
                auto B = (v0-v1)-A;
                auto C = v2-v0;
                auto D = v1;
                return D + frac * (C + frac * (B + frac * A));
            }
    
            template<class InputT1, class InputT2, class InputT3>
            constexpr auto clip(const InputT1& input, const InputT2& lowerbound, const InputT3& upperbound)
            {
                if (input < lowerbound)
                {
                    return static_cast<InputT1>(lowerbound);
                }
                if (input > upperbound)
                {
                    return static_cast<InputT1>(upperbound);
                }
                return input;
            }
        };
    }
    
    #endif
    
  • base_types.h: Базовые типы

    /* Develop by Jimmy Hu */
    
    #ifndef BASE_H
    #define BASE_H
    
    #include <cmath>
    #include <cstdbool>
    #include <cstdio>
    #include <cstdlib>
    #include <string>
    
    #define MAX_PATH 256
    #define FILE_ROOT_PATH "./"
    
    #define True true
    #define False false
    
    typedef unsigned char BYTE;
    
    typedef struct RGB
    {
        unsigned char channels[3];
    } RGB;
    
    typedef BYTE GrayScale;
    
    typedef struct HSV
    {
        long double channels[3];    //  Range: 0 <= H < 360, 0 <= S <= 1, 0 <= V <= 255
    }HSV;
    
    #endif
    
  • basic_functions.h: Основные функции

    /* Develop by Jimmy Hu */
    
    #ifndef BasicFunctions_H
    #define BasicFunctions_H
    
    #include <algorithm>
    #include <array>
    #include <cassert>
    #include <chrono>
    #include <complex>
    #include <concepts>
    #include <deque>
    #include <execution>
    #include <exception>
    #include <functional>
    #include <iostream>
    #include <iterator>
    #include <list>
    #include <map>
    #include <mutex>
    #include <numeric>
    #include <optional>
    #include <ranges>
    #include <stdexcept>
    #include <string>
    #include <tuple>
    #include <type_traits>
    #include <utility>
    #include <variant>
    #include <vector>
    
    namespace TinyDIP
    {
        template<typename T>
        concept is_back_inserterable = requires(T x)
        {
            std::back_inserter(x);
        };
    
        template<typename T>
        concept is_inserterable = requires(T x)
        {
            std::inserter(x, std::ranges::end(x));
        };
    
        //  recursive_invoke_result_t implementation
        template<typename, typename>
        struct recursive_invoke_result { };
    
        template<typename T, std::invocable<T> F>
        struct recursive_invoke_result<F, T> { using type = std::invoke_result_t<F, T>; };
    
        template<typename F, template<typename...> typename Container, typename... Ts>
        requires (
            !std::invocable<F, Container<Ts...>>&&
            std::ranges::input_range<Container<Ts...>>&&
            requires { typename recursive_invoke_result<F, std::ranges::range_value_t<Container<Ts...>>>::type; })
            struct recursive_invoke_result<F, Container<Ts...>>
        {
            using type = Container<typename recursive_invoke_result<F, std::ranges::range_value_t<Container<Ts...>>>::type>;
        };
    
        template<typename F, typename T>
        using recursive_invoke_result_t = typename recursive_invoke_result<F, T>::type;
    
        //  recursive_transform implementation (the version with unwrap_level)
        template<std::size_t unwrap_level = 1, class T, class F>
        constexpr auto recursive_transform(const T& input, const F& f)
        {
            if constexpr (unwrap_level > 0)
            {
                recursive_invoke_result_t<F, T> output{};
                std::ranges::transform(
                    std::ranges::cbegin(input),
                    std::ranges::cend(input),
                    std::inserter(output, std::ranges::end(output)),
                    [&f](auto&& element) { return recursive_transform<unwrap_level - 1>(element, f); }
                );
                return output;
            }
            else
            {
                return f(input);
            }
        }
    
        template<std::size_t dim, class T>
        constexpr auto n_dim_vector_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_vector_generator<dim - 1>(input, times);
                std::vector<decltype(element)> output(times, element);
                return output;
            }
        }
    
        template<std::size_t dim, std::size_t times, class T>
        constexpr auto n_dim_array_generator(T input)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_array_generator<dim - 1, times>(input);
                std::array<decltype(element), times> output;
                std::fill(std::ranges::begin(output), std::ranges::end(output), element);
                return output;
            }
        }
    
        template<std::size_t dim, class T>
        constexpr auto n_dim_deque_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_deque_generator<dim - 1>(input, times);
                std::deque<decltype(element)> output(times, element);
                return output;
            }
        }
    
        template<std::size_t dim, class T>
        constexpr auto n_dim_list_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                auto element = n_dim_list_generator<dim - 1>(input, times);
                std::list<decltype(element)> output(times, element);
                return output;
            }
        }
    
        template<std::size_t dim, template<class...> class Container = std::vector, class T>
        constexpr auto n_dim_container_generator(T input, std::size_t times)
        {
            if constexpr (dim == 0)
            {
                return input;
            }
            else
            {
                return Container(times, n_dim_container_generator<dim - 1, Container, T>(input, times));
            }
        }
    }
    #endif
    

Полный код тестирования

В оттенки серого Типовые данные были протестированы здесь.

/* Develop by Jimmy Hu */

#include "base_types.h"
#include "basic_functions.h"
#include "image.h"

void bicubicInterpolationTest();

int main()
{
    bicubicInterpolationTest();
    return 0;
}

void bicubicInterpolationTest()
{
    TinyDIP::Image<GrayScale> image1(3, 3, 1);
    std::cout << "Width: " + std::to_string(image1.getSizeX()) + "n";
    std::cout << "Height: " + std::to_string(image1.getSizeY()) + "n";
    image1 = image1.set(1, 1, 100);
    image1.print();

    auto image2 = image1.bicubicInterpolation(12, 12);
    image2.print();
}

Все предложения приветствуются.

Сводная информация:

  • На какой вопрос это продолжение?

    Реализация двумерной бикубической интерполяции на языках C и

    Функция шаблона recursive_transform с уровнем распаковки для произвольной вложенной итерируемой реализации произвольного типа в C ++.

  • Какие изменения были внесены в код с момента последнего вопроса?

    Я пытаюсь сделать функцию бикубической интерполяции версии C ++ bicubicInterpolation который может быть применен к двумерным вложенным векторам std::vector<std::vector<>> состав.

  • Почему запрашивается новый обзор?

    Если есть какие-то улучшения, пожалуйста, дайте мне знать.

1 ответ
1

мне нравится auto для многих вещей, но не как возвращаемый тип, если в этом нет необходимости.

Например, чтобы понять, что cast возвращает нужно покопаться в нескольких шаблонных функциях, слоях мета-шаблонов, а потом понять, что std::invoke_result_t делает в данном конкретном случае.

Пожалуйста, просто напишите тип возвращаемого значения.


    template<class InputT1, class InputT2, class InputT3>
    constexpr auto clip(const InputT1& input, const InputT2& lowerbound, const InputT3& upperbound)
    {
        if (input < lowerbound)
        {
            return static_cast<InputT1>(lowerbound);
        }
        if (input > upperbound)
        {
            return static_cast<InputT1>(upperbound);
        }
        return input;
    }

Стандартная библиотека уже предоставляет std :: clamp так что в этом нет необходимости.

Обратите внимание, что предоставление одного аргумента шаблона безопаснее, чем отдельные аргументы для всех входных данных. Преобразования (и, следовательно, сравнения) между разными типами могут быть нечетко определены, поскольку типы имеют разные диапазоны.


#define True true
#define False false

Почему? C ++ использует true а также false.


    std::vector<std::vector<ElementT>> image_data;

Быстрее (и обычно проще) использовать одномерный вектор: std::vector<ElementT> image_data; содержащий width * height элементов, и при необходимости вычислить индексы как y * width + x.

Итак, это:

    Image(const int newWidth, const int newHeight)
    {
        this->image_data.resize(newHeight);
        for (size_t i = 0; i < newHeight; ++i) {
            this->image_data[i].resize(newWidth);
        }
        this->image_data = recursive_transform<2>(this->image_data, [](ElementT element) { return ElementT{}; });
        return;
    }

Просто должно быть:

    Image(const unsigned int width, const unsigned int height):
        m_width(width),
        m_height(height),
        m_image_data(width * height) { }

Обратите внимание, что std::vector конструктор (и функция изменения размера) будет «инициализировать значение» элементов, поэтому нам не нужно делать это отдельно.

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


    template<class OutputT>
    constexpr auto cast()
    {
        return this->transform([](ElementT element) { return static_cast<OutputT>(element); });
    }

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

    std::vector<ElementT> const& getImageData() const { return m_imageData; }

Затем пользователь может использовать std::transform или как им нужно.


    constexpr auto get(const unsigned int locationx, const unsigned int locationy)
    {
        return this->image_data[locationy][locationx];
    }

    constexpr auto set(const unsigned int locationx, const unsigned int locationy, const ElementT& element)
    {
        this->image_data[locationy][locationx] = element;
        return *this;
    }

    template<class InputT>
    constexpr auto set(const unsigned int locationx, const unsigned int locationy, const InputT& element)
    {
        this->image_data[locationy][locationx] = static_cast<ElementT>(element);
        return *this;
    }

В C ++ более идиоматично предоставлять две версии get функция, одна из которых возвращает изменяемую ссылку, а другая — константную ссылку.

constexpr ElementT& at(const unsigned int x, const unsigned int y) { return m_image_data[y * width + x]; }
constexpr ElementT const& at(const unsigned int x, const unsigned int y) const { return m_image_data[y * width + x]; }

    constexpr auto getData()
    {
        return this->transform([](ElementT element) { return element; });   //  Deep copy
    }

Как и в случае с cast выше нам эта функция не нужна. Мы можем просто разоблачить const& данных изображения.


    void print()
    {
        for (auto& row_element : this->toString())
        {
            for (auto& element : row_element)
            {
                std::cout << element << "t";
            }
            std::cout << "n";
        }
        std::cout << "n";
        return;
    }

Более гибко предоставить оператор потока вывода, поскольку мы можем указать поток для печати.

Мы определенно не хотим создавать для этого временную копию, заполненную строками !!!

Мы можем просто использовать ElementT operator<< и отправляем элементы прямо в выходной поток.


    constexpr auto toString()
    {
        return this->transform([](ElementT element) { return std::to_string(element); });
    }

Это кажется маловероятным для кого-либо. Опять же, мы можем раскрыть данные изображения, const& и позволить пользователю использовать std::transform если они действительно этого хотят.


    constexpr auto bicubicInterpolation(const int& newSizeX, const int& newSizeY) ...

Нет смысла использовать ссылки для параметров размера. Опять же, они должны быть беззнаковыми, иначе мы должны проверить, что значения больше 0.

Эта функция, вероятно, была бы лучше как бесплатная функция, например:

   template<ElementT>
   Image<ElementT> copyResizeBicubic(Image<ElementT> const& image, int width, int height);

    Image<ElementT>& operator=(Image<ElementT> const& input)  //  Copy Assign
    {
        this->image_data = input.getData();
        return *this;
    }

    Image<ElementT>& operator=(Image<ElementT>&& other)       //  Move Assign
    {
        this->image_data = std::move(other.image_data);
        std::cout << "move assignedn";
        return *this;
    }

    Image(const Image<ElementT> &input)                      //  Copy Constructor
    {
        this->image_data = input.getData();
    }

    /*    Move Constructor
     */
    Image(Image<ElementT> &&input) : image_data(std::move(input.image_data))
    {
    }

Все это может быть = default нет?

Обратите внимание, что мы можем написать Image<ElementT> как просто Image внутри класса.


    template<class InputT>
    constexpr auto bicubicPolate(const ElementT* const ndata, const InputT& fracx, const InputT& fracy)
    {
        auto x1 = cubicPolate( ndata[0], ndata[1], ndata[2], ndata[3], fracx );
        auto x2 = cubicPolate( ndata[4], ndata[5], ndata[6], ndata[7], fracx );
        auto x3 = cubicPolate( ndata[8], ndata[9], ndata[10], ndata[11], fracx );
        auto x4 = cubicPolate( ndata[12], ndata[13], ndata[14], ndata[15], fracx );

        return clip(cubicPolate( x1, x2, x3, x4, fracy ), 0.0, 255.0);
    }

    template<class InputT1, class InputT2>
    constexpr auto cubicPolate(const InputT1& v0, const InputT1& v1, const InputT1& v2, const InputT1& v3, const InputT2& frac)
    {
        auto A = (v3-v2)-(v0-v1);
        auto B = (v0-v1)-A;
        auto C = v2-v0;
        auto D = v1;
        return D + frac * (C + frac * (B + frac * A));
    }

Этим функциям не требуется доступ к каким-либо данным класса. Это статические или даже не являющиеся членами вспомогательные функции в другом файле.

Как небольшие типы POD, параметры должны передаваться по значению, а не const&.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *