异想天开

What's the true meaning of light, Could you tell me why

写一个Python的C/C++模块

日期:2017-03-18 21:22:32
  
最后更新日期:2017-03-20 10:22:58
有时候项目需要实现一个Python的扩展功能,当你的项目用Python来实现,但某个三方库提供的是C/C++ API,那么就需要在此基础上封装成一个动态库模块,例如mysql的Python库或某些机器学习的库。实现一个动态库模块,有两种方式:一种利用python的dev包,另外一种就是利用Cython。这篇demo是利用Python的dev包来实现。开始在思考造一个什么类型的轮子好,后来想这世界已经很多轮子了,还不如解读其中一个实际功能的轮子。这样的意义更大。需要描述的这个模块为python-v4l2capture,功能利用linux 下的v4l2的用户态接口抓取摄像头视频或图像。

一. 下载安装python-v4l2capture 和 PIL
git clone https://github.com/gebart/python-v4l2capture.git
git clone https://github.com/whatupdave/pil.git
PIL模块为python的image图像处理库。比如这里会将抓取到数据,压缩为jpg。也可以为了简单旗舰,去掉压缩的代码,直接用位图即可。进入下载目录, python ./setup.py build 后, 再 python ./setup.py install。
若安装python-v4l2capture时,提示头文件libv4l2.h不存在, sudo apt-get install libv4l-dev安装v4l2 开发库。
若编译pil时,提示freetype/fterrors.h找不到,这个bug临时解决:
sudo ln -s /usr/include/freetype2 /usr/include/freetype
这里笔者电脑,还遇到一个问题,因为之前安装过anaconda2,anaconda2里面已经有了一份PIL,如/home/zhuangbian/anaconda2/lib/python2.7/site-packages/PIL。 不过,貌似不能用,故重新下载源码编译, 但最后安装的时候,需要先删除PIL或将PIL改名,再安装。

二. 测试python-v4l2capture
安装python-v4l2capture正确后,就可以测试下获取摄像头的图像。不过,这里笔者的笔记本的摄像头,就是代码里面描述的那种老式摄像头,需要在video.start()后面sleep几秒。
[code lang="cpp"]
import Image
import select
import time
import v4l2capture

# Open the video device.
video = v4l2capture.Video_device("/dev/video0")

# Suggest an image size to the device. The device may choose and
# return another size if it doesn't support the suggested one.
size_x, size_y = video.set_format(1280, 1024)

# Create a buffer to store image data in. This must be done before
# calling 'start' if v4l2capture is compiled with libv4l2. Otherwise
# raises IOError.
video.create_buffers(1)

# Start the device. This lights the LED if it's a camera that has one.
video.start()

# Wait a little. Some cameras take a few seconds to get bright enough.
time.sleep(2)

# Send the buffer to the device.
video.queue_all_buffers()

# Wait for the device to fill the buffer.
select.select((video,), (), ())

# The rest is easy :-)
image_data = video.read()
video.close()
image = Image.fromstring("RGB", (size_x, size_y), image_data)
image.save("image.jpg")
print "Saved image.jpg (Size: " + str(size_x) + " x " + str(size_y) + ")"
[/code]
上诉描述了视频设备编程的几个步骤:
1.打开视频设备文件
2.进行一些必要的设置。上例中为设置分辨率
3.创建用户态接收缓冲,用于接收视频输出的图片
4.将接收缓冲传给视频设备
5.等待读信号激活,读取数据
6.这里没有进行设置,读取到的格式为位图,将位图重新编码为jpg,存储到本地。

三.模块的源码分析
该模块仅由一个C文件v4l2capture.c,具体分析该文件。
1. 包含python模块开发库的头文件#include 。若没有这个头文件需要安装python-dev库。
2. 注意下面这个结构体,这个结构体表示一个python的PyObject对象。故结构体开头放一个PyObject_HEAD宏,猜测为指定一些公共的成员。
typedef struct {
PyObject_HEAD
int fd;
struct buffer *buffers;
int buffer_count;
} Video_device;
3. 模块初始化代码
[code lang="cpp"]
// 当import v4l2capture时,会根据是python 3还是python 2还执行一个模块init函数:
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC initv4l2capture(void)
#else
PyMODINIT_FUNC PyInit_v4l2capture(void)
#endif
{
// Video_device_type为该模块自定义的python的内置类型
Video_device_type.tp_new = PyType_GenericNew;
// 例化这个内置类型,个人描述
if(PyType_Ready(&Video_device_type) < 0)
{
#if PY_MAJOR_VERSION < 3
return;
#else
return NULL;
#endif
}
// 注册模块的自定义方法
PyObject *module;

#if PY_MAJOR_VERSION < 3
module = Py_InitModule3("v4l2capture", module_methods,
"Capture video with video4linux2.");
#else
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"v4l2capture",
"Capture video with video4linux2.",
-1,
module_methods,
NULL,
NULL,
NULL,
NULL
};
module = PyModule_Create(&moduledef);
#endif
// 将这个内置类型注册到模块,同时增加其引用计数
Py_INCREF(&Video_device_type);
PyModule_AddObject(module, "Video_device", (PyObject *)&Video_device_type);
//最后,若为python 3,返回模块指针
#if PY_MAJOR_VERSION >= 3
return module;
#endif

}
[/code]
4. 这里自定义对象类型,Video_device_methods定义该类型的方法,以及如果构造和析构该对象。
[code lang = "cpp"]
static PyTypeObject Video_device_type = {
#if PY_MAJOR_VERSION < 3
PyObject_HEAD_INIT(NULL) 0,
#else
PyVarObject_HEAD_INIT(NULL, 0)
#endif
"v4l2capture.Video_device", sizeof(Video_device), 0,
(destructor)Video_device_dealloc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, Py_TPFLAGS_DEFAULT, "Video_device(path)\n\nOpens the video device at "
"the given path and returns an object that can capture images. The "
"constructor and all methods except close may raise IOError.", 0, 0, 0,
0, 0, 0, Video_device_methods, 0, 0, 0, 0, 0, 0, 0,
(initproc)Video_device_init
};
[/code]
具体内置类型的字段,可参考https://docs.python.org/2/c-api/typeobj.html,或者对照着上诉代码修改几个函数即可。
这里看一下构造和析构自定义对象方法:
[code lang="cpp"]
// args表示直接传进来的参数 kwargs 表示有key的参数
static int Video_device_init(Video_device *self, PyObject *args,
PyObject *kwargs)
{
const char *device_path;
//提取参数
if(!PyArg_ParseTuple(args, "s", &device_path))
{
return -1;
}
//真正调研v4l2库打开设备
int fd = v4l2_open(device_path, O_RDWR | O_NONBLOCK);

if(fd < 0)
{
PyErr_SetFromErrnoWithFilename(PyExc_IOError, (char *)device_path);
return -1;
}

self->fd = fd;
self->buffers = NULL;
return 0;
}
static void Video_device_dealloc(Video_device *self)
{
if(self->fd >= 0)
{
if(self->buffers)
{
Video_device_unmap(self);
}

v4l2_close(self->fd);
}

Py_TYPE(self)->tp_free((PyObject *)self);
}
[/code]