win7中用C++动态链接库兼容加载YOLOv5网络的onnx模型实现安全帽检测


前言

  本节以安全帽检测任务,采用YOLOv5模型,实现在Win7上的模型部署和C++动态链接库调用加载。大致流程包括YOLOv5目标检测模型训练和导出、C++动态链接库编译生成、Python加载调用动态链接库、Win7上进行部署运行。整体系统流程框架如下图所示。

在这里插入图片描述


一、模型的训练和导出

  该节进行基于YOLOv5版本的PyTorch模型训练和转换导出,采用GPU训练,CPU导出。

(1)YOLOv5训练目标检测模型(GPU训练)

1.环境配置

  训练环境选用Win11深度学习工作站,具体硬件和软件配置环境如下表:

工作站配置类型软件环境安装版本
操作系统Win 11 家庭中文版64位编程语言Anaconda3+Python 3.8.8
CPUi7-13700KFGPU加速库CUDA 11.1.0 + cuDNN 8.0.4.30
GPUNVIDIA Geforce RTX 3080深度学习框架PyTorch 1.8.0 + TorchVision 0.9.0
内存32GB计算机视觉库OpenCV-Python 4.1.2.30
--推理框架Onnx 1.12.0 + ONNX Runtime 1.12.0
2.制作训练数据集

  本文使用的安全帽数据集总共包含有7581张图像,数据集名称为Safety_Helmet,数据集共包含两种类型,分别为hatperson。数据集下有Annotations、JPEGImages和TXT文件,其中Annotations为标注的xml文件;JPEGImages为原图,包含有.jpg.JPG类型;TXT为转换后的.txt文件。

  安全帽数据集Safety_Helmet的百度云下载链接如下,该数据集大小1GB多,若下载速度慢可下载小型数据集:

  若该数据集下载过慢的话,这里准备了一个安全帽小型数据集Safety_Helmet_Small,该数据集共有800张图像,总大小94M,下载链接如下:

  若对数据集不想手动划分的话,在本节最后给出了划分后的安全帽数据集和安全帽小型数据集。

  ①这里先给出.xml文件转为.txt文件的脚本代码xml_txt.py

 参考CSDN: https://blog.csdn.net/weixin_43387635/article/details/130307679

# 导入相关库
import os
from lxml import etree
from tqdm import tqdm

def voc2txt():
    # 获取xml文件夹下的所有xml文件名,存入列表
    xmls_list = os.listdir(xmls_path)
    for xml_name in tqdm(xmls_list):
        # 打开写入文件
        txt_name = xml_name.replace('xml', 'txt')

        f = open(os.path.join(txts_save_path, txt_name), 'w')  # 代开待写入的txt文件
        with open(os.path.join(xmls_path, xml_name), 'rb') as fp:
            # 开始解析xml文件
            xml = etree.HTML(fp.read())
            width = int(xml.xpath('//size/width/text()')[0])
            height = int(xml.xpath('//size/height/text()')[0])
            # 获取对象标签
            obj = xml.xpath('//object')
            for each in obj:
                name = each.xpath("./name/text()")[0]
                classes = dic[name]
                xmin = int(each.xpath('./bndbox/xmin/text()')[0])
                xmax = int(each.xpath('./bndbox/xmax/text()')[0])
                ymin = int(each.xpath('./bndbox/ymin/text()')[0])
                ymax = int(each.xpath('./bndbox/ymax/text()')[0])
                # 归一化
                dw = 1 / width
                dh = 1 / height
                x_center = (xmin + xmax) / 2
                y_center = (ymax + ymin) / 2
                w = (xmax - xmin)
                h = (ymax - ymin)
                x, y, w, h = x_center * dw, y_center * dh, w * dw, h * dh
                # 写入
                f.write(str(classes) + ' ' + str(x) + ' ' + str(y) + ' ' + str(w) + ' ' + str(h) + ' ' + '\n')
        f.close()  # 关闭txt文件

if __name__ == '__main__':
    dic = {'hat': "0",  # 创建字典用来对类型进行转换
           'person': "1"   # 此处的字典要与自己的classes.txt文件中的类对应,且顺序要一致
           }

    xmls_path = r"safety_helmet\\Annotations\\"   # xml文件所在的文件夹
    txts_save_path = r"Safety_Helmet/TXT"  # txt文件所在的文件夹

    os.mkdir(txts_save_path) if not os.path.exists(txts_save_path) else None

    voc2txt()

修改xmls_path为数据集下的xml路径,txts_save_path为保存输出的.txt文件路径,运行该程序需要提前安装好tqdm等依赖库,运行完成后自动生成.txt文件

  ②安全帽的数据集如下,其中数据集已经包括转换为.txt的文件:

  ③之后对数据集进行划分,这里只划分为训练集(train)和验证集(val),不对测试集(test)进行划分,划分比例为8:2,划分的脚本yolo_split.py如下:

import os, shutil, random
from tqdm import tqdm

'''
分割训练集,验证集,测试集
断点为需要修改的路径地址
'''

def split_img(img_path, label_path, split_list):
    try:  # 创建数据集文件夹
        Data = 'Safety_Helmet_YOLO'  # 此处必须使用相对路径
        os.mkdir(Data)

        train_img_dir = Data + '/images/train'
        val_img_dir = Data + '/images/val'
        test_img_dir = Data + '/images/test'

        train_label_dir = Data + '/labels/train'
        val_label_dir = Data + '/labels/val'
        test_label_dir = Data + '/labels/test'

        # 创建文件夹
        os.makedirs(train_img_dir)
        os.makedirs(train_label_dir)
        os.makedirs(val_img_dir)
        os.makedirs(val_label_dir)
        os.makedirs(test_img_dir)
        os.makedirs(test_label_dir)

    except:
        print('文件目录已存在')

    train, val, test = split_list
    all_img = os.listdir(img_path)
    all_img_path = [os.path.join(img_path, img) for img in all_img]
    # all_label = os.listdir(label_path)
    # all_label_path = [os.path.join(label_path, label) for label in all_label]
    train_img = random.sample(all_img_path, int(train * len(all_img_path)))
    train_img_copy = [os.path.join(train_img_dir, img.split('\\')[-1]) for img in train_img]
    train_label = [toLabelPath(img, label_path) for img in train_img]
    train_label_copy = [os.path.join(train_label_dir, label.split('\\')[-1]) for label in train_label]
    for i in tqdm(range(len(train_img)), desc='train ', ncols=80, unit='img'):
        _copy(train_img[i], train_img_dir)
        _copy(train_label[i], train_label_dir)
        all_img_path.remove(train_img[i])
    val_img = random.sample(all_img_path, int(val / (val + test) * len(all_img_path)))
    val_label = [toLabelPath(img, label_path) for img in val_img]
    for i in tqdm(range(len(val_img)), desc='val ', ncols=80, unit='img'):
        _copy(val_img[i], val_img_dir)
        _copy(val_label[i], val_label_dir)
        all_img_path.remove(val_img[i])
    test_img = all_img_path
    test_label = [toLabelPath(img, label_path) for img in test_img]
    for i in tqdm(range(len(test_img)), desc='test ', ncols=80, unit='img'):
        _copy(test_img[i], test_img_dir)
        _copy(test_label[i], test_label_dir)


