C++实现含std::vector<cv::Point>与cv::Mat的结构体磁盘序列化
不用第三方库实现自定义C++结构体的磁盘存储与读取
我完全理解你的困扰——Matlab里存个结构体简直顺手拈来,但C++里碰到带动态容器(比如std::vector)和OpenCV的cv::Mat这种非POD类型的结构体,直接用ofstream/ifstream按内存块读写肯定不行,因为它们的实际数据都在堆上,结构体本身只存了指针和元数据。不过不用第三方库也完全能搞定,核心思路就是手动序列化每个成员的元数据和实际内容,下面给你一步步讲清楚:
核心思路
对于你的pixels结构体,我们需要分别处理两个成员:
std::vector<cv::Point>:先存元素的数量,再逐个存储每个cv::Point的x、y坐标(因为cv::Point是固定大小的POD类型,直接读写成员没问题)cv::Mat:必须存储重建Mat所需的关键元数据(行数、列数、数据类型),然后再存储实际的像素数据块
完整实现代码
1. 头文件与结构体定义
#include <vector> #include <fstream> #include <iostream> #include <opencv2/opencv.hpp> struct pixels { std::vector<cv::Point> indexes; cv::Mat values; };
2. 序列化函数(写入磁盘)
这个函数负责把pixels结构体的内容写入二进制文件:
bool write_pixels(const pixels& data, const std::string& filename) { // 二进制模式打开文件,避免文本模式的转义破坏数据 std::ofstream out(filename, std::ios::binary); if (!out.is_open()) { std::cerr << "Failed to open file for writing!" << std::endl; return false; } // 处理std::vector<cv::Point>:先存元素个数,再逐个存坐标 size_t index_count = data.indexes.size(); out.write(reinterpret_cast<const char*>(&index_count), sizeof(index_count)); for (const auto& pt : data.indexes) { out.write(reinterpret_cast<const char*>(&pt.x), sizeof(pt.x)); out.write(reinterpret_cast<const char*>(&pt.y), sizeof(pt.y)); } // 处理cv::Mat:先存元数据,再存数据块 // 先确保Mat是连续的(避免ROI这类非连续情况导致数据不完整) cv::Mat mat_to_write = data.values.isContinuous() ? data.values : data.values.clone(); int rows = mat_to_write.rows; int cols = mat_to_write.cols; int type = mat_to_write.type(); size_t data_size = mat_to_write.total() * mat_to_write.elemSize(); out.write(reinterpret_cast<const char*>(&rows), sizeof(rows)); out.write(reinterpret_cast<const char*>(&cols), sizeof(cols)); out.write(reinterpret_cast<const char*>(&type), sizeof(type)); out.write(reinterpret_cast<const char*>(mat_to_write.data), data_size); out.close(); return true; }
3. 反序列化函数(读取磁盘)
这个函数负责从二进制文件恢复pixels结构体:
bool read_pixels(pixels& data, const std::string& filename) { std::ifstream in(filename, std::ios::binary); if (!in.is_open()) { std::cerr << "Failed to open file for reading!" << std::endl; return false; } // 恢复std::vector<cv::Point>:先读元素个数,再逐个恢复坐标 size_t index_count; in.read(reinterpret_cast<char*>(&index_count), sizeof(index_count)); data.indexes.resize(index_count); for (auto& pt : data.indexes) { in.read(reinterpret_cast<char*>(&pt.x), sizeof(pt.x)); in.read(reinterpret_cast<char*>(&pt.y), sizeof(pt.y)); } // 恢复cv::Mat:先读元数据创建Mat,再读数据块填充 int rows, cols, type; in.read(reinterpret_cast<char*>(&rows), sizeof(rows)); in.read(reinterpret_cast<char*>(&cols), sizeof(cols)); in.read(reinterpret_cast<char*>(&type), sizeof(type)); data.values.create(rows, cols, type); size_t data_size = data.values.total() * data.values.elemSize(); in.read(reinterpret_cast<char*>(data.values.data), data_size); in.close(); return true; }
4. 使用示例
int main() { // 创建测试数据 pixels my_pixels; my_pixels.indexes.emplace_back(10, 20); my_pixels.indexes.emplace_back(30, 40); my_pixels.indexes.emplace_back(50, 60); // 创建一个3x3的彩色Mat,给第一个像素设红色 my_pixels.values = cv::Mat::zeros(3, 3, CV_8UC3); my_pixels.values.at<cv::Vec3b>(0, 0) = cv::Vec3b(255, 0, 0); // 写入文件 if (!write_pixels(my_pixels, "pixels_data.bin")) { return 1; } // 读取文件 pixels loaded_pixels; if (!read_pixels(loaded_pixels, "pixels_data.bin")) { return 1; } // 验证数据是否正确 std::cout << "Loaded " << loaded_pixels.indexes.size() << " points:" << std::endl; for (const auto& pt : loaded_pixels.indexes) { std::cout << "(" << pt.x << ", " << pt.y << ")" << std::endl; } std::cout << "\nLoaded Mat info: " << loaded_pixels.values.rows << "x" << loaded_pixels.values.cols << ", type: " << loaded_pixels.values.type() << std::endl; cv::Vec3b first_pixel = loaded_pixels.values.at<cv::Vec3b>(0, 0); std::cout << "First pixel value: R=" << (int)first_pixel[0] << ", G=" << (int)first_pixel[1] << ", B=" << (int)first_pixel[2] << std::endl; return 0; }
关键注意事项
- 二进制模式必须开启:用
std::ios::binary打开文件,否则文本模式会自动转换换行符等字符,导致数据错位。 - 处理非连续Mat:如果你的
cv::Mat是从ROI截取的,它的数据是非连续的,直接写data指针会丢失部分数据,所以代码里先判断isContinuous(),非连续就克隆成连续的Mat再存储。 - 跨平台兼容性:如果需要在不同平台(比如32位和64位系统)之间读写,建议把
int换成固定大小的类型(比如std::int32_t),避免因整数大小差异导致元数据读取错误。 - 错误检查:示例里加了基本的文件打开检查,实际项目中还可以检查
in.gcount()是否等于预期的读写字节数,确保数据读写完整。
内容的提问来源于stack exchange,提问作者J. Mando
相关产品推荐
相关产品推荐

