windows环境下python调用cpp动态库(win10+opencv)

第一部分、前言

在windows环境下,可以利用python直接调用cpp的动态链接库,从而达到混合编程的目的。

一、cpp的动态链接库

windows下编译cpp的动态链接库的技术比较多了,这里指出两处需要特别注意的地方:

(1)要利用extern "C"关键字,实现C编译;

(2)pythoe与 cpp的接口最好重写封装,即在功能函数外面添加一层包装,在包装内实现数据交互。

二、python调用cpp的动态链接库

主要注意三个地方:

(1)dll如果依赖于其他动态库(如opencv),则必须保证其他被依赖的dll文件在环境变量的路径下(或者与被调用的dll扔在同一路径下),否则python调用的时候会找不到依赖项。

(2)使用ctypes模块实现python内调用cpp动态库时的数据交互,主要是用指针交互;对于x64平台,输出数据指针必须定义为64位无符整型(ctypes.c_uint64),否则会报错【注:此处指的是python内的接口数据定义,cpp内不必遵守,这看起来是一个矛盾的问题,具体原因未知,有些技术人员认为这是一个bug,留待以后进一步确认】。

(3)由cpp传回的数据指针,是由ASCII码表示的二进制,即不论cpp的返回数据类型是什么,python接收到的连续内存都会被且分为一个一个的8bit小单元,需要进一步数据拼接,才能恢复原始数据【注:比如cpp返回数据为short类型的12941,其二进制标表示为0011 0010 1000 1101,十六进制表示为0x328D,但是python返回的不是一个short的16位数据,而是被拆分为两个8bit的数位,先返回低8位0x8D,后返回高8位0x50,因此需要进一步处理】。

第二部分、代码示例

一、cpp功能函数封装dll

功能函数代码如下:

