C++与Numpy/Python绑定中复杂生命周期问题求解
C++与numpy/Python间的生命周期同步问题求解
我开发了cvnp库,基于pybind11实现cv::Mat与py::array对象的双向内存共享转换,该库已被多个项目使用。
目前遇到cv::Mat与py::array的生命周期关联问题:
cv::Mat→py::array方向:通过capsule让py::array持有cv::Mat的引用,运行正常;py::array→cv::Mat方向:生成的cv::Mat直接访问py::array的数据,但未持有其引用,无法保证二者生命周期一致,可能导致段错误。
相关实现代码
mat_to_nparray实现
py::capsule make_capsule_mat(const cv::Mat& m) { return py::capsule(new cv::Mat(m) , [](void *v) { delete reinterpret_cast<cv::Mat*>(v); } ); } pybind11::array mat_to_nparray(const cv::Mat& m) { return pybind11::array(detail::determine_np_dtype(m.depth()) , detail::determine_shape(m) , detail::determine_strides(m) , m.data , detail::make_capsule_mat(m) ); }
nparray_to_mat实现
cv::Mat nparray_to_mat(pybind11::array& a) { ... cv::Mat m(size, type, is_not_empty ? a.mutable_data(0) : nullptr); return m; }
段错误触发场景
- C++绑定函数:
m.def("test", [](cv::Mat mat) { return mat; });
- Python调用代码:
img = np.zeros(shape=(480, 640, 3), dtype=np.uint8) img = test(img)
此时py::array可能先于cv::Mat销毁,导致cv::Mat访问已释放内存,段错误是否出现取决于操作系统和Python版本。我已通过ASAN在CI中复现该问题,复现代码如下:
void test_lifetime() { // We need to create a big array to trigger a segfault auto create_example_array = []() -> pybind11::array { constexpr int rows = 1000, cols = 1000; std::vector<pybind11::ssize_t> a_shape{rows, cols}; std::vector<pybind11::ssize_t> a_strides{}; pybind11::dtype a_dtype = pybind11::dtype(pybind11::format_descriptor<int32_t>::format()); pybind11::array a(a_dtype, a_shape, a_strides); // Set initial values for(int i=0; i<rows; ++i) for(int j=0; j<cols; ++j) *((int32_t *)a.mutable_data(j, i)) = j * rows + i; printf("Created array data address =%p\n%s\n", a.data(), py::str(a).cast<std::string>().c_str()); return a; }; // Let's reimplement the bound version of the test function via pybind11: auto test_bound = [](pybind11::array& a) { cv::Mat m = cvnp::nparray_to_mat(a); return cvnp::mat_to_nparray(m); }; // Now let's reimplement the failing python code in C++ // img = np.zeros(shape=(480, 640, 3), dtype=np.uint8) // img = test(img) auto img = create_example_array(); img = test_bound(img); // Let's try to change the content of the img array *((int32_t *)img.mutable_data(0, 0)) = 14; // This triggers an error that ASAN catches printf("img data address =%p\n%s\n", img.data(), py::str(img).cast<std::string>().c_str()); }
需求与困惑
理想的解决方案是:
- 在
nparray_to_mat构造cv::Mat时调用pybind11::array.inc_ref() - 确保
cv::Mat实例销毁时调用pybind11::array.dec_ref()
但目前不知如何实现这一点。注:cv::Mat的自定义分配器在此场景无用,因为它不分配内存,仅复用py::array的内存。
期待相关解决方案建议。
内容的提问来源于stack exchange,提问作者Pascal T.
相关产品推荐
相关产品推荐

