Efficient image to matrix conversion - c++

I am a beginner in c++ (mainly worked with Python) and I do not yet know how to properly do things. I want to process some color images as signals over time and, in order to do that, I want them to be in a double matrix.
A grayscale image would be 1d vector, from top left corner to bottom right, the color image would be a 2d vector, the second dimension being the 3 colors. That is, I want to flatten the image to a long vector, which would contain size 3 vectors with the rgb information.
I open the image using dlib like so:
#include <dlib/gui_widgets.h>
#include <dlib/image_io.h>
#include <dlib/image_transforms.h>
using namespace dlib;
array2d<rgb_pixel> img;
load_image(img, image_name);
Which gives me a dlib array2d containing pixel structs. Now, I want to change that to a flattened image. I figured that, since the images dimensions might change, I would use a
std::vector<std::vector<double>>
as my matrix.
The naive way to convert it would be the following:
#include <vector>
#include <dlib/gui_widgets.h>
#include <dlib/image_io.h>
#include <dlib/image_transforms.h>
std::vector<std::vector<double>> image_to_frame(array2d<rgb_pixel> const &image)
{
const int total_num_of_px = image.nc() * image.nr();
std::vector<std::vector<double>> frame = std::vector<std::vector<double>>(total_num_of_px);
for (int i = 0; i < image.nr(); i++)
{
for (int j = 0; j < image.nc(); j++)
{
frame[(i+1)*j] = std::vector<double>(3);
frame[(i + 1)*j][0] = (double)image[i][j].red;
frame[(i + 1)*j][1] = (double)image[i][j].green;
frame[(i + 1)*j][2] = (double)image[i][j].blue;
}
}
return frame;
}
But this takes 8 seconds for an 1280x720 image. Which seems to me to be a bit long. Is there a better way to do this? A more efficient way of converting the array2d to vector matrix?
Or is there a more efficient data structure than the vector matrix? Or should I not be using dlib and open the image in another way to be easier to convert?
In Python I can open the image directly as a numpy array then do a reshape, which is very fast. Is there some equivalent to this in c++ that I am not aware of?

From API it looks like that image inside dlib is stored exactly like it is done in OpenCV (dlib::toMat converts it by reusing the same memory). It means that you can take a pointer to the first element of array2d, then reinterpret_cast it to the pointer to the struct { uchar r, uchar g, uchar b } (or whatever you would like), its length will be nc*nr. Here you can copy the whole buffer using memcpy.
But I don't really get why you would need it because lines are stored continuosly, so you should not expect any cache misses.
UPDATE: also, cmon, half of the time your program is wasting by converting uchars to doubles. You shouldn't save RGB using double. There are unsigned chars by default.
UPDATE2:
struct rgb
{
uchar r, g, b;
};
rgb* data = reinterpret_cast<rgb*>(&frame[0][0]);
std::vector<rgb> vect;
std::copy(data, data + nc * nr * sizeof(rgb), std::back_inserter(vect));
After that, you have flattened vector of the image that is stored directly in one piece of memory. If you don't need a copy, you can simply use your data pointer.
Also, if you want index-like access, you can use uchar[3] instead of rgb struct.

Related

Segmentation faults when modifying cv::Mat data in c++

Please let me know if this question is too broad, but I am trying to learn some c++ so I thought it would be a good idea to try and recreate some opencv functions.
I am still grabbing the frames or reading the image with opencv's API, but I then want to feed the cv::Mat into my custom function(s), where I modify its data and return it to display it. (For example a function to blur the image, where I pass the original Mat to a padding function, then the output of that to a fn that convolves the padded image with the blurring kernel, and returns the Mat to cv for displaying)
I am a little confused as to what the best (or right) way to do this is. OpenCV functions use a function argument as the return matrix ( cv_foo(cv::Mat src_frame, cv::Mat dst_frame) ) but I am not entirely clear how this works, so I have tried a more familiar approach, something like
cv::Mat my_foo(cv::Mat src_frame) {
// do processing on src_frame data
return dst_frame;
}
where to access the data from src_frame I use uchar* framePtr = frame.data; and to create the dst_frame I followed this suggestion
cv::Mat dst_frame = cv::Mat(n_rows, n_cols, CV_8UC3);
memcpy(dst_frame.data, &new_data_array, sizeof(new_data_array));
I have however encountered various segmentation faults that I find hard to debug, as it seems they occur almost at random (could this be due to the way I am handling the memory management with frame.data or something like that?).
So to come back to my original question, what is the best way to access, modify and pass the data from a cv::Mat in the most consistent way?
I think what would make the most intuitive sense to me (coming from numpy) would be to extract the data array from the original Mat, use that throughout my processing and then repackage it into a Mat before displaying, which would also allow me to feed any custom array into the processing without having to turn it into a Mat, but I am not sure how to best do that (or if it is the right approach).
Thank you!
EDIT:
I will try to highlight the main bug in my code.
One of the functions I am trying to replicate is a conversion from bgr to greyscale, my code looks like this
cv::Mat bgr_to_greyscale(cv::Mat& frame){
int n_rows = frame.rows;
int n_cols = frame.cols;
uchar* framePtr = frame.data;
int channels = frame.channels();
uchar grey_array[n_rows*n_cols];
for(int i=0; i<n_rows; i++){
for(int j=0; j<n_cols; j++){
uchar pixel_b = framePtr[i*n_cols*channels + j*channels];
uchar pixel_g = framePtr[i*n_cols*channels + j*channels + 1];
uchar pixel_r = framePtr[i*n_cols*channels + j*channels + 2];
uchar pixel_grey = 0.299*pixel_r + 0.587*pixel_g + 0.144*pixel_b;
grey_array[i*n_cols + j] = pixel_grey;
}
}
cv::Mat dst_frame = cv::Mat(n_rows, n_cols, CV_8UC1, &grey_array);
return dst_frame;
}
however when I display the result of this function on a sample image I get this result: the bottom part of the image looks like random noise, how can I fix this? what exactly is going wrong in my code?
Thank you!
This question is too broad to answer in any detail, but generally a cv::Mat is a wrapper around the image data akin to the way an std::vector<int> is a wrapper around a dynamically allocated array of int values or an std::string is a wrapper around a dynamically allocated array of characters with one exception: a cv::Mat will not perform a deep copy of the image data on assignment or usage of the copy constructor.
std::vector<int> b = { 1, 2, 3, 4};
std::vector<int> a = b;
// a now contains a copy of b and a[0] = 42 will not effect b.
cv::Mat b = cv::imread( ... );
cv::Mat a = b;
// a and b now wrap the same data.
But that said, you should not be using memcpy et. al. to copy a cv::Mat ... You can make copies with clone or copyTo. From the cv documentation:
Mat F = A.clone();
Mat G;
A.copyTo(G);

