最近用c++的opencv实现了一个算法,但是要导入到python里面做训练处理,看了网上的教程感觉都不全面,自己摸索了一个晚上终于调通了,现在总结一下。
1. 安装pybind11
先pip安装一波:
pip3 install pybind11
首先创建个工程目录test_pybind,在自己的工程目录下面从pybind11上面下载源码:
git clone https://github.com/pybind/pybind11
然后使用cmake编译工程准备使用
cd pybind11
mkdir build
cd build
cmake ..
cmake --build . --config Release --target check
上面一波常规操作之后,画风及相对路径如下所示:
2.举个例子
我们在上述目录下面创建一个.cpp文件来先写下c++的代码测试一下,创建完的画风:
看下面例子:
最简单的我们要调用c++的接口实现两个数相加。具体c++代码这样实现:
#include <pybind11/pybind11.h>
namespace py = pybind11;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m)
{
// optional module docstring
m.doc() = "pybind11 example plugin";
// expose add function, and add keyword arguments and default arguments
m.def("add", &add, "A function which adds two numbers", py::arg("i")=1, py::arg("j")=2);
}
这上面所有的封装代码都需要在 PYBIND11_MODULE 函数里面,具体是什么意思呢?定义如下:
PYBIND11_MODULE( 模块名, 模块实例对象 ){
m.doc() = "pybind11 example"; //可选,说明这个模块是做什么的
//封装的具体操作。这些操作包括普通的函数的封装,类的访问等下面用不同例子来说明问题
m.def( "给python调用方法名", &实际操作的函数, "函数功能说明" ). //其中函数功能说明为可选
}
完事后还要创建一个cmakelists文件:
cmakelists文件我直接把opencv也包含进去了,因为后面要用到:
cmake_minimum_required(VERSION 3.1)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
project(test_pybind)
find_package( OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_subdirectory(pybind11)
SET(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
pybind11_add_module(test_pybind ${SOURCES})
target_link_libraries( test_pybind PRIVATE ${OpenCV_LIBS} )
这样完事后,直接常规操作:
在当前工程目录下面:
恩恩,回车之后非常完美通过。再make一下。
可以看到当前目录下面应该会有一个.so文件:
把他从build文件夹copy到上一层目录:
ok,现在工程目录变成这样了:
接下来就可以在这个目录里面创建.py文件进行调用了:
import test_pybind
print(test_pybind.add(3, 4))
运行一下python文件:
简直ojbk。
3.opencv的例子
经历上面opencv的也就八九不离十了,下面的代码来自别的博客:
一个是用来将cv::Mat转换成numpy的
#include"mat_warper.h"
#include <pybind11/numpy.h>
/*
Python->C++ Mat
*/
cv::Mat numpy_uint8_1c_to_cv_mat(py::array_t<unsigned char>& input) {
if (input.ndim() != 2)
throw std::runtime_error("1-channel image must be 2 dims ");
py::buffer_info buf = input.request();
cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC1, (unsigned char*)buf.ptr);
return mat;
}
cv::Mat numpy_uint8_3c_to_cv_mat(py::array_t<unsigned char>& input) {
if (input.ndim() != 3)
throw std::runtime_error("3-channel image must be 3 dims ");
py::buffer_info buf = input.request();
cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC3, (unsigned char*)buf.ptr);
return mat;
}
/*
C++ Mat ->numpy
*/
py::array_t<unsigned char> cv_mat_uint8_1c_to_numpy(cv::Mat& input) {
py::array_t<unsigned char> dst = py::array_t<unsigned char>({ input.rows,input.cols }, input.data);
return dst;
}
py::array_t<unsigned char> cv_mat_uint8_3c_to_numpy(cv::Mat& input) {
py::array_t<unsigned char> dst = py::array_t<unsigned char>({ input.rows,input.cols,3}, input.data);
return dst;
}
//py::array_t<std::complex<float>> cv_mat_float_3c_to_numpy(cv::Mat& input) {
// py::array_t<std::complex<float>> dst = py::array_t<std::complex<float>>({ input.rows,input.cols,3}, input.data);
// return dst;
//}
py::array_t<float> mat_to_py(cv::Mat& input)
{
py::buffer_info buffer(
input.data,
sizeof(float),
py::format_descriptor<float>::format(),
2,
{input.rows, input.cols},
{ sizeof(float)* input.cols, sizeof(float)}
);
return py::array_t<float>(buffer);
}
//PYBIND11_MODULE(cv_mat_warper, m) {
//
// m.doc() = "OpenCV Mat -> Numpy.ndarray warper";
// m.def("numpy_uint8_1c_to_cv_mat", &numpy_uint8_1c_to_cv_mat);
// m.def("numpy_uint8_3c_to_cv_mat", &numpy_uint8_3c_to_cv_mat);
//}
头文件申明下:
#ifndef MAT_WARPER_H_
#include<opencv2/opencv.hpp>
#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>
namespace py = pybind11;
cv::Mat numpy_uint8_1c_to_cv_mat(py::array_t<unsigned char>& input);
cv::Mat numpy_uint8_3c_to_cv_mat(py::array_t<unsigned char>& input);
py::array_t<unsigned char> cv_mat_uint8_1c_to_numpy(cv::Mat & input);
py::array_t<unsigned char> cv_mat_uint8_3c_to_numpy(cv::Mat & input);
//py::array_t<std::complex<float>> cv_mat_float_3c_to_numpy(cv::Mat& input);
py::array_t<float> mat_to_py(cv::Mat& input);
#endif // !MAT_WARPER_H_
改下cmakelist文件把这两个文件包含进去。
cmake_minimum_required(VERSION 3.1)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
project(test_pybind)
find_package( OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_subdirectory(pybind11)
SET(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mat_warper.h
${CMAKE_CURRENT_SOURCE_DIR}/mat_warper.cpp
)
pybind11_add_module(test_pybind ${SOURCES})
target_link_libraries( test_pybind PRIVATE ${OpenCV_LIBS} )
main.cpp对接口进行调用:
#include<iostream>
#include<vector>
#include<opencv2/opencv.hpp>
#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>
#include<pybind11/stl.h>
#include"mat_warper.h"
namespace py = pybind11;
py::array_t<unsigned char> test_rgb_to_gray(py::array_t<unsigned char>& input) {
cv::Mat img_rgb = numpy_uint8_3c_to_cv_mat(input);
cv::Mat dst;
cv::cvtColor(img_rgb, dst, cv::COLOR_RGB2GRAY);
return cv_mat_uint8_1c_to_numpy(dst);
}
py::array_t<unsigned char> test_gray_canny(py::array_t<unsigned char>& input) {
cv::Mat src = numpy_uint8_1c_to_cv_mat(input);
cv::Mat dst;
cv::Canny(src, dst, 30, 60);
return cv_mat_uint8_1c_to_numpy(dst);
}
py::list normal_feature_estimation(py::array_t<unsigned char>& input) {
cv::Mat depth = numpy_uint8_1c_to_cv_mat(input);
cv::Mat nor(depth.size(), CV_32FC3);
depth.convertTo(depth, CV_32FC1);
// depth = depth / 1000;
for (int x = 1; x < depth.cols-1; x++) {
for (int y = 1; y < depth.rows-1; y++) {
cv::Vec3f t(x,y-1,depth.at<float>(y-1,x));
cv::Vec3f l(x-1,y,depth.at<float>(y, x-1));
cv::Vec3f c(x,y,depth.at<float>(y, x));
cv::Vec3f d = (l-c).cross(t-c);
cv::Vec3f n = normalize(d);
nor.at<cv::Vec3f>(y, x) = n;
// std::cout<<"normal: "<<n.val[0]<<" "<<n.val[1]<<" "<<n.val[2]<<std::endl;
}
}
std::vector<cv::Mat> channels;
cv::split(nor, channels);
py::list res;
for(int i=0;i<3;i++) {
res.append<py::array_t<float>>(mat_to_py(channels[i]));
}
return res;
}
/*
@return Python list
*/
py::list test_pyramid_image(py::array_t<unsigned char>& input) {
cv::Mat src = numpy_uint8_1c_to_cv_mat(input);
std::vector<cv::Mat> dst;
cv::buildPyramid(src, dst, 4);
py::list out;
for (int i = 0; i < dst.size(); i++)
{
out.append<py::array_t<unsigned char>>(cv_mat_uint8_1c_to_numpy(dst.at(i)));
}
return out;
}
PYBIND11_MODULE(test_pybind, m) {
m.doc() = "Simple opencv demo";
m.def("test_rgb_to_gray", &test_rgb_to_gray);
m.def("test_gray_canny", &test_gray_canny);
m.def("normal_feature_estimation", &normal_feature_estimation);
m.def("test_pyramid_image", &test_pyramid_image);
}
常规操作进去build文件夹,cmake… 然后make。接下来就可以通过python文件调用相对应接口了。