Passing cv::Mat as argument
Often times when we pass cv::Mat
, we forget one important thing: OpenCV
matrix does not respect the const
modifier.
In this post I would like to discuss what happens when cv::Mat
is passed around.
Changing the header of cv::Mat
Consider the following examples1, where we want to change what the datastructure is.
Note that we are trying to modify the HEADER of the cv::Mat
, hence the behavior.
Passing a copy – cv::Mat
.
This is totally OK, but the scoping rules apply – only local variable will be changed, and no changes will happen outside of the function.
1
2
3
void Foo(cv::Mat out) {
out = cv::Mat::ones(4, 4, CV_32F); // OK, but only local changes happen.
}
Passing a reference – cv::Mat&
.
This is totally fine, and the changes will propagate to outside of the function.
1
2
3
void Bar(cv::Mat& out) {
out = cv::Mat::ones(4, 4, CV_32F); // OK, Changes happen everywhere.
}
Passing a const
– const cv::Mat
.
This is NOT OK, even for local changes.
1
2
3
void Baz(const cv::Mat out) {
out = cv::Mat::ones(4, 4, CV_32F); // Error!!! Cannot change the header!
}
Passing a const
reference – const cv::Mat&
.
This is NOT OK!
1
2
3
void Qux(const cv::Mat& out) {
out = cv::Mat::ones(4, 4, CV_32F); // Error!!! Cannot change the header!
}
From the above examples, we see that cv::Mat
guarantees that the header of the data structure will respect the const
modifiers. However, if we look at the header for the core.hpp
we notice that the data in the structure cv::Mat
is stored as a raw pointer:
Because of that, when passing the cv::Mat
one has to be extra careful.
Because the way the data structure is implemented, passing it around as const
or const &
has slightly different behavior from what is expected.
Changing the data in the cv::Mat
Now take a look at the functions below, no matter what is being passed, the modifications are allowed – cv::Mat
doesn’t respect the const modifiers!2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Foo(cv::Mat out) {
out.data[0] = 5; // OK globally!
}
void Bar(cv::Mat& out) {
out.data[0] = 5; // OK globally!
}
void Baz(const cv::Mat out) {
out.data[0] = 5; // OK globally!
}
void Qux(const cv::Mat& out) {
out.data[0] = 5; // OK globally!
}
What about constant methods?
One might say that the examples above are artificial, and anyone writing code, will see it immediately. However, there are cases when the changes will happen in places where you won’t expect it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Foo {
void bar(const cv::Mat& out) const {
cv::Mat baz = out(cv::Rect(0, 0, 10, 10));
cv::cvtColor(baz, baz, cv::COLOR_BGR2RGB);
// Although we don't modify the `out`, and `baz` is only a subset,
// it is still changed!!!
}
}
// ...
int main() {
// ...
Foo foo;
foo.bar(image); // The colorspace of `image` is flipped now!!!
// ...
}
Conclusion
When handling the cv::Mat
– be very careful, and treat it as a smart pointer :).