J'ai cherché à fond et je n'ai pas trouvé de réponse simple à cette question.
En passant des matrices opencv (cv::Mat
) comme arguments à une fonction, nous passons un pointeur intelligent. Toute modification apportée à la matrice d'entrée dans la fonction modifie également la matrice en dehors de la portée de la fonction.
J'ai lu qu'en passant une matrice comme référence const, elle n'est pas modifiée dans la fonction. Mais un exemple simple montre que c'est le cas:
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";
}
Clairement, A
est modifié même s’il est envoyé en tant que const cv::Mat&
.
Cela ne me surprend pas, car dans la fonction, I2
est une simple copie du pointeur intelligent de I1
et toute modification de I2
modifiera donc I1
.
Ce qui me laisse perplexe, c'est que je ne comprends pas quelle différence pratique existe entre l'envoi de cv::Mat
, const cv::Mat
, const cv::Mat&
ou cv::Mat&
comme arguments d'une fonction.
Je sais comment remplacer ceci (remplacer Output = Input;
par Output = Input.clone();
résoudrait le problème) mais je ne comprends toujours pas la différence mentionnée ci-dessus.
Merci les gars!
C’est parce que OpenCV utilise Gestion automatique de la mémoire .
OpenCV gère automatiquement toute la mémoire.
Tout d'abord,
std::vector
,Mat
et les autres structures de données utilisées par les fonctions et méthodes ont des destructeurs qui libèrent les tampons de mémoire sous-jacents en cas de besoin. Cela signifie que les destructeurs ne désallouent pas toujours les tampons comme dans le cas deMat
. Ils prennent en compte un éventuel partage de données. Un destructeur décrémente le compteur de référence associé au tampon de données matriciel. Le tampon est désalloué si et seulement si le compteur de référence atteint zéro, c'est-à-dire si aucune autre structure ne fait référence au même tampon. De même, lorsqu'une instanceMat
est copiée, aucune donnée réelle n'est réellement copiée. Au lieu de cela, le compteur de référence est incrémenté pour mémoriser l'existence d'un autre propriétaire des mêmes données. Il existe également la méthodeMat::clone
qui crée une copie complète des données de la matrice.
Cela dit, pour que deux cv::Mat
s pointent vers des objets différents, vous devez leur allouer de la mémoire séparément. Par exemple, ce qui suit fonctionnera comme prévu:
void sillyFunc(const cv::Mat& Input, cv::Mat& Output){
Output = Input.clone(); // Input, Output now have seperate memory
Output += 1;
}
P.S: cv::Mat
contient un int* refcount
qui pointe vers le compteur de référence. Départ Gestion de la mémoire et comptage des références pour plus de détails:
Mat
est une structure qui conserve les caractéristiques de la matrice/image (nombre de lignes et de colonnes, type de données, etc.) et un pointeur sur les données. Donc rien ne nous empêche d’avoir plusieurs instances deMat
correspondant aux mêmes données.Mat
conserve un décompte de références indiquant si les données doivent être désallouées lorsqu'une instance particulière deMat
est détruite.
cv::Mat
, const cv::Mat
, const cv::Mat&
et cv::Mat&
en tant qu'arguments d'une fonction:cv::Mat Input
: transmet une copie de l'en-tête de Input
. Son en-tête ne sera pas changé en dehors de cette fonction, mais peut l'être dans la fonction. Par exemple:
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
: transmet une copie de l'en-tête de Input
. Son en-tête ne sera pas modifié en dehors de ou dans la fonction. Par exemple:
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
: passe une référence d'en-tête Input
. Garantit que l'en-tête de Input
ne sera pas modifié en dehors de ou dans la fonction. Par exemple:
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
: passe une référence d'en-tête Input
. Les modifications apportées à l'en-tête de Input
se produisent en dehors de et dans la fonction. Par exemple:
void sillyFunc(cv::Mat& Input, cv::Mat& Output){
Input = cv::Mat::ones(4, 4, CV_32F); // totally OK and does change
...
}
P.S.2: Je dois souligner que, dans les quatre situations (cv::Mat
, const cv::Mat
, const cv::Mat&
ou cv::Mat&
), seul l'accès à l'en-tête du tapis est restreint, pas aux données qu'il pointe. Par exemple, vous pouvez modifier ses données dans les quatre situations et ses données changeront en effet à l'extérieur et à l'intérieur de la fonction:
/*** 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
}
Lorsque vous transmettez un pointeur intelligent comme référence, vous pouvez théoriquement gagner du temps de traitement, car le constructeur de copie de ce pointeur n'est pas appelé.