Python使用ctypes调用动态库dll/so,关于opencv图片Mat对应的数据uchar*

文章目录

  • 英文比较好的同学推荐直接学习下面的链接:
  • 一、python通过ctypes 加载 c 动态库
  • 二、ctypes 怎么样调用 c 的函数库
    • python调用.dll/.so中的函数
    • C数据类型与ctypes之间的转换表格:
  • 三、结构体对应
  • 四、指针
  • 五、opencv中的Mat
  • 六、枚举类型
  • 七、特殊情况
  • **主要参考:**

英文比较好的同学推荐直接学习下面的链接:

https://docs.python.org/3/library/ctypes.html#

官方给的定义是 “ctypes is a foreign function library for Python. It provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python.” —— 引自 Python 3.5 chm 文档。其大意就是——ctypes 是一个为 Python 准备的外部函数库。它提供兼容C的数据类型,并允许调用DLL或共享库中的函数。通过它,可以使用纯粹的 Python 包装这些函数库(这样你就可以直接 import xxx 来使用这些函数库了)。
python 可以通过使用 ctypes 模块调用 c 函数,这其中必定包括可以定义 c 的变量类型(包括结构体类型、指针类型)。

一、python通过ctypes 加载 c 动态库

使用 ctypes.CDLL ,其定义如下(引自 Python 3.5 chm 文档 )

ctypes.CDLL(name, mode=DEFAULT_MODE, handle=None, use_errno=False, use_last_error=False)  

另外,在 windows 平台上会忽略 modes 参数。对于 windows 平台来说还可以调用 ctypes.WinDLL,与上面的 CDLL 几乎一样,唯一不同点是它假定库中函数遵循 Windows stdcall 调用约定,其他参数的意义见官方文档。
如果要调用libopencv_core.so可以写作 :

from ctypes import *
SO = cdll.LoadLibrary("/usr/lib/libopencv_core.so")#windows和linux通用
DLL = windll.LoadLibrary("x64/Release/opencv_core320.dll")#windows下还可以使用

**注意:**在windows下调用dll,如果依赖其他第三方库,则要一起写进来,特别要写在对应库前面:
比如下面这样就会报错:

from ctypes import *
cdll.LoadLibrary("x64/Release/opencv_imgproc320.dll")
cdll.LoadLibrary("x64/Release/opencv_core320.dll")	#windows下还可以使用

在这里插入图片描述
如果调换顺序就可以正常通过:

from ctypes import *
cdll.LoadLibrary("x64/Release/opencv_core320.dll")
cdll.LoadLibrary("x64/Release/opencv_imgproc320.dll")	#windows下还可以使用

所以如果出现上诉错误,就要查看一下自己还缺什么库,顺序自己慢慢测试了。

二、ctypes 怎么样调用 c 的函数库

ctype只支持C标准,所以一些C++的标准是不能用的,比如函数重载。
按照下面的方式,我们就定义了两个函数,我们需要注意这些函数的输入和输出,这样方便python调用。

#include 
#include 
using namespace std;
using namespace cv;
int flag;
// 外部申明
extern "C"{
     
	int flag_add(int a) 
	{
     
		flag += a;
		return flag;
	}
	int LPR_InitEx(const  char *pbyRootPath);
    char* mattostring(uchar* matrix, int rows, int cols, int channels)
    {
     
        ....
		//省略
    }
    
}

python调用.dll/.so中的函数

在 ctypes 读取 so/dll 时只知道存在这个函数,但是并不知到函数的形参类型和返回值的类型。
两个属性 restype 和 argtypes 赋值了,它们分别对应返回类型和参数类型:

from ctypes import *
import cv2
soFile = 'XXX.so'
MYDLL= cdll.LoadLibrary(soFile)

MYDLL.flag_add.argtypes = (c_int,) # 写明flag_add的输入类型,一定要加“,”号
MYDLL.flag_add.restype = (c_int,) # 写明flag_add的返回类型

