[TOC]

1. 系统环境

  • Ubuntu 18.04
  • OpenCV 3.4.6
  • RTX 3070

2. 下载并测试 DarkNet

  • DarkNet YOLO 官方网站:https://pjreddie.com/darknet/yolo/
    git clone https://github.com/pjreddie/darknet # 克隆项目
    cd darknet # 进入文件夹
    make # 编译
    wget https://pjreddie.com/media/files/yolov3.weights # 下载权重
    ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg # 预测 data/dog.jpg 这张图片中的目标
    ./darknet detect cfg/yolov3.cfg yolov3.weights # 运行后输入图片相对路径,测试多张图片
    

2.1 测试 yolov3-tiny

因为我的 3070 显存只有 8 GB ,所以只能测试 yolov3-tiny。

# 进入 darknet 文件夹
wget https://pjreddie.com/media/files/yolov3-tiny.weights # 下载权重文件
./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg # 执行

2.2 连接网络摄像头进行目标检测

  • 修改Makefile:
    在这里插入图片描述

  • 修改代码:
    在这里插入图片描述

  • 手机上安装软件,参考:https://zhangtianhao.blog.csdn.net/article/details/115279932
  • 执行命令(这个部分参考 github 上给出的提示,但我没有成功):
      # 修改为自己 ip 地址,两台设备处于同一局域网下使用
      ./darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights rtsp://login:pass@192.168.0.228:554 -i 0
    

3. 训练数据集

3.1 VOC 数据集

3.1.1 获取数据集

cd darknet/scripts/ # 进入文件夹
# 下载数据集
wget https://pjreddie.com/media/files/VOCtrainval_11-May-2012.tar
wget https://pjreddie.com/media/files/VOCtrainval_06-Nov-2007.tar
wget https://pjreddie.com/media/files/VOCtest_06-Nov-2007.tar
# 解压数据集,解压后的数据集均位于子目录 VOCdevkit 下。
tar xf VOCtrainval_11-May-2012.tar
tar xf VOCtrainval_06-Nov-2007.tar
tar xf VOCtest_06-Nov-2007.tar

数据集很大,你忍一下。忍不了可以用百度网盘下载。

Linux 百度网盘官方下载链接:https://pan.baidu.com/download
VOC 数据集百度网盘链接:https://pan.baidu.com/s/18MwwjNUi0Z5wkTmSzStFqA
密码: m4vh

3.1.2 为数据集生成标签

在这里插入图片描述

cd ~/darknet/scripts/ # 进入文件夹
python voc_label.py # 生成标签文件
cat 2007_train.txt 2007_val.txt 2012_*.txt > train.txt # 将包含图片路径的文本文件合并为一个

在这里插入图片描述

3.1.3 下载预训练权重

# 进入 darknet 文件夹(建议使用迅雷下载,该命令下载速度很慢)
wget https://pjreddie.com/media/files/darknet53.conv.74

3.1.4 开始训练

./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74

如果出现:fatal error: cudnn_version.h这个问题,这是cudnn没有配置好,解决方法参考【Ubuntu|cuDNN】训练 DarkNet 时解决 fatal error: cudnn_version.h

如果出现:error: ‘CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT’ undeclared (first use in this function)这个问题,是因为 DarkNet 没有适配 cudnn8.x,解决方法参考如何解决pjreddie版darknet不能使用cudnn8编译的问题

  • 跑起来后查看显卡状态:
      watch -n 0.1 -d nvidia-smi     #每隔 0.1 秒刷新一次
    

3.2 自己的数据集

3.2.1 数据集标注

工具:labelImg,全平台支持。

建议使用 conda 虚拟环境安装。

image-20210821135927015

# 下载源码,解压,进入其根目录
conda activate # 激活 conda 环境
conda install pyqt=5 # 安装 pyqt5
pyrcc5 -o resources.py resources.qrc
unset PYTHONPATH # 在当前终端移除 python2 环境变量
python3 python labelImg.py # 打开 labelImg 工具

3.2.2 生成一些文件

