Darknet-Yolov3训练自己的数据指导手册

文章目录

    • 环境搭建:
      • 1. 下载YOLOv3工程项目
      • 2. 修改Makefile配置
    • 模型训练
      • 1. 准备训练数据集
      • 2. 下载Imagenet上预先训练的权重
      • 3. 修改cfg/voc.data
      • 4. 修改data/voc.name为样本集的标签名
      • 5. 修改cfg/yolov3-voc.cfg
      • 6. 多尺度训练
      • 7.多GPU训练
      • 8. 开始训练
    • 模型测试评估及样本的预标注
      • 1. 测试
      • 2. c接口模型评估
      • 3. python接口模型评估
        • (1)正样本评估
        • (2)负样本评估
    • 可能的疑问
      • 1. 训练过程的可视化
      • 2. 训练日志的解读
      • 3. 训练日志的简化
      • 4. 模型保存问题
      • 5. 网络文件格式问题
      • 6. 个别服务器编译Darknet
    • 网络的裁剪与优化
      • 1. Yolov3 的网络结构
      • 2. Darknet53基础网络
      • 3. 裁剪优化的方向
      • 4. 裁剪实例
      • 5. 裁剪设计工具
    • Yolo的caffe部署
      • 1. 背景
      • 2. yolov3的网络结构
      • 3. 如何实现
      • 4. 出现的错误
    • Darknet2ncnn
      • 1. 环境搭建
        • 下载darknet2ncnn工程项目
        • 按照README.md文件的编译过程,进行编译
      • 2. 模型转换
        • 转换(仅生成转换之后的模型文件)
        • 转换+测试(生成转换之后的模型文件,同时可以测试单帧图片相应的效果)
        • 图片测试(仅输出打印检测结果,不会保存检测结果图片)
        • 图片测试(打印检测结果,同时保存检测结果图片)

# Darknet-Yolov3训练自己的数据指导手册

环境搭建:

1. 下载YOLOv3工程项目

git clone https://github.com/pjreddie/darknet  
cd darknet  

因为在用此工程的过程中,对源码进行了修改,可以直接从下边路径copy一份,进行编译即可。

略,哈哈哈哈,理解万岁。

2. 修改Makefile配置

GPU=1 #如果使用GPU设置为1,CPU设置为0
CUDNN=1  #如果使用CUDNN设置为1,否则为0
OPENCV=0  #如果调用摄像头或者测试视频文件,还需要设置OPENCV为1,否则为0
OPENMP=0  #如果使用OPENMP设置为1,否则为0
DEBUG=0  #如果使用DEBUG设置为1,否则为0
…
CC=gcc
NVCC=/usr/local/cuda-8.0/bin/nvcc   #NVCC=nvcc 修改为自己的路径
AR=ar
...
ifeq ($(GPU), 1) 
COMMON+= -DGPU -I/usr/local/cuda-8.0/include/  #修改为自己的路径
CFLAGS+= -DGPU
LDFLAGS+= -L/usr/local/cuda-8.0/lib64 -lcuda -lcudart -lcublas -lcurand  #修改为自己的路径

保存完成后,在此路径下执行make,如果出现如下错误:

错误一:

