Подтвердить что ты не робот

Различия в использовании функций "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(); решит проблему), но все еще не понимает вышеупомянутую разницу.

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

4b9b3361

Ответ 1

Все потому, что 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, соответствующих тем же данным. A 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
}

Ответ 2

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