赞
踩
目前深度学习算法大多数是基于Python实现,但一些项目的框架是用C++搭建,所以就出现了在C++中调用模型的问题。本文主要记录C++调用Python-YOLOv5模型的步骤,实现C++中读取图片然后传入YOLO模型中进行检测,最后将类名、坐标、置信度返回到C++中。开发环境为QT5、Python3.8、Opencv3.414以及运行YOLOv5源码的虚拟环境。
Python中自带有C++接口。首先需要导入Python目录下的include文件夹和库文件。
我的Python是用Anaconda安装,所以这里我加入的是Anaconda虚拟环境里的路径,同时也导入了Opencv3.4。
在.pro文件中加入以下内容,根据自己的路径更改:
- INCLUDEPATH+=D:/Opencv_Source/build_x64/install/include \
- D:/Anaconda/envs/yolov5/include \
- D:/Anaconda/envs/yolov5/Lib/site-packages/numpy/core/include/numpy
-
- Debug:
- {
- LIBS+=D:/Opencv_Source/build_x64/install/x64/vc14/lib/opencv_world3414d.lib \
- D:/Anaconda/envs/yolov5/libs/python38_d.lib
- }
-
- Release:
- {
- LIBS+=D:/Opencv_Source/build_x64/install/x64/vc14/lib/opencv_world3414.lib \
- D:/Anaconda/envs/yolov5/libs/python38.lib
- }
大致步骤为:
(1)初始化
(2)设置文件所在路径
(3)调用文件
(4)获得函数列
(5)调用函数
下面直接贴代码,包含前四部,可根据注释理解:
- PyObject* pModule; //.py文件
- PyObject* pFunc; //py文件中的函数
- PyObject* pClass; //类
- PyObject* pInstance; //实例
- PyObject* args;//参数
-
- Py_SetPythonHome(L"D:/Anaconda/envs/yolov5");//指定python.exe位置
- Py_Initialize();//使用python之前,要调用Py_Initialize();这个函数进行初始化
-
- PyRun_SimpleString("import sys");
- PyRun_SimpleString("sys.path.append('./')");//设置.py文件所在位置
-
- //file为不包含扩展名的文件名
- pModule = PyImport_ImportModule(file); //调用上述路径下的.py文件
- if (pModule == NULL)
- {
- cout << "Can't find the python file!" << endl;
- return 0;
- }
- cout << "find file succed" << endl;
-
- // 模块的字典列表
- PyObject* pDict = PyModule_GetDict(pModule); //获得Python模块中的函数列
- if (pDict == NULL)
- {
- cout << "Can't find the dictionary!" << endl;
- return 0;
- }
- cout << "find dictionary succed" << endl;
调用Python模块中的函数只需要两句代码,但C++中并没有直接将Mat类转换为Python数据类型的函数。
这里借鉴了别的博文的代码,将C++中Mat类型里的数据转换为Python里的元组作为参数传入Python模块.附链接:https://blog.csdn.net/qq_38109843/article/details/87969732
以下为C++中的代码
- import_array();
-
- int m, n;
- n = img.cols*3;
- m = img.rows;
- unsigned char *data = (unsigned char*)malloc(sizeof(unsigned char) * m * n);
- int p = 0;
- for (int i = 0; i < m; i++)
- {
- for (int j = 0; j < n; j++)
- {
- data[p] = img.at<unsigned char>(i, j);
- p++;
- }
- }
-
- npy_intp Dims[2] = { m, n }; //给定维度信息
- PyObject*PyArray = PyArray_SimpleNewFromData(2, Dims, NPY_UBYTE, data);
-
- PyObject *ArgArray = PyTuple_New(2);
- PyObject *arg = PyLong_FromLong(30);
- PyTuple_SetItem(ArgArray, 0, PyArray);
- PyTuple_SetItem(ArgArray, 1, arg);
-
- //pDict是Python模块中的函数列,function是函数名
- PyObject*pFunc = PyDict_GetItemString(pDict, function); //获取函数
-
- //ArgArray是传入的参数,pRet是返回值
- PyObject* pRet= PyObject_CallObject(pFunc, ArgArray); //调用函数
Python中的代码:
- def arrayreset(array):
- a = array[:, 0:len(array[0] - 2):3]
- b = array[:, 1:len(array[0] - 2):3]
- c = array[:, 2:len(array[0] - 2):3]
- a = a[:, :, None]
- b = b[:, :, None]
- c = c[:, :, None]
- m = np.concatenate((a, b, c), axis=2)
- return m
注意传入的图像必须是RGB图像。
YOLO的环境配置我也是照着别人的博文配置,作为一个知识尚匮乏的大学生就不再写一篇误人子弟了。
直接上链接:https://blog.csdn.net/kasaiki/article/details/108651751
训练自己模型的方法:https://blog.csdn.net/weixin_44936889/article/details/110661862
YOLOv5源码中就已经有detect.py文件用作目标检测,其功能也非常丰富,可以改变参数来实现不同的输入输出方式,这里我就直接做减法,只实现传入一张图片进行检测,返回目标的类名、坐标和置信度。
- def detect(image,a=1,
- weights='best.pt', # model.pt path(s)
- imgsz=640, # inference size (pixels)
- conf_thres=0.25, # confidence threshold
- iou_thres=0.45, # NMS IOU threshold
- device='cpu',
- max_det=1000, # maximum detections per image
- classes=None, # filter by class: --class 0, or --class 0 2 3
- agnostic_nms=False, # class-agnostic NMS
- augment=False, # augmented inference
- line_thickness=3, # bounding box thickness (pixels)
- hide_labels=False, # hide labels
- hide_conf=False, # hide confidences
- half=False, # use FP16 half-precision inference
- ):
-
- #Initialize
- set_logging()
- device = select_device(device)
- half &= device.type != 'cpu' # half precision only supported on CUDA
-
- #加载图像
- im0s = arrayreset(image)
- img = letterbox(im0s)[0]
- img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
- img = np.ascontiguousarray(img)
-
- # Load model
- model = attempt_load(weights, map_location=device) # load FP32 model
- stride = int(model.stride.max()) # model stride
- imgsz = check_img_size(imgsz, s=stride) # check image size
- names = model.module.names if hasattr(model, 'module') else model.names # get class names
- if half:
- model.half() # to FP16
-
- # Second-stage classifier
- classify = False
- if classify:
- modelc = load_classifier(name='resnet101', n=2) # initialize
- modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']).to(device).eval()
-
- # Run inference
- if device.type != 'cpu':
- model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # run once
- t0 = time.time()
-
- img = torch.from_numpy(img).to(device)
- img = img.half() if half else img.float() # uint8 to fp16/32
- img /= 255.0 # 0 - 255 to 0.0 - 1.0
- if img.ndimension() == 3:
- img = img.unsqueeze(0)
-
- # Inference
- t1 = time_synchronized()
- pred = model(img, augment=augment)[0]
-
- # Apply NMS
- pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
- t2 = time_synchronized()
-
- # Apply Classifier
- if classify:
- pred = apply_classifier(pred, modelc, img, im0s)
-
- info = []
-
- for i, det in enumerate(pred): # detections per image
- if len(det):
- # Rescale boxes from img_size to im0 size
- det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0s.shape).round()
-
- for *xyxy, conf, cls in reversed(det):# Add bbox to image
- c = int(cls) # integer class
- label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
- plot_one_box(xyxy, im0s, label=label, color=colors(c, True), line_thickness=line_thickness)
- x1,y1,x2,y2=xyxy[0].item(), xyxy[1].item(), xyxy[2].item(), xyxy[3].item()
- info.append(names[c])
- info.append((x1, y1, x2, y2,conf.item()))
-
- cv2.imshow('show', im0s)
- print(f'Done. ({time.time() - t0:.3f}s)')
- cv2.waitKey(0) # 1 millisecond
- return info
返回的info是一个列表,其格式是[name,(x1,y1,x2,y2,value)......],name是类名,以字符串表示,元组里是坐标和置信度。将识别到的目标依次存储到列表里,再返回到C++中解析即可。
C++中有函数能转换Python的数据类型,这里我们返回值里有列表、字符串和元组,都有对应的函数进行转换。
我是看这篇博文学习的:https://blog.csdn.net/stu_csdn/article/details/69488385
下面附上代码:
- char * buffer1; //储存Python文件返回值
- PyObject *ListItem;
-
- //定义坐标,置信度
- float x1 = 0;
- float y1=0;
- float x2 = 0;
- float y2=0;
- float value=0;
-
- if(PyList_Check(pRet)) //检查返回值是不是列表
- {
- for(int i=0;i<PyList_Size(pRet);i+=2)
- {
- ListItem=PyList_GetItem(pRet,i); //读取列表里的第i个元素
- PyArg_Parse(ListItem,"s",&buffer1); //转换为字符串类型
- buffer2=buffer1;
- cout<<buffer2<<endl;
-
- ListItem=PyList_GetItem(pRet,i+1);
- PyArg_ParseTuple(ListItem, "f|f|f|f|f", &x1,&y1,&x2,&y2,&value);//转换为浮点型
- cout<<"x1: "<<x1<<"y1: "<<y1<<"x2: "<<x2<<"y2: "<<y2<<" "<<value<<endl;
- }
- }
整个代码运行得到的结果:
想说一下自己短短涉猎深度学习半年多时间的感想。在学习深度学习之前我只是学习了Opencv来做一些比赛,后来看有图像处理的基础就跟着老师做项目才进阶深度学习。但我确实也像网上经常被嘲笑的调包侠一样,做深度学习也就会调个包,改改参数,工程能力确实有比较大的锻炼,但没有理论基础。所以接下来也会补一下理论知识来丰富自己,也想不只是学习深度学习的算法,多学一些Opencv的优秀算法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。