利用Cython打包复用ViBe运动目标检测C源码

再Notes: 对编译文件setup.py进行了修改。修改后内容见https://github.com/232525/ViBe.Cython

我发现ViBe官方开源的C代码里面提供的Makefile文件对代码进行编译时,是添加了优化参数-O3的,于是我把它使用了的编译参数直接添加到了setup.py里面,然后重新编译,再经由python调用的速度确实就和直接运行C源码差不多了。

改动部分:

assert os.system("gcc -shared -fPIC -c vibe-background-sequential.c -o vibe-background-sequential.o") == 0

改为

assert os.system("gcc -std=c99 -O3 -Wall -Werror -pedantic -Wno-unused-function -Wno-unused-parameter -Wno-deprecated -Wno-deprecated-declarations -Wno-sign-compare -Wno-unused-but-set-parameter -shared -fPIC -c vibe-background-sequential.c  -o vibe-background-sequential.o") == 0

2020/07/23添加

------------------分割线--------------------

Notes:.pyx文件进行了一点修改。修改后内容见https://github.com/232525/ViBe.Cython

主要是之前写的用Cython可视化之后发现和python交互的部分有点多,就改了一下,虽然交互变少了,但是速度没有啥明显变化,我单独测试了Cython中调用C函数的语句,和Python调用Cython的包装函数相比,时间差不多,所以说时间瓶颈应该就来自于C函数的调用?但是为什么通过Cython调用的C函数(Ubuntu 16.04环境下)相比我直接运行C代码(Windows 10)速度慢了不少呢?因为平台编译的差异?

2020/07/22 添加

------------------分割线--------------------

0 引言

直接用python复现的ViBe速度太慢,所以想用Cython直接复用ViBe开源的C源码,昨天(2020/07/20)找了一天的资料,翻遍整个百度,都是些不痛不痒的教程,官方的API又太长了,没耐心看。反正后面莫名其妙就打包复用成功了,对Cython还是一知半解(啥也不懂)。

所有代码:https://github.com/232525/ViBe.Cython

1 主要参考

2 复用逻辑

首先,手头上有的工具:

  • ViBe的C源码,包括vibe-background-sequential.hvibe-background-sequential.c,分别提供了ViBe算法相关函数的声明和实现
  • Cython,用来打包封装C源码,提供Python可调用的动态库文件
  • Python,调用Cython提供的动态库文件,间接调用了ViBe的C源码

2.1 Cython封装ViBe的C源码

参考Cython中文文档,就像C语言有着头文件和C文件一样,Cython有.pxd.pyx文件,其中.pxd包含我们需要从C源码中复用的函数声明,而.pyx文件需要编写复用这些函数的python接口。

2.1.1.pxd文件

对于.pxd文件,我们需要复用如下数据结构及核心函数(输入均为灰度图):

  • 结构体vibeModel_Sequential_t,定义了ViBe算法需要使用的相关参数,核心之核心
  • 函数libvibeModel_Sequential_New,用于创建一个结构体对象
  • 函数libvibeModel_Sequential_Free,用于释放资源
  • 函数libvibeModel_Sequential_AllocInit_8u_C1R,结构体参数初始化操作,ViBe算法核心操作之一,于视频第一帧时调用
  • 函数libvibeModel_Sequential_Segmentation_8u_C1R,获取视频帧前景背景识别mask,ViBe算法核心操作之二(算法的意义所在),于视频每一帧调用
  • 函数libvibeModel_Sequential_Update_8u_C1R,结构体参数更新操作,ViBe算法核心操作之三,于视频每一帧调用

.pxd文件内函数的声明完全复制自C源码的头文件,结构体的声明参考了Cython中文文档Cython官方文档2,数据类型的定义参考了这篇博客,也就是C99中实际定义的数据类型别名吧?

