使用darknet训练自己的数据集以及对python接口的调用

2 篇文章 0 订阅
2 篇文章 0 订阅
运行环境:UBUNTU 18.04、CUDA 10.0、CUDNN 7.5、OPENCV 4.1.2、GPU RTX2070SUPER

<文章中的内容有的是以前看的博客记得笔记,不记得链接了都,如果冒犯到了原作者,请您及时联系我,我会及时标明出处>

一、准备数据集

1.1 使用labelimg标注自己的数据集

   2020.12.17:更新labelimg下载链接
   糙汉在线为你们准备网盘连接【提取码:txgz】
   大概12.5M,可以直接点击labelImg.exe进行使用
   下面就是贴心小哥在线准备的使用指南,敬请享用

1.2 制作VOC数据集

在这里插入图片描述
        这些目录的命名是为了跟咱们训练脚本里边的路径相对应,免去了修改脚本路径的麻烦,其中Annotations文件夹里边是存放的标注好的xml文件。JPEGImages文件夹存放的是我们所有的原图片。ImageSets文件夹中有三个子目录,Main文件夹是我们用到的,其他的两个暂时用不到,其中Main文件夹中train.txt、val.txt、trainval.txt、test.txt四个文件,存放的的是训练集、验证集、训练集加验证集、测试集所用的图片的名称,里边内容只有图片的名称,没有后缀名。当然,一开始Main文件夹中是没有这四个文件的,这个是当我们整理好Annotations和JPEGImages文件夹后,使用脚本test.py生成这四个文件,代码如下:test.py(这里边的路径都是相对路径,注意看我上边test.py的位置,跟Annotations、JPEGImages同目录

import os
import random

trainval_percent = 0.9  # 训练和验证集所占比例,剩下的0.1就是测试集的比例
train_percent = 0.8  # 训练集所占比例,可自己进行调整
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
total_xml = os.listdir(xmlfilepath)

num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)

ftrainval = open('ImageSets/Main/trainval.txt', 'w')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('ImageSets/Main/val.txt', 'w')

for i in list:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)

ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

        至于其他的文件夹SegmentationClass、SegmentationObject,这些是涉及到分割一方面的,暂时不用管。重要的是大家尽量按照我图中所展示的名称对文件夹进行命名呢。
截止到这里,数据集准备完毕,跨上万里长城的第一步!

二、配置darknet

2.1 下载darknet源码

       git clone https://github.com/pjreddie/darknet    
在这里插入图片描述

2.2配置darknet

       cd darknet && ls
在这里插入图片描述
       用vim或者是其他的gedit也可以,打开Makefile文件,需要开启哪个接口,就将哪个接口后边的0改为1就ok:

GPU=1  # 开启GPU接口
CUDNN=1  # CUDNN是用于深度神经网络的GPU加速库
OPENCV=1  # OPENCV接口(这里需要大家源码安装opencv)
OPENMP=1  # CPU的多线程
DEBUG=0  # DEBUG接口,暂时没发现这个有什么用处,就不开了
2.3(如果你用的opencv版本是低于opencv4.0的,可以直接跳过此步骤。当然,如果你以后想用4.0的,也可以先打个预防针)

非常重要的一点:因为我是用的opencv4,Makefile文件夹中对应的opencv都是opencv3及opencv3以下版本的内容,需要修改的地方如下:(大家的电脑配置不一样,可能需要修改的地方也不一样,你可以先参照我的试一下,如果不成功的话,可以及时评论留言)

修改一

# 首先大家定位到Makefile的这个地方,我的是在第59行,大概就是这个位置
ifeq ($(OPENCV), 1)
COMMON+= -DOPENCV
CFLAGS+= -DOPENCV
# 下边的两行是搜索环境中的opencv
LDFLAGS+= `pkg-config --libs opencv4` -lstdc++  # 这是我修改的地方,将opencv改成了opencv4
COMMON+= `pkg-config --cflags opencv4`  # 这是我修改的地方,将opencv改成了opencv4
endif

