Сериализация OpenCV Mat_ - PullRequest
       3

Сериализация OpenCV Mat_

16 голосов
/ 13 ноября 2010

Я работаю над исследовательским проектом в области робототехники, где мне нужно сериализовать 2D-матрицы 3D-точек: в основном каждый пиксель представляет собой 3-вектор с плавающей точкой.Эти пиксели сохраняются в матрице OpenCV, и их необходимо отправлять по межпроцессному обмену данными и сохранять в файлы для обработки на нескольких компьютерах.Я хотел бы сериализовать их порядком, не зависящим от архитектуры и архитектурой, как можно быстрее .cv::imencode здесь было бы идеально, за исключением того, что он работает только на 8-битных и 16-битных элементах, и мы не хотим терять какую-либо точность.Файлы не должны быть удобочитаемыми (хотя мы делаем это сейчас, чтобы обеспечить переносимость данных, и это невероятно медленно).Есть ли лучшие практики для этого или элегантные способы сделать это?

Спасибо!

Ответы [ 7 ]

17 голосов
/ 30 января 2014

Предыдущие ответы хороши, но они не будут работать для не непрерывных матриц, которые возникают, когда вы хотите сериализовать области интереса (среди прочего).Кроме того, нет необходимости сериализовать elemSize(), потому что это происходит от значения type.

Вот некоторый код, который будет работать независимо от непрерывности (с include / namespace)

#pragma once

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/utility.hpp>
#include <opencv2/opencv.hpp>

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 & cols & rows & type & continuous;

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

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

}

} // namespace serialization
} // namespace boost
16 голосов
/ 11 июня 2011

Редактировать: Кристоф Хайндл прокомментировал этот пост со ссылкой на свой блог, где он улучшил этот код сериализации. Настоятельно рекомендуется!

http://cheind.wordpress.com/2011/12/06/serialization-of-cvmat-objects-using-boost/

-

Для тех, кому это может быть полезно: код для сериализации Mat & with boost :: serialization
Я не тестировал многоканальные данные, но все должно работать нормально.

#include <iostream>
#include <fstream>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/vector.hpp>

BOOST_SERIALIZATION_SPLIT_FREE(Mat)
namespace boost {
namespace serialization {

    /*** Mat ***/
    template<class Archive>
    void save(Archive & ar, const Mat& m, const unsigned int version)
    {
      size_t elemSize = m.elemSize(), elemType = m.type();

      ar & m.cols;
      ar & m.rows;
      ar & elemSize;
      ar & elemType; // element type.
      size_t dataSize = m.cols * m.rows * m.elemSize();

      //cout << "Writing matrix data rows, cols, elemSize, type, datasize: (" << m.rows << "," << m.cols << "," << m.elemSize() << "," << m.type() << "," << dataSize << ")" << endl;

      for (size_t dc = 0; dc < dataSize; ++dc) {
          ar & m.data[dc];
      }
    }

    template<class Archive>
    void load(Archive & ar, Mat& m, const unsigned int version)
    {
        int cols, rows;
        size_t elemSize, elemType;

        ar & cols;
        ar & rows;
        ar & elemSize;
        ar & elemType;

        m.create(rows, cols, elemType);
        size_t dataSize = m.cols * m.rows * elemSize;

        //cout << "reading matrix data rows, cols, elemSize, type, datasize: (" << m.rows << "," << m.cols << "," << m.elemSize() << "," << m.type() << "," << dataSize << ")" << endl;

        for (size_t dc = 0; dc < dataSize; ++dc) {
                  ar & m.data[dc];
        }
    }

}
}

Теперь мат можно сериализовать и десериализовать следующим образом:

    void saveMat(Mat& m, string filename) {
            ofstream ofs(filename.c_str());
            boost::archive::binary_oarchive oa(ofs);
            //boost::archive::text_oarchive oa(ofs);
            oa << m;
    }

    void loadMat(Mat& m, string filename) {
            std::ifstream ifs(filename.c_str());
            boost::archive::binary_iarchive ia(ifs);
            //boost::archive::text_iarchive ia(ifs);
            ia >> m;
    }

Я использовал здесь binary_oarchive и binary_iarchive, чтобы уменьшить использование памяти. Бинарный формат не обеспечивает переносимость между платформами, но при желании можно использовать text_oarchive / iarchive.

1 голос
/ 14 ноября 2010

Вы можете использовать boost::serialization для этого. Он сильно оптимизирован и довольно легко интегрируется.

Возможные ускорения для вашего случая включают сериализацию каждого объекта в виде необработанного двоичного блока (см. boost::serialization::make_binary) и отключение отслеживания версий (BOOST_SERIALIZATION_DISABLE_TRACKING).

