OpenCV C++ core and Python cv2 binding: Mat ownership and deallocation across the language boundary
26.5K reputation · 14 Dec 2023, 04:26 UTC
The integration goal is to define clear ownership and release timing for cv::Mat objects that cross from OpenCV C++ core into the Python cv2 binding.
The C++ core uses reference-counted cv::Mat with shallow copy semantics. The Python binding wraps a Mat in a capsule with a custom deallocator that decrements the C++ refcount when the Python object is finalized. Ownership is transferred on the C++ to Python boundary for functions returning Mat, and the Python wrapper holds the sole reference unless additional references are kept. UMat and GPU-backed Mats add a device memory layer whose lifetime is tied to both the C++ refcount and the Python wrapper lifetime. Behavior is version-sensitive between OpenCV 3.x and 4.x binding generation and varies with build options for OpenCL and CUDA.
OpenCV does not define a deterministic release contract for Mats created in C++ extensions and used from Python; explicit release is documented as available but not required. This leaves expectations about immediate native free versus GC-deferred free ambiguous across bindings.
What is the documented ownership transfer rule for Mats returned from C++ to Python and when is native memory guaranteed to be freed? Is explicit release required for deterministic deallocation of Mats originating in C++ extensions? How does UMat device buffer lifetime relate to Python wrapper finalization and C++ refcount?
1 answer
1 question comment
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 14 Dec 2023, 15:55 UTC
To expand on the deterministic deallocation point, it is important to note how NumPy's view mechanism interacts with the underlying cv::Mat reference count. When you create a slice of a Python image (e.g., roi = image[y:y+h, x:x+w]), NumPy creates a new array object that shares the same data buffer as the original.
In the cv2 binding, this means the internal C++ reference count is not decremented even if the original image variable is deleted. The native memory remains allocated as long as any NumPy view—no matter how small the slice—exists. To verify this behavior in a memory-constrained environment, you can inspect the .base attribute of the sliced array; if it points back to the original array, the C++ buffer is still pinned.
For those using OpenCV 4.x, ensuring that all views are cleared before calling gc.collect() is the only way to guarantee the C++ refcount can actually reach zero.