在训练自己的数据集时需要生成一些文件和文件夹,网上很多博客提供了步骤,但对于想尽快把代码的跑起来的搞应用的同学不是很友好,这里我编写了一个脚本文件,运行即可生成需要的文件和文件夹。

首先需要将图片和对应的标注文件分别放在 imglabel_xml 文件夹内:

├── img # 存放图片的文件夹
├── label_txt # 存放标签文件的文件夹(标签文件名除了后缀,与图片名相同)
└── generate.py # 用于生成文件和文件夹的预处理脚本

2 directories, 1 file

运行 generate.py 脚本后的项目目录为:

├── backup # 存放训练的中间结果
├── cornell.data # 存放类数量和一些文件(夹)的路径
├── cornell.names # 存放类名称
├── img # 存放图片的文件夹
├── label_txt # 存放标签文件的文件夹(标签文件名除了后缀,与图片名相同)
├── train.txt # 训练集路径
├── valid.txt # 验证集路径
└── generate.py # 用于生成文件和文件夹的预处理脚本

3 directories, 5 files

generate.py 脚本的内容如下:

# 脚本名:generate.py 用于生成文件和文件夹的预处理脚本
import os
import glob
import xml.etree.ElementTree as ET

# 图片的相对路径
img_path = 'img/'

# xml 文件路径
path = 'label_xml/'

# txt 保存路径
txt_save_path = 'label_txt/'
class_file_names_path = 'cornell.names'
file_data_path = 'cornell.data'
train_txt_path = 'train.txt'
valid_txt_path = 'valid.txt'
backup_dir_path = 'backup'

# 类名
class_names = []

# 类名集合
class_set = set()  # 创建空集合


# 查找一个 xml 文件中的 class_name 标签
def find_class_name(filename):
    tree = ET.parse(filename)
    root = tree.getroot()
    for member in root.findall('object'):
        class_name = member[0].text
        class_set.add(class_name)  # 将种类名称加入集合


def find_class_names(path):
    for xml_file in glob.glob(path + '*.xml'):  # 遍历文件夹下所有的 xml 文件
        find_class_name(xml_file)
    # print(class_set)  # 打印所有的标签名称


# 转换一个 xml 文件为 txt
def xml2txt(filename):

    tree = ET.parse(filename)
    root = tree.getroot()

    # 保存的 txt 文件路径
    filename0 = filename.split('.')[0]
    txt_file = txt_save_path + filename0.split('/')[1] + '.txt'
    # print(txt_file)

    with open(txt_file, 'w') as txt_file:
        for member in root.findall('object'):
            picture_width = int(root.find('size')[0].text)
            picture_height = int(root.find('size')[1].text)
            class_name = member[0].text
            # print(class_name)

            # 类名对应的 index
            class_num = class_names.index(class_name)
            box_x_min = int(member[4][0].text)  # 左上角横坐标
            box_y_min = int(member[4][1].text)  # 左上角纵坐标
            box_x_max = int(member[4][2].text)  # 右下角横坐标
            box_y_max = int(member[4][3].text)  # 右下角纵坐标

            # 转成相对位置和宽高
            x_center = (box_x_min + box_x_max) / (2 * picture_width)
            y_center = (box_y_min + box_y_max) / (2 * picture_height)
            width = (box_x_max - box_x_min) / picture_width
            height = (box_y_max - box_y_min) / picture_height
            # print(class_num, x_center, y_center, width, height)

            txt_file.write(
                str(class_num) + ' ' + str(x_center) + ' ' + str(y_center) +
                ' ' + str(width) + ' ' + str(height) + '\n')


# 转换文件夹下所有的 xml 文件为 txt 文件
def dir_xml2txt(path):
    if not os.path.exists('label_txt'):
        os.mkdir('label_txt')
        print('[INFO] Folder label_txt was created...')
    for xml_file in glob.glob(path + '*.xml'):
        xml2txt(xml_file)
    print('[INFO] xml has been transformed to txt...')


# 生成类名文件 .names
def generate_class_name_file(file_name_, class_names_):
    with open(file_name_, 'w') as tf:
        for class_name_ in class_names_:
            tf.write(class_name_ + '\n')
            # print(class_name_)
    print('[INFO] File ' + file_name_ + ' was created...')


