如何在C++中无需安装Python将cv::Mat保存为npy文件?
在C++中将cv::Mat保存为npy文件(无需Python/boost::python)
核心思路
直接遵循npy文件的官方格式规范,手动构造文件头部(元数据)并写入图像数据,完全不依赖Python相关库。
npy文件关键结构
npy文件分为三部分:
- 魔法头与版本:固定开头为
\x93NUMPY,后跟版本号(如v1.0为\x01\x00) - 元数据头部:JSON格式字符串,包含数据类型(
descr)、维度(shape)、存储顺序(fortran_order)等信息,头部长度用2字节大端无符号整数表示 - 原始数据:按行优先顺序存储的二进制数据(与cv::Mat默认存储顺序一致)
cv::Mat与numpy数据类型映射
需要将OpenCV的图像深度映射为numpy兼容的类型描述符:
- CV_8U →
|u1(无符号8位整数) - CV_8S →
|i1(有符号8位整数) - CV_16U →
<u2(小端无符号16位整数) - CV_16S →
<i2(小端有符号16位整数) - CV_32S →
<i4(小端有符号32位整数) - CV_32F →
<f4(小端32位浮点数) - CV_64F →
<f8(小端64位浮点数)
完整实现代码
#include <opencv2/opencv.hpp> #include <fstream> #include <stdexcept> #include <string> #include <vector> // 构造npy文件的元数据头部字符串 std::string buildNpyHeader(const cv::Mat& mat) { std::string dtype; switch(mat.depth()) { case CV_8U: dtype = "|u1"; break; case CV_8S: dtype = "|i1"; break; case CV_16U: dtype = "<u2"; break; case CV_16S: dtype = "<i2"; break; case CV_32S: dtype = "<i4"; break; case CV_32F: dtype = "<f4"; break; case CV_64F: dtype = "<f8"; break; default: throw std::invalid_argument("Unsupported cv::Mat depth type"); } // 构造维度信息:单通道为(rows, cols),多通道为(rows, cols, channels) std::vector<size_t> shape; if (mat.channels() == 1) { shape = {static_cast<size_t>(mat.rows), static_cast<size_t>(mat.cols)}; } else { shape = {static_cast<size_t>(mat.rows), static_cast<size_t>(mat.cols), static_cast<size_t>(mat.channels())}; } // 手动拼接JSON格式头部(避免依赖第三方JSON库) std::string header = "{\"descr\": \"" + dtype + "\", \"shape\": ("; for (size_t i = 0; i < shape.size(); ++i) { if (i > 0) header += ", "; header += std::to_string(shape[i]); } header += "), \"fortran_order\": false}"; return header; } // 将cv::Mat保存为npy文件 bool saveMatAsNpy(const cv::Mat& mat, const std::string& filePath) { std::ofstream file(filePath, std::ios::binary); if (!file.is_open()) { return false; } // 写入魔法头与版本号 const char magic_header[] = "\x93NUMPY\x01\x00"; file.write(magic_header, sizeof(magic_header) - 1); // 生成并写入元数据头部 std::string npy_header = buildNpyHeader(mat); uint16_t header_length = static_cast<uint16_t>(npy_header.size()); // 转换为大端字节序(npy规范要求) header_length = ((header_length >> 8) & 0xFF) | ((header_length << 8) & 0xFF00); file.write(reinterpret_cast<const char*>(&header_length), sizeof(header_length)); file.write(npy_header.c_str(), npy_header.size()); // 写入图像数据 if (mat.isContinuous()) { // 连续内存直接写入 file.write(reinterpret_cast<const char*>(mat.data), mat.total() * mat.elemSize()); } else { // 非连续内存逐行写入 size_t row_bytes = mat.cols * mat.elemSize(); for (int i = 0; i < mat.rows; ++i) { file.write(reinterpret_cast<const char*>(mat.ptr(i)), row_bytes); } } file.close(); return true; } // 使用示例 int main() { cv::Mat image = cv::imread("input_image.jpg"); if (image.empty()) { std::cerr << "无法读取图像文件" << std::endl; return -1; } // 注意:OpenCV imread默认读取为BGR格式,若目标Python系统需要RGB,需先转换 // cv::cvtColor(image, image, cv::COLOR_BGR2RGB); if (saveMatAsNpy(image, "output.npy")) { std::cout << "npy文件保存成功" << std::endl; } else { std::cerr << "npy文件保存失败" << std::endl; } return 0; }
注意事项
- 通道顺序:OpenCV默认使用BGR通道顺序,而多数Python视觉库(如PIL、PyTorch)默认使用RGB,需根据目标系统需求决定是否转换。
- 数据连续性:cv::Mat在某些操作后可能变为非连续内存,代码中已处理这种情况。
- 验证方法:可使用Python代码
import numpy as np; arr = np.load("output.npy")验证文件是否可正常读取。
内容的提问来源于stack exchange,提问作者suihe dolood
相关产品推荐
相关产品推荐

