使用"const cv :: Mat&","cv :: Mat&","cv :: Mat"或"const cv :: Mat"作为函数参数的区别?

CV_*_*ser 30 c++ parameters opencv function matrix

我彻底搜查过,并没有找到一个直截了当的答案.

将opencv matrices(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";
}
Run Code Online (Sandbox Code Playgroud)

很明显,A即使它是作为一个发送,也会被改变const cv::Mat&.

这并不让我感到惊讶,因为在函数内部I2是一个简单I1的智能指针副本,因此任何改变I2都会改变I1.

什么是难不倒我的是,我不明白发送之间存在什么实际区别cv::Mat,const cv::Mat,const cv::Mat&cv::Mat&作为参数的函数.

我知道如何覆盖这个(替换Output = Input;Output = Input.clone();将解决问题)但仍然不明白上述差异.

多谢你们!

her*_*tao 57

这都是因为OpenCV使用自动内存管理.

OpenCV自动处理所有内存.

首先,std::vector,Mat,由函数和方法中使用和其他数据结构具有在需要时解除分配基础存储缓冲器析构函数.这意味着析构函数并不总是释放缓冲区,如果的话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;
}
Run Code Online (Sandbox Code Playgroud)

PS:cv::Mat包含int* refcount指向参考计数器的.查看内存管理和引用计数以获取更多详细信息:

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


发送之间的差异cv::Mat,const cv::Mat,const 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
        //...
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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
        //...
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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
        ...
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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
        ...
    }
    
    Run Code Online (Sandbox Code Playgroud)

PS2:我必须指出的是,在所有四种情况(cv::Mat,const cv::Mat,const cv::Mat&cv::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
}
Run Code Online (Sandbox Code Playgroud)

  • ps2是问题的核心,"克隆"部分给出了"你想要的东西".做得很好.+1 (3认同)
  • 感谢您的好答案。但是,当将“垫”传递给函数时,如何强制“垫”所指向的“数据”在该函数内部不变?也就是说,在上面的清单中,我如何导致Input.data [0] = 5; 抛出错误? (2认同)