如何在OpenCV中将浮点矩阵写入文件

36

我有一个矩阵,

Mat B(480,640,CV_32FC1);

其中包含浮点数值。我想将这个矩阵写入一个文件,可以在记事本Microsoft WordExcel中打开以查看内部的值并进行存储,但是imwrite函数只能保存8位或16位图像...

这可行吗?如果可以,怎么做?


你可以将矩阵写入XML或YAML文件中。 - Saikat
见我个人观点最佳答案。 - sturkmen
6个回答

63
使用纯OpenCV API调用:
// Declare what you need
cv::FileStorage file("some_name.ext", cv::FileStorage::WRITE);
cv::Mat someMatrixOfAnyType;

// Write to file!
file << "matName" << someMatrixOfAnyType;

// Close the file and release all the memory buffers
file.release();

文件扩展名可以是xml或yml。 在这两种情况下,您会得到一个小标题,您可以轻松地删除/解析,然后以浮点格式访问数据。 我成功地使用这种方法(使用yml文件)将数据导入MATLABMatplotlib
获取数据的方法如下:
  1. 使用任何编辑器打开文件
  2. 然后删除所有文本和数字,只保留数据标签的内容(即像素值)。
  3. 完成后,将文件保存为.txt或.csv扩展名,并使用MATLAB打开它(拖放操作可行)。
完成。如果MATLAB没有正确猜测图像大小,您可能需要在MATLAB命令行上重新调整结果矩阵的形状。

22
我认为你需要给这个矩阵起个名字,例如 file << "matName" << someMatrixOfAnyType;否则,你将会得到这个错误:OpenCV Error: Unspecified error (No element name has been given) in operator<<, file /usr/local/include/opencv2/core/operations.hpp, line 2910 - Daniel Golden
8
应该在此之后使用file.release()。作为一个经验法则,关闭和释放文件IO资源是很好的做法。 - dev_nut
4
@dev_nut:FileStorage析构函数会调用release(),不需要显式调用。 - hauron
2
需要显式调用 file.release()。否则,它不会保存实际数据。 - WhaSukGO
2
需要明确调用file.release()。否则,它将不会保存实际数据。 - WhaSukGO
显示剩余7条评论

18

您可以使用简单的C++文件处理将cv::Mat写入文本文件。

以下是您可以执行此操作的方式:

#include <iostream>
#include <fstream>

using namespace std;

void writeMatToFile(cv::Mat& m, const char* filename)
{
    ofstream fout(filename);

    if(!fout)
    {
        cout<<"File Not Opened"<<endl;  return;
    }

    for(int i=0; i<m.rows; i++)
    {
        for(int j=0; j<m.cols; j++)
        {
            fout<<m.at<float>(i,j)<<"\t";
        }
        fout<<endl;
    }

    fout.close();
}

int main()
{
    cv::Mat m = cv::Mat::eye(5,5,CV_32FC1);

    const char* filename = "output.txt";

    writeMatToFile(m,filename);

}

1
也想采纳这个答案...但只能选一个...不管怎样,这种简单的方法也非常有效!1 - Karthik Murugan
@smttsp 我不这么认为。OpenCV 的实现可能会被优化。 - sgarizvi
谢谢,我怀疑编译器优化是否可以使两种方法具有相同的速度。 - smttsp
我已经检查了两个答案,你的比另一个答案慢50-60%左右,对于一个1024x1024的双精度矩阵写入XML文件。 - smttsp
@smttsp... 当然,我的解决方案只是一种基本方法。而OpenCV的实现则更加优化。 - sgarizvi
1
@sgarizvi,相比被接受的答案,您的解决方案更适用于批处理。如果我们需要将数百个cv::mat对象写入文件,按照被接受的答案建议的方式打开和修改每个xml/yml文件将会很困难,除非我们再次编写一些代码来自动修改。 - Ruchir

5

OpenCV可以将其对象序列化(保存)为JSONXMLYAML格式。您可以使用任何理解这些格式的编辑器来读取这些文件,或使用OpenCV从这些文件中下载数据(反序列化)。如何完成此操作的详细说明可以在此处找到。简而言之,要将数据存储到XML文件中,您需要调用

