我在用机器学习/深度学习对点云进行分类时,需要对原始点云数据进行增强(Data Aumentation),但原始点云数据为PCD文件,我后续还要用PCL点云库(C++)进行特征提取等操作,因此就想在C++中进行。数据增强的代码当然也可以用C++写,但想学习用一下Cython接口就用了Python(当然Python写起来也简单==)。。。 这部分代码详见我的这篇博客: https://blog.csdn.net/shaozhenghan/article/details/81265817
知乎有一篇文章,文章简要介绍了怎样用Cython编写函数并生成 *.c 和 *.h,然后用C/C++调用Cython中用Python语法写的函数:
https://www.zhihu.com/question/23003213
但文章中并没有很详细地介绍,在C/C++中调用Cython编写的函数时,怎样向这个Python函数传递参数(C/C++ to Python),以及怎样接受返回值放进C/C++ 变量中(Pyhton to C/C++)。
这里主要想记录一下过程和一些细节,遇到的坑。
首先将原先的 .py 改成 .pyx,除了文件名后缀外,在需要在C/C++中调用的即生成接口的函数那,把 def 改为 cdef, 后面再加上其public关键字,其他部分不用变。(原先的代码见:https://blog.csdn.net/shaozhenghan/article/details/81265817)
如下所示:
cdef public augment_data(point, rotation_angle, sigma, clip):
# -*- coding: utf-8 -*-
#######################################
########## Data Augmentation ##########
#######################################
import numpy as np
###########
# 绕Z轴旋转 #
###########
# point: vector(1*3)
# rotation_angle: scaler 0~2*pi
def rotate_point (point, rotation_angle):
point = np.array(point)
cos_theta = np.cos(rotation_angle)
sin_theta = np.sin(rotation_angle)
rotation_matrix = np.array([[cos_theta, sin_theta, 0],
[-sin_theta, cos_theta, 0],
[0, 0, 1]])
rotated_point = np.dot(point.reshape(-1, 3), rotation_matrix)
return rotated_point
###################
# 在XYZ上加高斯噪声 #
##################
def jitter_point(point, sigma=0.01, clip=0.05):
assert(clip > 0)
point = np.array(point)
point = point.reshape(-1,3)
Row, Col = point.shape
jittered_point = np.clip(sigma * np.random.randn(Row, Col), -1*clip, clip)
jittered_point += point
return jittered_point
#####################
# Data Augmentation #
#####################
cdef public augment_data(point, rotation_angle, sigma, clip):
return jitter_point(rotate_point(point, rotation_angle), sigma, clip).tolist()
注意我把最后一行加上了 .tolist() 后面再解释。
然后在命令行中:$ cython name.pyx 自动生成 源代码 name.c 和 接口name.h。 打开name.c,可以看到,函数的形参与返回值都是 PyObject * 类型,即Python的动态类型特性:
__PYX_EXTERN_C PyObject *augment_data(PyObject *, PyObject *, PyObject *, PyObject *); /*proto*/
写一个C++ 源文件测试一下:输入点坐标(1,2,3),绕Z轴旋转3.14即180度。正太分布噪声均值0,方差0.01,并且限制在+-0.05 之间。
#include
#include "data_aug.h"
#include
int main(int argc, char const *argv[])
{
PyObject *point;
PyObject *angle;
PyObject *sigma;
PyObject *clip;
PyObject *augmented_point;
Py_Initialize();
initdata_aug();
// 浮点形数据必须写为1.0, 2.0 这样的,否则Py_BuildValue()精度损失导致严重错误
point = Py_BuildValue("[f,f,f]", 1.0, 2.0, 3.0);
angle = Py_BuildValue("f", 3.14);
sigma = Py_BuildValue("f", 0.01);
clip = Py_BuildValue("f", 0.05);
augmented_point = augment_data(point, angle, sigma, clip);
float x=0.0, y=0.0, z=0.0;
PyObject *pValue = PyList_GetItem(augmented_point, 0);
PyObject *pValue_0 = PyList_GET_ITEM(pValue, 0);
PyObject *pValue_1 = PyList_GET_ITEM(pValue, 1);
PyObject *pValue_2 = PyList_GET_ITEM(pValue, 2);
x = PyFloat_AsDouble(pValue_0);
y = PyFloat_AsDouble(pValue_1);
z = PyFloat_AsDouble(pValue_2);
std::cout << PyList_Size(pValue) << std::endl;
std::cout << x << "\n" << y << "\n" << z << std::endl;
Py_Finalize();
return 0;
}
必须有 下面三个语句:
Py_Initialize();
initdata_aug();
Py_Finalize();
CMakeLists.txt 这样写:
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(data_aug)
add_executable (data_aug test_data_aug.cpp data_aug.c)
运行结果:
-1.00187
-1.99964
3.01885
符合 (1,2,3)绕Z轴旋转180度并加上微小噪声的结果。
// 浮点形数据必须写为1.0, 2.0 这样的,否则Py_BuildValue()精度损失导致严重错误
point = Py_BuildValue("[f,f,f]", 1.0, 2.0, 3.0);
具体Py_BuildValue()的用法在下面的参考文献里。
C++ 接受Python 函数的返回值,主要用到 PyList_GetItem()以及 数据类型转换 PyFloat_AsDouble 等。因为用到PyList_GetItem()所以我把pyx文件中最后一行加上了 .tolist(),把numpy数组变为list列表。
所以用下面的语句才依次提取出 x, y, z:
PyObject *pValue = PyList_GetItem(augmented_point, 0);
PyObject *pValue_0 = PyList_GET_ITEM(pValue, 0);
PyObject *pValue_1 = PyList_GET_ITEM(pValue, 1);
PyObject *pValue_2 = PyList_GET_ITEM(pValue, 2);
x = PyFloat_AsDouble(pValue_0);
y = PyFloat_AsDouble(pValue_1);
z = PyFloat_AsDouble(pValue_2);
Cython 中的函数形参以及返回值类型也可以使用静态类型,Cython的静态类型关键字!例如:
cdef public char great_function(const char * a,int index):
return a[index]
这样的好处是,生成的C代码长这样:
__PYX_EXTERN_C DL_IMPORT(char) great_function(char const *, int);
更加C风格,基本没有Python的痕迹了。
这样的局限性是:当Cython的函数中使用的是列表List或者字典dict等Python独有的类型时,就很难用C的类型关键字了。
所以个人认为使用Python动态类型 PyObject * ,结合Py_BuildValue() 更方便。
具体PyList_GetItem 和 PyFloat_AsDouble 的用法见下面参考文献。
我遇到问题时找到了几篇很好的参考文献:
https://www.ibm.com/developerworks/cn/linux/l-pythc/
https://www.cnblogs.com/DxSoft/archive/2011/04/01/2002676.html
https://blog.csdn.net/vampirem/article/details/12948955
https://www.zhihu.com/question/23003213