本文教程参考自微软官方文档:创建适用于 Python 的 C++ 扩展

前言背景

看了好多人写的方法,最后发现只有在微软官网文档上找到的方法最可行。封装C++代码主流方法都是封装成dll文件调用。

首先我们必须明白为什么我们能在Python里面调用C++的方法,因为我们主流使用的Python解释器是C写的,为了区别开用其他语言实现的python,为此我们通常叫它Cpython,可以理解为:用C实现的python。

使用 C/C++编写的模块常用于扩展 Python 解释器的功能和启用对低级别操作系统功能的访问。 主要有以下 3 种类型的模块:

  • 加速器模块:Python 是一种解释型语言,某些代码段可使用 C++ 进行编写来提高性能。
  • 包装器模块:向 Python 代码公开现有 C/C++ 接口,或公开易于通过 Python 使用的更“Python 化”的 API。
  • 低级别系统访问模块:用于访问 CPython 运行时的较低级别功能、操作系统或基础硬件。

环境准备

VS环境:VS2019/2017/2015
Python环境:原生的Python3.6(非Anaconda)

实现最基础的C2Python接口

先创建一个空的C++项目

第一步:创建完成后,在新项目中创建 C++ 文件,选择“添加” > “新建项”,选择“C++ 文件”,将其命名为 module.cpp。

第二步:右键单击“解决方案资源管理器”中的 C++ 项目,然后选择“属性”。

第三步:在显示的“属性页”对话框顶部,将“配置”设置为“所有配置”,并将“平台”设置为“x64”(根据你的项目需求选择平台)
在这里插入图片描述
第四步:如下表配置属性:
在这里插入图片描述
在这里插入图片描述
第六步:生成,按照上面配置无误不会出现报错

第七步:编写C++函数:
实现一个对自增300的函数C_func

#include <Windows.h>
#include <cmath>
#include <Python.h>

//我们即将在python里面调用这个函数
PyObject* C_Func(PyObject*, PyObject* o) { //o是python对象,Cpython接口利用Pyobject进行数据交互
    double x = PyFloat_AsDouble(o); //把Pyobject转换为double,因为在python以浮点数的形式传进来的
    double result = x + 300;
    return PyFloat_FromDouble(result);//然后把C++的double转回PyObject
}

static PyMethodDef superfastcode_methods[] = {
    { "Py_Func", (PyCFunction)C_Func, METH_O, nullptr }, //Py_Func 是在python里面引用的名字
    { nullptr, nullptr, 0, nullptr }
};

static PyModuleDef superfastcode_module = {
    PyModuleDef_HEAD_INIT,
    "C2Python",            // Module name to use with Python import statements 模块的名字
    "Module description",  // Module description 模块信息描述
    0,
    superfastcode_methods  // Structure that defines the methods of the module
};

PyMODINIT_FUNC PyInit_C2Python() {
    return PyModule_Create(&superfastcode_module);
}

打开生成目录,找到以pyd结尾的文件,这是编译出来的二进制文件
在这里插入图片描述
第八步:在同个目录下,创建一个python文件:xxxx.py
代码如下:

import C2Python

a=C2Python.Py_Func(100) #Py_Func 这个函数名字是在C++的PyMethodDef里面写的
print(a)

输出:400.0
至此,调用成功。

C++的Opencv的Mat类型转Python的numpy数组

我们知道在C++里面的opencv定义了一个独一无二的Mat类,和opencv-python里面直接使用numpy数组是有差异的。Cpython里面提供的接口可以交互的数据类型:int,float,bytes。所以,我们进行数据交互只能用bytes类型。网上不少文章的方法会出现内存读取错误,如果我们按照元数据作为字节数据转换很有可能会出现问题。我们可以从网络图片数据传输方法得到灵感,使用Base64编码进行数据交互。

第一步:头文件声明

创建一个Trans.hpp,代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string.h>
#include<algorithm>
#include<opencv.hpp>
using namespace cv;
using namespace std;

Mat Base2Mat(string& base64_data);
string Mat2Base64(const Mat& img, string imgType);
char* string2char(string code);
第二步:写C++传输类