def _copy(from_path, to_path):
    shutil.copy(from_path, to_path)


def toLabelPath(img_path, label_path):
    img = img_path.split('\\')[-1]

    if img[-4:] == '.jpg':
        label = img.split('.jpg')[0] + '.txt'
    elif img[-4:] == '.JPG':
        label = img.split('.JPG')[0] + '.txt'

    return os.path.join(label_path, label)


def main():
    img_path = r'.\\Safety_Helmet\\JPEGImages'
    label_path = r'.\\Safety_Helmet\\TXT'
    split_list = [0.8, 0.2, 0.0]  # 数据集划分比例[train:val:test]
    split_img(img_path, label_path, split_list)


if __name__ == '__main__':
    main()

修改main函数中图像路径img_path和txt文件路径label_path,输出Safety_Helmet_YOLO为划分数据集的文件夹。
注意:由于数据集中包含有两种类型图片(.jpg.JPG),因此在toLabelPath函数中,对图片的后缀文件名进行了相应的判断后再分割,用于其它数据时需要对应修改,此处修改的代码如下:

if img[-4:] == '.jpg':
    label = img.split('.jpg')[0] + '.txt'
elif img[-4:] == '.JPG':
    label = img.split('.JPG')[0] + '.txt'

如程序运行过程中报错,可删除Safety_Helmet_YOLO文件夹后重新执行程序进行划分。

  这里给出划分样本后的安全帽数据集Safety_Helmet_YOLO,该数据集较大,若下载速度慢可下载安全帽小型数据集:

  同时给出整理划分样本后的安全帽小型数据集Safety_Helmet_Small,该数据集可直接添加到程序中进行训练:

3.训练模型

  本文使用基于YOLOv5两类的安全帽目标检测模型,hat表示佩戴安全帽person表示没有安全帽。更复杂的安全帽检测模型可以参考这个CSDN,总共有六种类别的安全帽检测。

  下面使用YOLOv5模型进行训练、导出。对此步骤熟悉的话可以跳过,下文中也给出了训练完成后的安全帽模型.pt和导出转换模型.onnx的下载链接。

  ①下载GitHub官方的YOLOv5模型——YOLOv5官方源码,并一同下载训练权重,YOLOv5用于目标检测的预训练权重包括有YOLOv5n、YOLOv5s、YOLOv5m、YOLOv5l、YOLOv5x,本文使用YOLOv5s权重,这里给出YOLOv5n、YOLOv5s和YOLOv5m权重的下载链接:

  ②由于YOLOv5在GitHub上的代码一直都在不断更新,最新下载的YOLOv5包需要按照说明要求安装相应的库。本文使用7.0版本的YOLOv5程序,这里给出YOLOv5修改后的程序代码,代码中相关配置文件的程序已经修改,并放置好了权重文件yolov5s.pt

  代码中需要加入安全帽数据集Safety_Helmet_YOLO,将Safety_Helmet_YOLO数据集放置于yolov5-master/data 文件夹下面即可进行训练。

下面详细说明对代码修改的每一处,对此步骤熟悉可以直接略过[Ⅰ]~[Ⅲ]。
  [Ⅰ] 修改models/yolov5s.yaml 文件配置,修改参数nc为2,表示类别为两类,其它的不变。

# YOLOv5 🚀 by Ultralytics, GPL-3.0 license

# Parameters
nc: 2  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple

  [Ⅱ] 修改data/coco128.yaml文件,修改数据集路径path和类名names

# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: data/Safety_Helmet_YOLO  # dataset root dir
train: images/train  # train images (relative to 'path') 128 images
val: images/val  # val images (relative to 'path') 128 images
test:  # test images (optional)

# Classes
names:
  0: hat
  1: person

# Download script/URL (optional)
download: https://ultralytics.com/assets/coco128.zip

  [Ⅲ] 修改train.pyparse_opt函数的参数,根据自身情况合理设置参数。本文指定cfg路径为models/yolov5s.yaml,epochs设置为200步,batch_size批大小设置为8,workers工作进程设置为1,指定选用GPU训练,下面展示了修改后的参数:

parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='initial weights path')
parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model.yaml path')
parser.add_argument('--epochs', type=int, default=200, help='total training epochs')
parser.add_argument('--batch-size', type=int, default=8, help='total batch size for all GPUs, -1 for autobatch')
parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--workers', type=int, default=1, help='max dataloader workers (per RANK in DDP mode)')

  ③运行训练程序train.py,在cmd中yolov5-master的路径下执行python train.py,终端打印输出,开始训练模型,如下图所示。程序有可能会报其它的错误,比如“OSError:页面文件太小,无法完成操作”,但只要不影响训练过程就没关系。

开始训练模型

在RTX 3080显卡上训练200步用了18小时训练完成,得到权重模型文件best.pt,权重文件大小约14M,如下图:

在这里插入图片描述

(2)模型导出格式转换(CPU导出)

1.导出转换onnx

  训练完成后进行权重模型文件的导出,把.pt导出转换为.onnx模型文件,导出过程采用CPU进行导出。在export.py文件中,修改.pt的文件路径和指定转换输出格式为onnx。

parser.add_argument('--weights', nargs='+', type=str, default=ROOT / r'runs/train/exp/weights/best.pt', help='model.pt path(s)')

parser.add_argument(
    '--include',
    nargs='+',
    default=['onnx'],
    # default=['engine'],
    help='torchscript, onnx, openvino, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle')

在终端执行python export.py进行模型的导出,得到best.onnx文件,权重大小约为27.1M。

