You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:10:22