修改二

需要修改darknet/src下的image_opencv.cpp文件,你直接打开这个文件后,直接ctrl+a→
删除,然后把我下边的直接复制过去再保存就行了,省的去修改细节了。。。。。。

#ifdef OPENCV
 
#include "stdio.h"
#include "stdlib.h"
#include "opencv2/opencv.hpp"
#include "image.h"
 
using namespace cv;
 
extern "C" {
 
/*IplImage *image_to_ipl(image im)
{
    int x,y,c;
    IplImage *disp = cvCreateImage(cvSize(im.w,im.h), IPL_DEPTH_8U, im.c);
    int step = disp->widthStep;
    for(y = 0; y < im.h; ++y){
        for(x = 0; x < im.w; ++x){
            for(c= 0; c < im.c; ++c){
                float val = im.data[c*im.h*im.w + y*im.w + x];
                disp->imageData[y*step + x*im.c + c] = (unsigned char)(val*255);
            }
        }
    }
    return disp;
}
image ipl_to_image(IplImage* src)
{
    int h = src->height;
    int w = src->width;
    int c = src->nChannels;
    image im = make_image(w, h, c);
    unsigned char *data = (unsigned char *)src->imageData;
    int step = src->widthStep;
    int i, j, k;
    for(i = 0; i < h; ++i){
        for(k= 0; k < c; ++k){
            for(j = 0; j < w; ++j){
                im.data[k*w*h + i*w + j] = data[i*step + j*c + k]/255.;
            }
        }
    }
    return im;
}*/
 
Mat image_to_mat(image im)
{
    image copy = copy_image(im);
    constrain_image(copy);
    if(im.c == 3) rgbgr_image(copy);
 
	int x, y, c;
	Mat m = Mat(Size(im.w, im.h), CV_8UC(im.c));
	int step = m.step;
 
	for (y = 0; y < im.h; ++y) {
			for (x = 0; x < im.w; ++x) {
					for (c = 0; c < im.c; ++c) {
							float val = copy.data[c*im.h*im.w + y * im.w + x];
							m.data[y*step + x * im.c + c] = (unsigned char)(val * 255);
					}
			}
	}
 
/*    IplImage *ipl = image_to_ipl(copy);
    Mat m = cvarrToMat(ipl, true);
    cvReleaseImage(&ipl);*/
    free_image(copy);
    return m;
}
 
image mat_to_image(Mat m)
{
/*    IplImage ipl = m;
    image im = ipl_to_image(&ipl);
    rgbgr_image(im);*/
	int h = m.rows;
	int w = m.cols;
	int c = m.channels();
	image im = make_image(w, h, c);
	unsigned char *data = (unsigned char *)m.data;
	int step = m.step;
	int i, j, k;
 
	for (i = 0; i < h; ++i) {
			for (k = 0; k < c; ++k) {
					for (j = 0; j < w; ++j) {
							im.data[k*w*h + i * w + j] = data[i*step + j * c + k] / 255.;
					}
			}
	}
    if (im.c == 3) rgbgr_image(im);
	
    return im;
}
 
void *open_video_stream(const char *f, int c, int w, int h, int fps)
{
    VideoCapture *cap;
    if(f) cap = new VideoCapture(f);
    else cap = new VideoCapture(c);
    if(!cap->isOpened()) return 0;
    if(w) cap->set(CAP_PROP_FRAME_WIDTH, w);
    if(h) cap->set(CAP_PROP_FRAME_HEIGHT, w);
    if(fps) cap->set(CAP_PROP_FPS, w);
    return (void *) cap;
}
 
image get_image_from_stream(void *p)
{
    VideoCapture *cap = (VideoCapture *)p;
    Mat m;
    *cap >> m;
    if(m.empty()) return make_empty_image(0,0,0);
    return mat_to_image(m);
}
 
image load_image_cv(char *filename, int channels)
{
    int flag = -1;
    if (channels == 0) flag = -1;
    else if (channels == 1) flag = 0;
    else if (channels == 3) flag = 1;
    else {
        fprintf(stderr, "OpenCV can't force load with %d channels\n", channels);
    }
    Mat m;
    m = imread(filename, flag);
    if(!m.data){
        fprintf(stderr, "Cannot load image \"%s\"\n", filename);
        char buff[256];
        sprintf(buff, "echo %s >> bad.list", filename);
        system(buff);
        return make_image(10,10,3);
        //exit(0);
    }
    image im = mat_to_image(m);
    return im;
}
 
int show_image_cv(image im, const char* name, int ms)
{
    Mat m = image_to_mat(im);
    imshow(name, m);
    int c = waitKey(ms);
    if (c != -1) c = c%256;
    return c;
}
 
void make_window(char *name, int w, int h, int fullscreen)
{
    namedWindow(name, WINDOW_NORMAL); 
    if (fullscreen) {
        setWindowProperty(name, WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN);
    } else {
        resizeWindow(name, w, h);
        if(strcmp(name, "Demo") == 0) moveWindow(name, 0, 0);
    }
}
 
}
 
