38

I've searched thoroughly and have not found a straightforward answer to this.

Passing opencv matrices (cv::Mat) as arguments to a function, we're passing a smart pointer. Any change we do to the input matrix inside the function alters the matrix outside the function scope as well.

I read that by passing a matrix as a const reference, it is not altered within the function. But a simple example shows it does:

void sillyFunc(const cv::Mat& Input, cv::Mat& Output){
    Output = Input;
    Output += 1;
}

int main( int argc, char** argv ){
    cv::Mat A = cv::Mat::ones(3,3,CV_8U);
    std::cout<<"A = \n"<<A<<"\n\n";
    cv::Mat B;
    sillyFunc(A,B);
    std::cout<<"A = \n"<<A<<"\n\n";
    std::cout<<"B = \n"<<B<<"\n\n";
}

Clearly, A is altered even though it is sent as a const cv::Mat&.

This does not surprise me as within the function I2 is a simple copy of I1's smart pointer and thus any change in I2 will alter I1.

What does baffle me is that I don't understand what practical difference exists between sending cv::Mat, const cv::Mat, const cv::Mat& or cv::Mat& as arguments to a function.

I know how to override this (replacing Output = Input; with Output = Input.clone(); would solve the problem) but still don't understand the above mentioned difference.

Thanks guys!

herohuyongtao
  • 49,413
  • 29
  • 133
  • 174
CV_User
  • 1,183
  • 2
  • 12
  • 20
  • nice pathological examples ;) – berak May 05 '14 at 08:58
  • 1
    not 100% sure but I think `cv::Mat` is a "header" class that holds some bytes of information, so `cv::Mat &` as parameter does not need to copy that header. Const vs non-const might show the user that the matrix elements are NOT INTENDED to be changed when calling the function, while reading the header/documentation. – Micka May 05 '14 at 09:17
  • 5
    If you think of the `Mat`'s as headers around some data, you can see how you can copy the header and so change the data via a different route, however you can't change the "header" itself if you passed as `const`. For example, you can't change the number of rows, columns or type of `Input` and if you try to do this via `Output`, it would compile but would not change `Input`, it would just reassign `Output` to some new data. – Roger Rowland May 05 '14 at 09:18
  • If your function had been `void sillyFunc(const cv::Mat* Input, cv::Mat* Output)`, the compiler wouldn't have let you cast a `const cv::Mat*` into a `cv::Mat*`. Not sure why this is not the case with references... – BConic May 05 '14 at 09:24
  • 1
    I am not sure what the question is, but if you are asking why you get this counter-intuitive behaviour, the answer is that at some point someone decided that it would be a good idea to design `cv::Mat` in this way. – juanchopanza May 05 '14 at 09:34
  • I forgot to mention I saw online codes where const cv::Mat& was used as a readable aid to remind the programmer that it is not *supposed* to be changed within the function. – CV_User May 05 '14 at 10:28
  • @Aldur It's not casting references; it's using the values they refer to. – Alan Stokes May 05 '14 at 12:34
  • @AlanStokes Setting aside the fact that I might have misused the word `cast`, why does the compiler allows `Output = Input;` when `Input` is a const reference and `Output` a non-const reference, but not when `Input` is a const pointer and `Output` a non-const pointer? – BConic May 05 '14 at 12:45
  • 2
    @Aldur Because the equivalent expression with pointers would be *Output = *Input, which is valid. The assignment is working on the values referred to, not the references themselves. – Alan Stokes May 05 '14 at 12:58

1 Answers1

70

It's all because OpenCV uses Automatic Memory Management.

OpenCV handles all the memory automatically.

First of all, std::vector, Mat, and other data structures used by the functions and methods have destructors that deallocate the underlying memory buffers when needed. This means that the destructors do not always deallocate the buffers as in case of Mat. They take into account possible data sharing. A destructor decrements the reference counter associated with the matrix data buffer. The buffer is deallocated if and only if the reference counter reaches zero, that is, when no other structures refer to the same buffer. Similarly, when a Mat instance is copied, no actual data is really copied. Instead, the reference counter is incremented to memorize that there is another owner of the same data. There is also the Mat::clone method that creates a full copy of the matrix data.

