Serialización de OpenCV Mat_

Estoy trabajando en un proyecto de investigación de robótica en el que necesito serializar matrices 2D de puntos 3D: básicamente, cada píxel es un vector de 3 flotadores. Estos píxeles se guardan en una matriz OpenCV, y deben enviarse a través de la comunicación entre procesos y guardarse en archivos para procesarlos en varias computadoras. Me gustaría serializarlos de una manera endian / independiente de la architecture y con un uso eficiente del espacio , tan rápido como sea posible. cv::imencode aquí sería perfecto, excepto que solo funciona en elementos de 8 y 16 bits, y no queremos perder precisión. No es necesario que los archivos sean legibles por humanos (aunque lo hacemos ahora para garantizar la portabilidad de los datos, y es increíblemente lento). ¿Hay mejores prácticas para esto o maneras elegantes de hacerlo?

¡Gracias!

Editar: Christoph Heindl ha comentado esta publicación con un enlace a su blog donde ha mejorado este código de serialización. ¡Muy recomendable!

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

Para quien sea que se pueda beneficiar: Algunos códigos para serializar Mat & con boost :: serialización
No he probado con datos multicanal, pero todo debería funcionar bien.

 #include  #include  #include  #include  #include  #include  BOOST_SERIALIZATION_SPLIT_FREE(Mat) namespace boost { namespace serialization { /*** Mat ***/ template 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 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]; } } } } 

Ahora, mat se puede serializar y deserializar de la siguiente manera:

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

He usado el binary_oarchive y el binary_iarchive aquí para mantener el uso de la memoria. El formato binario no proporciona portabilidad entre plataformas, pero si lo desea, se puede usar text_oarchive / iarchive.

Las respuestas anteriores son buenas, pero no funcionarán para matrices no continuas que surgen cuando desea serializar regiones de interés (entre otras cosas). Además, no es necesario serializar elemSize() porque esto se deriva del valor de type .

Aquí hay un código que funcionará independientemente de la continuidad (con includes / namespace)

 #pragma once #include  #include  #include  #include  namespace boost { namespace serialization { template 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 

Podría usar boost::serialization para eso. Está muy optimizado y es bastante fácil de integrar.

Las posibles aceleraciones para su caso incluyen la serialización de cada objeto como un bloque binario en bruto (ver boost::serialization::make_binary ) y deshabilitar el seguimiento de versiones ( BOOST_SERIALIZATION_DISABLE_TRACKING ).

Además, puede experimentar agregando compresión en sus rutinas de serialización para ahorrar espacio (y tiempo en el caso de datos que se pueden comprimir fácilmente ). Esto se puede implementar con boost::iostreams , por ejemplo.

Recientemente me hice una pregunta similar, aunque específicamente estaba tratando de serializar los objetos Mat y MatND . Usar boost::serialize es bueno, pero requiere un par de trucos. Como no quiere modificar las partes internas de OpenCV para serializar estos objetos, se ve obligado a utilizar lo que se llama una función “libre”. Dado que es complicado serializar los objetos de OpenCV, descubrí que me vi obligado a dividir la operación de serialización en guardar y cargar, cada uno con una implementación ligeramente diferente. Necesita usar boost/serialization/split_free.hpp para esta tarea. Boost proporciona una buena documentación para esto aquí: http://www.boost.org/doc/libs/1_45_0/libs/serialization/doc/index.html .

¡Buena suerte!

¿Qué tal simplemente convertir tu Mat a un vector y usar fwrite?

El proceso de conversión a vector puede afectar el rendimiento, pero es seguro. Sospecho que todas las respuestas anteriores, ya sea recorriendo los datos de la imagen como en la respuesta aceptada, o utilizando make_array como en la publicación de Christoph, supongamos que los datos de Mat son contiguos, lo que no es necesariamente el caso. Cuando los datos de Mat no son contiguos, el resultado de estas respuestas no será correcto.

Puede usar msgpack también Crear un adaptador https://github.com/msgpack/msgpack-c/wiki/v2_0_cpp_adaptor Aquí hay un código de ejemplo. Puede ser útil:

 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 { 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(buffer); cols = *reinterpret_cast(buffer + 1 * sizeof(int)); type = *reinterpret_cast(buffer + 2 * sizeof(int)); cv::Mat(rows, cols, type, (void *) (buffer + 3 * sizeof(int))).copyTo(v); return o; } }; template<> struct pack { template clmdep_msgpack::packer &operator()(clmdep_msgpack::packer &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 { 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 

Escribí este código:

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