Convert std::list to cv::Mat in C++ using OpenCV

I'm trying to solve an equation system using SVD: cv::SVD::solveZ(A, x);, but A needs to be a Matrix. OpenCV doesn't offer any convertion of a std::list to cv::Mat. So my question is, whether there is a smart way to convert it without having to convert the std::list to a std::vector before.
The Matrix A is a 3xN matrix. My list contains N cv::Point3d elements.
My code looks something like this:
std::list<cv::Point3d> points; // length: N
cv::Mat A = cv::Mat(points).reshape(1); // that's how I do it with a std::vector<cv::Point3d>
cv::Mat x;
cv::SVD::solveZ(A, x); // homogeneous linear equation system Ax = 0
If anybody has an idea about it, then please tell me.
cv::Mat can handle only continously stored data, so there are no suitable conversion from std::list. But you can implement it by yourself, as follows:
std::list<cv::Point3d> points;
cv::Mat matPoints(points.size(), 1, CV_64FC3);
int i = 0;
for (auto &p : points) {
matPoints.at<cv::Vec3d>(i++) = p;
}
matPoints = matPoints.reshape(1);

Save cv::Mat data for later usage using NO C++ constructs

I'm using OpenCV within a DLL that provides plain C interfaces, no C++objects are allowed to be handed over to the calling application.
One part of this DLL performs fiducial learning for later pattern recognition which results in a list of keypoints and a Mat object. These data have to be stored by the calling application.
Handing over the keypoints via DLL interface is no problem by using a plain C struct, the members of such a keypoint can be converted easily. But I don't see which parts of cv::Mat are really needed. Or to be more exact: my Mat-object makes use of the member "data" which points to a memory area but I have no idea how much data are contained.
So my question: how can I convert a cv::Mat object into a plain C-style structure, how can I estimate the exact length of the data field?
Thanks!
The easy way is to convert cv::Mat to the classical OpenCV C structure: IplImage.
cv::Mat mat = imread(...);
IplImage img(mat); // hope it's the correct syntax...
A more detailed explanation of the Mat parameters:
data: pointer to data
rows, columns: ...
type() - data type:
channels() - number of channels
step() - stride between two consecutive rows in the image, in bytes. "Includes the gaps, if any"
size_t elemSize() similar to CV_ELEM_SIZE(cvmat->type)
size_t elemSize1() returns the size of element channel in bytes.
And here's how you calculate data field length:
Mat::rows * Mat::step()
If you need to pass a raw pointer to image data, then in the worst case you'll have to do some copying with pointer magic, because image data may not be continous. It is well described in this tutorial.
int channels = I.channels();
int nRows = I.rows * channels;
int nCols = I.cols;
if (I.isContinuous())
{
nCols *= nRows;
nRows = 1;
}
int i,j;
uchar* p;
for( i = 0; i < nRows; ++i)
{
p = I.ptr<uchar>(i);
// And here "p" points to "nCols" components
// row size = nCols * channels * component size (1 byte usually)
}

PCA project and backproject in Opencv