# 创建 backup 文件夹
def mk_backup_dir(dir_name):
    if not os.path.exists(dir_name):  # 若不存在 backup 文件夹
        os.mkdir(dir_name)  # 创建 backup 文件夹
    print('[INFO] Folder ' + dir_name + ' was created...')


# 生成用于存放输入路径的 .data 文件
def generate_data_file(data_file, class_size):
    # 相对路径转化为绝对路径
    train_txt_abs_path = os.path.abspath(train_txt_path)
    valid_txt_abs_path = os.path.abspath(valid_txt_path)
    names_abs_path = os.path.abspath(class_file_names_path)
    backup_dir_abs_path = os.path.abspath(backup_dir_path)

    # 写入 .data 文件
    with open(data_file, 'w') as tf:
        tf.write('class = ' + str(class_size) + '\n')
        tf.write('train  = ' + str(train_txt_abs_path) + '\n')
        tf.write('valid  = ' + str(valid_txt_abs_path) + '\n')
        tf.write('names  = ' + str(names_abs_path) + '\n')
        tf.write('backup  = ' + str(backup_dir_abs_path) + '\n')

    print('[INFO] File ' + data_file + ' was created...')


def generate_train_and_val(path, txt_file):
    img_abs_path = os.path.abspath(img_path) + '/'  # 相对路径转化为绝对路径
    with open(txt_file, 'w') as tf:
        for img_file in glob.glob(path + '*.png'):  # 读取文件夹下所有的图片路径
            # print(img_file)
            tf.write(img_file + '\n')  # 图片的绝对路径写入 txt

    print('[INFO] File ' + txt_file + ' was created...')


if __name__ == '__main__':
    find_class_names(path)
    class_names = list(class_set.intersection(class_set))  # 将集合转化为列表
    dir_xml2txt(path)  # 转换文件夹下所有的 xml 文件为 txt 文件
    generate_train_and_val(img_path, 'train.txt')  # 生成存储训练集路径的 txt 文件
    generate_train_and_val(img_path, 'valid.txt')  # 生成存储验证集路径的 txt 文件
    generate_class_name_file(class_file_names_path,
                             class_names)  # 生成类名文件 .names
    mk_backup_dir(backup_dir_path)  # 创建 backup 文件夹
    generate_data_file(file_data_path,
                       len(class_names))  # 生成用于存放输入路径的 .data 文件

yolov3-voc.cfg 复制到自己新建的文件夹 ~/darknet/cornell_detect/ 下,然后修改:

<img src="https://gitee.com/zhang_ma_nong/img_bed/raw/master/img/20210826224238.png" alt="QQ20210826-224217<a href=" https:="" github.com="" 2x"="" title="@2x" class="at-link">@2x">

<img src="https://gitee.com/zhang_ma_nong/img_bed/raw/master/img/20210826224424.png" alt="QQ20210826-224404<a href=" https:="" github.com="" 2x"="" title="@2x" class="at-link">@2x">

<img src="https://gitee.com/zhang_ma_nong/img_bed/raw/master/img/20210826224659.png" alt="QQ20210826-224646<a href=" https:="" github.com="" 2x"="" title="@2x" class="at-link">@2x">

3.2.3 开始训练

cd ~/darknet/ # 进入克隆下来的 darknet 根目录
./darknet detector train cornell_detect/cornell.data  cornell_detect/yolov3-voc.cfg darknet53.conv.74

3.2.4 测试模型

训练过程中,中间结果权重会保存在 ~/darknet/cornell_detect/backup/ 中。

cd ~/darknet/ # 进入克隆下来的 darknet 根目录
./darknet detector test cornell_detect/cornell.data cornell_detect/yolov3-voc.cfg cornell_detect/backup/yolov3-voc_30000.weights ~/桌面/dataset/img/pcd0145r.png # 参数依次是 .data 文件,网络模型,权重文件,图片路径

参考博客

[1] DarkNet-YOLOv3 训练自己的数据集 Ubuntu16.04+cuda8.0