使用" const cv :: Mat&"," cv :: Mat&"," cv :: Mat"或" const cv :: Mat"作为功​​能参数?

时间:2014-05-05 08:55:15

标签: c++ function opencv matrix parameters

我已经彻底搜查过,并没有找到直截了当的答案。

将opencv矩阵(cv::Mat)作为函数的参数传递,我们传递一个智能指针。我们对函数内部的输入矩阵所做的任何改变都会改变函数范围之外的矩阵。

我通过传递一个矩阵作为const引用来读取它,它在函数中没有被改变。但是一个简单的例子表明它确实:

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";
}

显然,即使A作为const cv::Mat&发送,I2也会被更改。

这并不让我感到惊讶,因为函数I1内是I2智能指针的简单副本,因此I1中的任何更改都会改变{{1} }。

让我感到困惑的是,我不明白将cv::Matconst cv::Matconst cv::Mat&cv::Mat&作为参数传递到函数之间存在哪些实际差异

我知道如何覆盖此问题(将Output = Input;替换为Output = Input.clone();可以解决问题),但仍然不了解上述差异。

谢谢你们!

2 个答案:

答案 0 :(得分:53)

这一切都是因为OpenCV使用Automatic Memory Management

  

OpenCV会自动处理所有内存。

     

首先,std::vectorMat以及函数和方法使用的其他数据结构都具有析构函数,可在需要时释放底层内存缓冲区。这意味着析构函数不会像Mat那样始终释放缓冲区。他们考虑了可能的数据共享。析构函数递减与矩阵数据缓冲区关联的引用计数器。当且仅当引用计数器达到零时,即当没有其他结构引用相同的缓冲区时,缓冲区被释放。 同样,复制Mat实例时,实际上并未复制任何实际数据。相反,引用计数器递增以记住存在相同数据的另一个所有者。还有Mat::clone方法可以创建矩阵数据的完整副本。

也就是说,为了使两个cv::Mat指向不同的东西,你需要为它们分别分配内存。例如,以下内容将按预期工作:

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

P.S cv::Mat包含指向引用计数器的int* refcount。查看Memory management and reference counting了解更多详情:

  

Mat是一种保持矩阵/图像特征(行数和列数,数据类型等)和指向数据的指针的结构。因此,没有什么能阻止我们使用与Mat对应的相同数据的多个实例。 Mat保留引用计数,该引用计数指示在销毁Mat的特定实例时是否必须取消分配数据。


cv::Matconst cv::Matconst cv::Mat&cv::Mat&作为参数发送给函数之间的差异:

  1. cv::Mat Input:传递Input标题的副本。它的标题不会在此函数之外更改,但可以在函数内更改。例如:

    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:传递Input标题的副本。它的标题不会在函数之外或之内更改。例如:

    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:传递Input标题的引用。保证Input的标题不会在函数之外或之内更改。例如:

    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:传递Input标题的引用。对Input标题的更改发生在函数之外和之内。例如:

    void sillyFunc(cv::Mat& Input, cv::Mat& Output){
        Input = cv::Mat::ones(4, 4, CV_32F); // totally OK and does change
        ...
    }
    
  5. PS2 :我必须指出,在所有四种情况下(cv::Matconst cv::Matconst cv::Mat&cv::Mat&),仅限制对Mat的标题的访问,而不是对其指向的数据的访问。例如,您可以在所有四种情况下更改其数据,其数据确实会在函数之外和之内发生变化:

    /*** 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
    }
    

答案 1 :(得分:0)

当您将智能指针作为参考传递时,理论上可以节省一些处理时间,因为不会调用智能指针的复制构造函数。