Сохранить структуру, содержащую вектор и cv :: Mat на диск - Сериализация данных в C ++ - PullRequest
0 голосов
/ 27 апреля 2018

Я хотел бы сохранить структуру ниже на диске и иметь возможность читать ее снова: (C ++)

struct pixels {
    std::vector<cv::Point> indexes;
    cv::Mat values;
};

Я пытался использовать ofstream и ifstream, но им нужен размер переменной, который я не знаю, как рассчитать в этой ситуации. Это не простая структура с некоторыми int и double. Есть ли способ сделать это на C ++, желательно без использования сторонних библиотек.

(Я на самом деле пришел с языка Matlab. Это было легко сделать на этом языке, используя save: save(filename, variables)).

Edit:
Я только что попробовал увеличить сериализацию. К сожалению, это очень медленно для моего использования.

1 Ответ

0 голосов
/ 28 апреля 2018

Несколько подходов приходят на ум с различными минусами и плюсами.

  • Использование OpenCV XML / YAML персистентность функциональность.
    • Формат XML (переносимый)
    • Формат YAML (переносной)
    • формат JSON (переносной)
  • Использование Усиление. Сериализация
    • Простой текстовый формат (переносной)
    • Формат XML (переносимый)
    • двоичный формат (непереносимый)
  • Необработанные данные до std::fstream
    • двоичный формат (непереносимый)

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


Сначала включим общие структуры данных и вспомогательные функции, которые мы будем использовать:

#include <opencv2/opencv.hpp>

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>

#include <boost/filesystem.hpp>

#include <boost/serialization/vector.hpp>

#include <chrono>
#include <fstream>
#include <vector>

// ============================================================================

using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::microseconds;

namespace ba = boost::archive;
namespace bs = boost::serialization;
namespace fs = boost::filesystem;

// ============================================================================

struct pixels
{
    std::vector<cv::Point> indexes;
    cv::Mat values;
};

struct test_results
{
    bool matches;
    double write_time_ms;
    double read_time_ms;
    size_t file_size;
};

// ----------------------------------------------------------------------------

bool validate(pixels const& pix_out, pixels const& pix_in)
{
    bool result(true);
    result &= (pix_out.indexes == pix_in.indexes);
    result &= (cv::countNonZero(pix_out.values != pix_in.values) == 0);
    return result;
}

pixels generate_data()
{
    pixels pix;
    for (int i(0); i < 10000; ++i) {
        pix.indexes.emplace_back(i, 2 * i);
    }
    pix.values = cv::Mat(1024, 1024, CV_8UC3);
    cv::randu(pix.values, 0, 256);

    return pix;
}

void dump_results(std::string const& label, test_results const& results)
{
    std::cout << label << "\n";
    std::cout << "Matched = " << (results.matches ? "true" : "false") << "\n";
    std::cout << "Write time = " << results.write_time_ms << " ms\n";
    std::cout << "Read time = " << results.read_time_ms << " ms\n";
    std::cout << "File size = " << results.file_size << " bytes\n";
    std::cout << "\n";
}

// ============================================================================

Использование OpenCV FileStorage

Это первый очевидный выбор - использовать функциональность сериализации, предоставляемую OpenCV - cv::FileStorage, cv::FileNode и cv::FileNodeIterator. В документации по 2.4.x есть хороший учебник , который я сейчас не могу найти в новых документах.

Преимущество здесь в том, что у нас уже есть поддержка cv::Mat и cv::Point, так что реализовывать очень мало.

Однако все представленные форматы являются текстовыми, поэтому чтение и запись значений будут довольно дорогими (особенно для cv::Mat). Может быть полезно сохранить / загрузить cv::Mat с использованием cv::imread / cv::imwrite и сериализовать имя файла. Я оставлю это читателю для реализации и тестирования.

// ============================================================================