void detect(const cv::Mat img, std::vector& rect)
{
	cv::Mat dst;
	if (3 == img.channels()) { cv::cvtColor(img, dst, cv::COLOR_RGB2GRAY); }
	else { img.copyTo(dst); }
	std::vector> contours;
	std::vector hierarchy;
	cv::Rect tmp;
	if (CV_8U != dst.type()) { dst.convertTo(dst, CV_8U); }
	cv::findContours(dst, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
	for (int index = 0; index >= 0; index = hierarchy[index][0])
	{
		tmp = cv::boundingRect(contours[index]);
		rect.push_back(tmp);
	}
}

上述cpp代码实现检测目标物体轮廓,并返回最小外接矩形。

封装接口如下

short* py2cpp(int height, int width, uchar* src_data)
{
    //> 由python接口传入的数据转换为适合功能函数获取的数据
    cv::Mat src(height, width, CV_8UC3, (void*)src_data);
    //> 调用功能函数
    std::vector list;
    detect(src, list);
    //> 将结果转换为适合python接口返回的数据形式
    cv::Mat tres((int)list.size(), 4, CV_16SC1);
    g_value = (int)list.size();
    for (int i = 0; i < tres.rows; i++)
    {
        tres.at(i, 0) = list[i].x;
        tres.at(i, 1) = list[i].y;
        tres.at(i, 2) = list[i].width;
        tres.at(i, 3) = list[i].height;
        //> 输出cpp内计算的计算结果
        std::cout << "[" << list[i].x << ", " << list[i].y << ", " << list[i].width << ", " << list[i].height << "]" << std::endl;
    }
    short *bb = (short *)(tres.data);
    //> 输出内存首地址
    std::cout << bb << std::endl;
    return bb;
}

输入值:图像的高度height,图像宽度width, 以及图像指针src_data。

返回值:short类型指针,指向std::vector的首地址,其内存放的是 轮廓最小外接矩形。

该接口封装函数,实现了将由python传进来的数据,整合成cpp功能函数方便调用的数据类型,再将功能函数的返回值转化为方便python获取的形式再传出,从而完成数据通信。同时, 还应当返回一个数据长度的量,以方便在内存中进行读取,在上述借口封装的代码中,已经将这个数据长度存储在全局变量g_value下,另写一个函数返回这个全局变量即可。代码如下:

int lenght()
{
	std::cout << g_value << std::endl;
	return g_value;
}

以上,是dll的代码实现,写在cpp文件中,其头文件内容如下:

#pragma once

#include 
#include 

#define DLLEXPORT __declspec(dllexport)

extern "C"
{
	int g_value;
	DLLEXPORT short* __stdcall py2cpp(int height, int width, uchar* src_data);

	DLLEXPORT int lenght();

	void detect(const cv::Mat img, std::vector& rect);
}

注意在第一部分提到的,用extern "C"关键字转译为C编译。生成的DLL用于python调用即可。

二、python调用DLL

直接上python代码如下

import numpy as np
import ctypes
import cv2

#载入dll文件的路径
test = ctypes.cdll.LoadLibrary("..\\x64\\Release\\dll2py.dll")
#读入图片
frame = cv2.imread('..\\circle.png')
#将opencv的mat数据类型转换为char*的数据格式
frame_data = np.asarray(frame, dtype=np.uint8)
frame_data = frame_data.ctypes.data_as(ctypes.c_char_p)
#定义输出数据格式(此处似乎有一个bug,即x64框架下仅支持uint64格式,其他格式会报错)
test.py2cpp.restype = ctypes.c_uint64
test.lenght.restype = ctypes.c_uint64
#调用dll的接口函数
buf = test.py2cpp(frame.shape[0], frame.shape[1], frame_data)
mlenght = test.lenght()
#打印返回值的指针首地址
print('地址值:')
print('%#x'%buf)
#打印返回框的个数
print('框个数:')
print(mlenght)
#数据解析:返回的指针是short类型(16位),但返回地址是按照8位存储的(此处实际上是存储的二进制数据,但是是以ASCII码形式存储的,因此仅能一次性存储8位)。我们的16位返回数据,是被拆解为高8位和低8位分别存储的,且先读出来的是低位,厚度出来的是高位,按照这一原则进行数据拼接。
bit_typr = 4*2
for num in range(0, mlenght):#框的个数循环
    #x数据解析,先读低8位,后读高8位
    L8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr, 1), byteorder='big', signed=False)
    H8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr + 1, 1), byteorder='big', signed=False)
    x = H8 << 8
    x = x + L8
    #y数据解析,先读低8位,后读高8位
    L8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr + 2, 1), byteorder='big', signed=False)
    H8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr + 3, 1), byteorder='big', signed=False)
    y = H8 << 8
    y = y + L8
    #w数据解析,先读低8位,后读高8位
    L8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr + 4, 1), byteorder='big', signed=False)
    H8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr + 5, 1), byteorder='big', signed=False)
    w = H8 << 8
    w = w + L8
    #h数据解析,先读低8位,后读高8位
    L8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr + 6, 1), byteorder='big', signed=False)
    H8 = int.from_bytes(ctypes.string_at(buf + num * bit_typr + 7, 1), byteorder='big', signed=False)
    h = H8 << 8
    h = h + L8
    print(x, y, w, h)
    cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 1, 8)

cv2.namedWindow('src', cv2.WINDOW_AUTOSIZE)
cv2.imshow('src', frame)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果如下图所示:

windows环境下python调用cpp动态库(win10+opencv)_第1张图片

实现对图中两个圆的外接矩形定位框检测。

log信息分享如下:

windows环境下python调用cpp动态库(win10+opencv)_第2张图片

其中红色框内是由dll内部输出的log信息,分别是:矩形框的坐标(起点坐标和框的长宽尺寸)、返回的首地址值、返回的数据长度值(有几个short数据);

黄色框内是python脚本打印的log信息,分别是:返回的首地址值、返回的数据长度值(有几个short数据)、矩形框的坐标(起点坐标和框的长宽尺寸)。

可以看到结果完全一致,是能够对的上的。

再次提醒:第一部分前言中需要注意关注的几点,否则可能会各种报错。

你可能感兴趣的:(C/C++,python)