#endif

       修改之后,直接在darknet目录下进行make -j8 (我这里是开了8个线程,直接make也可以,是默认调用的1个线程),如果你开了GPU接口报了错的话,先看看cuda和cudnn的路径是否修改正确,还有可能报opencv找不到的错误,还有一点要极其注意的就是gcc、g++的版本。如果还有其他报错,欢迎大家留言评论区的呢😎😎😎
       以下这个就是make成功的打印信息,以及make之后的文件夹目录:
在这里插入图片描述

2.3 环境测试
    2.3.1 下载Yolov3模型(大概237M)

         Yolov3模型官网下载地址: wget https://pjreddie.com/media/files/yolov3.weights

         PS: 快说,是不是有的小伙伴还在为官网下载的20-40k的超快网速而苦恼😟😟😟,作为贴心天使的我早已想到,下面就是百度云链接,赶紧去爽吧(如果有人没有百度网盘会员,就去下载个PanDownload这个软件,基本就是百度网盘破解版,非常的nice)😇😇😇

         Yolov3模型网盘下载地址: https://pan.baidu.com/s/1vylFjHwmf747Qwk6W3QdaA

         PanDownload下载地址: http://pandownload.com/

    2.3.2 进行测试

         ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
       必须在darknet根目录下运行此命令,要注意yolov3.weight的位置,当然你也可以测试其他的图片。
在这里插入图片描述
    如果运行完命令,你也出现了这张经典的dog图像,恭喜贺喜,配置成功!!!🥂🥂🥂可能有的人会出现没有打印出来图像的现象,这个就是opencv的问题了,或者是opencv接口没有打开。在检测完成之后,darknet目录下会有一张名为predictions.jpg的图片,这是刚检测完直接保存下来的图片,也可以直接打开进行查看😚😚😚
    OK,到了这里,万事俱备,只欠训练啦啦啦@@@开森不开森呀,Happy New Year!!!
卡忙 ★,°:.☆( ̄▽ ̄)/ : ∗ . ° ★ ∗ 。 北 鼻 ( o ゜ ▽ ゜ ) o ☆ [ B I N G O ! ] > 卡 忙 ∗ ★ , ° ∗ : . ☆ (  ̄ ▽  ̄ ) / :*.°★* 。北鼻(o゜▽゜)o☆[BINGO!]>卡忙 *★,°*:.☆( ̄▽ ̄)/ :.°(o)o[BINGO!]>,°:.()/:.°★ 。北鼻
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

三、模型的训练

    来吧,一鼓作气,直捣黄龙///

3.1 生成训练集及测试集的路径文件

在darknet/scripts中有个voc_label.py文件,

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

# 这里就体现出来了咱们在1.2步骤的时候我说的尽量按照那个目录名进行操作的优势,
# 在这可以剩下很多去修改名称的精力
# sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
sets=[ ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]  # 我只用了VOC2007

# classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
classes = ["face"]  # 修改为自己的label

def convert(size, box):
    dw = 1./(size[0])  # 有的人运行这个脚本可能报错,说不能除以0什么的,你可以变成dw = 1./((size[0])+0.1)
    dh = 1./(size[1])  # 有的人运行这个脚本可能报错,说不能除以0什么的,你可以变成dh = 1./((size[0])+0.1)
    x = (box[0] + box[1])/2.0 - 1
    y = (box[2] + box[3])/2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x,y,w,h)

def convert_annotation(year, image_id):
    in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
    out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w')
    tree=ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult)==1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
        bb = convert((w,h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

wd = getcwd()

for year, image_set in sets:
    if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)):
        os.makedirs('VOCdevkit/VOC%s/labels/'%(year))
    image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()
    list_file = open('%s_%s.txt'%(year, image_set), 'w')
    for image_id in image_ids:
        list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
        convert_annotation(year, image_id)
    list_file.close()

# 这块是路径拼接,暂时用不上,先都注释了
# os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt")
# os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")

在运行voc_label.py的时候,如果就是按照我所贴的代码的话,那就把咱们1.2步骤所制作好的VOCdevkit文件夹放在/darknet/scripts/目录下,因为代码中是相对路径。当然,你可以把路径改为存放制作好VOCdevkit文件夹的绝对路径,这样就不用把制作好的文件夹放在scripts下了。
voc.label.py可以根据xml文件生成训练所需的label文件以及训练所用的2007_train.txt、2007_test.txt、2007_trainval,这个label文件是直接跟Annoataions与JPEGImages是同目录的文件夹,内容嘛,也没多少东西,如下啦啦啦啦啦:

label生成的txt内容示例:
 
0 0.571875 0.500925925925926 0.3979166666666667 0.5851851851851853
代表物体的分类编号和两个坐标的xy在图片中的归一化的值

3.2 修改.cfg文件

   用vim或者gedit打开cfg/yolov3.cfg,你要是用其他的模型就修改你所用的那个cfg文件就行,比如还有yolov2.cfg、yolov2-tiny.cfg、yolov3-tiny.cfg等等,但是修改的地方都是相似的。这里边是有Testing和Training接口的,训练的时候,把Training这个接口取消注释就好了,测试也是同样的道理。有的人在用训练好的模型进行测试的时候,发现没有效果,测试不出来,大概率就是因为这个Testing这个接口没有打开,一定要细心!

[net]
# Testing  测试的的时候把下边的两个接口打开,并进行设置
# batch=1
# subdivisions=1
# Training 训练的时候把下边的两个接口打开,并进行设置
batch=64
subdivisions=16
width=608  # 送入网络的图片大小
height=608
channels=3  # 图片通道
momentum=0.9  # 动量,这个值直接影响梯度下降到最优值的速度,一般都设置成0.9
decay=0.0005  # 权重衰减正则项,防止过拟合
angle=0  # 旋转角度  (数据增强)
saturation = 1.5  # 饱和度  (数据增强)
exposure = 1.5  # 曝光量  (数据增强)
hue=.1  # 色调   (数据增强)

learning_rate=0.001  # 学习率
burn_in=1000  # 学习率衰减指标
max_batches = 500200  # 迭代次数
policy=steps  # 学习率衰减方式
steps=400000,450000 
scales=.1,.1  # 学习率下调倍数

还需要修改的地方有:(看网络结构,yolov3有3个这样的地方,直接遍历一下,遍历到的都改一下)

[convolutional]
size=1
stride=1
pad=1
filters=255  # 这个值的计算公式  filters = (classes + 5) * 3
activation=linear
[yolo]
mask = 6,7,8
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326  # 如果你自己聚类了anchor 可以直接修改
classes=80  # 你数据集总共多少类
num=9  # 指定每一个anchor预测bbox的数量
jitter=.3  # 数据增强,随机调整宽高比的范围
ignore_thresh = .7  #  预测检测框与GT框的比值大于0.7,则不参与损失计算
truth_thresh = 1 
random=1  # 随机多尺度训练 
3.3 修改voc.data

   用vim或者gedit打开cfg/voc.data:

