Различия в использовании const cv::Mat &, cv::Mat &, cv::Mat или const cv::Mat в качестве параметров функции?

Я тщательно искал и не нашел прямого ответа на этот вопрос.

Передавая матрицы opencv (cv::Mat) в качестве аргументов функции, мы передаем интеллектуальный указатель. Любое изменение, которое мы вносим во входную матрицу внутри функции, также изменяет матрицу за пределами области действия функции.

Я читал, что при передаче матрицы в качестве константной ссылки она не изменяется внутри функции. Но простой пример показывает, что это так:

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.

Что меня сбивает с толку, так это то, что я не понимаю, какая практическая разница существует между отправкой cv::Mat, const cv::Mat, const cv::Mat& или cv::Mat& в качестве аргументов функции.

Я знаю, как переопределить это (замена Output = Input; на Output = Input.clone(); решит проблему), но до сих пор не понимаю вышеупомянутой разницы.

Спасибо ребята!


person CV_User    schedule 05.05.2014    source источник
comment
хорошие патологические примеры ;)   -  person berak    schedule 05.05.2014
comment
не уверен на 100%, но я думаю, что cv::Mat - это класс заголовка, который содержит несколько байтов информации, поэтому cv::Mat & в качестве параметра не нужно копировать этот заголовок. Const vs non-const может показать пользователю, что элементы матрицы НЕ ПРЕДНАЗНАЧЕНЫ для изменения при вызове функции при чтении заголовка/документации.   -  person Micka    schedule 05.05.2014
comment
Если вы думаете о Mat как о заголовках для некоторых данных, вы можете увидеть, как вы можете скопировать заголовок и, таким образом, изменить данные по другому маршруту, однако вы не можете изменить сам заголовок, если вы передали как const. Например, вы не можете изменить количество строк, столбцов или тип Input, и если вы попытаетесь сделать это через Output, он скомпилируется, но не изменит Input, он просто переназначит Output на какие-то новые данные.   -  person Roger Rowland    schedule 05.05.2014
comment
Если бы ваша функция была void sillyFunc(const cv::Mat* Input, cv::Mat* Output), компилятор не позволил бы вам преобразовать const cv::Mat* в cv::Mat*. Не уверен, почему это не относится к ссылкам...   -  person BConic    schedule 05.05.2014
comment
Я не уверен, в чем вопрос, но если вы спрашиваете, почему у вас такое нелогичное поведение, ответ таков: в какой-то момент кто-то решил, что было бы неплохо спроектировать cv::Mat таким образом.   -  person juanchopanza    schedule 05.05.2014
comment
Я забыл упомянуть, что видел онлайн-коды, в которых const cv::Mat& использовалась как удобочитаемая помощь, чтобы напомнить программисту, что ее не предполагается изменять внутри функции.   -  person CV_User    schedule 05.05.2014
comment
@Aldur Это не ссылки; он использует значения, на которые они ссылаются.   -  person Alan Stokes    schedule 05.05.2014
comment
@AlanStokes Если оставить в стороне тот факт, что я мог неправильно использовать слово cast, почему компилятор разрешает Output = Input;, когда Input является константной ссылкой, а Output неконстантной ссылкой, но не когда Input является константным указателем, а Output неконстантным указателем. ?   -  person BConic    schedule 05.05.2014
comment
@Aldur Потому что эквивалентное выражение с указателями будет *Output = *Input, что является допустимым. Присваивание работает с указанными значениями, а не с самими ссылками.   -  person Alan Stokes    schedule 05.05.2014


Ответы (2)


Это все потому, что 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;
}

P.S: cv::Mat содержит int* refcount, указывающий на счетчик ссылок. Дополнительные сведения см. в разделе управление памятью и подсчет ссылок:

Mat — это структура, которая хранит характеристики матрицы/изображения (количество строк и столбцов, тип данных и т. д.) и указатель на данные. Так что ничто не мешает нам иметь несколько экземпляров Mat, соответствующих одним и тем же данным. 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
        //...
    }
    
  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
        ...
    }
    

P.S.2: я должен отметить, что во всех четырех ситуациях (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
}
person herohuyongtao    schedule 06.05.2014
comment
PS2 — это суть проблемы, а часть clone дает то, что вы хотите. Хорошо сделано. +1 - person chappjc; 08.05.2014
comment
Нужно по-настоящему понять автоматическое управление памятью, чтобы понять суть Мата. Хороший ответ! - person Lihang Li; 25.03.2015
comment
Спасибо за хороший ответ. Но при передаче Mat функции, как я могу обеспечить, чтобы данные, на которые указывает Mat, не изменялись внутри этой функции? То есть в приведенном выше листинге, как я могу вызвать Input.data[0] = 5; скинуть ошибку? - person hAcKnRoCk; 22.07.2015
comment
@hAcKnRoCk AFAIK, нельзя допустить, чтобы он выдал ошибку. Самый безопасный способ для этого — передать одну клонированную версию самого ввода. - person herohuyongtao; 24.07.2015
comment
Будет ли когда-нибудь иметь смысл использовать const cv::Mat Input вместо const cv::Mat& Input? - person Gianni; 03.09.2018
comment
@hAcKnRoCk — подробное обсуждение см. в этом сообщении об ошибке. $cv::InputArray$ немного помогает, но, похоже, реального способа нет. - person nbubis; 27.10.2019

Когда вы передаете интеллектуальный указатель в качестве ссылки, теоретически вы можете сэкономить некоторое время обработки, поскольку конструктор копирования интеллектуального указателя не вызывается.

person ivg    schedule 06.05.2014