本文提供了将Python
算法嵌入C++
或C
语言的两种实现思路。算法大多是由Python
语言编写,而我们开发软件大多使用的还是C++
,比如常见的QT
、C#
等,两种不同的语言之间如何实现通信呢?有的将算法打包为.exe
文件,通过软件去启动这个.exe
,这种方法并不优雅。我们知道Python
的底层是C
语言编写,叫CPython
,本文方法便是通过CPython
调用Python
脚本,实现数据交互。
假设我们现在使用QT
开发软件界面,通过摄像头去识别物体,所用算法为YoloV5
,将带有检测结果的视频显示在QT
界面上。
QT
开启一个视频接收的线程,通过opencv
接收,将每一帧图像存放在消息队列中,假设消息队列叫srcMatQueue
;再开启一个图像检测线程,将图像从srcMatQueue
中取出,将图像传递给Python
算法,获取返回之后的图像,并存入消息队列detectMatQueue
中;再主线程(GUI
)中,取出detectMatQueue
中的图像,转为QImage
格式,然后显示即可。
这里重点介绍第二个图像检测线程。首先,要调用CPython
,我们需要包含其头文件Python.h
,如下
#include
#include
#include
#include
#include
#include
调用Python
算法可分为一下几步:
Python
环境Python
模块显然,我们需要将Python
算法封装为一个类然后调用,(不封装也可以,只是封装为类后,我们可以在初始化时先加载模型,这样可以节省检测时多次加载模型),以YoloV5
为例,我们只要将detect.py
文件重新封装一下即可。我们封装的函数主要有三个,分别是__init__
、loadModel
、detect
,__init__
是实例化这个类时执行的函数,主要对一些变量进行初始化;loadModel
函数是单独加载模型文件的函数,一般来说,加载模型比较耗时,你也可以直接将其写在初始化函数里;detect
函数是对QT
传给它的图像进行处理的函数,所以需要有一个入口参数frame
,算法对frame
进行处理后将结果return
即可,需要注意的是返回的类型,QT
端对返回值进行解析,得到处理后的图片和检测结果。
以下是YoloV5
中detect.py
封装示例。
# This Python file uses the following encoding: utf-8
import os
import sys
from pathlib import Path
import cv2
import torch
# import torch.backends.cudnn as cudnn
import numpy as np
FILE = Path(__file__).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
from utils.augmentations import letterbox
from models.common import DetectMultiBackend
from utils.datasets import IMG_FORMATS, VID_FORMATS
from utils.general import (LOGGER, non_max_suppression, scale_coords)
from utils.plots import Annotator, colors
from utils.torch_utils import select_device, time_sync
class YoloV5:
def __init__(self) -> None:
self.img_size = 640
self.stride = 32
self.weights = ROOT / 'yolov5s.pt'
self.data = ROOT / 'data/coco128.yaml'
self.device = 0
self.conf_thres = 0.25
self.iou_thres = 0.45
self.max_det = 1000
self.dnn = False
self.imgsz = (640,640)
self.augment=False
self.visualize=False
self.classes=None
self.agnostic_nms=False
self.line_thickness=3
self.hide_labels=False # hide labels
self.hide_conf=True # hide confidences
self.view_img=False
def load_img(self, img0):
# Padded resize
img = letterbox(img0, self.img_size, stride=self.stride, auto=True)[0]
# Convert
img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
img = np.ascontiguousarray(img)
return img
def select_dev(self, dev):
self.device = dev
def load_model(self):
self.dev = select_device(self.device)
self.model = DetectMultiBackend(self.weights, self.dev, self.dnn, self.data)
def detect(self, srcImg):
names = self.model.names
# imgsz = check_img_size(imgsz, s=stride) # check image size
im = self.load_img(srcImg)
im = torch.from_numpy(im).to(self.dev)
im = im.float() # uint8 to fp16/32
im /= 255
if len(im.shape) == 3:
im = im[None]
pred = self.model(im, augment=self.augment, visualize=self.visualize)
# NMS
pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, self.classes, self.agnostic_nms, max_det=self.max_det)
# Process predictions
det = pred[0]
im0 = srcImg.copy()
im0rect = [0,0,0,0]
# roi_rect = RECT()
# f(((0, 0), (400, 300)), (10, 10))
isexsit = 0
annotator = Annotator(im0, line_width=self.line_thickness, example=str(names))
if len(det):
# Rescale boxes from img_size to im0 size
det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round()
for *xyxy, conf, cls in reversed(det):
c = int(cls) # integer class
label = None if self.hide_labels else (names[c] if self.hide_conf else f'{names[c]} {conf:.2f}')
if label == 'person':
isexsit = 1
annotator.box_label(xyxy, label, color=colors(c, True))
im0rect[0] = (int(xyxy[0]))
im0rect[1] = (int(xyxy[1]))
im0rect[2] = (int(xyxy[2]))
im0rect[3] = (int(xyxy[3]))
im0 = annotator.result()
if self.view_img:
cv2.imshow("img", im0)
cv2.waitKey(0) # 1 millisecond
return im0.copy(),isexsit,im0rect[0],im0rect[1],im0rect[2],im0rect[3]
if __name__ == '__main__':
yolo = YoloV5()
yolo.load_model()
img = cv2.imread(str(ROOT / 'data/images/bus.jpg'))
im0, nd = yolo.detect(img)
cv2.imshow("img", im0)
key = cv2.waitKey(0)
if key == ord('q'):
cv2.destroyAllWindows()
这种方法我已经在QT
端进行了实现,需要说明的是如果算法检测速度够快,第二个线程不要也行,直接在GUI
线程进行调用也是可以的。
这种方法未进行实现,理论上可行。即 将视频接收与处理都在Python
端完成,QT
端只进行对Python
脚本的开启与处理后的图像接收即可。具体来说,QT
启动Python
脚本,Python
将处理后的图像和数据存放在变量中,QT
定时查询变量即可。
演示视频:点击
开源仓库:点击