为什么要用C++调Python
我们训练部署CNN模型时,服务器用Pytorch测试的精度比我们部署端精度高0.5%。经过多方排查,发现是由于Pytorch预处理图片使用PIL进行图片读取和尺寸调整,但是部署端采用OpenCV进行图片读取和尺寸调整。两种实现方式实现的Resize操作差异非常大。为了快速完成项目,暂时用C++调用Python的PIL库进行图像预处理,检查实现的精度。
安装Python环境
因为C在编译的时候需要链接到Python函数的一些链接库,并且Linux环境下更改默认Python可能会造成桌面系统损坏,所以我们重新编译了一个Python环境。
1. 安装Python所需的依赖项sudo apt install libbz2-dev
2. 下载Python源代码:https://www.python.org/ftp/python/3.10.14/Python-3.10.14.tgz
3. 新建python_env目录,并将tgz压缩包复制到其中,使用tar -xvf Python-3.10.14.tgz 解压该文件,得到如下目录结构:
python_env|
| Python-3.10.14
| Python-3.10.14.tgz
4.cd Python-3.10.14
5.配置Python安装文件,打开–enable-shared生成可以连接到g++的动态链接库文件,使用—prefix指定安装目录:
./configure –enable-shared –prefix=/home/dell/Work4/workspace/user_work4/python_env
6.make
7.make -j16 install
8.此时所需的Python环境已经安装到/home/dell/Work4/workspace/user_work4/python_env目录下
9.安装成功后会显示需要添加动态链接库的位置:LD_LIBRARY_PATH
10.将之前显示的动态链接库路径添加到环境变量(可以放到~/.bashrc下):
export LD_LIBRARY_PATH=/home/dell/Work4/workspace/user_work4/python_env/Python-3.10.14
安装Pytorch OpenCV
在对应的python环境下安装pytorch:
/home/dell/Work4/workspace/user_work4/python_env/bin/python3 -m pip install torch torchvision torchaudio –index-url https://download.pytorch.org/whl/cpu
在对应的python环境下安装opencv:
/home/dell/Work4/workspace/user_work4/python_env/bin/python3 -m pip install opencv-python
编写Python脚本
编写Python脚本pil_load_pic_mod.py
from PIL import Image
import os
import cv2
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import numpy as np
import torchvision.transforms as transforms
import pdb
def pil_load_pic(img_path):
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
trans=transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),normalize])
# PIL Loading PIC
# img = Image.open(img_path)
# img_rgb = img.convert('RGB')
# Opencv Loading PIC
img = cv2.imread(img_path)
img_rgb = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
img_rgb = Image.fromarray(img_rgb)
# Resize
img_resized = trans(img_rgb)
img_numpy = img_resized.numpy()
# pdb.set_trace()
# print("Python Successfully load %s"%img_path) #dtype=float32
return img_numpy.reshape(-1).tolist()
if __name__=="__main__":
print(pil_load_pic("/home/dell/Work3/data_set/ImageNet/ILSVRC2012_img_val/ILSVRC2012_val_00000001.JPEG"))
#234
#!/home/dell/Disk03/students/user/python_env/bin/python3
#112
#!/home/dell/Disk03/user/python_env/bin/python3
编写C++程序
#include<stdio.h>
#include<Python.h>
int main(){
// PIL load and resize pic Init
float mean[3]={0.485, 0.456, 0.406}, std[3]={0.229, 0.224, 0.225}; //rgb
#define PIC_OUT_SIZE 224
Py_Initialize();
if (!Py_IsInitialized()) {
printf("ERROR: Python init fail
");
exit(1);
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyObject *pModule = PyImport_ImportModule("pil_load_pic_mod");
if (pModule == NULL){
printf("ERROR: No Python module loaded!
");
PyErr_Print();
exit(1);
}
PyObject *pFunc = PyObject_GetAttrString(pModule, "pil_load_pic");
if (!PyCallable_Check(pFunc)){
printf("ERROR: No Python module loaded!
");
PyErr_Print();
exit(1);
}
char picture_file_path[255] = {0};
char *img_resized;
img_resized = (char *)malloc(PIC_OUT_SIZE*PIC_OUT_SIZE*3);
for (int pic_num = 1; pic_num <=1; pic_num++){
sprintf(picture_file_path, "/home/dell/Work4/workspace/user_work4/c_and_python/ILSVRC2012_val_%08d.JPEG", pic_num);
// OpenCV load and resize pic
// cv_load_pic(picture_file_path,img_resized);
// PIL load and resize pic
PyObject *loadImageArgs = Py_BuildValue("(s)", picture_file_path);
PyObject *pValue = PyObject_CallObject(pFunc, loadImageArgs);
Py_DECREF(loadImageArgs);
if (pValue != NULL) {
printf("Python Successfully load %s
",picture_file_path);
if (PyList_Check(pValue)) {
Py_ssize_t size = PyList_Size(pValue);
if(size != (PIC_OUT_SIZE*PIC_OUT_SIZE*3)){
printf("PIL output pic size error!
");
exit(1);
}
int row=0, col=0, cc=0, out_idx=0;
for (Py_ssize_t i = 0; i < size; i++) {
PyObject *item = PyList_GetItem(pValue, i);
// if(col<3 && row<3){
// printf("col:%d row:%d cc:%d %f
", col, row, cc, PyFloat_AsDouble(item));
// }
img_resized[out_idx++] = (PyFloat_AsDouble(item) * std[cc] + mean[cc]) * 255;
if(cc==3-1 && row==PIC_OUT_SIZE-1 && col==PIC_OUT_SIZE-1){
cc = 0;
}
else if(row==PIC_OUT_SIZE-1 && col==PIC_OUT_SIZE-1){
cc++;
}
if(row==PIC_OUT_SIZE-1 && col==PIC_OUT_SIZE-1){
row = 0;
}
else if(col==PIC_OUT_SIZE-1){
row++;
}
if(col==PIC_OUT_SIZE-1){
col = 0;
}
else{
col++;
}
}
}
Py_DECREF(pValue);
} else {
PyErr_Print();
}
}
return 0;
}
编写Makefile
输入指令查看所需编译参数:
/home/dell/Work4/workspace/user_work4/python_env/bin/python3-config –cflags –embed
输入指令查看所需链接参数:
# /home/dell/Work4/workspace/user_work4/python_env/bin/python3-config –ldflags –embed
最终编写Makefile:
# /home/dell/Work4/workspace/user_work4/python_env/bin/python3-config --cflags --embed
COMPILE_FLAG = -I/home/dell/Work4/workspace/user_work4/python_env/include/python3.10 -I/home/dell/Work4/workspace/user_work4/python_env/include/python3.10 -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall
# /home/dell/Work4/workspace/user_work4/python_env/bin/python3-config --ldflags --embed
LINK_FLAG = -L/home/dell/Work4/workspace/user_work4/python_env/lib -lpython3.10 -lcrypt -lpthread -ldl -lutil -lm -lm
.PHONY: host
host: main_c.o
gcc main_c.o -o run.exe $(LINK_FLAG)
main_c.o: main_c.cpp
gcc main_c.cpp -c -o main_c.o $(COMPILE_FLAG)
1