我们创建一个新的Cpp文件:Trans.cpp,用于辅助数据传输(保证当前项目已经进行正确的opencv环境配置

#include "Trans.hpp"
string base64Decode(const char* Data, int DataByte)
{
	//解码表
	const char DecodeTable[] =
	{
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		62, // '+'
		0, 0, 0,
		63, // '/'
		52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'
		0, 0, 0, 0, 0, 0, 0,
		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z'
		0, 0, 0, 0, 0, 0,
		26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
		39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z'
	};
	//返回值
	string strDecode;
	int nValue;
	int i = 0;
	while (i < DataByte)
	{
		if (*Data != '\r' && *Data != '\n')
		{
			nValue = DecodeTable[*Data++] << 18;
			nValue += DecodeTable[*Data++] << 12;
			strDecode += (nValue & 0x00FF0000) >> 16;
			if (*Data != '=')
			{
				nValue += DecodeTable[*Data++] << 6;
				strDecode += (nValue & 0x0000FF00) >> 8;
				if (*Data != '=')
				{
					nValue += DecodeTable[*Data++];
					strDecode += nValue & 0x000000FF;
				}
			}
			i += 4;
		}
		else// 回车换行,跳过
		{
			Data++;
			i++;
		}
	}
	return strDecode;
}
string base64Encode(const unsigned char* Data, int DataByte)
{
	//编码表
	const char EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	//返回值
	string strEncode;
	unsigned char Tmp[4] = { 0 };
	int LineLength = 0;
	for (int i = 0; i < (int)(DataByte / 3); i++)
	{
		Tmp[1] = *Data++;
		Tmp[2] = *Data++;
		Tmp[3] = *Data++;
		strEncode += EncodeTable[Tmp[1] >> 2];
		strEncode += EncodeTable[((Tmp[1] << 4) | (Tmp[2] >> 4)) & 0x3F];
		strEncode += EncodeTable[((Tmp[2] << 2) | (Tmp[3] >> 6)) & 0x3F];
		strEncode += EncodeTable[Tmp[3] & 0x3F];
		if (LineLength += 4, LineLength == 76) { strEncode += "\r\n"; LineLength = 0; }
	}
	//对剩余数据进行编码
	int Mod = DataByte % 3;
	if (Mod == 1)
	{
		Tmp[1] = *Data++;
		strEncode += EncodeTable[(Tmp[1] & 0xFC) >> 2];
		strEncode += EncodeTable[((Tmp[1] & 0x03) << 4)];
		strEncode += "==";
	}
	else if (Mod == 2)
	{
		Tmp[1] = *Data++;
		Tmp[2] = *Data++;
		strEncode += EncodeTable[(Tmp[1] & 0xFC) >> 2];
		strEncode += EncodeTable[((Tmp[1] & 0x03) << 4) | ((Tmp[2] & 0xF0) >> 4)];
		strEncode += EncodeTable[((Tmp[2] & 0x0F) << 2)];
		strEncode += "=";
	}
	return strEncode;
}

string Mat2Base64(const Mat& img, string imgType)
{
	//Mat转base64
	string img_data;
	vector<uchar> vecImg;
	vector<int> vecCompression_params;
	vecCompression_params.push_back(CV_IMWRITE_JPEG_QUALITY);
	vecCompression_params.push_back(90);
	imgType = "." + imgType;
	imencode(imgType, img, vecImg, vecCompression_params);
	img_data = base64Encode(vecImg.data(), vecImg.size());
	return img_data;
}
Mat Base2Mat(string& base64_data)
{
	Mat img;
	string s_mat;
	s_mat = base64Decode(base64_data.data(), base64_data.size());
	vector<char> base64_img(s_mat.begin(), s_mat.end());
	img = imdecode(base64_img,CV_LOAD_IMAGE_COLOR);
	return img;
}

char* string2char(string code)
{
	char* dst = new char[strlen(code.c_str()) + 1];
	strcpy(dst, code.c_str());
	return dst;
}
第三步:写C++主函数

然后,我们在moudle.cpp里面写:

#include"Trans.hpp"
#include <Python.h>

Mat Process(Mat src) // Here is your opencv program
{
    Mat Img = src;
    return Img;
}


PyObject* C_Func(PyObject*, PyObject* o) {
    string Matcode = PyBytes_AsString(o);
	Mat src_img = Base2Mat(Matcode);
    Mat tar_img=Process(src_img); // Here is your opencv program
    string code=Mat2Base64(tar_img,"jpg");
    char *dst = string2char(code);
    return PyBytes_FromString(dst);
}


static PyMethodDef code_methods[] = {  
    { "Py_Func", (PyCFunction)C_Func, METH_O, nullptr},
    { nullptr, nullptr, 0, nullptr }
};


static PyModuleDef code_module = {
    PyModuleDef_HEAD_INIT,
    "C2Python",                        
    "Here is your moudle details", 
    0,
    code_methods 
};

PyMODINIT_FUNC PyInit_downBH() {
    return PyModule_Create(&code_module);
}

第四步:调用

然后生成,在有pyd文件的目录下,创建一个xxxxx.py,代码如下:

import C2Python
import base64
import cv2
import numpy as np

def decode_base64(base64_data):  
    img = base64.b64decode(base64_data)
    nparr = np.fromstring(img, np.uint8)
    img_np = cv2.imdecode(nparr, 1)
    img_np=cv2.cvtColor(img_np,cv2.COLOR_BGR2RGB)
    return img_np

def __img2base64Bytes(img):
    x = cv2.imencode(".jpg",img)[1].tobytes()
    base64_data = base64.b64encode(x)
    return base64_data

img=cv2.imread('D:/1.jpg')
base64_data=__img2base64Bytes(img)

buf=C2Python.Py_Func(base64_data) # Here is your C++ func
re_img=decode_base64(buf) #return bytes so you need to decode

cv2.imshow('Preview',re_img)
cv2.waitKey(0)

至此,完成C++封装的所有任务。

在一台没有opencv的电脑上调用

我们在opencv的build目录里面找到类似:opencv_world346.dll的文件,把他复制到跟pyd一个目录下,就能完成调用了。目录结构如下:
在这里插入图片描述

GitHub 加速计划 / opencv31 / opencv
77.38 K
55.71 K
下载
OpenCV: 开源计算机视觉库
最近提交(Master分支:2 个月前 )
48668119 dnn: use dispatching for Winograd optimizations 1 天前
3dace76c flann: remove unused hdf5 header 1 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