void save_pixels(pixels const& pix, cv::FileStorage& fs)
{
    fs << "indexes" << "[";
    for (auto const& index : pix.indexes) {
        fs << index;
    }
    fs << "]";
    fs << "values" << pix.values;
}

void load_pixels(pixels& pix, cv::FileStorage& fs)
{
    cv::FileNode n(fs["indexes"]);
    if (n.type() != cv::FileNode::SEQ) {
        throw std::runtime_error("Input format error: `indexes` is not a sequence.");;
    }

    pix.indexes.clear();
    cv::FileNodeIterator it(n.begin()), it_end(n.end());
    cv::Point pt;
    for (; it != it_end; ++it) {
        (*it) >> pt;
        pix.indexes.push_back(pt);
    }

    fs["values"] >> pix.values;
}

// ----------------------------------------------------------------------------

test_results test_cv_filestorage(std::string const& file_name, pixels const& pix)
{
    test_results results;
    pixels pix_in;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    {
        cv::FileStorage fs(file_name, cv::FileStorage::WRITE);

        save_pixels(pix, fs);
    }
    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    {
        cv::FileStorage fs(file_name, cv::FileStorage::READ);

        load_pixels(pix_in, fs);
    }
    high_resolution_clock::time_point t3 = high_resolution_clock::now();

    results.matches = validate(pix, pix_in);
    results.write_time_ms = static_cast<double>(duration_cast<microseconds>(t2 - t1).count()) / 1000;
    results.read_time_ms = static_cast<double>(duration_cast<microseconds>(t3 - t2).count()) / 1000;
    results.file_size = fs::file_size(file_name);

    return results;
}

// ============================================================================

Использование ускоренной сериализации

Другим потенциальным подходом является использование библиотеки Boost.Serialization, о которой вы уже упоминали. У нас есть три варианта формата архива, два из которых являются текстовыми (и переносимыми), а один - двоичным (не переносимым, но гораздо более эффективным).

Здесь еще много работы. Нам нужно обеспечить хорошую сериализацию для cv::Mat, cv::Point и нашей pixels структуры. Предоставляется поддержка std::vector, а для обработки XML нам нужно сгенерировать пары ключ-значение.

В случае двух текстовых форматов снова может быть полезно сохранить cv::Mat как изображение и только сериализовать путь. Читатель может попробовать этот подход. Для двоичного формата это, скорее всего, будет компромисс между пространством и временем. Опять же, не стесняйтесь проверить это (вы даже можете использовать cv::imencode и imdecode).

// ============================================================================

namespace boost { namespace serialization {

template<class Archive>
void serialize(Archive &ar, cv::Mat& mat, const unsigned int)
{
    int cols, rows, type;
    bool continuous;

    if (Archive::is_saving::value) {
        cols = mat.cols; rows = mat.rows; type = mat.type();
        continuous = mat.isContinuous();
    }

    ar & boost::serialization::make_nvp("cols", cols);
    ar & boost::serialization::make_nvp("rows", rows);
    ar & boost::serialization::make_nvp("type", type);
    ar & boost::serialization::make_nvp("continuous", continuous);

    if (Archive::is_loading::value)
        mat.create(rows, cols, type);

    if (continuous) {
        size_t const data_size(rows * cols * mat.elemSize());
        ar & boost::serialization::make_array(mat.ptr(), data_size);
    } else {
        size_t const row_size(cols * mat.elemSize());
        for (int i = 0; i < rows; i++) {
            ar & boost::serialization::make_array(mat.ptr(i), row_size);
        }
    }
}

template<class Archive>
void serialize(Archive &ar, cv::Point& pt, const unsigned int)
{
    ar & boost::serialization::make_nvp("x", pt.x);
    ar & boost::serialization::make_nvp("y", pt.y);
}

template<class Archive>
void serialize(Archive &ar, ::pixels& pix, const unsigned int)
{
    ar & boost::serialization::make_nvp("indexes", pix.indexes);
    ar & boost::serialization::make_nvp("values", pix.values);
}

}}