模型导出onnx
下面给出了训练完成后的安全帽模型best.pt和导出转换模型best.onnx的下载链接:

2.模型测试验证

  下面编写一个简单的Python脚本文件加载onnx模型并推理,运行onnx_inference.py文件:

 参考CSDN: https://blog.csdn.net/qq128252/article/details/127105463

import os
import cv2
import numpy as np
import onnxruntime
import time

CLASSES=['hat', 'person'] #安全帽检测类别

class YOLOV5():
    def __init__(self,onnxpath):
        self.onnx_session=onnxruntime.InferenceSession(onnxpath)
        self.input_name=self.get_input_name()
        self.output_name=self.get_output_name()
    #-------------------------------------------------------
	#   获取输入输出的名字
	#-------------------------------------------------------
    def get_input_name(self):
        input_name=[]
        for node in self.onnx_session.get_inputs():
            input_name.append(node.name)
        return input_name
    def get_output_name(self):
        output_name=[]
        for node in self.onnx_session.get_outputs():
            output_name.append(node.name)
        return output_name
    #-------------------------------------------------------
	#   输入图像
	#-------------------------------------------------------
    def get_input_feed(self,img_tensor):
        input_feed={}
        for name in self.input_name:
            input_feed[name]=img_tensor
        return input_feed
    #-------------------------------------------------------
	#   1.cv2读取图像并resize
	#	2.图像转BGR2RGB和HWC2CHW
	#	3.图像归一化
	#	4.图像增加维度
	#	5.onnx_session 推理
	#-------------------------------------------------------
    def inference(self,img_path):
        img=cv2.imread(img_path)
        or_img=cv2.resize(img,(640,640))
        img=or_img[:,:,::-1].transpose(2,0,1)  #BGR2RGB和HWC2CHW
        img=img.astype(dtype=np.float32)
        img/=255.0
        img=np.expand_dims(img,axis=0)
        input_feed=self.get_input_feed(img)
        pred=self.onnx_session.run(None,input_feed)[0]
        return pred,or_img

#dets:  array [x,6] 6个值分别为x1,y1,x2,y2,score,class 
#thresh: 阈值
def nms(dets, thresh):
    x1 = dets[:, 0]
    y1 = dets[:, 1]
    x2 = dets[:, 2]
    y2 = dets[:, 3]
    #-------------------------------------------------------
	#   计算框的面积
    #	置信度从大到小排序
	#-------------------------------------------------------
    areas = (y2 - y1 + 1) * (x2 - x1 + 1)
    scores = dets[:, 4]
    keep = []
    index = scores.argsort()[::-1] 

    while index.size > 0:
        i = index[0]
        keep.append(i)
		#-------------------------------------------------------
        #   计算相交面积
        #	1.相交
        #	2.不相交
        #-------------------------------------------------------
        x11 = np.maximum(x1[i], x1[index[1:]]) 
        y11 = np.maximum(y1[i], y1[index[1:]])
        x22 = np.minimum(x2[i], x2[index[1:]])
        y22 = np.minimum(y2[i], y2[index[1:]])

        w = np.maximum(0, x22 - x11 + 1)                              
        h = np.maximum(0, y22 - y11 + 1) 

        overlaps = w * h
        #-------------------------------------------------------
        #   计算该框与其它框的IOU,去除掉重复的框,即IOU值大的框
        #	IOU小于thresh的框保留下来
        #-------------------------------------------------------
        ious = overlaps / (areas[i] + areas[index[1:]] - overlaps)
        idx = np.where(ious <= thresh)[0]
        index = index[idx + 1]
    return keep


def xywh2xyxy(x):
    # [x, y, w, h] to [x1, y1, x2, y2]
    y = np.copy(x)
    y[:, 0] = x[:, 0] - x[:, 2] / 2
    y[:, 1] = x[:, 1] - x[:, 3] / 2
    y[:, 2] = x[:, 0] + x[:, 2] / 2
    y[:, 3] = x[:, 1] + x[:, 3] / 2
    return y


def filter_box(org_box,conf_thres,iou_thres): #过滤掉无用的框
    #-------------------------------------------------------
	#   删除为1的维度
    #	删除置信度小于conf_thres的BOX
	#-------------------------------------------------------
    org_box=np.squeeze(org_box)
    conf = org_box[..., 4] > conf_thres
    box = org_box[conf == True]
    #-------------------------------------------------------
    #	通过argmax获取置信度最大的类别
	#-------------------------------------------------------
    cls_cinf = box[..., 5:]
    cls = []
    for i in range(len(cls_cinf)):
        cls.append(int(np.argmax(cls_cinf[i])))
    all_cls = list(set(cls))     
    #-------------------------------------------------------
	#   分别对每个类别进行过滤
	#	1.将第6列元素替换为类别下标
	#	2.xywh2xyxy 坐标转换
	#	3.经过非极大抑制后输出的BOX下标
	#	4.利用下标取出非极大抑制后的BOX
	#-------------------------------------------------------
    output = []
    for i in range(len(all_cls)):
        curr_cls = all_cls[i]
        curr_cls_box = []
        curr_out_box = []
        for j in range(len(cls)):
            if cls[j] == curr_cls:
                box[j][5] = curr_cls
                curr_cls_box.append(box[j][:6])
        curr_cls_box = np.array(curr_cls_box)
        # curr_cls_box_old = np.copy(curr_cls_box)
        curr_cls_box = xywh2xyxy(curr_cls_box)
        curr_out_box = nms(curr_cls_box,iou_thres)
        for k in curr_out_box:
            output.append(curr_cls_box[k])
    output = np.array(output)
    return output

def draw(image,box_data):  
    #-------------------------------------------------------
    #	取整,方便画框
	#-------------------------------------------------------
    boxes=box_data[...,:4].astype(np.int32)
    scores=box_data[...,4]
    classes=box_data[...,5].astype(np.int32)

    for box, score, cl in zip(boxes, scores, classes):
        top, left, right, bottom = box
        print('class: {}, score: {}'.format(CLASSES[cl], score))
        print('box coordinate left,top,right,down: [{}, {}, {}, {}]'.format(top, left, right, bottom))

        cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
        cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),(top, left ),cv2.FONT_HERSHEY_SIMPLEX,0.6, (0, 0, 255), 2)