Кроме того, вы можете поэкспериментировать с добавлением сжатия в подпрограммы сериализации для экономии места (и времени в случае данных, которые легко сжимаются ). Это может быть реализовано, например, с помощью boost::iostreams.

0 голосов
/ 09 августа 2018

Я написал этот код:

/*
Will save in the file:
cols\n
rows\n
elemSize\n
type\n
DATA
*/
void serializeMatbin(Mat& mat, std::string filename){
    if (!mat.isContinuous()) {
        cout << "Not implemented yet" << endl;
        exit(1);
    }
    int elemSizeInBytes = (int)mat.elemSize();
    int elemType        = (int)mat.type();
    int dataSize        = (int)(mat.cols * mat.rows * mat.elemSize());

    FILE* FP = fopen(filename.c_str(), "wb");
    int sizeImg[4] = {mat.cols, mat.rows, elemSizeInBytes, elemType };
    fwrite(/*buffer*/ sizeImg, /*howmanyelements*/ 4, /* size of each element */ sizeof(int), /*file*/ FP);
    fwrite(mat.data, mat.cols * mat.rows, elemSizeInBytes, FP);
    fclose(FP);
}

Mat deserializeMatbin(std::string filename){
    FILE* fp = fopen(filename.c_str(), "r");
    int header[4];
    fread(header, sizeof(int), 4, fp);
    int cols = header[0]; 
    int rows = header[1];
    int elemSizeInBytes = header[2];
    int elemType = header[3];

    Mat outputMat = Mat(rows, cols, elemType);

    fread(outputMat.data, elemSizeInBytes, cols * rows, fp);
    fclose(fp);
    return outputMat;
}

void testSerializeMatbin(){
    Mat a = Mat::ones(/*cols*/ 10, /* rows */ 5, CV_8U) * 2;
    std::string filename = "test.matbin";
    serializeMatbin(a, filename);
    Mat b = deserializeMatbin(filename);
    cout << "Rows: " << b.rows << " Cols: " << b.cols << " type: " << b.type()<< endl;
}
0 голосов
/ 20 июня 2018

Вы также можете использовать msgpack Создать адаптер https://github.com/msgpack/msgpack-c/wiki/v2_0_cpp_adaptor Вот пример кода. Это может быть полезно:

