求助:如何在C++中不使用外部库或API读取.mat文件?
不用外部库在C++中读取.mat文件的方法
你的代码直接通过seekg(2665452)定位读取img1的方式是错误的——.mat文件不是简单的二进制数据拼接,它遵循Level 5 MAT-file格式规范,包含文件头、变量元数据(类型、尺寸、名称)、数据块等结构,必须按格式解析才能正确读取内容。
下面是手动解析的核心步骤和代码示例:
1. 读取并验证文件头
.mat文件开头128字节是固定格式的文件头,包含版本、字节序等关键信息:
#include <fstream> #include <cstdint> #include <string> #include <vector> #include <opencv2/opencv.hpp> // 字节交换工具函数 uint32_t swap32(uint32_t val) { return ((val >> 24) & 0xFF) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | ((val << 24) & 0xFF000000); } double swap64(double val) { uint64_t tmp = *reinterpret_cast<uint64_t*>(&val); tmp = ((tmp >> 56) & 0xFF) | ((tmp >> 40) & 0xFF00) | ((tmp >> 24) & 0xFF0000) | ((tmp >> 8) & 0xFF000000) | ((tmp << 8) & 0xFF00000000) | ((tmp << 24) & 0xFF0000000000) | ((tmp << 40) & 0xFF000000000000) | ((tmp << 56) & 0xFF00000000000000); return *reinterpret_cast<double*>(&tmp); } struct MatHeader { char magic[4]; // 固定为"MATL" uint16_t version; // Level 5版本为0x0100 char endian[2]; // "IM"表示小端字节序,"MI"表示大端 char reserved[120]; // 预留空间,无需处理 }; int main() { std::ifstream file("file.mat", std::ios::binary); if (!file.is_open()) { std::cerr << "文件打开失败" << std::endl; return 1; } // 读取文件头 MatHeader header; file.read(reinterpret_cast<char*>(&header), sizeof(MatHeader)); // 验证文件格式 if (std::string(header.magic, 4) != "MATL" || header.version != 0x0100) { std::cerr << "不是Level 5格式的.mat文件" << std::endl; return 1; } // 判断是否需要字节交换(如果文件字节序和当前系统不一致) bool need_swap = (std::string(header.endian, 2) == "MI");
2. 循环解析变量数据块
每个变量都以8字节的标签开头(前4字节是数据类型,后4字节是数据长度),我们需要循环读取标签,筛选出目标变量:
struct MatTag { uint32_t type; uint32_t length; }; MatTag tag; while (file.read(reinterpret_cast<char*>(&tag), sizeof(tag))) { // 处理字节序 if (need_swap) { tag.type = swap32(tag.type); tag.length = swap32(tag.length); } switch (tag.type) { case 0x00000007: { // miDOUBLE类型,对应你的img1、img2 // 读取变量名(miUTF8类型标签) MatTag name_tag; file.read(reinterpret_cast<char*>(&name_tag), sizeof(name_tag)); if (need_swap) { name_tag.type = swap32(name_tag.type); name_tag.length = swap32(name_tag.length); } if (name_tag.type != 0x0000001B) { // 确认是UTF8字符串 file.seekg(name_tag.length, std::ios::cur); continue; } std::string var_name(name_tag.length, '\0'); file.read(&var_name[0], name_tag.length); // 读取数组维度(miINT32类型,2维数组对应2个整数:行×列) MatTag dim_tag; file.read(reinterpret_cast<char*>(&dim_tag), sizeof(dim_tag)); if (need_swap) { dim_tag.type = swap32(dim_tag.type); dim_tag.length = swap32(dim_tag.length); } if (dim_tag.type != 0x00000004) { // 确认是INT32类型 file.seekg(dim_tag.length, std::ios::cur); continue; } uint32_t dims[2]; file.read(reinterpret_cast<char*>(dims), dim_tag.length); if (need_swap) { dims[0] = swap32(dims[0]); dims[1] = swap32(dims[1]); } // 读取并处理目标变量数据 size_t data_size = dims[0] * dims[1] * sizeof(double); if (var_name == "img1") { cv::Mat img1(dims[0], dims[1], CV_64F); file.read(reinterpret_cast<char*>(img1.data), data_size); // 如果需要字节交换,逐个转换double值 if (need_swap) { double* ptr = img1.ptr<double>(); for (size_t i = 0; i < dims[0]*dims[1]; ++i) { ptr[i] = swap64(ptr[i]); } } // 转换为8位图像并保存 cv::Mat img1P; img1.convertTo(img1P, CV_8U); cv::imwrite("img1P.png", img1P); } else if (var_name == "img2") { // 按同样逻辑处理img2 } break; } case 0x00000013: { // miSTRUCT类型,对应你的s结构体 // 不需要解析结构体的话,直接跳过对应长度的字节 file.seekg(tag.length, std::ios::cur); break; } default: { // 其他未知类型,直接跳过 file.seekg(tag.length, std::ios::cur); break; } } } file.close(); return 0; }
关键注意事项
- 不能直接按已知大小跳过数据,必须通过标签解析定位变量
- 必须处理字节序问题,否则读取的double值会完全错乱
- 结构体类型如果不需要解析,直接跳过其标签标记的长度即可
内容的提问来源于stack exchange,提问作者user24497374
相关产品推荐
相关产品推荐