if __name__=="__main__":
    onnx_path='best.onnx'
    model=YOLOV5(onnx_path)
    output,or_img=model.inference('Safety_Helmet\\JPEGImages\\000000.jpg')
    outbox=filter_box(output,0.65,0.45)
    draw(or_img,outbox)
    #cv2.imwrite('res.jpg',or_img)
    cv2.imshow('show',or_img)
    cv2.waitKey(0)

运行该文件,输出检测结果用OpenCV可视化,正常显示表明模型加载正常,如下图:

在这里插入图片描述


二、动态链接库编译生成

  该节使用C++编程实现对动态链接库dll可执行文件的生成,包括软件环境安装配置、Visual Studio的C++程序编写和可执行文件的生成。

(1)软件环境安装配置

  动态链接库的生成需要安装好相应的开发工具,包括Visual Studio、OpenCV和ONNX Runtime,已安装好的可跳过本小节。本节基于C++编程语言,使用的相关安装包的版本如下表:

编程语言C++
操作系统Windows 11
开发工具Visual Studio 2017+ VC 15
图像处理库OpenCV 4.1.2
推理框架ONNX Runtime 1.5.1
1.Visual Studio安装

  开发工具常用Microsoft Visual Studio进行C++程序的开发,使用VS2017版本进行后续开发,VS2017专业版和企业版的在线安装链接如下,包里附有激活码,推荐安装专业版

2.OpenCV安装

  下载安装C++版本的图像视觉库OpenCV,官方下载连接:https://opencv.org/releases/ ,下载指定Windows版本,下载后进行解压提取,这里给出OpenCV 4.1.2版本的分享下载链接,下载后解压至非中文路径下:

3.ONNX Runtime安装

  下载C++版本的ONNX Runtime推理框架,官方下载链接:https://github.com/microsoft/onnxruntime/releases/
推荐下载低版本ONNX Runtime,这里在win7系统上部署用基于CPU的64位OnnxRuntime 1.5.1版本,如下图所示,下载后解压至非中文路径下:
在这里插入图片描述

4.创建动态链接库项目

  ①新建项目。打开VS软件,在文件栏中新建项目,如下图所示。在Visual C++下的Windows桌面中,选中动态链接库(DLL),名称起名为detection,点击确定后完成创建一个动态链接库项目。

