我已经彻底搜查过,并没有找到直截了当的答案。
将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::Mat
,const cv::Mat
,const cv::Mat&
或cv::Mat&
作为参数传递到函数之间存在哪些实际差异
我知道如何覆盖此问题(将Output = Input;
替换为Output = Input.clone();
可以解决问题),但仍然不了解上述差异。
谢谢你们!
答案 0 :(得分:53)
这一切都是因为OpenCV使用Automatic Memory Management。
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;
}
P.S :cv::Mat
包含指向引用计数器的int* refcount
。查看Memory management and reference counting了解更多详情:
Mat
是一种保持矩阵/图像特征(行数和列数,数据类型等)和指向数据的指针的结构。因此,没有什么能阻止我们使用与Mat
对应的相同数据的多个实例。Mat
保留引用计数,该引用计数指示在销毁Mat
的特定实例时是否必须取消分配数据。
cv::Mat
,const cv::Mat
,const cv::Mat&
或cv::Mat&
作为参数发送给函数之间的差异: 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
//...
}
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
//...
}
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
...
}
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
...
}
PS2 :我必须指出,在所有四种情况下(cv::Mat
,const cv::Mat
,const 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)
当您将智能指针作为参考传递时,理论上可以节省一些处理时间,因为不会调用智能指针的复制构造函数。