I am working in Ubuntu Opencv.I am trying to do PCA analysis of a single image.I take the 3 channel image and change it to a single channel image with 3 columns and r*c number of rows.r and c being the rows and columns of the original image.When I try to display the reconstructed image after doing the backprojection on the PCA it gives me a green image.Here is my code
Mat pcaset=cvCreateMat(image->height*image->width,image->nChannels,CV_8UC1);
for(int i=0;i<image->height;i++)
{
for(int j=0;j<image->width;j++)
{
for(int k=0;k<image->nChannels;k++)
(ptrpcaset+i*pcaset.step)[k]=((ptrimage+i*image->widthStep)[3*j+k]);
}
}
int nEigens=3;
Mat databackprojected;
PCA pca(pcaset,Mat(),CV_PCA_DATA_AS_ROW,nEigens);
Mat dataprojected(pcaset.rows,nEigens,CV_8UC1);
pca.project(pcaset,dataprojected);
pca.backProject(dataprojected,databackprojected);
Mat backprojectnorm;//(databackprojected.rows,nEigens,CV_8UC1);
normalize(databackprojected,backprojectnorm,0,255,NORM_MINMAX,-1);
Mat finaldataafterreshaping(image->height,image->width,CV_8UC3);
uchar* finalptr=(uchar*)finaldataafterreshaping.data;
uchar* ptrnorm=(uchar*)backprojectnorm.data;
int x=0,y=0,i=0;
while(i<backprojectnorm.rows)
{
while(x<image->height)
{
while(y<image->width)
{
for(int k=0;k<image->nChannels;k++)
{
(finalptr+x*finaldataafterreshaping.step)[3*y+k]=(ptrnorm+i*backprojectnorm.step)[k];
}
y=y+1;i=i+1;
}
x=x+1;y=0;
}
}
imshow("Reconstructed data",finaldataafterreshaping);
You need to make the following changes:
(ptrpcaset+(j + i*image->width)*pcaset.step)[k]=((ptrimage+i*image->widthStep)[3*j+k]);
because you are not taking the j coordinate into account when you transform your data so that at the end you only save the last line of your image in the new matrix.
When you reshape your data, you need to do something like this:
float* val = (float*)&(ptrnorm+i*backprojectnorm.step)[(k*4)];
(finalptr+x*finaldataafterreshaping.step)[3*y+k]=*val;
because the matrix you get as a result is of type float and not uchar. So you need to some kind of conversion. I am not sure, if it is a good idea to do it this way, but it works. I would suggest that you have a look at the C++ API of OpenCV 2, which can handle this things in a much nicer way.
Also, the whole while(i<backprojectnrom.rows) loop is not needed.

OpenCV image array, 4D matrix

I am trying to store a IPL_DEPTH_8U, 3 channel image into an array so that I can store 100 images in memory.
To initialise my 4D array I used the following code (rows,cols,channel,stored):
int size[] = { 324, 576, 3, 100 };
CvMatND* cvImageBucket; = cvCreateMatND(3, size, CV_8U);
I then created a matrix and converted the image into the matrix
CvMat *matImage = cvCreateMat(Image->height,Image->width,CV_8UC3 );
cvConvert(Image, matImage );
How would I / access the CvMatND to copy the CvMat into it at the position of stored?
e.g. cvImageBucket(:,:,:,0) = matImage; // copied first image into array
You've tagged this as both C and C++. If you want to work in C++, you could use the (in my opinion) simpler cv::Mat structure to store each of the images, and then use these to populate a vector with all the images.
For example:
std::vector<cv::Mat> imageVector;
cv::Mat newImage;
newImage = getImage(); // where getImage() returns the next image,
// or an empty cv::Mat() if there are no more images
while (!newImage.empty())
{
// Add image to vector
imageVector.push_back(image);
// get next image
newImage = getImage();
}
I'm guessing something similar to:
for ith matImage
memcpy((char*)cvImageBucket->data+i*size[0]*size[1]*size[2],(char*)matImage->data,size[0]*size[1]*size[2]);
Although I agree with #Chris that it is best to use vector<Mat> rather than a 4D matrix, this answer is just to be a reference for those who really need to use 4D matrices in OpenCV (even though it is a very unsupported, undocumented and unexplored thing with so little available online and claimed to be working just fine!).
So, suppose you filled a vector<Mat> vec with 2D or 3D data which can be CV_8U, CV_32F etc.
One way to create a 4D matrix is
vector<int> dims = {(int)vec.size(), vec[0].rows, vec[0].cols};
Mat m(dims, vec[0].type(), &vec[0]);
However, this method fails when the vector is not continuous which is typically the case for big matrices. If you do this for a discontinuous matrix, you will get a segmentation fault or bad access error when you would like to use the matrix (i.e. copying, cloning, etc). To overcome this issue, you can copy matrices of the vector one by one into the 4D matrix as follows:
Mat m2(dims, vec[0].type());
for (auto i = 0; i < vec.size(); i++){
vec[i].copyTo(temp.at<Mat>(i));
}
Notice that both methods require the matrices to be the same resolution. Otherwise, you may get undesired results or errors.
Also, notice that you can always use for loops but it is generally not a good idea to use them when you can vectorize.