# -*- coding: utf-8 -*-
"""
Created on 2020/7/20

@author: curya
"""
# filename: c_lib.pxd
cdef extern from "vibe-background-sequential.h":
    ctypedef unsigned char            uint8_t
    ctypedef unsigned short int         uint16_t
    ctypedef unsigned int             uint32_t
    ctypedef int                   int32_t
        
    ctypedef struct vibeModel_Sequential_t:
        pass
    
    vibeModel_Sequential_t *libvibeModel_Sequential_New()
    int32_t libvibeModel_Sequential_Free(vibeModel_Sequential_t *model)
    int32_t libvibeModel_Sequential_AllocInit_8u_C1R(vibeModel_Sequential_t *model, const uint8_t *image_data, 
                                                     const uint32_t width, const uint32_t height)
    int32_t libvibeModel_Sequential_Segmentation_8u_C1R(vibeModel_Sequential_t *model, const uint8_t *image_data, uint8_t *segmentation_map)
    int32_t libvibeModel_Sequential_Update_8u_C1R(vibeModel_Sequential_t *model, const uint8_t *image_data, uint8_t *updating_mask)

2.1.2.pyx文件

现在我们有了Cython的头文件.pxd,需要编写.pyx文件进行实际的封装。同样,参考Cython中文文档,我们对.pxd文件中声明的结构体编写一个包装类(将其定义为ViBe),编译之后,可以在python中导入这个包装类ViBe,通过它的内部函数来间接调用C代码(它的内部函数直接调用C代码,需要我们编写内部函数)。

我们需要将之前的.pxd文件导入进来cimport c_vibe,然后对于里面声明了的C函数(即我们需要复用的C函数),可以直接通过c_vibe.function_name()进行调用;除此之外还有import numpy as np,数据类型的定义和.pxd文件一致。

我们的包装类ViBe只有一个变量,即结构体c_vibe.vibeModel_Sequential_t定义的指针model,对于前面我们声明了的五个函数,分别包装在类的不同函数中,当调用类ViBe的函数时,即相应的调用了其包含的C函数:

  • c_vibe.libvibeModel_Sequential_New,在包装类的函数__cinit__()中调用,在创建ViBe对象时自动调用,即相当于通过__cinit__调用的C函数创建了一个C结构体对象;
  • c_vibe.libvibeModel_Sequential_Free,在包装类的函数__dealloc__()中调用
  • c_vibe.libvibeModel_Sequential_AllocInit_8u_C1R,函数AllocInit()
  • c_vibe.libvibeModel_Sequential_Segmentation_8u_C1R,函数Segmentation()
  • c_vibe.libvibeModel_Sequential_Update_8u_C1R,函数Update()

问题: 函数c_vibe.libvibeModel_Sequential_AllocInit_8u_C1Rc_vibe.libvibeModel_Sequential_Segmentation_8u_C1Rc_vibe.libvibeModel_Sequential_Update_8u_C1R的输入参数包含视频图像帧的指针,而我们用python-opencv读取视频帧的话,数据类型为np.array,因此需要进行转换,参考Cython官方文档1

if not image.flags['C_CONTIGUOUS']:
    image = np.ascontiguousarray(image) # 保证内存连续
cdef uint8_t[:, :] image_data = image # 类型转换,此处image为灰度图,即大小为H x W,不含通道channel维度
# Pointer: &image_data[0, 0] # 取地址
# -*- coding: utf-8 -*-
"""
Created on 2020/7/20

@author: curya
"""
# filename: py_vibe.pyx
# refer to: 
# https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html#pass-data-from-a-c-function-via-pointer
# https://moonlet.gitbooks.io/cython-document-zh_cn/content/ch3-using_c_libraries.html
# https://github.com/cython/cython/blob/master/Demos/libraries/setup.py
# https://stackoverflow.com/questions/14657375/cython-fatal-error-numpy-arrayobject-h-no-such-file-or-directory
import numpy as np
# cimport numpy as np
cimport c_vibe

ctypedef unsigned char            uint8_t
ctypedef unsigned short int         uint16_t
ctypedef unsigned int             uint32_t
ctypedef int                   int32_t
    