MYDLL.LPR_InitEx.argtypes=(c_char_p,)
MYDLL.LPR_InitEx.restype = c_int	

MYDLL.mattostring.argtypes = (POINTER(ctypes.c_ubyte), c_int,c_int,c_int) 
MYDLL.mattostring.restype = (c_void_p, )

//举例调用mattostring函数
img = cv2.imread('image.jpg')
cols = img.shape[1]
rows = img.shape[0]
channels = 0
if 3==len(img.shape):
	channels = 3
pubyIm = img.ctypes.data_as(POINTER(c_ubyte))
ret = MYDLL.mattostring(pubyIm, rows, cols, channels)

如果函数的返回值是 void 那么你可以赋值为 None,至于其他的类型可以查看下面的对应表格

C数据类型与ctypes之间的转换表格:

Python使用ctypes调用动态库dll/so,关于opencv图片Mat对应的数据uchar*_第1张图片

三、结构体对应

如果在c中定义了结构体

#define _NL_TC_MAX 20
#define _NL_TC_NAME_MAX 100
typedef struct NLDJ_TC_Out
{
     
	int dwNum;
	float fScales;
	char model[128];
	char dsClassName[_NL_TC_MAX][_NL_TC_NAME_MAX];
} NLDJ_TC_Out;

则python中要定义成class

class Struct_NLDJ_TC_Out(Structure):
    _fields_ = [("dwNum", c_int),("fScales", c_float), ("model", c_char*128),("dsClassName",c_char * 100 *20)]

//声明一个结构体变量   
djTCVarOut = Struct_NLDJ_TC_Out() 

特别注意:二维数组的时候要倒着写维度

四、指针

函数 说明
byref(x [, offset]) 返回 x 的地址,x 必须为 ctypes 类型的一个实例。相当于 c 的 &x 。 offset 表示偏移量。
pointer(x) 创建并返回一个指向 x 的指针实例, x 是一个实例对象。
POINTER(type) 返回一个类型,这个类型是指向 type 类型的指针类型, type 是 ctypes 的一个类型。

byref 很好理解,传递参数的时候就用这个,用 pointer 创建一个指针变量也行,不过 byref 更快。
而 pointer 和 POINTER 的区别是,pointer 返回一个实例,POINTER 返回一个类型。甚至你可以用 POINTER 来做 pointer 的工作:

>>> a = c_int(66)         # 创建一个 c_int 实例
>>> b = pointer(a)        # 创建指针
>>> c = POINTER(c_int)(a) # 创建指针
>>> b
<__main__.LP_c_long object at 0x00E12AD0>
>>> c
<__main__.LP_c_long object at 0x00E12B20>
>>> b.contents            # 输出 a 的值
c_long(66)
>>> c.contents            # 输出 a 的值
c_long(66)

其实前面我们已经有用到了POINTER,看二部分的代码

五、opencv中的Mat

Mat并不是c的语法,uchar* 是,所以一般有两种方式进行转换uchar*:
第一种:

pubyIm = img.ctypes.data_as(POINTER(c_ubyte))  

第二种:

pubyIm = img.astype(np.uint8).tostring()   

第一种前面案例已经用到过了,而第二种借助numpy来进行转换,两者的区别就是,第一种传的是指针,如果参数进去,在mattostring函数内对变量pubyIm进行修改则会影响最终输出的内容,第二种方式不会有影响。
--------------------------2020-08-07补充----------------------------
** 给定 unsigned char* 地址,然后需要用python显示出来 **
比如已知长 height,宽 widht,地址 frameData,类型 CV_16UC1

imageMatIr = cv::Mat(height, width, CV_16UC1, frameData);

转python输出:注意我这边是深度图 CV_16UC1 ,所以 *2

srcPointer= frameData
srcString = ctypes.string_at(srcPointer, height * width *2)
# imageMatIr = np.asarray(bytearray(srcString), np.uint16)
imageMatIr = np.frombuffer(srcString, np.uint16)
imageMatIr = imageMatIr.reshape(height, width, 1)
cv2.imshow("test", imageMatIr);

