Ask Your Question
1

returning a Mat from a function

asked 2016-06-17 07:20:34 -0600

hovnatan gravatar image

updated 2016-09-16 07:31:30 -0600

Hi. I am new to OpenCV. I have the following code that outputs garbage (some random numbers). Can you please help me understand the problem?

#include <opencv2/core/core.hpp>           
#include <opencv2/highgui/highgui.hpp>     
#include <opencv2/imgproc/imgproc.hpp>     

#include <iostream>                        

using namespace std;                       

cv::Mat transformToCVMatrix()              
{                                          
    double matrix[2][2] = { { 1.0, 0.0 },  
                            { 0.0, 1.0 }   
                          };                                     

    return cv::Mat(2, 2, CV_64FC1, matrix);
}                                          


int main()                                 
{                                          
    cv::Mat mat = transformToCVMatrix();   
    cout << mat << endl;                   
    cv::Mat imat = mat.inv();              
    cout << imat << endl;                   
    return 0;                              
}
edit retag flag offensive close merge delete

2 answers

Sort by ยป oldest newest most voted
4

answered 2016-06-17 12:58:47 -0600

pklab gravatar image

updated 2016-06-28 14:10:27 -0600

From the doc If the array header is built on top of user-allocated data, you should handle the data by yourself ... in your case the returned cv::Mat.data points to the memory of matrix variable that is local and is destroyed on function exit. Consequently the cv::Mat.data becomes a dangling pointer.

see also this about method to declare an image processing function

Some examples:

CASE (1) This works because OpenCV is the owner of the img memory. The local img is destroyed but its memory is safely managed by OpenCV because is used by main()

cv::Mat transformToCVMatrix()
{
  //img is local and has its own memory
  cv::Mat img(rows,cols,CV_64FC1);
  ...
  return img; 
}
void main()
{
    cv::Mat mat = transformToCVMatrix();
}

CASE (2) this works because the user-allocated data is within the scope of the caller function main(). The memory is still valid after transformToCVMatrix returns

const int rows = 2;
const int cols = 3;
double userdata[rows][cols] = {
    { 0, 1, 2 },
    { 3, 4, 5 }
};

cv::Mat transformToCVMatrix()
{
    //here img points to an user-allocated data that is GLOBAL
    cv::Mat img(rows,cols,CV_64FC1,userdata);
  ...
  return img;
}
void main()
{
    cv::Mat mat = transformToCVMatrix();
}

CASE (3) This works with care until you don't explicitly delete userdata. You have to delete[] userdata somewhere. Is common case that userdata is allocated by some other 3rd function or API. Maybe it will dispose the memory for you. In this case you have to be sure that doesn't happen while you are using the Mat otherwise it will become a dangling Mat. But if you manage the memory you can delete it using he Mat.data pointer.

cv::Mat transformToCVMatrix()
{
  int rows = 2, cols = 3;
  double *userdata = new double[cols*rows];
  // fill with test values;
  for (int r = 0; r < rows; r++)
    for (int c = 0; c < cols; c++)
    {
      int idx = c + r*cols;
      userdata[idx] = double(idx);
    }
  //we are making a header for user-allocated on the heap.
  //This will survive after function returns
  cv::Mat img(rows, cols, CV_64FC1, userdata);
  return img;
}
void main()
{
    cv::Mat mat = transformToCVMatrix();
    // use your mat but remember to delete the memory
    delete[](double *)mat.data;
}

CASE (4) this (is your case) does not work because OpenCV can't manage the user-allocated data. img.data points to userdata that is destroyed on return... than img.data becomes a dangling pointer

cv::Mat transformToCVMatrix()
{
  const int rows = 2;
  const int cols = 3;
  double userdata[rows][cols] = {
                                        { 0, 1, 2 },
                                        { 3, 4, 5 }
  };
  //we are making a header for user-allocated data is LOCAL. this is not safe
  cv::Mat img(rows,cols,CV_64FC1,userdata);
  ...
  return img;
}

In your case the solution is to copy the user data into the cv::Mat to as suggested by @berak using ::clone()

return cv::Mat(rows, cols, CV_64FC1, matrix).clone();

or build a Matx with the flag copyData=true

return Mat(cv::Matx<double, rows, cols>(*matrix), true);

both options will switch your case to CASE (1)

edit flag offensive delete link more
2

answered 2016-06-17 07:40:09 -0600

berak gravatar image

updated 2016-06-17 08:06:22 -0600

you have to be extremely careful, when constructing a Mat like this.

what you've got there, is called a borrowed pointer, the memory does not get copied (only the pointer), thus, when the original pointer goes out of scope (e.g. when you leave that function), it is invalid !

though Mat is usually refcounted, and thus safe to return from a function (or to pass by reference to others) this is an exception to the rule. since it does not "own" the memory, there is no refcounting in this case.

there are a couple of solutions:

  1. clone it (make a deep copy, then you also got proper refcounting again):

    return cv::Mat(2, 2, CV_64FC1, matrix).clone();

  2. use Matx class:

    Matx22d m(1,0,1,0); return m;

  3. use Mat_<double> and the << operator:

    Mat_<double> m(2,2); m << 1,0,1,0; return m;

  4. don't make it a (seperate) function. as long as your initialization and usage stays in the same scope, you're safe.

edit flag offensive delete link more

Comments

1

thank you. Isn't this a bug? If there is reference counting then it should work with that code.

hovnatan gravatar imagehovnatan ( 2016-06-17 07:56:54 -0600 )edit

"If there is reference counting"

thanks for reminding me. no, this is the one exception to the rule, there is no refcounting in this case.

berak gravatar imageberak ( 2016-06-17 08:03:14 -0600 )edit

Can you get OpenCV to allocate memory in a way that will be reference counted without incurring a penalty? In my case, I need to read data from a binary file and store it as a cv::Mat. I'd like to be able to pass the memory address from the cv::Mat to an ifstream::read call and have the data written directly into the referenced counted memory without having to do a memcpy or the like.

JedLeggett gravatar imageJedLeggett ( 2017-04-12 13:51:30 -0600 )edit

Can you explain a little bit why just returning the matrix works, I am clear about why returning the cv mat does not work as it goes out of scope after the function call, what about the matrix ? should not it go out of the scope as well?

kcDharma gravatar imagekcDharma ( 2018-03-04 23:07:38 -0600 )edit

Question Tools

3 followers

Stats

Asked: 2016-06-17 07:20:34 -0600

Seen: 20,312 times

Last updated: Jun 28 '16