cv::FileStorage fs("/path/to/file.xml", cv::FileStorage::WRITE); // Create FileStorage object
cv::Mat cameraM; // Matrix, which you need to save, do not forget to fill it with some data
fs << "cameraMatrix" << cameraM; // Command to save the data
fs.release(); // Releasing the file.

如果您想使用JSON或YAML,请将扩展名更改为.json.yaml/.yml - OpenCV会自动理解您的意图。
重要的是命令。
fs << "cameraMatrix" << cameraM;

"cameraMatrix"是标签名称,此矩阵将存储在该标签下,并且可以使用该标签在文件中查找此矩阵。

请注意,xml格式不允许您使用带有空格和某些特殊字符的标签名称,因为只允许字母数字值、点、破折号和下划线(详见XML规范),而在YAMLJSON中,您可以使用如下内容:

fs << "Camera Matrix" << cameraM;

3
我编写了这段代码来序列化矩阵,以下是示例代码。
#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

/*
Will save in the file:
cols\n
rows\n
elemSize\n
type\n
DATA
*/
void serializeMatbin(cv::Mat& mat, std::string filename){
    if (!mat.isContinuous()) {
        std::cout << "Not implemented yet" << std::endl;
        exit(1);
    }
    
    int elemSizeInBytes = (int)mat.elemSize();
    int elemType        = (int)mat.type();
    int dataSize        = (int)(mat.cols * mat.rows * mat.elemSize());

    FILE* FP = fopen(filename.c_str(), "wb");
    int sizeImg[4] = {mat.cols, mat.rows, elemSizeInBytes, elemType };
    fwrite(/* buffer */ sizeImg, /* how many elements */ 4, /* size of each element */ sizeof(int), /* file */ FP);
    fwrite(mat.data, mat.cols * mat.rows, elemSizeInBytes, FP);
    fclose(FP);
}

cv::Mat deserializeMatbin(std::string filename){
    FILE* fp = fopen(filename.c_str(), "rb");
    int header[4];
    fread(header, sizeof(int), 4, fp);
    int cols            = header[0]; 
    int rows            = header[1];
    int elemSizeInBytes = header[2];
    int elemType        = header[3];

    //std::cout << "rows="<<rows<<" cols="<<cols<<" elemSizeInBytes=" << elemSizeInBytes << std::endl;

    cv::Mat outputMat = cv::Mat::ones(rows, cols, elemType);
    
    size_t result = fread(outputMat.data, elemSizeInBytes, (size_t)(cols * rows), fp);

    if (result != (size_t)(cols * rows)) {
        fputs ("Reading error", stderr);
    }

    std::cout << ((float*)outputMat.data)[200] << std::endl;
    fclose(fp);
    return outputMat;
}

void testSerializeMatbin(){
    cv::Mat a = cv::Mat::ones(/*cols*/ 10, /* rows */ 5, CV_32F) * -2;
    std::string filename = "test.matbin";
    serializeMatbin(a, filename);
    cv::Mat b = deserializeMatbin(filename);
    std::cout << "Rows: " << b.rows << " Cols: " << b.cols << " type: " << b.type()<< std::endl;
}

需要解释一下。例如,什么是想法/要点?有什么示例输出?为什么在只调用输出时包括反序列化?来自帮助中心“...始终解释为什么您提出的解决方案是合适的以及它如何工作”。请通过编辑(更改)您的答案,而不是在此处进行评论(不带“编辑:”,“更新:”或类似内容-答案应该看起来像今天写的)。 - Peter Mortensen

2
使用写二进制:
FILE* FP = fopen("D.bin", "wb");
int sizeImg[2] = { D.cols, D.rows };
fwrite(sizeImg, 2, sizeof(int), FP);
fwrite(D.data, D.cols * D.rows, sizeof(float), FP);
fclose(FP);

然后你可以在MATLAB中读取它。

读取大小,然后重新整形(类型=single):

fp = fopen(fname);
data = fread(fp, 2, 'int');
width = data(1); height = data(2);
B = fread(fp, Inf, type);

imageOut = reshape(B, [width, height])';

fclose(fp);

0
从OpenCV 3.4.4 开始,imwrite可以将CV_32F图像序列化为TIFF格式。虽然比“原始”代码慢,但它确保文件在所有架构之间可移植。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接