classes= 20  # 你数据集总共多少类
train  = /home/pjreddie/data/voc/train.txt  # 这个就是3.1中所生成的2007_train.txt的绝对路径
valid  = /home/pjreddie/data/voc/2007_test.txt  # 这个就是3.1中所生成的2007_test.txt的绝对路径
names = data/voc.names
backup = backup

在这里插入图片描述在这里插入图片描述

3.4 开始训练

   训练的话,大家可以采取有预训练模型和无预训练模型两种方式,我基本都是使用的前者。下边是预训练模型的下载地址(darknet53.conv.74):

链接:https://pan.baidu.com/s/1JQO985Y9zj1XfEnFliW3gQ 提取码:dkid

预训练模型下载完成之后放到darknet的根目录下就行,当然也可以放到其他位置,就是训练的时候,路径修改了就行。

(base) wyl@wyl-Computer:~$ cd darknet
(base) wyl@wyl-Computer:~/darknet$ ./darknet detector train cfg/voc.data cfg/yolov3-tiny.cfg darknet53.conv.74
命令行训练参数解读:
1、  ./darknet:  类似于windows的.exe的可执行文件
2、  detector:    detector.c
3、  test:                 detector.c中,函数test_detector()

这是加载网络结构后的打印信息:
在这里插入图片描述
打印完网络结构之后,就开始往网络里边送数据,训练模型了,下边是开始训练的打印信息:
平常训练的时候,当loss小于0.060730avg,训练就可以停止了!
在这里插入图片描述
下边来讲解一下每一步迭代的打印参数:
1、Region XXX:这是尺度下的信息,比如yolov3-tiny是下采样两次,就有两个region,也就是两个尺度的信息
2、Avg Iou:在当前Subdivision内的图片的平均IOU,预测框与GT的IOU
3、Class:物体分类的正确率, 期望值趋近于1
4、Obj:是物体的概率,期望值趋近于1
5、No Obj:不是物体的概率,期望值趋近于0,但不能为0
6、.5R/.7R:当前模型在Subdivision图片中检测出正样本与实际的正样本的比值
7、count:当前Subdivision图片中包含正样本的图片的数量

番外参数:
1、在src/image.c中,这是可以修改检测时候的画框的函数
2、include/darknet.h,这里边是存放大部分的函数声明
3、examples/detector.c,大概在138行,可以修改模型保存的参数
4、src/region_layer.c,可以修改打印信息(批次)的参数
5、examples/detector.c,可以修改总体的打印信息

3.5 开始测试

这是训练好的模型文件:
在这里插入图片描述

./darknet detector test cfg/voc.data cfg/yolov3-tiny.cfg backup/yolov3-final.weights

如果不出意外的话,会出来测试效果,如果有什么问题,就留言评论区。
在这里插入图片描述

四、python接口的调用

4.1修改python文件中的参数

先进入到python文件夹,然后打开darknet.py文件,划拉到最后,修改模型文件的路径以及cfg文件的路径,还有voc.data的路径,修改完成之后,保存。

然后直接在python文件夹下运行python darknet.py,不出意外的话,会报错的
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200330153924181.gif
                

Traceback (most recent call last):
File “darknet.py”, line 48, in
lib = CDLL(“libdarknet.so”, RTLD_GLOBAL)

这是因为找不到libdarknet.so文件了,把darknet文件夹下的libdarknet.so文件放到python文件夹下就ok啦!O(∩_∩)O
遇到什么问题大家尽可以留言评论区呢,别忘了用小手手给俺点赞赞呦!
最后祝大家以后写代码No error! No warning!

  • 52
    点赞
  • 242
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值