![9d7eb652953888bc3d189e67f491131a.png](https://img-blog.csdnimg.cn/img_convert/9d7eb652953888bc3d189e67f491131a.png)
参观飞机工厂不能让你学得流体力学,也不能让你学会开飞机。然而如果你会开飞机又懂流体力学,参观飞机工厂可以带给你最大的乐趣和价值。-- 侯捷
献给 @AliceInt-ZLJ by Knight @2019/11/23
Knight:191123 使用 Pybind11 和 OpenCV 创建 Python 库zhuanlan.zhihu.com![582c380fba6eb01a3dcce6299d2d9f7c.png](https://img-blog.csdnimg.cn/img_convert/582c380fba6eb01a3dcce6299d2d9f7c.png)
2020.03.01 更新:
新增 github 项目地址 ausk/keras-unet-deploy 。
本文内容可在 keras-unet-deploy/cpp/unet/pycv 目录下查看。
C++ 是一种编译型(compiled)语言,设计重点是性能、效率和使用灵活性,偏向于系统编程、嵌入式、资源受限的软件和系统。Mordern C++ 支持宏、泛型重载、面向过程、面向对象、函数式等不同风格。模板类型萃取、容器、迭代器、算法是C++ STL 标准库的重要组成部分,善用之可改善编码效率。
Python是一种解释型(interpreted)语言,同样也支持不同的编程范式。Python 内置了常用数据结构(str, tuple, list, dict),支持鸭子类型(动态类型)和自动垃圾回收,加上简洁的语法、丰富的内置库(os,sys,urllib,...)和三方库(numpy, tf, torch ...),使其成为瑞士军刀,啥都能做。
有时候,我们需要编译型语言(C++)性能,以及解释型语言(Python)的灵活, 而 pybind11 则可以用作 C++ 和 Python 之间沟通的桥梁。Pybind11 是一个轻量级只包含头文件的库,用于 Python 和 C++ 之间接口转换,可以为现有的 C++ 代码创建 Python 接口绑定。Pybind11 通过 C++ 编译时的自省来推断类型信息,来最大程度地减少传统拓展 Python 模块时繁杂的样板代码, 已经实现了 STL 数据结构、智能指针、类、函数重载、实例方法等到Python的转换,其中函数可以接收和返回自定义数据类型的值、指针或引用。
最近在研究 openpose 人体骨骼关键点检测的非官方 keras 代码实现,转 pb 模型文件后,使用 c++ 版 tensorflow 进行推理及后处理。由于使用 C++ 重写了 keras python 版对应的后处理部分(当然 openpose 源码里有实现,不过由于其嵌入到了工程里,导致难以直接复用), 典型的后处理部分从 200 ms 减少到 20 ms。突发奇想,可不可以把重写后的代码暴露给 Python 调用。在 C++ 端,使用 cv::Mat 表示多维矩阵;对应地在 Python 端使用 numpy.ndarray 表示多维矩阵。经过各种尝试后,发现了使用 pybind11 基本可以实现 C++ 和 Python 之间的无缝对接,从而实现代码复用。
需要暴露接口的典型的 OpenCV 数据结构有 cv::Point, cv::Rect, cv::Mat, 其中 cv::Point 是 cv::Point_<int> 特化,cv::Rect 是 cv::Rect_<int> 特化,而常用的 cv::Mat 中的元素类型有 CV_8U (uchar), CV_32S (int), CV_32F (float)。
核心,是对上述需要转换的数据结构 T,通过添加模板重载结构体 pybind11::detail::type_caster<T>,实现对类型 T 的转换方法 load 和 cast 的注册。
1. 点与元组转换 cv::Point <=> tuple(x,y)
实现 cv::Point 和 tuple(x,y) 之间的转换, 需要特化模板结构体 pybind11::detail::type_caster<cv::Point>,然后实现 load 和 cast 方法。
简洁版:
namespace pybind11 {
namespace detail{
template<>
struct type_caster<cv::Point>{
PYBIND11_TYPE_CASTER(cv::Point, _("tuple_xy"));
bool load(handle obj, bool){
py::tuple pt = reinterpret_borrow<py::tuple>(obj);
value = cv::Point(pt[0].cast<int>(), pt[1].cast<int>());
return true;
}
static handle cast(const cv::Point& pt, return_value_policy, handle){
return py::make_tuple(pt.x, pt.y