在这里插入图片描述

  ②配置OpenCV库。右方的解决方案资源管理器中,打开项目配置属性,选中VC++目录,分别在包含目录和库目录下添加OpenCV下的include路径和lib路径,如下图,完整详细路径如下:

  • 包含目录:opencv\build\include
  • 库目录:opencv\build\x64\vc15\lib

  • 在这里插入图片描述

      接着,在链接器下的输入中,附加依赖项添加opencv_world412.lib文件,如下图,该文件在OpneCV中的完整参考路径如下:

  • opencv_world412.lib文件路径:opencv\build\x64\vc15\lib\opencv_world412.lib

  • ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2d9f44f4000040a5861a2081b6910ece.pn

      ③配置ONNX Runtime库。在项目配置属性中,选中C/C++目录,在附加包含目录中添加onnxruntime的include路径;选中链接器目录,在链接器下的附加库目录中添加lib路径,如下图所示,完整详细路径如下:

  • 包含目录:onnxruntime-win-x64-1.5.1\include
  • 库目录:onnxruntime-win-x64-1.5.1\lib

  • 在这里插入图片描述

    在这里插入图片描述

      接着,在链接器下的输入中,附加依赖项添加onnxruntime.lib文件,如下图,该文件在ONNX Runtime中的完整参考路径如下:

  • onnxruntime.lib文件路径:onnxruntime-win-x64-1.5.1\lib\onnxruntime.lib

  • 在这里插入图片描述

      添加配置完成后,分别创建头文件detection.h和源文件detection.cpp,并指定配置为Release模型下的x64平台,如下图所示,若使用Debug或者x86的需要更换相应的配置文件,否则会报错。

    在这里插入图片描述
      最后,在头文件中写入以下代码,代码编译后若不报错则表明库文件添加配置成功,若报错需要重新添加配置:

    #include <iostream>
    #include<fstream>
    #include<opencv2/opencv.hpp>
    #include <onnxruntime_cxx_api.h>
    

    (2)VS中的C++编程

      在C++编写程序中,主要定义了以下两个函数,动态链接库的导出函数和图像重设大小函数,其中作为动态链接库的导出函数是object_detection,函数输入图像路径和模型路径,函数输出为自定义结构体类型stru_detection。两个函数的具体信息如下表:

    函数动态导出函数图像重设大小函数
    函数名称object_detectionresize_image
    输入参数image_path:图像路径(const char *srcimg:原始图像(cv::Mat
    model_path:模型路径(const wchar_t *newwnewh :重设高度和宽度大小,默认为640
    -padhpadw:填充高度和宽度像素值,默认为640
    输出参数value:判断是否检测成功(intdstimg:重设大小后的图像(cv::Mat
    pred_image:预测结果图像(uchar*-
    heightweight:图像的高度和宽度(int
    1.头文件编程(.h)

      在detection.h头文件中,导入相应的库,并进行函数和导出的动态链接库函数的声明,编写代码如下:

    #pragma once
    #include <iostream>
    #include<fstream>
    #include <string.h>
    #include<opencv2/opencv.hpp>
    #include <onnxruntime_cxx_api.h>
    
    using namespace cv;
    using namespace std;
    
    
    //图像高度和宽度缩放比
    double r;
    
    //声明重设大小函数 
    cv::Mat resize_image(cv::Mat srcimg, int* newh, int* neww, int* top, int* left);
    
    
    //结构体,用于动态链接库的返回,包括检测状态值和检测图像
    struct stru_detection
    {
    	int value = 0;		    //判断是否存在特殊情况  
    					     //(图片读取失败 - 1, 模型加载失败 - 2, 未检测到 - 3, 其它情况0)
    
    	uchar* pred_image;  //返回检测后图像
    	
    	int height, weight; //返回图像的高度和宽度
    };
    
    //动态链接库中导出该函数
    extern "C" _declspec(dllexport) stru_detection  object_detection(const char * image_path, const wchar_t* model_path);
    

     上述代码中,object_detection为需要导出的动态链接库函数,其返回值为自定义结构体类型stru_detection,包括四个参数——value用于判断图像是否检测成功, pred_image为预测后的图像,heightweight分别为输入图像的高度和宽度。

    2.源代码文件编程(.cpp)

      在源代码文件detection.cpp中,进行详细的程序编写实现目标检测的功能,并返回相应的值,包括模型加载推理,图片加载预测等,编写代码如下:

    #include "pch.h"
    #include "detection.h"
    #include <vector>
    
    stru_detection object_detection(const char* image_path, const wchar_t* model_path)
    {
    	//创建自定义结构体yolo_detection
    	stru_detection yolo_detection;
    
    	//创建实例
    	Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "yolov5s");
    	Ort::SessionOptions session_options;
    
    	session_options.SetIntraOpNumThreads(1);
    	session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
    
    
    	try  //判断模型是否存在
    	{
    		Ort::Session session(env, model_path, session_options);
    	}
    	catch (const std::exception& ex)
    	{
    		//模型不存在返回value为-1
    		yolo_detection.value = -1;
    		return yolo_detection;
    	}
    
    	Ort::Session session(env, model_path, session_options);
    	// print model input layer (node names, types, shape etc.)
    	Ort::AllocatorWithDefaultOptions allocator;
    
    	//输入和输出模型节点名称
    	size_t num_input_nodes = session.GetInputCount();
    	std::vector<const char*> input_node_names = { "images" };
    	std::vector<const char*> output_node_names = { "output0" };
    
    	size_t input_tensor_size = 3 * 640 * 640;
    	std::vector<float> input_tensor_values(input_tensor_size);
    	//读取图像
    	cv::Mat srcimg = cv::imread(image_path);
    
    	//判断图像是否存在
    	if (srcimg.empty())
    	{
    		//图像不存在返回value为-2
    		yolo_detection.value = -2;
    		return yolo_detection;
    	}
    
    	int newh = 0, neww = 0, padh = 0, padw = 0;
    
    	//容器数组存放类名
    	std::vector<const char*> class_names = { "hat","person" };
    
    	//dstimg为重设后的图像大小   [640 x 640]
    	cv::Mat dstimg = resize_image(srcimg, &newh, &neww, &padh, &padw);// 重设图像大小640×640×3
    
    	//像素值归一化并将输入的BGR转成RGB
    	float ratioh = (float)srcimg.rows / newh, ratiow = (float)srcimg.cols / neww;
    	for (int c = 0; c < 3; c++)
    	{
    		for (int i = 0; i < 640; i++)
    		{
    			for (int j = 0; j < 640; j++)
    			{
    				float pix = dstimg.ptr<uchar>(i)[j * 3 + 2 - c];
    				input_tensor_values[c * 640 * 640 + i * 640 + size_t(j)] = pix / 255.0;
    			}
    		}
    	}
    
    	//创建输入张量
    	std::vector<int64_t> input_node_dims = { 1, 3, 640, 640 };
    
    	auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
    	Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), input_node_dims.size());
    
    	std::vector<Ort::Value> ort_inputs;
    	ort_inputs.push_back(std::move(input_tensor));
    	// score model & input tensor, get back output tensor
    	std::vector<Ort::Value> output_tensors = session.Run(Ort::RunOptions{ nullptr }, input_node_names.data(), ort_inputs.data(), input_node_names.size(), output_node_names.data(), output_node_names.size());
    
    	//获取指向输出浮点值张量的指针
    	const float* rawOutput = output_tensors[0].GetTensorData<float>();
    	//generate proposals
    	std::vector<int64_t> outputShape = output_tensors[0].GetTensorTypeAndShapeInfo().GetShape();
    	size_t count = output_tensors[0].GetTensorTypeAndShapeInfo().GetElementCount(); //count:151200
    	std::vector<float> output(rawOutput, rawOutput + count);
    
    	std::vector<cv::Rect> boxes;
    	std::vector<float> confs;
    	std::vector<int> classIds;
    	int numClasses = (int)outputShape[2] - 5;
    	int elementsInBatch = (int)(outputShape[1] * outputShape[2]);   //25200*7=176400
    	//std::cout << "输出形状:" << output.size() << std::endl << "类别数量:" << numClasses << std::endl;
    
    	//置信度阈值
    	float confThreshold = 0.65; //                           (1, 25200, 7)     outputShape[2]:7
    	for (auto it = output.begin(); it != output.begin() + elementsInBatch; it += outputShape[2])
    	{
    		float clsConf = *(it + 4);//object scores
    		if (clsConf > confThreshold)
    		{
    			int centerX = (int)(*it);
    			int centerY = (int)(*(it + 1));
    			int width = (int)(*(it + 2));
    			int height = (int)(*(it + 3));
    			//int x1 = centerX - width / 2;
    			//int y1 = centerY - height / 2;
    
    			int x1 = (centerX - padw - width / 2.0)*ratiow;
    			int y1 = (centerY - padh - height / 2.0)*ratiow;
    
    			width = width / r;
    			height = height / r;
    
    			//在容器末尾添加一个新的元素  (左上方x和y坐标,boxes的高和宽)
    			boxes.emplace_back(cv::Rect(x1, y1, width, height));
    
    			// first 5 element are x y w h and obj confidence
    			int bestClassId = -1;
    			float bestConf = 0.0;
    
    			for (int i = 5; i < numClasses + 5; i++)
    			{
    				if ((*(it + i)) > bestConf)
    				{
    					bestConf = it[i];
    					bestClassId = i - 5;
    				}
    			}
    
    			//confs.emplace_back(bestConf * clsConf);
    			confs.emplace_back(clsConf);
    			classIds.emplace_back(bestClassId);
    		}
    	}
    
    	//iou阈值
    	float iouThreshold = 0.45;
    	std::vector<int> indices;
    
    	//非极大值抑制算法
    	cv::dnn::NMSBoxes(boxes, confs, confThreshold, iouThreshold, indices);
    
    	//定义box方框颜色表Color
    	Scalar Color[] = { Scalar(0,0,255),  //红色 hat
    					   Scalar(255,0,0),  //蓝色 person
    	};
    
    	//定义标签颜色表label
    	Scalar Color_label[] = { Scalar(0,255,0),  //绿色 hat
    							Scalar(255,0,255), //粉色 person
    	};
    
    	//判断是否未检测到目标
    	if (indices.empty())
    	{
    		//未检测到目标返回value为-3
    		yolo_detection.value = -3;
    		return yolo_detection;
    	}
    	//图像长度和宽度
    	int height = srcimg.rows, width = srcimg.cols;
    
    	for (int i = 0; i < indices.size(); ++i)
    	{
    		//目标物体索引值index, classIds[index]为类别标签索引
    		int index = indices[i];
    
    
    		//置信度分数,保留两位小数
    		float scores = round(confs[index] * 100) / 100;
    		std::ostringstream oss;
    		oss << scores;		//输出字符串留
    
    		cv::rectangle(srcimg, cv::Point(boxes[index].tl().x, boxes[index].tl().y), cv::Point(boxes[index].br().x, boxes[index].br().y), Color[classIds[index]], 2);
    		cv::putText(srcimg, class_names[classIds[index]], Point(boxes[index].tl().x, boxes[index].tl().y - 5), FONT_HERSHEY_SIMPLEX, 0.5, Color_label[classIds[index]], 2);
    	}
    
    	//cv::imshow("pred_image", srcimg);
    	//cv::imwrite("pred_image.jpg", srcimg);
    	//cv::waitKey(0);
    
    	//图像类型从BGR转换为RGB
    	cv::cvtColor(srcimg, srcimg, cv::COLOR_BGR2RGB);
    
    	//检测成功value为1
    	yolo_detection.value = 1;
    
    	//申请内存空间
    	uchar* pred_image = (uchar*)malloc(sizeof(uchar) * height * width * 3);
    	//内存拷贝
    	memcpy(pred_image, srcimg.data, height * width * 3);  //buffer为输出
    	//预测结果图像
    	yolo_detection.pred_image = pred_image;
    	//图像的高度和宽度
    	yolo_detection.height = height;
    	yolo_detection.weight = width;
    
    	return yolo_detection;
    }
    
    //重设大小函数
    Mat resize_image(cv::Mat srcimg, int* newh, int* neww, int* top, int* left)
    {
    	int srch = srcimg.rows, srcw = srcimg.cols;
    	int inpHeight = 640;
    	int  inpWidth = 640;
    	*newh = inpHeight;
    	*neww = 640;
    	bool keep_ratio = true;
    	Mat dstimg;
    	double height_scale = 1.0 *inpHeight / srcw, weight_scale = 1.0* inpWidth / srch; //后期box还原
    	r = min(height_scale, weight_scale); //取最小值
    	if (keep_ratio && srch != srcw) {
    		float hw_scale = (float)srch / srcw;
    		if (hw_scale > 1) {
    			*newh = inpHeight;
    			*neww = int(inpWidth / hw_scale);
    			resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
    			*left = int((inpWidth - *neww) * 0.5);
    			copyMakeBorder(dstimg, dstimg, 0, 0, *left, inpWidth - *neww - *left, BORDER_CONSTANT, 114);
    		}
    		else {
    			*newh = (int)inpHeight * hw_scale;
    			*neww = inpWidth;
    			resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
    			*top = (int)(inpHeight - *newh) * 0.5;
    			copyMakeBorder(dstimg, dstimg, *top, inpHeight - *newh - *top, 0, 0, BORDER_CONSTANT, 114);
    		}
    	}
    	else {
    		resize(srcimg, dstimg, Size(*neww, *newh), INTER_AREA);
    	}
    	return dstimg;
    }
    

      在上述代码中,集成了onnx模型加载,检测框架推理,绘制方框等各环节。其中,resize_image函数的作用是将输入图像重设为640×640大小像素。
      推理过程中设置置信度阈值为0.65,iou阈值为0.45,在绘制后将图像从BGR转换为RGB,这样方便后续在Matplotlib上可视化显示。由于需要返回预测图像至Python中,因此需要开辟内存空间,使用内存拷贝将cv::Mat类型转换为uchar*类型。

    头文件和源代码文件编写完成后,为了检测程序中是否有误,在‘生成’选项中选中‘编译’,实现对程序整体进行编译,编译正常不报错表明程序无问题。

    (3)生成.dll可执行文件

      在Visual Studio中,需要对动态链接库项目进行.dll可执行文件的生成,生成的方式如下图,可采用两种方式来生成:
    方法①:在‘生成’选项中选中‘生成解决方案’以生成可执行文件,当程序代码更改后需要‘重新生成解决方案’才能生效。
    方法②:生成.dll可执行文件还可以在VS右侧资源管理器中单机右键项目,选中‘生成’或者‘重新生成’,即可生成.dll文件。

    生成的.dll文件名称为detection.dll,项目名称不同,生成的文件名也不同,有关detection.dll文件的分享下载链接在后文第三节(4)中给出。生成的文件路径为:

  • detection.dll文件路径:detection\x64\Release\detection.dll

  • 在这里插入图片描述

    在这里插入图片描述

    注意:动态链接库的项目不可以进行调试和运行,只能用于.dll可执行文件生成,否则会报如下错误:
    无法启动程序 ;
    程序不是有效的Win32应用程序。

    在这里插入图片描述


    三、Python加载调用动态链接库

      本节主要介绍了对动态链接库进行调用,在Win11和Win7系统上编写Python程序实现对动态链接库文件的正常调用。

    (1)Python加载调用

      编写Python程序实现对动态链接库的加载调用,返回结果图像用Matplotlib可视化,无需调用OpenCV包。
      Matplotlib是一个强大的可视化图像的库,可以绘画出各种美观好看的图,这里我们仅用它来做可视化操作。编写加载调用动态链接库的Python程序onnx_detection_dll.py,该程序在Win7电脑上配置好环境后也可以运行,代码如下:

    import ctypes,os
    import numpy as np
    import matplotlib.pyplot as plt
    
    #定义返回类型
    class MyStruct(ctypes.Structure):
        _fields_ = [('value', ctypes.c_int), ('pred_image', ctypes.POINTER(ctypes.c_ubyte)),('height', ctypes.c_int),('weight', ctypes.c_int)]
    
    
    #Dll路径
    DLL_path = "detection.dll"
    
    #图像路径
    image_path = "Safety_Helmet\\JPEGImages\\000000.jpg"
    
    # 图像路径string转换为C++可以读取的 const char*
    image_path_char = (bytes(image_path, 'utf-8'))
    
    #onnx模型路径
    onnx_path = "best.onnx"  # const wchar_t*类型不用进行转换
    
    detection_dll = ctypes.cdll.LoadLibrary(DLL_path)  #加载DLL文件
    
    #定义动态链接库dll的返回值,返回值为结构体类型,结构体中的各个参数(判断检测是否正常,返回检测结果图像)
    detection_dll.object_detection.restype = MyStruct
    
    # 定义动态链接库dll传送参数,包括图像路径和模型路径
    params_1 = {
        "param1": ctypes.c_char_p(image_path_char),
        "param2": ctypes.c_wchar_p(onnx_path),
    }
    
    # 调用目标检测dll,传递参数
    result = detection_dll.object_detection(params_1["param1"], params_1["param2"])
    
    #判断安全帽检测是否成功
    value = result.value
    
    #返回值seg_exist为-1表示模型加载失败,-2表示图像读取失败,-3表示模型未检测到
    if value!=1:
        if value==-1:
            print("模型加载失败")
        elif value==-2:
            print("图像读取失败")
        elif value==-3:
            print("未检测到物体")
        else:
            print("其它情况")
    
    if value==1:
        row = result.height #图像高度
        col = result.weight #图像宽度
        # 检测结果预测图像
        pred_image = result.pred_image
        #类型转换为array并重设大小
        pred_image =  np.array(np.fromiter(pred_image, dtype=np.uint8, count=row*col*3))
        pred_image = pred_image.reshape((row,col,3)) #图像转换为array
        #展示图像
        plt.imshow(pred_image)
        plt.show()
    

      Python调用代码编写较为简单,该代码所包含的输入和输出变量参数整理如下表格所示,输入包含三个参数,输出两个参数,输出的参数value值为1时表明检测成功,其它值时表明检测有问题;pred_image为输出检测后的预测图像。

    参数变量名赋值含义
    输入DLL_path "detection.dll"动态链接库文件路径
    image_path "000000.jpg"图像文件路径
    onnx_path "best.onnx".onnx模型文件路径
    输出value
    1检测成功
    -1模型加载失败
    -2图像读取失败
    -3未检测到目标
    其它值其它情况
    pred_imageNumpy类型矩阵预测图像

    注意:执行该程序前需要把opencv_world412.dll文件放入操作系统的系统文件夹System32下,具体文件路径和操作系统文件夹路径如下:

  • opencv_world412.dll文件路径:opencv\build\x64\vc15\bin\opencv_world412.dll
  • 系统文件夹路径:C:\Windows\System32\
  •   这里附上该文件的下载链接,需要的可以自取:

    若在系统环境变量中缺失该文件,会导致程序报如下两种类型的错误
    ①由于找不到opencv_world412.dll,无法继续执行代码。重新安装程序可能会解决此问题。

    在这里插入图片描述
    ②FileNotFoundError: Could not find module ‘detection.dll’ (or one of its dependencies). Try using the full path with constructor syntax.

    在这里插入图片描述


    (2)Win7虚拟机安装

      采用虚拟机VMware17安装Win7系统,首先下载VMware17软件,下载链接:

     VMware17下载: http://www.xz7.com/downinfo/590026.html

      使用迅雷下载系统IOS镜像地址,该地址的操作系统中包含许多操作系统,这里只下载Win7旗舰版的镜像:

      cn_windows_7_ultimate_x64_dvd_x15-66043.iso

     下载ios镜像: https://msdn.itellyou.cn/

     之后启动VMware17进行安装,在VMware17上按步骤操作来安装虚拟环境:

     参考CSDN: https://blog.csdn.net/u011029104/article/details/131502315

      这里给虚拟环境起名为test_dll,设置CPU数量为1,内核数为4,内存为8GB,硬盘空间设置100~200GB,之后Win7系统的虚拟环境就成功创建。

    在这里插入图片描述


    (3)虚拟机环境配置

      虚拟环境安装好后,首先需要在在Win7系统属性中开启允许其它计算机远程连接这台计算机,如下图所示:

    在这里插入图片描述

      往虚拟环境传送文件可以用VMware Tools工具,这里使用IPv4地址远程桌面连接进行文件传输,需要在cmd终端使用ipconfig指令查看IPv4地址,如下图所示。这里的IPv4地址为192.168.72.129,之后即可启用远程桌面连接,用户名为test_dll,密码为创建环境时自己设定的。同时为了方便,可以在虚拟机中对计算机管理中的磁盘管理进行压缩卷分区。

    在这里插入图片描述

     之后,在Win7上下载安装Anaconda,Win7上安装清华镜像Anaconda3包的版本为Anaconda3-2019.10-Windows-
     x86_64.exe,清华镜像的官方下载链接为:
    Anaconda3-2019.10-Windows-x86_64.exe
     点击链接即可直接下载。

     Anaconda3下载后在Win7系统上按照步骤安装即可,安装过程如果遇到如下图所示的报错,则表明可能缺少了部分安装所需的动态链接库文件,需要将这些动态链接库.dll文件放到系统文件夹System32下面,这些.dll文件的下载链接如下:

    在这里插入图片描述

    (4)Win7加载调用

      在Win7上拷贝测试的图像、权重模型best.onnx、动态链接库detection.dll、测试程序onnx_detection_dll.py,这里整理好在Win7上拷贝所需的各个文件,在Win10和Win11上同样也可以运行:

      在cmd终端运行onnx_detection_dll.py程序,可得出Matplotlib可视化安全帽检测结果,如下图。

    在这里插入图片描述

    在这里插入图片描述
    注意:若程序报错的话可能是系统文件夹中缺少相关的.dll文件,需要在Win7的系统文件夹System32中放置所需的.dll可执行文件,缺少这些文件均会导致程序报错,具体在Win7上的程序报错说明和解决方案见第四节(2)


    四、报错原因说明

    (1)Win11上错误

      ① 找不到opencv_world412.dll

      缺少opencv_world412.dll文件,可能会报以下两种错误:
      1. 由于找不到opencv_world412.dll,无法继续执行代码。重新安装程序可能会解决此问题。

    在这里插入图片描述
      2. FileNotFoundError: Could not find module ‘detection.dll’ (or one of its dependencies). Try using the full path with constructor syntax.

    在这里插入图片描述

      以上两个错误的原因都是因为系统文件夹中缺少opencv_world412.dll文件,该文件的分享链接见本节(2)①中。

      ② 未在VS中添加附加依赖项onnxruntime.lib

      在VS中生成动态链接库dll时,如果未在属性配置中添加附加依赖项onnxruntime.lib,则可能会报以下错误:
      无法解析的外部符号 OrtGetApiBase;1 个无法解析的外部命令

    在这里插入图片描述

      该错误的解决方法需要在Visual Studio(VS)项目的配置属性中,链接器选项下的输入中,在附加依赖项中添加onnxruntime.lib,如下图所示:

    在这里插入图片描述
      ③ VS中onnxruntime包编译报错

      生成动态链接库时报错:class “OrtApi” 没有成员……
                 ……不是 “OrtApi” 的成员

    在这里插入图片描述
      出现该错误的原因是由于在属性配置中onnxruntime包的版本用的不对,使用1.6.0版本的onnxruntime库会出现该类问题,使用1.5.1版本的onnxruntime即可。

    (2)Win7上错误

      ① 丢失opencv_world412.dll

      无法启动此程序,因为计算机中丢失opencv_world412.dll。尝试重新安装该程序以解决此问题。
    在这里插入图片描述
      出现该错误的原因是由于在系统文件夹System32中缺失了opencv_world412.dll文件,需要在系统文件夹C:\Windows\System32下补充添加该文件。opencv_world412.dll的下载链接:

      ② 丢失onnxruntime.dll

      无法启动此程序,因为计算机中丢失onnxruntime.dll。尝试重新安装该程序以解决此问题。
    在这里插入图片描述
      出现该错误的原因是由于在系统文件夹System32中缺失了onnxruntime.dll文件,需要在系统文件夹C:\Windows\System32下补充添加该文件。onnxruntime.dll的下载链接:

      ③ 丢失VCRUNTIME140_1.dll

      无法启动此程序,因为计算机中丢失VCRUNTIME140_1.dll。尝试重新安装该程序以解决此问题。
    在这里插入图片描述
      出现该错误的原因是由于在系统文件夹System32中缺失了vcruntime140_1.dll文件,需要在系统文件夹C:\Windows\System32下补充添加该文件。vcruntime140_1.dll的下载链接:

      ④ 内存位置访问无效

      OSError:[WinError 998] 内存位置访问无效。

    在这里插入图片描述
      导致该错误的原因是由于系统文件夹下的onnxruntime.dll文件版本过低,推荐使用onnxruntime.dll1.5.1版本。
      使用过高的onnxruntime.dll版本文件的话需要在系统文件夹下放置其它相关的.dll文件,比如1.8.0及其以上的版本。

      ⑤ exception:access violation reading

      OSError: exception: access violation reading 0x0000000000000018

    在这里插入图片描述

      出现该错误的原因是因为动态链接库编译生成的.dll版本过高,需要在属性配置中修改加载读取onnxruntime库的版本,如下图所示,分别在附加库目录和附加包含目录中添加onnxruntime库下的includelib路径。

    在这里插入图片描述


    附件:Win11安装包下载链接

      本节整理在Win11上环境配置时有关安装包的下载链接,包括Anaconda、显卡加速库、深度学习库和其它安装包。

    (1)Anaconda3

      Anaconda3所用版本为 Anaconda3-2021.04-Windows-x86_64,下载链接为:
      Anaconda3-2021.04-Windows-x86_64.exe
      点击链接即可直接下载。

    (2)CUDA 和 cuDNN

      CUDA使用版本为 11.1.0,下载链接为:
      cuda_11.1.0_456.43_win10
      点击链接即可直接下载。

      CUDA其它历史版本的下载官网为:
      https://developer.nvidia.com/cuda-toolkit-archive

      cuDNN使用版本为 8.0.4,由于下载cuDNN需要登陆,仅给出cuDNN历史版本的下载官网:
      https://developer.nvidia.com/rdp/cudnn-archive

    (3)PyTorch 和 TorchVision

      PyTorch使用版本为 1.8.0,下载链接为:
      pytorch-1.8.0-py3.8_cuda11.1_cudnn8_0.tar.bz2
      点击链接即可直接下载。

      TorchVision使用版本为 0.9.0,下载链接为:
      torchvision-0.9.0-py38_cu111.tar.bz2
      点击链接即可直接下载。

    注意:PyTorch 和 TorchVision 下载成功后需要在终端用 conda install 指令来安装以上安装包。

    (4)其它安装包

    ① OpenCV-Python

      OpenCV-Python使用版本为 4.1.2.30,下载链接为:
      opencv_python-4.1.2.30-cp38-cp38-win_amd64.whl
      点击链接即可直接下载。

    ② Numpy

      Numpy版本过低的话在后面安装其它包时可能需要升级至高版本,Numpy使用版本为 1.21.0,下载链接为:
      numpy-1.21.0-cp38-cp38-win_amd64.whl
      点击链接即可直接下载。

    ③ protobuf

      protobuf版本为 3.16.0,下载链接为:
      protobuf-3.16.0-py2.py3-none-any.whl
      点击链接即可直接下载。

    ④ Onnx

      Onnx版本为 1.12.0,下载链接为:
      onnx-1.12.0-cp38-cp38-win_amd64.whl
      点击链接即可直接下载。

    ⑤ ONNX Runtime

      OnnxRuntime版本为 1.12.0,下载链接为:
      onnxruntime-1.12.0-cp38-cp38-win_amd64.whl
      点击链接即可直接下载。

    ⑥ TensorBoard

      TensorBoard版本为 2.4.1,下载链接为:
      tensorboard-2.4.1-py3-none-any.whl
      点击链接即可直接下载。

    ⑦ grpcio

      grpcio版本为 1.26.0,下载链接为:
      grpcio-1.26.0-cp38-cp38-win_amd64.whl
      点击链接即可直接下载。

    ⑧ GitPython

      执行yolo训练程序train.py时如果缺少GitPython包,程序有可能会报以下错误:
      ModuleNotFoundError: No module named ‘git’

      GitPython版本为 3.0.0,下载链接为:
      GitPython-3.0.0-py3-none-any.whl
      点击链接即可直接下载。

    ⑨ gitdb2

      缺少gitdb2包在执行训练程序时可能会报以下错误:
      ModuleNotFoundError: No module named ‘gitdb.utils.compat’

      gitdb2版本为 3.0.1,下载链接为:
      gitdb2-3.0.1-py2.py3-none-any.whl
      点击链接即可直接下载。

    安装以上包也可以使用清华镜像来安装,比如清华镜像安装 tensorboard 2.4.1:
    pip install tensorboard==2.4.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

    注意:以上安装包下载成功后需要在终端用 pip install 指令来安装以上安装包。


  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值