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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 00:12:23