ndarray python 映射_191123 使用 Pybind11 和 OpenCV 创建 Python 库
参观飞机工厂不能让你学得流体力学,也不能让你学会开飞机。然而如果你会开飞机又懂流体力学,参观飞机工厂可以带给你最大的乐趣和价值。-- 侯捷
献给 @AliceInt-ZLJ by Knight @2019/11/23
Knight:191123 使用 Pybind11 和 OpenCV 创建 Python 库zhuanlan.zhihu.com
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).release(); }
};
}}//! end namespace pybind11::detail
加上注释和参数检验:
namespace pybind11 { namespace detail{//! 实现 cv::Point 和 tuple(x,y) 之间的转换。
template<>
struct type_caster<cv::Point>{//! 定义 cv::Point 类型名为 tuple_xy, 并声明类型为 cv::Point 的局部变量 value。PYBIND11_TYPE_CASTER(cv::Point, _("tuple_xy"));//! 步骤1:从 Python 转换到 C++。 //! 将 Python tuple 对象转换为 C++ cv::Point 类型, 转换失败则返回 false。 //! 其中参数2表示是否隐式类型转换。 bool load(handle obj, bool){ // 确保传参是 tuple 类型 if(!py::isinstance<py::tuple>(obj)){ std::logic_error("Point(x,y) should be a tuple!"); return false; } // 从 handle 提取 tuple 对象,确保其长度是2。 py::tuple pt = reinterpret_borrow<py::tuple>(obj); if(pt.size()!=2){ std::logic_error("Point(x,y) tuple should be size of 2"); return false; } //! 将长度为2的 tuple 转换为 cv::Point。 value = cv::Point(pt[0].cast<int>(), pt[1].cast<int>()); return true; }//! 步骤2: 从 C++ 转换到 Python。 //! 将 C++ cv::Mat 对象转换到 tuple,参数2和参数3常忽略。 static handle cast(const cv::Point& pt, return_value_policy, handle){ return py::make_tuple(pt.x, pt.y).release(); }
};
}} //! end namespace pybind11::detail
2. 矩形与元组转换 cv::Rect <=> tuple(x,y,w,h)
与 cv::Point 转换类似,实现 cv::Rect 和 tuple(x,y,w,h) 之间的转换, 也需要上述步骤。
namespace pybind11 { namespace detail{template<>
struct type_caster<cv::Rect>{ PYBIND11_TYPE_CASTER(cv::Rect, _("tuple_xywh")); bool load(handle obj, bool){ if(!py::isinstance<py::tuple>(obj)){ std::logic_error("Rect should be a tuple!"); return false; } py::tuple rect = reinterpret_borrow<py::tuple>(obj); if(rect.size()!=4){ std::logic_error("Rect (x,y,w,h) tuple should be size of 4"); return false; } value = cv::Rect(rect[0].cast<int>(), rect[1].cast<int>(), rect[2].cast<int>(), rect[3].cast<int>()); return true; } static handle cast(const cv::Rect& rect, return_value_policy, handle){ return py::make_tuple(rect.x, rect.y, rect.width, rect.height).release(); }
};
}} //! end namespace pybind11::detail
3. cv::Mat 与 numpy.ndarray 之间转换
Python 支持一种通用的插件间数据交换缓冲区协议(buffer protocol)。让类型暴露一个缓冲区视图(buffer view), 这样可以对原始内部数据进行直接访问,常用于矩阵类型。
Pybind11 提供了 py::buffer_info 类型,来映射 Python 缓冲区协议(buffer protocol)。
struct buffer_info { void *ptr; /* Pointer to buffer */ ssize_t itemsize; /* Size of one scalar */ std::string format; /* Python struct-style format descriptor */ ssize_t ndim; /* Number of dimensions */ std::vector<ssize_t> shape; /* Buffer dimensions */ std::vector<ssize_t> strides; /* Strides (in bytes) for each index */
};
由于 cv::Mat 和 numpy.ndarray 的数据存储方式都是 C 风格的行主序,所以使用 buffer_info 映射时比较一致而方便。考虑到常用的 cv::Mat 元素类型有 uchar、int、float, 常用的维度有 2d、3d,因此对这几种情况提供转换。
namespace pybind11 { namespace detail{template<>
struct type_caster<cv::Mat>{public: PYBIND11_TYPE_CASTER(cv::Mat, _("numpy.ndarray")); //! 1. cast numpy.ndarray to cv::Mat bool load(handle obj, bool){ array b = reinterpret_borrow<array>(obj); buffer_info info = b.request(); int nh = 1; int nw = 1; int nc = 1; int ndims = info.ndim; if(ndims == 2){ nh = info.shape[0]; nw = info.shape[1]; } else if(ndims == 3){ nh = info.shape[0]; nw = info.shape[1]; nc = info.shape[2]; }else{ throw std::logic_error("Only support 2d, 2d matrix"); return false; } int dtype; if(info.format == format_descriptor<unsigned char>::format()){ dtype = CV_8UC(nc); }else if (info.format == format_descriptor<int>::format()){ dtype = CV_32SC(nc); }else if (info.format == format_descriptor<float>::format()){ dtype = CV_32FC(nc); }else{ throw std::logic_error("Unsupported type, only support uchar, int32, float"); return false;} value = cv::Mat(nh, nw, dtype, info.ptr);return true; } //! 2. cast cv::Mat to numpy.ndarray static handle cast(const cv::Mat& mat, return_value_policy, handle defval){ std::string format = format_descriptor<unsigned char>::format();size_t elemsize = sizeof(unsigned char);int nw = mat.cols;int nh = mat.rows;int nc = mat.channels();int depth = mat.depth();int type = mat.type();int dim = (depth == type)? 2 : 3;if(depth == CV_8U){format = format_descriptor<unsigned char>::format();elemsize = sizeof(unsigned char);}else if(depth == CV_32S){format = format_descriptor<int>::format();elemsize = sizeof(int);}else if(depth == CV_32F){format = format_descriptor<float>::format();elemsize = sizeof(float);}else{ throw std::logic_error("Unsupport type, only support uchar, int32, float");} std::vector<size_t> bufferdim;std::vector<size_t> strides;if (dim == 2) {bufferdim = {(size_t) nh, (size_t) nw};strides = {elemsize * (size_t) nw, elemsize};} else if (dim == 3) {bufferdim = {(size_t) nh, (size_t) nw, (size_t) nc};strides = {(size_t) elemsize * nw * nc, (size_t) elemsize * nc, (size_t) elemsize};}return array(buffer_info( mat.data, elemsize, format, dim, bufferdim, strides )).release();
}};}}//! end namespace pybind11::detail
4. 创建 pycv 简单的调用 opencv 的库
有了上述的类型转换代码,就可以把现有的使用了 cv::Point、cv::Rect、及 cv::Mat 等类型的代码编译为 Python 的库。
如,可以定义 addpt, addrect, addmat, imwrite 这4个函数,分别实现点相加、矩形相加、矩阵元素相加,保存矩阵为图片等操作,然后封装为简单的 cv 库。
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <pybind11/stl.h>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>namespace pybind11 { namespace detail{//! 上述几个实现}}//!//! 点相加
cv::Point addpt(cv::Point& lhs, cv::Point&rhs){return cv::Point(lhs.x + rhs.x, lhs.y + rhs.y);
}//! 矩形相加
cv::Rect addrect(cv::Rect& lhs, cv::Rect&rhs){int x = std::min(lhs.x, rhs.x);int y = std::min(lhs.y, rhs.y);int width = std::max(lhs.x + lhs.width-1, rhs.x + rhs.width-1) - x + 1;int height = std::max(lhs.y + lhs.height-1, rhs.y + rhs.height-1) - y + 1;return cv::Rect(x, y, width, height);
}//! 矩阵相加
cv::Mat addmat(cv::Mat& lhs, cv::Mat& rhs){return lhs + rhs;
}//! 写入图片
void imwrite(std::string fpath, const cv::Mat& img){cv::imwrite(fpath, img);
}PYBIND11_MODULE(pycv, m){m.doc() = "pycv - A small OpenCV binding created using Pybind11";m.def("addpt", &addpt, "add two point");m.def("addrect", &addrect, "add two rect");m.def("addmat", &addmat, "add two matrix"); m.def("imwrite", &imwrite, "write into the file");
}
可以使用 cmake 创建工程,编译为动态库 pycv.so 或 pycv.pyd,然后使用 python 测试。
导入 python 后查看帮助:
>>> import pycv
>>> help(pycv)Help on module pycv:NAMEpycv - A small OpenCV binding created using Pybind11FUNCTIONSaddmat(...) method of builtins.PyCapsule instanceaddmat(arg0: numpy.ndarray, arg1: numpy.ndarray) -> numpy.ndarrayadd two matrixaddpt(...) method of builtins.PyCapsule instanceaddpt(arg0: tuple_xy, arg1: tuple_xy) -> tuple_xyadd two pointaddrect(...) method of builtins.PyCapsule instanceaddrect(arg0: tuple_xywh, arg1: tuple_xywh) -> tuple_xywhadd two rectimwrite(...) method of builtins.PyCapsule instanceimwrite(arg0: str, arg1: numpy.ndarray) -> Nonewrite into file
测试:
# 2019/11/23
# create by knight
import pycv
import numpy as npdef test():# 矩阵相加a = np.random.random((2,3)).astype(np.float32)b = np.ones((2,3)).astype(np.float32)c = pycv.addmat(a, b)print("a = n", a)print("b = n", b)print("c = n", c)# 点相加a = (1,2)b = (3,-1)c = pycv.addpt(a, b)print("{} + {} = {}".format(a, b, c))# 矩形相加a = (10, 20, 20, 10) # lt(10, 20) => rb(30, 30)b = (5, 5, 20, 20) # lt(5, 5) => rb(25, 25)c = pycv.addrect(a, b) # lt(5, 5) => rb(30, 30) => wh(25, 25)print("a = n", a)print("b = n", b)print("c = n", c)if __name__ == "__main__": test()
测试结果:
a = [[0.0108426 0.8217642 0.49240646][0.7879406 0.82095915 0.5334907 ]]
b = [[1. 1. 1.][1. 1. 1.]]
c = [[1.0108426 1.8217642 1.4924065][1.7879406 1.8209591 1.5334907]](1, 2) + (3, -1) = (4, 1)a = (10, 20, 20, 10)
b = (5, 5, 20, 20)
c = (5, 5, 25, 25)
5. 更多测试
将重新用 C++ 写的 openpose 后处理部分,同样转换为 python 库,并在 python 中调用测试,循环了5次,只计算后处理的时间,结果如下:
=> Finish 4 people, #1 pyb.pyfindHeatmapPafPose in 17.04 ms
=> Finish 4 people, #2 pyb.pyfindHeatmapPafPose in 15.27 ms
=> Finish 4 people, #3 pyb.pyfindHeatmapPafPose in 14.98 ms
=> Finish 4 people, #4 pyb.pyfindHeatmapPafPose in 15.01 ms
=> Finish 4 people, #5 pyb.pyfindHeatmapPafPose in 15.05 ms
使用 openpose 检测人体骨架关键点,得到的结果看上去还不错。
参考链接:
1. https://en.wikipedia.org/wiki/C%2B%2B
2. https://en.wikipedia.org/wiki/Python_(programming_language)
3. https://github.com/pybind/pybind11
4. https://pybind11.readthedocs.io/en/stable/
5. https://github.com/CMU-Perceptual-Computing-Lab/openpose/
6. https://github.com/michalfaber/keras_Realtime_Multi-Person_Pose_Estimation
《完》
献给 @AliceInt-ZLJ by Knight
ndarray python 映射_191123 使用 Pybind11 和 OpenCV 创建 Python 库相关推荐
- python人脸识别理论_使用OpenCV和Python进行人脸识别
介绍 人脸识别是什么?或识别是什么?当你看到一个苹果时,你的大脑会立刻告诉你这是一个苹果.在这个过程中,你的大脑告诉你这是一个苹果水果,用简单的语言来说就是识别.那么什么是人脸识别呢?我肯定你猜对了. ...
- opencv 训练人脸对比_【项目案例python与人脸识别】基于OpenCV开源计算机视觉库的人脸识别之python实现...
" 本项目是一个基于OpenCV开源库使用python语言程序实现人脸检测的项目,该项目将从[项目基础知识](即人脸识别的基本原理).[项目实践](人脸识别所需要的具体步骤及其python程 ...
- 使用opencv和python进行智能图像处理_使用OpenCV在Python中进行图像处理
编辑推荐: 本文将先讨论一些图像处理,然后再继续介绍可以方便使用图像处理的不同应用程序/场景,希望对您的学习有所帮助. 本文来自于tecdat ,由火龙果软件Alice编辑.推荐. 介绍 在本教程中, ...
- python找图片不同_用openCV和Python 实现图片对比,并标识出不同点的方式
最近项目中需要实现两组图片对比,并能将两者的区别标识出来. 在网上搜索一大堆找到一篇大神的文章,最终实现该功能,在这里记录下: 想要实现此demo,首先我们得确保电脑上已安装 openCV 和 Pyt ...
- python实现对数转换_利用opencv在python平台上实现二值变换,伽马变换,对数变换,补色变换等...
如何用 opencv 在 python 平台上实现灰度图像的二值化,对数变换, ,伽马变换以及补色变 换.代码如下 import cv2 import copy import math import ...
- Python虚拟环境(一):基于virtualenv+virtualenvwrapper创建python虚拟环境
文章目录 1. 概述 2. virtualenv + virtualenvwrapper 1. virtualenv 安装virtualenv 基本使用 2. virtualenvwrapper 安装 ...
- 《python》学习笔记(Day1),创建python发布
一.创建发布,并使用模块中的函数. (1)添加系统环境变量.我的电脑----->属性----->高级----->环境变量----->系统变量,选择系统变量中的变量path,单击 ...
- 【Android 逆向】使用 Python 代码解析 ELF 文件 ( PyCharm 中创建 Python 程序 | 导入 ELFFile 库 | 解析 ELF 文件 )
文章目录 一.PyCharm 中创建 Python 程序 二.导入 ELFFile 依赖库 三. 解析 ELF 文件 四. 博客源码 一.PyCharm 中创建 Python 程序 在 PyCharm ...
- python映射类型有哪些_什么是python中唯一的映射类型
字典是python中唯一的映射类型,采用键值对(key-value)的形式存储数据.python对key进行哈希函数运算,根据计算的结果决定value的存储地址,所以字典是无序存储的,且key必须是可 ...
最新文章
- 调试linux内核前的多虚拟机网络配置(图文教程)
- SURF角点检测(python)
- kcbzps oracle_快速进行Oracle安装及配置
- 曲线 线性回归_GRAPHPAD作图技巧(二)--拟合曲线
- 如何安装_如何安装吸顶灯?吸顶灯安装注意事项
- vba实现粘贴复制功能
- java数独求交集方法,标准数独解题之旅(用一道数独题讲解最基本的5种解题技巧)(二)...
- 同源、跨域、跨站、SameSite与withCredentials
- SCI英语论文长难句攻略
- 洛谷p3398仓鼠找suger题解
- Python函数combination
- 夜神模拟器换完本机的ip连不上忘 fiddler也抓不到模拟器的包
- 矿小助 全局主题 | 一个插件实现网易云音乐主题效果 | Flutter
- android蓝牙传文件,安卓手机怎样使用蓝牙连接传输文件
- Kali Linux学习笔记—Web渗透(1)
- Zbrush一些基本操作
- 【疑难杂症】JavaScript执行期上下文
- 今年有多少周?今天是今年的第几天?第几周?
- python计算两个日期的相隔时间
- Opencv+Zbar二维码识别(标准条形码/二维码识别)
热门文章
- pve虚拟机导入gho_迁移WIN10和VMW虚拟机到ProXmoX VE(二):PVE设置和迁移windows
- uFrame近况(2016年4月8日更新)
- OpenShift 4 - 用KubeletConfig和ContainerRuntimeConfig分别修改集群节点的Kubelet和cri-o的配置
- OpenShift 4 - 通过DaemonSet在指定Node上运行守护程序
- 基于 .Net5.0 的快速开发框架,YuebonCore1.0.3 版已发布
- 如何创建从Visual Studio到Wolfram Mathematica的简单调用
- 全新设计的 Xcode 12
- .NET Core Web API:您需要了解的最少知识(第2部分,共2部分)
- 如何为项目和产品提供资源——优化工作时间、激励团队和预算
- 开源项目的名称背后都有哪些故事?