我想将下面的结构存储在磁盘中,并能够再次读取它:(C++)
struct pixels {
std::vector<cv::Point> indexes;
cv::Mat values;
};
我尝试使用
ofstream
和ifstream
,但是它们需要变量的大小,在这种情况下我真的不知道该如何计算。这不是具有一些int和double的简单结构。有什么办法可以用C++做到这一点,最好不要使用任何第三方库。(我实际上来自
Matlab
语言。使用save:save(filename, variables)
用该语言很容易做到)。编辑:
我刚刚尝试了Boost序列化。不幸的是,我的使用非常慢。
最佳答案
考虑到多种方法,各有利弊。
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文档中有一个nice tutorial,我现在似乎无法在新文档中找到它。这样做的好处是我们已经支持
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序列化
正如您提到的那样,另一种可能的方法是使用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)
注意:我们正在测试10000
indexes
和values
为1024x1024 BGR图像。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方法,如果需要跨平台功能,请对其进行调整。
关于c++ - 将包含 vector 和cv::Mat的结构存储到磁盘-C++中的数据序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50060569/