另外附上一段单独测试代码:读图,然后取地址,再恢复显示图片

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import ctypes

srcImg = cv2.imread('./1.jpg')
rows = srcImg.shape[0]
cols = srcImg.shape[1]
if 3==len(srcImg.shape):
    channels = 3	

src = np.asarray(srcImg, dtype=np.uint8) 
srcPointer= src.ctypes.data_as(ctypes.c_char_p)
b = ctypes.string_at(srcPointer, cols * rows * channels)
nparr = np.frombuffer(b, np.uint8)
# nparr = np.asarray(bytearray(b), dtype="uint8")
nparr = nparr.reshape(rows,cols,channels)
cv2.imshow(" dst ", nparr)
cv2.waitKey()

--------------------------2020-08-07补充----------------------------

六、枚举类型

例如下面的C代码

/**
* @brief Specifies the type of image frame.
*/
typedef enum {
     
	DepthFrame = 0,              //!< Depth frame with 16 bits per pixel in millimeters.
	IRFrame = 1,                 //!< IR frame with 16 bits per pixel.
	RGBFrame = 2,                //!< RGB frame with 24 bits per pixel in RGB/BGR format.
}FrameType;

int getFrame(FrameType frameType)

转python,直接赋值变量即可,python定义和调用:

# 定义枚举变量
(DepthFrame, IRFrame, RGBFrame) = (0,1,2)
# 声明参数类型和返回值类型
getFrame.argtypes = (c_int,)
getFrame.restype = c_int	
# 调用
ret = getFrame(DepthFrame )

--------------------------2020-11-26补充----------------------------

七、特殊情况

这几天在处理某个动态库转换的时候,发现输出的类型,如下

#define _NL_MAX_FACENUM     (128)
#define _NL_MAX_FEATURESZ   (4100)
//图像特征输出数据结构
typedef struct NLDJ_FR_VarOut
{
     
	int dwNum; 				    	//实际特征数量
	int dwFeatureSz; 				//实际特征维度
	char ppbyFeature[_NL_MAX_FACENUM][_NL_MAX_FEATURESZ]; //归一化特征
} NLDJ_FR_VarOut;

// 实际操作的情况如下
for (int i = 0; i < 1; i++) 
{
     
	pfFeature = (float*)djVarOut_FR.ppbyFeature[i];
	for (int f = 0; f < dwFeatureDim; f++) 
	{
     
		cout<< pfFeature[f] << " ";
	}
}

一开始我的ctype定义如下:

class Struct_FE_VarOut(Structure):
    _fields_ = [("dwNum", c_int), ("dwFeatureSz", c_int), ("ppbyFeature", c_char*4100*128)]

这样做也是没错,但是实际使用的时候,C里面是把char数组转float*指针来处理的,所以在用的时候就要根据实际情况来定义输出的结构体,不然我们在Ctypes很难同时做取地址数据类型转换两个操作,于是改如下的定义就比较好用了::

class Struct_FE_VarOut(Structure):
    _fields_ = [("dwNum", c_int), ("dwFeatureSz", c_int), ("ppbyFeature", c_float*1025*128)]

for i in range(dwFeatureDim):
    c = sizeof(nlfaceExtract.djFEVarOut.ppbyFeature[0])     #512
    print(nlfaceExtract.djFEVarOut.ppbyFeature[0][i])

注意:做出上面的修改,为什么4100变成了1025,主要是根据64位的系统,char 是1字节,float是4字节,所以 4100/4 = 1025

主要参考:

windows下python调用含有opencv Mat类型的dll文件的方法
python ctypes 探究 ---- python 与 c 的交互
python 与c++动态库之间传递opencv图片数据
在ctypes.Structure中使用枚举
不同语言基本数据类型(int、char、float、double…)对应字节大小

你可能感兴趣的:(Python,python,dll,opencv)