最通俗易懂的封装C/C++代码为Python模块【附OpenCV-Mat转换方法】
本文教程参考自微软官方文档:创建适用于 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一个目录下,就能完成调用了。目录结构如下:
更多推荐
所有评论(0)