namespace clmdep_msgpack {
MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) {
    namespace adaptor {

        //I am sending as bin (int)(int)(int)(char*)
        //Mat values:         rows,cols,type,data
        template<>
        struct convert<cv::Mat> {
            clmdep_msgpack::object const &operator()(clmdep_msgpack::object const &o, cv::Mat &v) const
            {
                if(o.type != clmdep_msgpack::type::BIN) throw clmdep_msgpack::type_error();
                char *buffer = (char *) o.via.bin.ptr;
                int buffer_size = o.via.bin.size;
                int rows, cols, type;
                rows = *reinterpret_cast<int *>(buffer);
                cols = *reinterpret_cast<int *>(buffer + 1 * sizeof(int));
                type = *reinterpret_cast<int *>(buffer + 2 * sizeof(int));
                cv::Mat(rows, cols, type, (void *) (buffer + 3 * sizeof(int))).copyTo(v);

                return o;
            }
        };

        template<>
        struct pack<cv::Mat> {
            template<typename Stream>
            clmdep_msgpack::packer<Stream> &operator()(clmdep_msgpack::packer<Stream> &o, cv::Mat const &v) const
            {
                // packing member variables as bin.
                size_t mat_size;
                if(v.isContinuous()) {
                    mat_size = v.total() * v.elemSize();
                } else {
                    mat_size = v.step[v.dims - 1];
                    for(int t = 0; t < v.dims; t++) {
                        // calculate total size of multi dimensional matrix by multiplying dimensions
                        mat_size *= v.size[t];
                    }
                }
                int extra_ints = 3;
                int buffer_size = extra_ints * sizeof(int) + mat_size;
                // Allocate destination image buffer
                char *imagebuffer = new char[buffer_size];
                int type = v.type();
                std::memcpy(imagebuffer, &(v.rows), sizeof(int));
                std::memcpy(imagebuffer + 1 * sizeof(int), &(v.cols), sizeof(int));
                std::memcpy(imagebuffer + 2 * sizeof(int), &type, sizeof(int));

                if(v.isContinuous()) {
                    std::memcpy((imagebuffer + 3 * sizeof(int)), (char *) v.data, mat_size);
                } else {
                    const size_t rowsize = v.step[v.dims - 1] * v.size[v.dims - 1];
                    size_t coordinates[v.dims - 1] = {0};
                    size_t srcptr = 0, dptr = extra_ints * sizeof(int);
                    while(dptr < buffer_size) {
                        // we copy entire rows at once, so lowest iterator is always [dims-2]
                        // this is legal since OpenCV does not use 1 dimensional matrices internally (a 1D matrix is a 2d matrix with only 1 row)
                        std::memcpy(&imagebuffer[dptr], &(((char *) v.data)[srcptr]), rowsize);
                        // destination matrix has no gaps so rows follow each other directly
                        dptr += rowsize;
                        // src matrix can have gaps so we need to calculate the address of the start of the next row the hard way
                        // see *brief* text in opencv2/core/mat.hpp for address calculation
                        coordinates[v.dims - 2]++;
                        srcptr = 0;
                        for(int t = v.dims - 2; t >= 0; t--) {
                            if(coordinates[t] >= v.size[t]) {
                                if(t == 0) break;
                                coordinates[t] = 0;
                                coordinates[t - 1]++;
                            }
                            srcptr += v.step[t] * coordinates[t];
                        }
                    }
                }
                o.pack_bin(buffer_size);
                o.pack_bin_body(imagebuffer, buffer_size);
                return o;
            }
        };

        template<>
        struct object_with_zone<cv::Mat> {
            void operator()(clmdep_msgpack::object::with_zone &o, cv::Mat const &v) const
            {
                size_t mat_size;
                if(v.isContinuous()) {
                    mat_size = v.total() * v.elemSize();
                } else {
                    mat_size = v.step[v.dims - 1];
                    for(int t = 0; t < v.dims; t++) {
                        // calculate total size of multi dimensional matrix by multiplying dimensions
                        mat_size *= v.size[t];
                    }
                }
                int extra_ints = 3;
                int buffer_size = extra_ints * sizeof(int) + mat_size;
                // Allocate destination image buffer
                char *imagebuffer = new char[buffer_size];
                int type = v.type();
                std::memcpy(imagebuffer, &(v.rows), sizeof(int));
                std::memcpy(imagebuffer + 1 * sizeof(int), &(v.cols), sizeof(int));
                std::memcpy(imagebuffer + 2 * sizeof(int), &type, sizeof(int));

                if(v.isContinuous()) {
                    std::memcpy((imagebuffer + 3 * sizeof(int)), (char *) v.data, mat_size);
                } else {
                    const size_t rowsize = v.step[v.dims - 1] * v.size[v.dims - 1];
                    size_t coordinates[v.dims - 1] = {0};
                    size_t srcptr = 0, dptr = extra_ints * sizeof(int);
                    while(dptr < buffer_size) {
                        // we copy entire rows at once, so lowest iterator is always [dims-2]
                        // this is legal since OpenCV does not use 1 dimensional matrices internally (a 1D matrix is a 2d matrix with only 1 row)
                        std::memcpy(&imagebuffer[dptr], &(((char *) v.data)[srcptr]), rowsize);
                        // destination matrix has no gaps so rows follow each other directly
                        dptr += rowsize;
                        // src matrix can have gaps so we need to calculate the address of the start of the next row the hard way
                        // see *brief* text in opencv2/core/mat.hpp for address calculation
                        coordinates[v.dims - 2]++;
                        srcptr = 0;
                        for(int t = v.dims - 2; t >= 0; t--) {
                            if(coordinates[t] >= v.size[t]) {
                                if(t == 0) break;
                                coordinates[t] = 0;
                                coordinates[t - 1]++;
                            }
                            srcptr += v.step[t] * coordinates[t];
                        }
                    }
                }
                o.type = type::BIN;
                o.via.bin.size = buffer_size;
                o.via.bin.ptr = imagebuffer;
            }
        };


    } // namespace adaptor
} // MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS)
} // names
0 голосов
/ 24 марта 2016

Как насчет просто преобразовать ваш Mat в вектор и использовать fwrite?

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

0 голосов
/ 02 февраля 2011

Недавно я задавал себе подобный вопрос, хотя конкретно я пытался сериализовать объекты Mat и MatND opencv. Использование boost::serialize приятно, но требует нескольких уловок. Поскольку вы не хотите модифицировать внутренние компоненты самого OpenCV для сериализации этих объектов, вы вынуждены использовать так называемую «свободную» функцию. Поскольку сериализовать объекты OpenCV сложно, я обнаружил, что вынужден был разделить операцию сериализации на сохранение и загрузку, каждый из которых имеет немного отличную реализацию. Вам нужно использовать boost/serialization/split_free.hpp для этой задачи. Boost предоставляет хорошую документацию для этого здесь: http://www.boost.org/doc/libs/1_45_0/libs/serialization/doc/index.html.

Удачи!

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