Efficiently Load a Large Mat into Memory in Opencv

Efficiently load a large Mat into memory in OpenCV

Are you ok with a 100x speedup?


You should save and load your images in binary format. You can do that with the matwrite and matread function in the code below.

I tested both loading from a FileStorage and the binary file, and for a smaller image with 250K rows, 192 columns, type CV_8UC1 I got these results (time in ms):

// Mat: 250K rows, 192 cols, type CV_8UC1
Using FileStorage: 5523.45
Using Raw: 50.0879

On a image with 1M rows and 192 cols using the binary mode I got (time in ms):

// Mat: 1M rows, 192 cols, type CV_8UC1
Using FileStorage: (can't load, out of memory)
Using Raw: 197.381

NOTE

  1. Never measure performance in debug.
  2. 3 minutes to load a matrix seems way too much, even for FileStorages. However, you'll gain a lot switching to binary mode.

Here the code with the functions matwrite and matread, and the test:

#include <opencv2\opencv.hpp>
#include <iostream>
#include <fstream>

using namespace std;
using namespace cv;


void matwrite(const string& filename, const Mat& mat)
{
ofstream fs(filename, fstream::binary);

// Header
int type = mat.type();
int channels = mat.channels();
fs.write((char*)&mat.rows, sizeof(int)); // rows
fs.write((char*)&mat.cols, sizeof(int)); // cols
fs.write((char*)&type, sizeof(int)); // type
fs.write((char*)&channels, sizeof(int)); // channels

// Data
if (mat.isContinuous())
{
fs.write(mat.ptr<char>(0), (mat.dataend - mat.datastart));
}
else
{
int rowsz = CV_ELEM_SIZE(type) * mat.cols;
for (int r = 0; r < mat.rows; ++r)
{
fs.write(mat.ptr<char>(r), rowsz);
}
}
}

Mat matread(const string& filename)
{
ifstream fs(filename, fstream::binary);

// Header
int rows, cols, type, channels;
fs.read((char*)&rows, sizeof(int)); // rows
fs.read((char*)&cols, sizeof(int)); // cols
fs.read((char*)&type, sizeof(int)); // type
fs.read((char*)&channels, sizeof(int)); // channels

// Data
Mat mat(rows, cols, type);
fs.read((char*)mat.data, CV_ELEM_SIZE(type) * rows * cols);

return mat;
}

int main()
{
// Save the random generated data
{
Mat m(1024*256, 192, CV_8UC1);
randu(m, 0, 1000);

FileStorage fs("fs.yml", FileStorage::WRITE);
fs << "m" << m;

matwrite("raw.bin", m);
}

// Load the saved matrix

{
// Method 1: using FileStorage
double tic = double(getTickCount());

FileStorage fs("fs.yml", FileStorage::READ);
Mat m1;
fs["m"] >> m1;

double toc = (double(getTickCount()) - tic) * 1000. / getTickFrequency();
cout << "Using FileStorage: " << toc << endl;
}

{
// Method 2: usign raw binary data
double tic = double(getTickCount());

Mat m2 = matread("raw.bin");

double toc = (double(getTickCount()) - tic) * 1000. / getTickFrequency();
cout << "Using Raw: " << toc << endl;
}

int dummy;
cin >> dummy;

return 0;
}

Large data in a cv::Mat or a CvMat*

As far as I can tell, you can do this sort of. You can use the Mat_ template class. Below is a short example I wrote:

#include <opencv2/core/core.hpp>    
#include <iostream>

using namespace std;
using namespace cv;


int main(int argc, char* argv[])
{
Mat_<long double> testing(Size(5, 5));

// initialize matrix to ones
for(int i = 0; i < testing.rows; i++)
{
for(int j = 0; j < testing.cols; j++)
{
testing.at<long double>(i, j) = 1;
}
}

cout << "Element size in bytes is " << testing.elemSize() << "." << endl;

return 0;
}

Now for the caveat... If you try to use many of the helper methods, ones, zeros, operator<<, and others), you will likely see this error:

OpenCV Error: Unsupported format or combination of formats () in scalarToRawData

Hopefully that will be enough that you can use it for some things, but it won't be as clean as usual.

Why there is always 4.5 times difference between RAM and hard disk size of a cv::Mat?

Take a look at the contents of your XML or YML or .bin file with Notepad++. (By the way, if you specify a path ending in .bin, OpenCV will write it in a YAML format...)

You will see that each float from your CV_32F Mat has been written in a format like this 6.49999976e-001. This represents 15 bytes instead of the 4 bytes expected for a float. This is a ratio of 15 / 4 = 3.75. If you add to that all the characters for formatting like ',' '\n' or ' ', you may reach a size that is more than 4 times bigger than what you had on RAM.

If you try to save a Mat with only zeros inside, you will see that the size is quite similar to what you had in RAM because the zeros are written 0.. It is actually smaller if you save it in XML format.

Change data of OpenCV matrix from pointer

You need to create a deep copy. You can use clone:

cv::Mat colorFrame = cv::Mat(height, width, CV_8UC3, pointerToMemoryOfCamera).clone();

You can also speed up the process of saving the images using matwrite and matread functions.



Related Topics



Leave a reply



Submit