That said, in order to make two cv::Mats point to different things, you need to allocate memory separately for them. For example, the following will work as expected:

void sillyFunc(const cv::Mat& Input, cv::Mat& Output){
    Output = Input.clone(); // Input, Output now have seperate memory
    Output += 1;
}

P.S: cv::Mat contains an int* refcount that points to the reference counter. Check out Memory management and reference counting for more details:

Mat is a structure that keeps matrix/image characteristics (rows and columns number, data type etc) and a pointer to data. So nothing prevents us from having several instances of Mat corresponding to the same data. A Mat keeps a reference count that tells if data has to be deallocated when a particular instance of Mat is destroyed.


Differences between sending cv::Mat, const cv::Mat, const cv::Mat& or cv::Mat& as arguments to a function:

  1. cv::Mat Input: pass a copy of Input's header. Its header will not be changed outside of this function, but can be changed within the function. For example:

    void sillyFunc(cv::Mat Input, cv::Mat& Output){
        Input = cv::Mat::ones(4, 4, CV_32F); // OK, but only changed within the function
        //...
    }
    
  2. const cv::Mat Input: pass a copy of Input's header. Its header will not be changed outside of or within the function. For example:

    void sillyFunc(const cv::Mat Input, cv::Mat& Output){
        Input = cv::Mat::ones(4, 4, CV_32F); // Error, even when changing within the function
        //...
    }
    
  3. const cv::Mat& Input: pass a reference of Input's header. Guarantees that Input's header will not be changed outside of or within the function. For example:

    void sillyFunc(const cv::Mat& Input, cv::Mat& Output){
        Input = cv::Mat::ones(4, 4, CV_32F); // Error when trying to change the header
        ...
    }
    
  4. cv::Mat& Input: pass a reference of Input's header. Changes to Input's header happen outside of and within the function. For example:

    void sillyFunc(cv::Mat& Input, cv::Mat& Output){
        Input = cv::Mat::ones(4, 4, CV_32F); // totally OK and does change
        ...
    }
    

P.S.2: I must point out that, in all the four situations (cv::Mat, const cv::Mat, const cv::Mat& or cv::Mat&), only the access to the Mat's header is restrained, not to the data it points to. For example, you can change its data in all the four situations and its data will indeed change outside of and within the function:

/*** will work for all the four situations ***/
//void sillyFunc(cv::Mat Input){
//void sillyFunc(const cv::Mat Input){
//void sillyFunc(const cv::Mat &Input){
void sillyFunc(cv::Mat &Input){
    Input.data[0] = 5; // its data will be changed here
}
Armali
  • 18,255
  • 14
  • 57
  • 171
herohuyongtao
  • 49,413
  • 29
  • 133
  • 174
  • 5
    The p.s.2 is the heart of the issue, and the `clone` part gives the "what you want". Nicely done. +1 – chappjc May 08 '14 at 04:18
  • 1
    Got to really understand the auto memory management in order to grasp the heart of Mat. Nice answer! – Lihang Li Mar 25 '15 at 07:30
  • 2
    Thanks for the nice answer. But, when passing a "Mat" to a function, how do i enforce that the "data" pointed to by "Mat" does not change inside that function? That is, in the above listing, how do i cause Input.data[0] = 5; to throw an error? – hAcKnRoCk Jul 22 '15 at 15:07
  • @hAcKnRoCk AFAIK, there is no way to let it throw an error. The safest way for this is to pass one clone version of the input itself. – herohuyongtao Jul 24 '15 at 12:32
  • 1
    Would it ever make sense to use `const cv::Mat Input` rather than `const cv::Mat& Input`? – Gianni Sep 03 '18 at 14:49
  • @hAcKnRoCk - See this bug post for a detailed discussion. $cv::InputArray$ helps a bit, but there seems to be no real way. – nbubis Oct 27 '19 at 06:22
  • Can someone explain how this modification of `const cv::Mat` compiles when this here doesn't? `struct Wat { Wat(){}; char data[10]; }; int main(int argc, char *argv[]) { const Wat w; w.data[0] = 0; return 0; }` The `data` member is still accessed through a const variable, so should the compiler not prevent mutations? – oarfish Oct 27 '21 at 09:53