cdef class ViBe:
    cdef c_vibe.vibeModel_Sequential_t* model
    def __cinit__(self):
        self.model = c_vibe.libvibeModel_Sequential_New()
        if self.model is NULL:
            raise MemoryError()
    
    def __dealloc__(self):
        if self.model is not NULL:
            c_vibe.libvibeModel_Sequential_Free(self.model)

    def AllocInit(self, image):
        cdef uint32_t width = <uint32_t>image.shape[1]
        cdef uint32_t height = <uint32_t>image.shape[0]
        
        if not image.flags['C_CONTIGUOUS']:
            image = np.ascontiguousarray(image)
        cdef uint8_t[:, :] image_data = image

        # &image_data[0, 0] --> the pointer of image
        # call C function
        c_vibe.libvibeModel_Sequential_AllocInit_8u_C1R(self.model, &image_data[0, 0], width, height)

    def Segmentation(self, image):
        if not image.flags['C_CONTIGUOUS']:
            image = np.ascontiguousarray(image)
        cdef uint8_t[:, :] image_data = image
        cdef uint8_t[:, :] segmentation_map = np.zeros_like(image)
        # call C function
        c_vibe.libvibeModel_Sequential_Segmentation_8u_C1R(self.model, &image_data[0, 0], &segmentation_map[0, 0])
        # Error: need convert segmentation_map to numpy.array
        # convert on python main function
        return segmentation_map

    def Update(self, image, segmentation):
        if not image.flags['C_CONTIGUOUS']:
            image = np.ascontiguousarray(image)
        if not segmentation.flags['C_CONTIGUOUS']:
            segmentation = np.ascontiguousarray(image)
        cdef uint8_t[:, :] image_data = image
        cdef uint8_t[:, :] updating_mask = segmentation
        # call C function
        c_vibe.libvibeModel_Sequential_Update_8u_C1R(self.model, &image_data[0, 0], &updating_mask[0, 0])

2.1.3 setup.py文件

编写了.pxd.pyx文件之后,我们需要编写setup.py文件将我们写的Cython代码编译为Python可调用的动态库文件。(也可以直接通过gcc进行命令行编译)

python setup.py build_ext --inplace

一个大致的逻辑就是:

  1. 将C源码编译为动态库文件.so或者静态库文件.a
  2. 将Cython代码转换为C代码
  3. 将Cython转换过来的C代码,联合第一步编译的库文件,编译为Python可调用的动态库文件
# -*- coding: utf-8 -*-
"""
Created on 2020/7/20

@author: curya
"""
from __future__ import absolute_import, print_function

import os
import sys

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy as np

# For demo purposes, we build our own tiny library.
try:
    print("building libmymath.a")
    # 将C代码编译为.o目标文件
    assert os.system("gcc -shared -fPIC -c vibe-background-sequential.c -o vibe-background-sequential.o") == 0
    # 将.o目标文件编译为.a静态库文件
    assert os.system("ar rcs libvibe-background-sequential.a vibe-background-sequential.o") == 0
except:
    if not os.path.exists("libvibe-background-sequential.a"):
        print("Error building external library, please create libmymath.a manually.")
        sys.exit(1)

# Here is how to use the library built above.
ext_modules = cythonize([
    Extension("py_vibe",
              sources=["py_vibe.pyx"],
              include_dirs=[os.getcwd(), np.get_include()],  # path to .h file(s), np.get_include(): avoid fatal error: numpy/arrayobject.h: No such file or directory”
              library_dirs=[os.getcwd()],  # path to .a or .so file(s)
              libraries=['vibe-background-sequential'])  # 静态库文件libvibe-background-sequential.a的名称
])
# , annotate=True


setup(
    name='Demos',
    ext_modules=ext_modules,
)

2.2 Python调用Cython封装的动态库

测试代码参考上一篇博客,只需要将导入部分from lib_vibe import vibe_gray替换为from lib_vibe.py_vibe import ViBe,以及vibe = vibe_gray()替换为vibe = ViBe()即可。

测试结果,相比python肯定是快了不止一丁点,对比纯C,效率有一点折扣。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值