// ----------------------------------------------------------------------------

template <typename OArchive, typename IArchive>
test_results test_bs_filestorage(std::string const& file_name
    , pixels const& pix
    , bool binary = false)
{
    test_results results;
    pixels pix_in;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    {
        std::ios::openmode mode(std::ios::out);
        if (binary) mode |= std::ios::binary;
        std::ofstream ofs(file_name.c_str(), mode);
        OArchive oa(ofs);

        oa & boost::serialization::make_nvp("pixels", pix);
    }
    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    {
        std::ios::openmode mode(std::ios::in);
        if (binary) mode |= std::ios::binary;
        std::ifstream ifs(file_name.c_str(), mode);
        IArchive ia(ifs);

        ia & boost::serialization::make_nvp("pixels", pix_in);
    }
    high_resolution_clock::time_point t3 = high_resolution_clock::now();

    results.matches = validate(pix, pix_in);
    results.write_time_ms = static_cast<double>(duration_cast<microseconds>(t2 - t1).count()) / 1000;
    results.read_time_ms = static_cast<double>(duration_cast<microseconds>(t3 - t2).count()) / 1000;
    results.file_size = fs::file_size(file_name);

    return results;
}

// ============================================================================

Исходные данные к std::fstream

Если мы не заботимся о переносимости файлов данных, мы можем просто выполнить минимальный объем работы для сброса и восстановления памяти. С некоторыми усилиями (за счет скорости) вы можете сделать это более гибким.

// ============================================================================

void save_pixels(pixels const& pix, std::ofstream& ofs)
{
    size_t index_count(pix.indexes.size());
    ofs.write(reinterpret_cast<char const*>(&index_count), sizeof(index_count));
    ofs.write(reinterpret_cast<char const*>(&pix.indexes[0]), sizeof(cv::Point) * index_count);

    int cols(pix.values.cols), rows(pix.values.rows), type(pix.values.type());
    bool continuous(pix.values.isContinuous());

    ofs.write(reinterpret_cast<char const*>(&cols), sizeof(cols));
    ofs.write(reinterpret_cast<char const*>(&rows), sizeof(rows));
    ofs.write(reinterpret_cast<char const*>(&type), sizeof(type));
    ofs.write(reinterpret_cast<char const*>(&continuous), sizeof(continuous));

    if (continuous) {
        size_t const data_size(rows * cols * pix.values.elemSize());
        ofs.write(reinterpret_cast<char const*>(pix.values.ptr()), data_size);
    } else {
        size_t const row_size(cols * pix.values.elemSize());
        for (int i(0); i < rows; ++i) {
            ofs.write(reinterpret_cast<char const*>(pix.values.ptr(i)), row_size);
        }
    }
}

void load_pixels(pixels& pix, std::ifstream& ifs)
{
    size_t index_count(0);
    ifs.read(reinterpret_cast<char*>(&index_count), sizeof(index_count));
    pix.indexes.resize(index_count);
    ifs.read(reinterpret_cast<char*>(&pix.indexes[0]), sizeof(cv::Point) * index_count);

    int cols, rows, type;
    bool continuous;

    ifs.read(reinterpret_cast<char*>(&cols), sizeof(cols));
    ifs.read(reinterpret_cast<char*>(&rows), sizeof(rows));
    ifs.read(reinterpret_cast<char*>(&type), sizeof(type));
    ifs.read(reinterpret_cast<char*>(&continuous), sizeof(continuous));

    pix.values.create(rows, cols, type);

    if (continuous) {
        size_t const data_size(rows * cols * pix.values.elemSize());
        ifs.read(reinterpret_cast<char*>(pix.values.ptr()), data_size);
    } else {
        size_t const row_size(cols * pix.values.elemSize());
        for (int i(0); i < rows; ++i) {
            ifs.read(reinterpret_cast<char*>(pix.values.ptr(i)), row_size);
        }
    }
}