Loadingweights from yolo.weights...Done!
CUDA Error:invalid device function
darknet: ./src/cuda.c:21: check_error: Assertion `0' failed.
Aborted (core dumped)

这是因为配置文件Makefile中配置的GPU架构和本机GPU型号不一致导致的。更改前默认配置如下(不同版本可能有变):

ARCH= -gencode arch=compute_30,code=sm_30 \
      -gencode arch=compute_35,code=sm_35 \
      -gencode arch=compute_50,code=[sm_50,compute_50] \
      -gencode arch=compute_52,code=[sm_52,compute_52]
#      -gencode arch=compute_20,code=[sm_20,sm_21] \ This one is deprecated?
# This is what I use, uncomment if you know your arch and want to specify
# ARCH= -gencode arch=compute_52,code=compute_52

CUDA官方说明文档:

http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#virtual-architecture-feature-list

错误二:

如果出现/usr/bin/ld: 找不到 -lippicv;找不到nvcc
请参考:

https://blog.csdn.net/tmosk/article/details/76578082

然后重新编译即可

模型训练

1. 准备训练数据集

(1)准备的训练数据集文件夹的结构:

按下列文件夹结构,将训练数据集放到各个文件夹下面。

VOCdevkit                          #数据集名称(一般为项目名称)
—VOC2007                           #数据集子类名称(可以将搜集的不同的数据集以此种形式放置)
——Annotations                      #xml文件
——JPEGImages                       #图片文件

(2)生成yolo训练和测试所需要的数据集。

生成训练集和评估集的脚本在如下路径:

略,哈哈哈哈,理解万岁。

CreatTrainNameList.py脚本为生成训练数据集的脚本,CreatValidNameList.py脚本为生成评估集数据集的脚本,对脚本进行如下修改(以训练集为例):

if __name__ == "__main__":
#========================================================================
sets=[( VOC2007, 'train'),( VOC2012,'train')] #(VOC2007,VOC2012两个数据集生成train数据集)
    classes = ["Person_HS"]                   #检测的类别
    parse = argparse.ArgumentParser()
    parse.add_argument("--input_path", type=str,default=' VOCdevkit', help="...") #项目名称
# =======================================================================

生成后的数据集的文件结果如下:

VOCdevkit
—VOC2007
——Annotations
——ImageSets
———Main
————train.txt /val.txt       #训练集和评估集在一起时,两个文档共存(一般选择分开)。
——JPEGImages

VOCdevkit_train.txt / VOCdevkit_val.txt #yolov3训练和评估时的索引文档。

2. 下载Imagenet上预先训练的权重

如果你选用基础网络为darknet53的yolov3时可以采用如下的预训练模型

wget https://pjreddie.com/media/files/darknet53.conv.74  

3. 修改cfg/voc.data

classes= 2  #classes为训练样本集的类别总数(不包含背景)
train  = /home/user/darknet/ VOCdevkit _train.txt  #train的路径为训练样本集所在的路径
valid  = /home/user/darknet/ VOCdevkit _val.txt  #valid的路径为验证样本集所在的路径
names = data/voc.names  #names的路径为data/voc.names文件所在的路径
backup = backup  #模型的保存路径(文件夹必须保证在darknet文件夹下面已存在)

注:

如果觉得每次都得创建模型保存路径的文件夹费事,请按照下图对源码(example\detector.c)进行修改:

#include "darknet.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
...
void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear )
{
    list *options = read_data_cfg(datacfg);
    char *train_images = option_find_str(options, "train", "data/train.list");
    char *backup_directory = option_find_str(options, "backup", "/backup/");
    if(-1==access(backup_directory,00)){
        mkdir(backup_directory,S_IRWXU);}
...
}

4. 修改data/voc.name为样本集的标签名

Class1
Class2

5. 修改cfg/yolov3-voc.cfg

关于cfg修改,以10类目标检测为例,主要有以下几处调整(蓝色标出):

[net]
# Testing             	### 测试模式                                          
# batch=1
# subdivisions=1
# Training            	### 训练模式,每次前向的图片数目 = batch/subdivisions 
batch=64
subdivisions=16
width=416           	### 网络的输入宽、高、通道数(可以直接修改,但是要修改网络避免深层网络的浪费)
height=416          	### 宽高比的比例可依据输入图像的尺寸进行设置(但需要为32的倍数)      
channels=3
momentum=0.9     		### 动量: DeepLearning1中最优化方法中的动量参数,这个值影响着梯度下降到最优值得速度。 详情参考:https://blog.csdn.net/qq_33270279/article/details/102796812
decay=0.0005         	### 权重衰减:权重衰减正则项,防止过拟合.每一次学习的过程中,将学习后的参数按照固定比例进行降低,为了防止过拟合,decay参数越大对过拟合的抑制能力越强。
angle=0              	###旋转角度(单位:度)
saturation = 1.5       	### 饱和度
exposure = 1.5        	### 曝光度 
hue=.1               	### 色调
learning_rate=0.001   	### 学习率的调整参考 :https://blog.csdn.net/qq_33485434/article/details/80452941 
burn_in=1000         	### 模型预热,小于1000batch时采用(0-learning_rate)递增的方式。
max_batches = 50200  	### 迭代次数                                          
policy=steps         	### 学习率策略: 这个是学习率调整的策略,有policy:constant, steps, exp, poly, step, sig, RANDOM,constant等方式参考[https://nanfei.ink/2018/01/23/YOLOv2%E8%B0%83%E5%8F%82%E6%80%BB%E7%BB%93/#more](https://nanfei.ink/2018/01/23/YOLOv2调参总结/#more)            
steps=40000,45000   	### 学习率变动步长 
scales=.1,.1           	### 学习率变动因子  

[convolutional]
batch_normalize=1    	### BN
filters=32           	### 卷积核数目
size=3               	### 卷积核尺寸
stride=1             	### 卷积核步长
pad=1                	### pad
activation=leaky     	###激活函数的类型:linear(线性:不做改变)relu(值>0时保持不变,小于零时置零)leaky(值>0时保持不变,小于零时*0.1)

......

[convolutional]
size=1
stride=1
pad=1
filters=45              #每一个[region/yolo]层前的最后一个卷积层中的 filters=(classes+cords+1)*anchors_num,其中anchors_num 是该层mask的数量。如果没有maskanchors_num=num。coords+1:论文中的tx,ty,tw,th,to
activation=linear

[yolo]
mask = 6,7,8           #预测anchors[]中的后三个较大尺寸的anchor。因为其感受野较大
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326 
						#(width, height)通过聚类脚本计算样本宽高
classes=2               #类别
num=9        			#如果在配置文件中anchors的数量大于num时,仅使用前num个,小于时内存越界。
jitter=.3               #利用数据抖动产生更多数据, jitter就是crop的参数, jitter=.3,就是在0~0.3中进行crop(具体的裁剪方式请参考src/data.c中的load_data_detection()函数)
ignore_thresh = .5  	#参数解释:ignore_thresh 指得是参与计算的IOU阈值大小。当预测的检测框与ground true的IOU大于ignore_thresh的时候,参与loss的计算,否则,检测框的不参与损失计算。参数目的和理解:目的是控制参与loss计算的检测框的规模,当ignore_thresh过于大,接近于1的时候,那么参与检测框回归loss的个数就会比较少,同时也容易造成过拟合;而如果ignore_thresh设置的过于小,那么参与计算的会数量规模就会很大。同时也容易在进行检测框回归的时候造成欠拟合。参数设置:一般选取0.5-0.7之间的一个值,之前的计算基础都是小尺(13*13)用的是0.7,(26*26)用的是0.5。这次先将0.5更改为0.7。 实验结果:AP=0.5121(有明显下降)
truth_thresh = 1
random=0               	#1,如果显存很小,将random设置为0,关闭多尺度训练;

......

[yolo]
mask = 3,4,5   			#预测anchors[]中的中间三个较小尺寸的anchor。因为其感受野适中
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
classes=2 
num=9
jitter=.3   
ignore_thresh = .5
truth_thresh = 1
random=0 

......

[yolo]
mask = 0,1,2  			#预测anchors[]中的前三个较小尺寸的anchor。因为其感受野较小
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326 
classes=2  
num=9  
jitter=.3  
ignore_thresh = .5  
truth_thresh = 1  
random=0 

#可以添加没有标注框的图片和其空的txt文件,作为negative数据(生成训练数据集的脚本已经支持,只需要添加图片即可)

#可以在第一个[yolo]层之前的倒数第二个[convolutional]层末尾添加 stopbackward=1,以此提升训练速度

#即使在用416*416训练完之后,也可以在cfg文件中设置较大的width和height,增加网络对图像的分辨率,从而更可能检测出图像中的小目标,而不需要重新训练。

6. 多尺度训练

原始的yolov3的多尺度训练如下:

random如果为1,每次迭代图片大小随机从320到608,步长为32,如果为0,每次训练大小与输入大小一致。

当修改训练的分辨率时(支持宽高比不为1:1),需要修改尺度变化的范围,所以需要修改训练函数(example/detector.c)train_detector()和run_detector()如下所示:

void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear )
{
    ...

    int iDataWidth = net->w;
    int iDataHeight = net->h;
    int imgs = net->batch * net->subdivisions * ngpus;
    printf("Learning Rate: %g, Momentum: %g, Decay: %g\n", net->learning_rate, net->momentum, net->decay);
    data train, buffer;
    layer l = net->layers[net->n - 1];
    int classes = l.classes;
    float jitter = l.jitter;

    list *plist = get_paths(train_images);
    //int N = plist->size;
    char **paths = (char **)list_to_array(plist);

    load_args args = get_base_args(net);
    args.coords = l.coords;
    args.paths = paths;
    args.n = imgs;
    args.m = plist->size;
    args.classes = classes;
    args.jitter = jitter;
    args.num_boxes = l.max_boxes;
    args.d = &buffer;
    args.type = DETECTION_DATA;
    //args.type = INSTANCE_DATA;
    args.threads = 64;

    pthread_t load_thread = load_data(args);
    double time;
    int count = 0;
    //while(i*imgs < N*120){
    while(get_current_batch(net) < net->max_batches){
        if(l.random && count++%10 == 0){
            printf("Resizing\n");
        int iRand = rand() % 10;
            int dim_W = (iRand + iDataWidth / 32 - 2) * 32;
        float fRatio = dim_W * 1.0 / iDataWidth;
        //int dim_H = (iRand + iDataHeight / 32 - 2) * 32;
        int dim_H = (int)(iDataHeight * fRatio);
            if (get_current_batch(net)+200 > net->max_batches){
        dim_W = (iDataWidth / 32 + 5) * 32;
        fRatio = dim_W * 1.0 / iDataWidth;
        //dim_H = (iDataHeight / 32 + 5) * 32;
        dim_H = (int)(iDataHeight * fRatio);
        } 
        int delt_H=dim_H % 32;
        if(delt_H>16) dim_H=dim_H+32-delt_H;
        else dim_H=dim_H-delt_H; 
            printf("dim_W:%d\n", dim_W);
        printf("dim_H:%d\n", dim_H);
            args.w = dim_W;
            args.h = dim_H;

            pthread_join(load_thread, 0);
            train = buffer;
            free_data(train);
            load_thread = load_data(args);

            #pragma omp parallel for
            for(i = 0; i < ngpus; ++i){
                resize_network(nets[i], dim_W, dim_H);
        //printf("resize_network Done!");
            }
            net = nets[0];
        }
...
    
}

7.多GPU训练

So for one GPU, the relevant portion of the .cfg file would be:

learning_rate=0.001
burn_in=1000

And for two GPUs, the relevant portion of the .cfg file would be:

learning_rate=0.0005
burn_in=2000

And for four GPUs, the relevant portion of the .cfg file would be:

learning_rate=0.00025
burn_in=4000

8. 开始训练

./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 -gpus 0,1 2>1 | tee train_yolov3.log

voc.data:数据集以及输出模型的保存路径

yolov3-voc.cfg:网络参数和网络结构文档

darknet53.conv.74:预训练模型(当训练中断后,也可加载“模型名.backup”继续训练)

2>1 | tee train_yolov3.log:保存的日志名称。

https://blog.csdn.net/zsl091125/article/details/85010833 (参考)

模型测试评估及样本的预标注

1. 测试

(1)测试图片

测试单张图片:

./darknet detector test    

文件中batch和subdivisions两项必须为1。

测试时还可以用-thresh和-hier选项指定对应参数。

(2)生成预测结果

./darknet detector valid    

文件中batch和subdivisions两项必须为1。

结果生成在的results指定的目录下以开头的若干文件中,若没有指定results,那么默认为/results。

对源码进行了修改可以用-thresh:

detector 中的valid函数不支持-thresh置信度阈值的设置,修改example/detector.c如下所示,并重新编译。

void validate_detector(char *datacfg, char *cfgfile, char *weightfile, char *outfile, float _thresh)
{
……
    //float thresh = .005;
    float thresh = _thresh;
	float nms = .45;
………
}
void run_detector(int argc, char **argv)
{
………
    else if(0==strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights,outfile, thresh);
……………
}

(3)计算recall(执行这个命令需要修改detector.c文件,修改信息请参考“detector.c修改”)

./darknet detector recall   

文件中batch和subdivisions两项必须为1。

输出在stderr里,重定向时请注意。

RPs/Img、IOU、Recall都是到当前测试图片的均值。

detector.c中对目录处理有错误,可以参照validate_detector对validate_detector_recall最开始几行的处理进行修改。

(还未实现,实现过程可参考:https://blog.csdn.net/mieleizhi0522/article/details/79989754 )

(4)测试视频

相应的参数请查看官网或者darknet/example/detector.c源码

./darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights 

(5)python接口测试(主要应用在评估工具的编写)

darknet/example/detector.py:无opencv编译时,运行。(修改相应的路径即可运行)

darknet/example/detector-scipy-opencv.py:有OpenCV编译,运行此脚本。(原脚本存在问题,需要修改,修改方法如下),包括两点:(上传的工程中,此部分已经做了修改)

a、/darknet/python/darknet.py 中,增加如下函数:

def detect_np(net, meta, im, thresh=.5, hier_thresh=.5, nms=.45):
    num = c_int(0)
    pnum = pointer(num)
    predict_image(net, im)
    dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, None, 0, pnum)
    num = pnum[0]
    if (nms): do_nms_obj(dets, num, meta.classes, nms)
    res = []
    for j in range(num):
        for i in range(meta.classes):
            if dets[j].prob[i] > 0:
                b = dets[j].bbox
                res.append((meta.names[i], dets[j].prob[i], (b.x, b.y, b.w, b.h)))
    res = sorted(res, key=lambda x: -x[1])
    #free_image(im)
    free_detections(dets, num)
    print 'detect_np over'
	return res

b、detector-scipy-opencv.py中,‘#OpenCV’下边的代码全部删掉,改为如下代码

# OpenCV
arr = cv2.imread('../data/dog.jpg')
im = array_to_image(arr)
dn.rgbgr_image(im)
r = dn.detect_np(net, meta, im)
print r

2. c接口模型评估

(1)待标注样本数据集准备与训练数据集的准备基本相同,区别:没有标注文件夹。如下所示:

按下列文件夹结构,将预标注数据集放到各个文件夹下面。

VOCdevkit                          #数据集名称(一般为项目名称)
—VOC2007                           #数据集子类名称(可以将搜集的不同的数据集以此种形式放置)
——JPEGImages                       #图片文件

(2)生成yolo测试(预标注跟测试评估一致)所需要的数据集。

CreatValidNameList.py脚本为生成评估集数据集的脚本:

#-*- coding: utf-8 -*-
#######################################
#     本程序用于yolov3生成测试评估的数据集#
#######################################
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

import sys,argparse

def checkpath(path):
    if not os.path.exists(path):
        print("错误的路径: {}".format(path))
        sys.exit()

def makepath(path):
    if not os.path.exists(path):
        os.makedirs(path)

if __name__ == "__main__":
     # ==================================================================================================================
    sets=[('VOC100', 'val')]
    parse = argparse.ArgumentParser()
    parse.add_argument("--input_path", type=str,default='Person', help="...")
    # ==================================================================================================================
    flags, unparsed = parse.parse_known_args(sys.argv[1:])
    input_path = flags.input_path
    checkpath(input_path)
    for year, image_set in sets:
        output_path = input_path+'/%s/ImageSets/Main'%(year)
        makepath(output_path)
        path = os.path.join(input_path, '%s'%(year), 'JPEGImages')
        imgs = os.listdir(path)
        save_path = os.path.join(output_path, 'val.txt')
        fw = open(save_path, 'w')
        for img in imgs:
            print(img)
            img_name = img.split('.')[0]
            fw.write(img_name + '\n')
        fw.close()

    wd = getcwd()
    list_file = open(input_path+'_val.txt', 'a')
    for year, image_set in sets:
    	image_ids = open(input_path+'/%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split('\n')
    	for image_id in image_ids:
        	list_file.write('%s/'%(wd)+input_path+'/%s/JPEGImages/%s.jpg\n'%(year, image_id))
    list_file.close()

生成后的数据集的文件结果如下:

VOCdevkit
—VOC2007
——ImageSets
———Main
————val.txt       
——JPEGImages

VOCdevkit_val.txt #yolov3评估时的索引文档。

(3)生成comp4_det_test_命名.txt(在result中)

./darknet detector valid cfg/voc.data cfg/yolo-voc.cfg backup/yolo-voc.weights –thresh 0.05

voc.data文件里修改如下:

classes= 2  #classes为训练样本集的类别总数
valid  = /home/user/darknet/ VOCdevkit _val.txt  #valid的路径为评估样本集所在的路径
names = data/voc.names  #names的路径为data/voc.names文件所在的路径

(4)用Yolov3ResultToxml.py脚本生成所需的xml文件。

Yolov3ResultToxml.py路径如下:

略,哈哈哈哈,理解万岁。

#-*-coding:utf-8-*-
#######################################
#     本程序用于yolo评估模型:将生成的目标框信息(txt)转换为xml文件#
#######################################
...
if __name__ == "__main__":
    #=======================================================================
    parse = argparse.ArgumentParser()
    parse.add_argument("--txt_path", type=str, default='xxx.txt',
                       help="yoloV3生成的txt")
    parse.add_argument("--img_path", type=str, default='.../JPEGImages/', help="图像路径")
    parse.add_argument("--output_path", type=str, default='xxx2.txt',
                       help="保存的新txt")
    parse.add_argument("--save_path", type=str, default='xxx',
                       help="生成xml的保存路径")
    #==========================================================================
    #xml文件配置
    config = {
       'roi_size': [1920, 1080, 3],  # 图片大小
       'roi_num': 0,  # 生成xml数 应等于图片数
       'obj_name': ['Car','Person_HS'],   # 此脚本未用
       'count': 0,   # 此脚本未用
    }
    #===========================================================================
   ...

(5)生成的xml与测试集gt_xml通过评估工具进行评估(AutoAssessTool工具)。

略,哈哈哈哈,理解万岁。

3. python接口模型评估

略,哈哈哈哈,理解万岁。

(1)正样本评估

1.准备评估集(与训练集有相同的样本分布,最少为训练集的一半)

VOCdevkit                          #数据集名称(一般为项目名称)
—VOC2007                           #数据集子类名称(可以将搜集的不同的数据集以此种形式放置)
——Annotations                      #xml文件
——JPEGImages                       #图片文件

2.评估集检测

略,哈哈哈哈,理解万岁。

运行环境:python3+cv2,其他环境由yolov3以及ssd编译环境而定。

##################################################
#目标检测,并生成xml,目前支持yolov3和ssd
#Author          : 小楞
#Created         : 2019-09-06
#备注:用于样本的与标注以及测试集的评估(与AutoAssessTool工具配合使用)##################################################
...
if __name__ == "__main__":
    # ==============修改输入参数=====================
    parse = argparse.ArgumentParser()
    parse.add_argument("--input_path", type=str, default='.../value_data',
                        help="评估数据集路径,下边包含一个或多个样本集")
    parse.add_argument("--model_path", type=str, default='.../models',
                       help="保存模型的文件夹,其中每个模型又是一个文件夹,一个模型文件包括:.weights .cfg .names .data")
    parse.add_argument("--output_path", type=str, default='.../detetct_xml_result',
                        help="检测结果保存在xml文件中")
    parse.add_argument("--thresh", type=float,default=0.05, help="置信度阈值")
    # ==========修改输入参数end=======================

3.模型自动评估

略,哈哈哈哈,理解万岁。

##################################################
#   File Name:  autotest.py
#   Author:    小楞
#   Created:   2019.8.30
#   Description:  自动模型评测系统
##################################################
...
if __name__ == '__main__':
    tester = Tester(
        output_path='..\\Annotations',#检测出的xml
        gt_path='...\\Annotations',#标注的xml
        log_path='example.txt', save_log=True, pr_steps=0.01,#配置参数
        readin=True, readin_paths=['compare.txt'])#带比较的评估结果
    tester.test()

4.评估结果详细分析:

会分别保存误检和漏检结果,具体问题具体分析。

略,哈哈哈哈,理解万岁。

##################################################
#根据生成的评测样本的xml结果与gt_xml比较,得到误检和漏检。
#Author          : 小楞
#Created         : 2019-09-06
#备注:支持多模型同时评测,接受dete_xml_generate工具生成的结果。##################################################
...
 # =================修改输入参数=============================================
    parse = argparse.ArgumentParser()
    parse.add_argument("--gt_path", type=str, default='...\\TestSets',help="评估数据集gt路径,下边包含一个或多个Set,每个set下包括JPEG和XML文件夹")
    parse.add_argument("--det_path", type=str, default='...\\detetct_xml_generate_result',help="评估数据集det路径,下边包含一个或多个Set,每个set下包括JPEG和XML文件夹")
    parse.add_argument("--model_path", type=str, default='...\\models',help="模型路径,第二层文件夹,运行一次只能评估一个模型")
    parse.add_argument("--output_path", type=str,default='...\\positive_samples_evaluation_false',help="错误检测结果保存路径")
    parse.add_argument("--conf_thresh", type=float, default=0.05, help="置信度阈值")
    parse.add_argument("--pos_overlap", type=float, default=0.3, help="IOU阈值")
 # ===============修改输入参数end============================================

5.板卡芯片结果评估:

略,哈哈哈哈,理解万岁。

##################################################
#板卡芯片检测结果txt准换成xml用于评估
#Author          : 小楞
#Created         : 2019-09-07
##################################################
...
# ==========修改输入参数=============================================
    parse = argparse.ArgumentParser()
    parse.add_argument("--Ori_TxtPath", type=str, default='...\\resulttest', help="原始txt文件路径")
    parse.add_argument("--Det_AnnoPath", type=str,default='...\\resulttest_xml',help="xml文件路径")
# =========修改输入参数end============================================

用autotest.py进行评估。

(2)负样本评估

1.纯负样本的获取:

网络爬图工具:略,哈哈哈哈,理解万岁。

Crawl_Pictures.py
names.txt

在names.txt中写入需要下载图片的关键词即可,需要设置需要下载图片的数量如下:

C:\soft\Anaconda3\python.exe .../Crawl_Pictures.py
请输入每类图片的下载数量 500
正在检测图片总数,请稍等.....
经过检测山路类图片共有1020张
找到关键词:山路的图片,即将开始下载图片...
正在下载第1张图片,图片地址:http://www.fotosay.com/userimages/blogimages/2010/1026/zndnn/10521382527b.jpg

2.负样本评估:

负样本检测脚本:略,哈哈哈哈,理解万岁。

##################################################
#纯负样本评估,输出各个模型不同类别的误检情况。
#Author          : 小楞
#Created         : 2019-09-06
#备注:支持yolov3和ssd,支持多模型同时对比评测。##################################################
...
# =============修改输入参数=============================================
    parse = argparse.ArgumentParser()
    parse.add_argument("--img_path", type=str, default='.../Bg_img',help="负样本图片路径")
    parse.add_argument("--model_path", type=str, default='.../models',help="保存模型的文件夹,其中每个模型又是一个文件夹")
    parse.add_argument("--conf_thresh", type=float, default=0.2, help="置信度阈值")
    parse.add_argument("--is_save_false", type=int, default=1, help="1表示保存檢測錯誤的結果")
    parse.add_argument("--output_path", type=str, default=".../neg_img_result",help="结果保存路径")
    parse.add_argument("--isresize", type=int, default=0, help="是否将原图片填补为宽高比1:1的图片")
# ===========修改输入参数end============================================

负样本结果分析:略,哈哈哈哈,理解万岁。

##################################################
#分析负样本评估打印日志,输出不同模型误检情况的柱状图
#Author          : 小楞
#Created         : 2019-09-06
#关键词:二维字典,二维并列柱状图
##################################################
...
if __name__ == "__main__":
    # ================修改输入参数=============================================
    configs = {
        'Neg_ResultPath': '...\\neg_result.txt',
        'label_list': ['class1', 'class2'],
    }
    # ==============修改输入参数end============================================

可能的疑问

1. 训练过程的可视化

Loss可视化:

Darknet-Yolov3训练自己的数据指导手册_第1张图片

IOU可视化:

Darknet-Yolov3训练自己的数据指导手册_第2张图片

参考:

https://blog.csdn.net/cgt19910923/article/details/80783614

修改好的脚本文件如下路径:

略,哈哈哈哈,理解万岁。

2. 训练日志的解读

因为.cfg文件中定义batch=64,每个batch分为8组,所以打印8组信息。每组包含三个yolo层的训练信息。
Darknet-Yolov3训练自己的数据指导手册_第3张图片
Region xx: cfg文件中yolo-layer的索引;

**Avg IOU:**当前迭代中,预测的box与标注的box的平均交并比,越大越好,期望数值为1;

Class: 标注物体的分类准确率,越大越好,期望数值为1;

obj: 越大越好,期望数值为1;

No obj: 越小越好;

.5R: 以IOU=0.5为阈值时候的recall; recall = 检出的正样本/实际的正样本

0.75R: 以IOU=0.75为阈值时候的recall;

**count:**正样本数目。

7634: 指示当前训练的迭代次数;

1.077007: 是总体的 Loss(损失);

0.980247 avg: 是平均 Loss, 这个数值应该越低越好, 一般来说, 一旦这个数值低于 0.060730 avg 就可以终止训练了;

0.001000 rate: 代表当前的学习率, 是在.cfg文件中定义的;

7.004570 seconds: 表示当前批次训练花费的总时间;

488576 images: 这一行最后的这个数值是 979864ngpus 的大小, 表示到目前为止, 参与训练的

在这里插入图片描述

如果出现上图-nan的情况,不要慌:只是说明本组图像中在此尺度下的特征图中检测不到正样本而已。

3. 训练日志的简化

目的:

解决训练过程中cpu吃紧的问题。

解决办法:

detector-nolog.c替换darknet\examples\detector.c

yolo_layer-nolog.c替换darknet\src\yolo_layer.c

(文件在:略,哈哈哈哈,理解万岁。)

重新make即可。

4. 模型保存问题

A:保存模型出错?

一般是 .data 文件中指定的文件夹无法创建,导致模型保存时出错。自己手动创建即可。

B:模型什么时候保存?如何更改

迭代次数小于1000时,每100次保存一次,大于1000时,每10000次保存一次。

自己可以根据需求进行更改,然后重新编译即可[ 先 make clean ,然后再 make]。

代码位置: examples/detector.c line 138
Darknet-Yolov3训练自己的数据指导手册_第4张图片
C:使用预训练模型直接保存问题

darknet53.conv.74作为预训练权重文件,因为只包含卷积层,所以可以从头开始训练。

xxx.weights作为预权重文件训练,因为包含所有层,相当于恢复快照训练,会从已经保存的迭代次数往下训练。如果cfg中迭代次数没改,所以不会继续训练,直接保存结束。

5. 网络文件格式问题

问题:

在这里插入图片描述

原因:

配置文件的编码问题,将文件格式改为unix格式。(在linux下,拷贝的文件来自windows)

解决:

在vim下 输入 :set ff=unix

保存退出。

6. 个别服务器编译Darknet

进行编译,训练时会出现如下报错:

Darknet-Yolov3训练自己的数据指导手册_第5张图片

原因:未知(代码中有除0操作,出现段错误,但是其他服务器未出现此错误)

暂时解决方案:

拷贝编译好的框架直接使用即可:scp -r host@…: …/darknet ./

其他问题请参考如下链接:

https://blog.csdn.net/lilai619/article/details/79695109

网络的裁剪与优化

1. Yolov3 的网络结构

Darknet-Yolov3训练自己的数据指导手册_第6张图片
DBL: 如图1左下角所示,也就是代码中的Darknetconv2d_BN_Leaky,是yolo_v3的基本组件。就是卷积+BN+Leaky relu。对于v3来说,BN和leaky relu已经是和卷积层不可分离的部分了(最后一层卷积除外),共同构成了最小组件。

resn**:**n代表数字,有res1,res2, … ,res8等等,表示这个res_block里含有多少个res_unit。这是yolo_v3的大组件,yolo_v3开始借鉴了ResNet的残差结构,使用这种结构可以让网络结构更深(从v2的darknet-19上升到v3的darknet-53,前者没有残差结构)。对于res_block的解释,可以在图1的右下角直观看到,其基本组件也是DBL。

concat**:**张量拼接。将darknet中间层和后面的某一层的上采样进行拼接。拼接的操作和残差层add的操作是不一样的,拼接会扩充张量的维度,而add只是直接相加不会导致张量维度的改变。

2. Darknet53基础网络

Darknet-Yolov3训练自己的数据指导手册_第7张图片
整个v3结构里面,是没有池化层和全连接层的。前向传播过程中,张量的尺寸变换是通过改变卷积核的步长来实现的,比如stride=(2, 2),这就等于将图像边长缩小了一半(即面积缩小到原来的1/4)。在yolo_v2中,要经历5次缩小,即1/32。输入为416x416,则输出为13x13(416/32=13)。yolo_v3也和v2一样,backbone都会将输出特征图缩小到输入的1/32。所以,通常都要求输入图片是32的倍数。

3. 裁剪优化的方向

\1. 主干网络裁剪

(1) 减少res基础结构个数。

(2) 减少通道数。(2的指数倍)

(3) 减少降维的倍率。

(4) 改变降维方式:卷积降维改为pooling(在裁剪的最终网络中,每修改一处,效率提升10个点左右,但是效果有所下降。仅供参考)

Darknet-Yolov3训练自己的数据指导手册_第8张图片

\2. 降低分辨率

必须为降维倍率的整数倍,最好为基数倍(这样回归的框会比较准)。

\3. Yolo层以及anchor的裁剪

在尺度变换不是很大的应用场景,可以减少anchor的数量,以及yolo的层数。

Darknet-Yolov3训练自己的数据指导手册_第9张图片

yolo v3输出了3个不同尺度的feature map,如上图所示的y1, y2, y3。这也是v3论文中提到的为数不多的改进点:predictions across scales

这个借鉴了FPN(feature pyramid networks),采用多尺度来对不同size的目标进行检测,越精细的grid cell就可以检测出越精细的物体。

y1,y2和y3的深度都是255,边长的规律是13:26:52

对于COCO类别而言,有80个种类,所以每个box应该对每个种类都输出一个概率。yolo v3设定的是每个网格单元预测3个box,所以每个box需要有(x, y, w, h, confidence)五个基本参数,然后还要有80个类别的概率。所以3*(5 + 80) = 255。这个255就是这么来的。

优化关注点:

第一点, 9个anchor会被三个输出张量平分的。根据大中小三种size各自取自己的anchor。

第二点,作者使用了logistic回归来对每个anchor包围的内容进行了一个目标性评分(objectness score)。根据目标性评分来选择anchor prior进行predict,而不是所有anchor prior都会有输出。

4. 裁剪实例

layer     filters    size              input                output
    0 conv     16  3 x 3 / 1   224 x 224 x   3   ->   224 x 224 x  16  0.043 BFLOPs
    1 max          2 x 2 / 2   224 x 224 x  16   ->   112 x 112 x  16
    2 conv     32  3 x 3 / 1   112 x 112 x  16   ->   112 x 112 x  32  0.116 BFLOPs
    3 max          2 x 2 / 2   112 x 112 x  32   ->    56 x  56 x  32
    4 conv     64  3 x 3 / 1    56 x  56 x  32   ->    56 x  56 x  64  0.116 BFLOPs
    5 conv    128  3 x 3 / 2    56 x  56 x  64   ->    28 x  28 x 128  0.116 BFLOPs
    6 conv     64  1 x 1 / 1    28 x  28 x 128   ->    28 x  28 x  64  0.013 BFLOPs
    7 conv    128  3 x 3 / 1    28 x  28 x  64   ->    28 x  28 x 128  0.116 BFLOPs
    8 res    5                  28 x  28 x 128   ->    28 x  28 x 128
    9 conv     64  1 x 1 / 1    28 x  28 x 128   ->    28 x  28 x  64  0.013 BFLOPs
   10 conv    128  3 x 3 / 1    28 x  28 x  64   ->    28 x  28 x 128  0.116 BFLOPs
   11 res    8                  28 x  28 x 128   ->    28 x  28 x 128
   12 conv    256  3 x 3 / 2    28 x  28 x 128   ->    14 x  14 x 256  0.116 BFLOPs
   13 conv    128  1 x 1 / 1    14 x  14 x 256   ->    14 x  14 x 128  0.013 BFLOPs
   14 conv    256  3 x 3 / 1    14 x  14 x 128   ->    14 x  14 x 256  0.116 BFLOPs
   15 res   12                  14 x  14 x 256   ->    14 x  14 x 256
   16 conv    128  1 x 1 / 1    14 x  14 x 256   ->    14 x  14 x 128  0.013 BFLOPs
   17 conv    256  3 x 3 / 1    14 x  14 x 128   ->    14 x  14 x 256  0.116 BFLOPs
   18 res   15                  14 x  14 x 256   ->    14 x  14 x 256
   19 conv    128  1 x 1 / 1    14 x  14 x 256   ->    14 x  14 x 128  0.013 BFLOPs
   20 conv    256  3 x 3 / 1    14 x  14 x 128   ->    14 x  14 x 256  0.116 BFLOPs
   21 conv     18  1 x 1 / 1    14 x  14 x 256   ->    14 x  14 x  18  0.002 BFLOPs
   22 yolo
   23 route  19
   24 conv    128  1 x 1 / 1    14 x  14 x 128   ->    14 x  14 x 128  0.006 BFLOPs
   25 upsample            2x    14 x  14 x 128   ->    28 x  28 x 128
   26 route  25 11
   27 conv     64  1 x 1 / 1    28 x  28 x 256   ->    28 x  28 x  64  0.026 BFLOPs
   28 conv    128  3 x 3 / 1    28 x  28 x  64   ->    28 x  28 x 128  0.116 BFLOPs
   29 conv     18  1 x 1 / 1    28 x  28 x 128   ->    28 x  28   18  0.004 BFLOPs
   30 yolo

5. 裁剪设计工具

支持基于yolov3原始网络的网络设计和裁剪,主要功能有参数设定,通道裁剪,以及结构的裁剪设计。

工具路径:

略,哈哈哈哈,理解万岁。

Yolov3_Net_Design.py配置文件信息如下:

##################################################
#自动裁剪yolov3网络
#Author          : 小楞
#Created         : 2019-08-19
#注:目前支持yolov3网络的通道裁剪,参数设定, 结构裁剪。
#问题:   配置文件的编码问题,将文件格式改为unix格式。(在linux下,拷贝的文件来自windows)
#解决:   在vim下 输入  :set ff=unix
#参考:网络裁剪算力与裁剪方式减少比值之间的关系
#     1.算力减少比例/像素点个数减少比例:1
#     2.算力减少比例/通道数裁剪比例:1.7
#     3.算力减少比例/res模块减少数:0.02-0.03
##################################################
...
# 设置配置参数configs = {    
'classNum':22,#检测的类别数    
 'yoloNum':3,#yolo层的个数,暂不支持修改    
 'width':608,    
 'height':352,#输入分辨率    
 'angle':5,#样本旋转    
 'random':1,#是否开启多尺度训练  
 'ignore_thresh':.5,#参与loss计算的Iou阈值,参考0.5-0.7,标注质量(框的位置)较差时选用0.5较好。
 'filt_ReduceRatio': 0.75,#通道缩减比例    
 'base_Module':{'res1':0,'res2':0,'res8_1':0,'res8_2':0,'res4':0},#建议裁剪res8_1和res8_2;参考值:'res1':1,'res2':2,'res8_1':8,'res8_2':8,'res4':4    
 'anchors': 'anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326',    
 'ori_CfgDir': "...\yolov3_ori.cfg",   
 'det_CfgDir': "...\\yolov3_manual.cfg",}

按照如上结构进行网络参数配置,即可自动生成所需网络。

Yolov3_Compute_Ops.py配置文件:

##################################################
#计算yolov3算力
#Author          : 小楞
#Created         : 2019-09-03
#注:需要darknet框架输出的单层算力值
#结论:网络裁剪算力与裁剪方式减少比值之间的关系
#     1.算力减少比例/像素点个数减少比例:1
#     2.算力减少比例/通道数裁剪比例:1.7
#     3.算力减少比例/res模块减少数:0.020.03
##################################################
...
# 设置配置参数
configs = {'ori_CfgDir': "...\\darknet_yolov3net.txt",}

darknet_yolov3net.txt为darknet训练时,打印的网络结构:

yolov3
layer     filters    size              input                output
    0 conv     32  3 x 3 / 1   608 x 608 x   3   ->   608 x 608 x  32  0.639 BFLOPs
    1 conv     64  3 x 3 / 2   608 x 608 x  32   ->   304 x 304 x  64  3.407 BFLOPs
    2 conv     32  1 x 1 / 1   304 x 304 x  64   ->   304 x 304 x  32  0.379 BFLOPs
    3 conv     64  3 x 3 / 1   304 x 304 x  32   ->   304 x 304 x  64  3.407 BFLOPs
    4 res    1                 304 x 304 x  64   ->   304 x 304 x  64
    ...
   82 yolo
   83 route  79
   84 conv    256  1 x 1 / 1    19 x  19 x 512   ->    19 x  19 x 256  0.095 BFLOPs
   85 upsample            2x    19 x  19 x 256   ->    38 x  38 x 256
   86 route  85 61
    ...
   94 yolo
   95 route  91
   96 conv    128  1 x 1 / 1    38 x  38 x 256   ->    38 x  38 x 128  0.095 BFLOPs
   97 upsample            2x    38 x  38 x 128   ->    76 x  76 x 128
   98 route  97 36
    ...
  106 yolo

Yolo的caffe部署

1. 背景

在工程应用中,yolo的部署方式有很多种,但是由于各种原因,选择了caffe环境部署。所以需要将yolo的模型以及网络文件转换为caffe格式。

2. yolov3的网络结构

想要转化为Caffe框架,就要先了解yolov3的网络结构,如下图。

Darknet-Yolov3训练自己的数据指导手册_第10张图片

如果有运行过darknet应该会很熟悉,这是darknet运行成功后打印log信息,这里面包含了yolo网络结构的一些信息。yolov3与v2相比,网络结构中加入了残差(shortcut层),并且引入了上采样(upsample层),并为了将采样后的特征图进行融合引入了拼接(route层),最后融合的特征图以三个不同的大小131375,262675,525275输入给yolo层最后得到目标的位置及分类信息,加上卷积层convolution,这些便是yolov3的网络基本构造。因此只要我们如果在Caffe中找到对应的层按照相应的进行构造就能够使用Caffe实现yolov3了。

Darknet-Yolov3训练自己的数据指导手册_第11张图片

卷积层不说,yolov3中的shortcut层可以用eltwise替代,route层可以用concat替代,而upsample层和yolo层则需要自己实现,并添加到Caffe中即可。upsample层主要完成了上采样的工作,这里不细说。本文主要讲一下yolo层如何实现,上图中的YOLO Detection即为yolo层的所在位置,接收三种不同大小的特征图,并完成对特征图的解析,得到物体的位置和类别信息。所以其实yolo层主要起到了解析特征并输出检测结果的作用,这一过程我们完全可以在外部实现而无需加入到网络结构当中,也就是说我们无需将实现的yolo层加入到Caffe当中去。

3. 如何实现

下面这部分将着重讲一下如何实现从darknet向caffe的转换,首先这一过程要感谢chenyingpeng提供的代码。

参考(https://blog.csdn.net/Chen_yingpeng/article/details/80692018)。

1.加入upsample层并编译Caffe

upsample层的代码在(https://pan.baidu.com/s/13GpoYoqKSCeFX0m0ves_fQ#list/path=%2F 提取码:bwrd)

其中的upsample_layer.hpp放入include/caffe/layers下面;upsample_layer.cpp与upsample_layer.cu放在src/caffe/layers下面。

修改相应的caffe.proto文件,src/caffe/proto/caffe.proto中的LayerParameter的最后一行加入加入:

 message LayerParameter {
        .....
        optional UpsampleParameter upsample_param = 149;
    }

注意149为新层的ID号,该ID号请根据个人的caffe.proto文件指定即可。

然后再caffe.proto中添加upsample层的参数:

    message UpsampleParameter{
      optional int32 scale = 1 [default = 1];
    }

紧接着重新编译Caffe,这样就完成了在Caffe中添加upsample层。

上面说过转换到Caffe后只包含推理过程,因此我们需要将训练好的模型(.cfg)和权重文件(.weights)转换到对应Caffe下的.proto和.caffemodel,代码可以借鉴github上的模型转换工具(https://github.com/marvis/pytorch-caffe-darknet-convert )。该工具需要pytorch支持请自行百度安装安装。

且该工具应用于Yolov2,因为我们在Caffe中加入了相应的upsample层并且yolov3和v2的网络结构有变化,因此需要替换相应的darknet2caffe.py,代码在这里(略,哈哈哈哈,理解万岁。)。

yolov3-darknet2caffe.py脚本需要修改caffe路径,如下图:

Darknet-Yolov3训练自己的数据指导手册_第12张图片

至此我们的准备工作就结束了,这样通过Caffe我们就能得到相应的blobs,这些blobs里包含的信息和darknet输入给yolo层的信息是一样的。接下来我们将展示如何转换模型:

把要转的yolov3的模型(yolov3.weights)和模型文件(yolov3.cfg(测试网络))copy到pytorch-caffe-darknet-convert目录下;

执行如下命令:

python yolov3_darknet2caffe.py yolov3.cfg yolov3.weights yolov3.prototxt yolov3.caffemodel

上面转换后的模型不包含yolo层,需要在部署的环境中自己实现yolo层的编写(不要担心,当然是已经做好了,直接用就行)。需要注意的是:yolo层中包含许多所使用模型信息(种类、anchor信息),需要与所训模型匹配(也不用担心找不到相关的参数:相关的参数在生成的.proto文件最后边)。

因此我们转换到Caffe框架下的yolov3也仅能实现推理过程,具体的训练还需要通过darknet来完成。

4. 出现的错误

报错如下:

运行darknet2caffe.py后生成了.prototxt文件 没能生成.caffemodel文件,报错如下:

 unknow layer type yolo [libprotobuf ERROR google/protobuf/text_format.cc:274] Error parsing text-format caffe.NetParameter: 2622:20: Message type "caffe.LayerParameter" has no field named "upsample_param". WARNING: Logging before InitGoogleLogging() is written to STDERR F0831 16:30:17.545022 32644 upgrade_proto.cpp:79] Check failed: ReadProtoFromTextFile(param_file, param) Failed to parse NetParameter file: yolov3.prototxt *** Check failure stack trace: *** 已放弃 (核心已转储)

解决办法:

Caffe编译有问题,重新按要求编译caffe。(如果出现在caffe路径下不能进行编译,请在build路径下进行编译。)

make clean
make all
make pycaffe 

Darknet2ncnn

1. 环境搭建

下载darknet2ncnn工程项目

git clone https://github.com/xiangweizeng/darknet2ncnn.git

darknet2ncnn目录:
Darknet-Yolov3训练自己的数据指导手册_第13张图片

其中,darknet与ncnn需要单独下载,并放置该目录文件夹中。

按照README.md文件的编译过程,进行编译

遇到并解决的问题

在这里插入图片描述

报错:/usr/bin/ld: 找不到 -lippicv

collect2: error: ld returned 1 exit status

解决办法:

cp /home/username/opencv-3.1.0/3rdparty/ippicv/unpack/ippicv_lnx/lib/intel64/liboppicv.a

至 /usr/local/lib

参考博客:https://blog.csdn.net/dengshuai_super/article/details/51895120

2. 模型转换

环境搭建之后,可以进行模型转换,有以下几种形式:

转换(仅生成转换之后的模型文件)

./darknet2ncnn data/yolov3-Person3-416_PileG_Digt.cfg data/yolov3-Person3-416_PileG_Digt.weights example/zoo/yolov3-Pile_Digt.param example/zoo/yolov3-Pile_Digt.bin

转换+测试(生成转换之后的模型文件,同时可以测试单帧图片相应的效果)

./convert_verify data/yolov3-Person3-416_PileG_Digt.cfg data/yolov3-Person3-416_PileG_Digt.weights example/zoo/yolov3-Pile_Digt.param example/zoo/yolov3-Pile_Digt.bin example/data/14.jpg

图片测试(仅输出打印检测结果,不会保存检测结果图片)

./classifier zoo/yolov3-Pile_Digt.param zoo/yolov3-Pile_Digt.bin data/14.jpg data/PileDigt.names

图片测试(打印检测结果,同时保存检测结果图片)

./yolo zoo/yolov3-Pile_Digt.param zoo/yolov3-Pile_Digt.bin data/14.jpg data/PileDigt.names

详细介绍可参考下载文件中的README.md文件

你可能感兴趣的:(深度学习yolov3)