// ----------------------------------------------------------------------------

test_results test_raw(std::string const& file_name, pixels const& pix)
{
    test_results results;
    pixels pix_in;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    {
        std::ofstream ofs(file_name.c_str(), std::ios::out | std::ios::binary);

        save_pixels(pix, ofs);
    }
    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    {
        std::ifstream ifs(file_name.c_str(), std::ios::in | std::ios::binary);

        load_pixels(pix_in, ifs);
    }
    high_resolution_clock::time_point t3 = high_resolution_clock::now();

    results.matches = validate(pix, pix_in);
    results.write_time_ms = static_cast<double>(duration_cast<microseconds>(t2 - t1).count()) / 1000;
    results.read_time_ms = static_cast<double>(duration_cast<microseconds>(t3 - t2).count()) / 1000;
    results.file_size = fs::file_size(file_name);

    return results;
}

// ============================================================================

Завершено main()

Давайте запустим все тесты для различных подходов и сравним результаты.

Код

// ============================================================================

int main()
{
    namespace ba = boost::archive;

    pixels pix(generate_data());

    auto r_c_xml = test_cv_filestorage("test.cv.xml", pix);
    auto r_c_yaml = test_cv_filestorage("test.cv.yaml", pix);
    auto r_c_json = test_cv_filestorage("test.cv.json", pix);

    auto r_b_txt = test_bs_filestorage<ba::text_oarchive, ba::text_iarchive>("test.bs.txt", pix);
    auto r_b_xml = test_bs_filestorage<ba::xml_oarchive, ba::xml_iarchive>("test.bs.xml", pix);
    auto r_b_bin = test_bs_filestorage<ba::binary_oarchive, ba::binary_iarchive>("test.bs.bin", pix, true);

    auto r_b_raw = test_raw("test.raw", pix);

    // ----

    dump_results("OpenCV - XML", r_c_xml);
    dump_results("OpenCV - YAML", r_c_yaml);
    dump_results("OpenCV - JSON", r_c_json);
    dump_results("Boost - TXT", r_b_txt);
    dump_results("Boost - XML", r_b_xml);
    dump_results("Boost - Binary", r_b_bin);
    dump_results("Raw", r_b_raw);

    return 0;
}

// ============================================================================

Консольный вывод (i7-4930k, Win10, MSVC 2013)

NB: Мы тестируем это с 10000 indexes и values как изображение BGR 1024x1024.

OpenCV - XML
Matched = true
Write time = 257.563 ms
Read time = 257.016 ms
File size = 12323677 bytes

OpenCV - YAML
Matched = true
Write time = 135.498 ms
Read time = 311.999 ms
File size = 16353873 bytes

OpenCV - JSON
Matched = true
Write time = 137.003 ms
Read time = 312.528 ms
File size = 16353873 bytes

Boost - TXT
Matched = true
Write time = 1293.84 ms
Read time = 1210.94 ms
File size = 11333696 bytes

Boost - XML
Matched = true
Write time = 4890.82 ms
Read time = 4042.75 ms
File size = 62095856 bytes

Boost - Binary
Matched = true
Write time = 12.498 ms
Read time = 4 ms
File size = 3225813 bytes

Raw
Matched = true
Write time = 8.503 ms
Read time = 2.999 ms
File size = 3225749 bytes

Заключение

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

OpenCV работает намного лучше, XML сбалансирован при чтении и записи, YAML / JSON (очевидно, идентичен) быстрее при записи, но медленнее при чтении. Все еще довольно вяло, поэтому написание values в качестве изображения и сохранение имени файла все еще может быть полезным.

Необработанный подход является самым быстрым (не удивительно), но также негибким. Конечно, вы могли бы внести некоторые улучшения, но, похоже, требуется гораздо больше кода, чем при использовании бинарного Boost.Archive - здесь это не стоит. Тем не менее, если вы делаете все на одной машине, это может сработать.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...