AlexeyAB/darknet (YOLO)的编译 和 作为动态库进行使用 以及 训练 自定义 数据(检测网络和分类网络)

ubuntu 编译

https://github.com/AlexeyAB/darknet
这个库很贴心了,当然,主要是 darknet的实现也很硬核, 自带图像编解码,各种造轮子
文档 把 各种用法都详细列了一遍

但是,还是碰到问题

1 cudnn 的路径
Makefile 里寻找 cudnn路径和通常情况不一样
我改成了

ifeq ($(CUDNN), 1)
COMMON+= -DCUDNN
ifeq ($(OS),Darwin) #MAC
CFLAGS+= -DCUDNN -I/usr/local/cuda/include
LDFLAGS+= -L/usr/local/cuda/lib -lcudnn
else
#CFLAGS+= -DCUDNN -I/usr/local/cudnn/include
CFLAGS+= -DCUDNN -I/usr/local/cuda/include
#LDFLAGS+= -L/usr/local/cudnn/lib64 -lcudnn
LDFLAGS+= -L/usr/local/cuda/lib64 -lcudnn
endif
endif

对了, cuda 版本为cuda_8.0.44_linux.run
cudnn版本为 cudnn-8.0-linux-x64-v6.0.tgz
解压后为:libcudnn.so.6.0.20

一开始用的 cuda9, 运行时 报错,而且谷歌没搜到相关信息

2 opencv 的支持
编译太麻烦了,直接使用

sudo apt-get install libopencv-dev

然后 make 就好了

windows 编译

基本按照
https://github.com/AlexeyAB/darknet#how-to-compile-on-windows-legacy-way
这节 的操作进行, 主要步骤有:
1 打开 sln 文件, 加载失败,然后编辑 darknet.vcxproj 文件,修改里面的opencv 路径为实际路径
重新加载,依然报错
在这里插入图片描述
2 cuda 相关, 之前电脑装了cuda10 , 但没和 vs2015集成, 所以需要把 相关文件拷贝 到上面截图指定的目录
直接使用 everything 搜到 CUDA 10.0.props 所在路径,将路径下 几个文件全部拷贝到 上述截图中指定的路径下面。
关闭 VS ,重新加载 该解决方案

安装cuda失败的话参考 https://blog.csdn.net/zzpong/article/details/80282814

3 开始编译, 报opencv的 一些错误,直接在工程属性中 配置 opencv 的头文件路径 和 库文件路径即可
注意,按照 文档说的, 使用 opencv 3.4.0 比较稳妥

4 编译成功后,按照文档说的,把 几个 dll 拷贝到 darknet.exe 所在路径

动态库封装

Makefile中 LIBSO=1 即可生成 so 文件

然后单独建立一个工程
CMakeLists.txt 内容如下:

cmake_minimum_required(VERSION 2.8)

project(yoloTest)

add_definitions(-std=c++11)
ADD_DEFINITIONS(-DOPENCV)

# find opencv
find_package(OpenCV REQUIRED)
message(${OpenCV_INCLUDE_DIRS})

#这个是 yolo 动态库的路径
find_library(darknet libdarknet.so /home/xx/xxxx/yoloTest/lib/)

include_directories(${OpenCV_INCLUDE_DIRS})
// yolo 代码路径
include_directories(/home/xxx/xxx/darknet/include) 
  
aux_source_directory(./src/ DIR_SRC)

add_executable(yoloTest ./src/yoloTest.cpp)

target_link_libraries(yoloTest ${OpenCV_LIBS} ${darknet})

编写测试代码如下:

#include <iostream>
#include <iomanip>
#include <vector>
#include <fstream>

#include <opencv2/opencv.hpp>            // C++
#include <opencv2/highgui/highgui_c.h>   // C
#include <opencv2/imgproc/imgproc_c.h>   // C
#include "yolo_v2_class.hpp" 

using namespace std;
using namespace cv;

void show_console_result(std::vector<bbox_t> const result_vec, std::vector<std::string> const obj_names, int frame_id = -1) {
    if (frame_id >= 0) std::cout << " Frame: " << frame_id << std::endl;
    for (auto &i : result_vec) {
	if (obj_names.size() > i.obj_id) std::cout << obj_names[i.obj_id] << " - ";
	std::cout << "obj_id = " << i.obj_id << ",  x = " << i.x << ", y = " << i.y
	    << ", w = " << i.w << ", h = " << i.h
	    << std::setprecision(3) << ", prob = " << i.prob << std::endl;
    }
}
void draw_boxes(cv::Mat mat_img, std::vector<bbox_t> result_vec, std::vector<std::string> obj_names,
	int current_det_fps = -1, int current_cap_fps = -1)
{
    int const colors[6][3] = { { 1,0,1 },{ 0,0,1 },{ 0,1,1 },{ 0,1,0 },{ 1,1,0 },{ 1,0,0 } };

    for (auto &i : result_vec) {
	cv::Scalar color = obj_id_to_color(i.obj_id);
	cv::rectangle(mat_img, cv::Rect(i.x, i.y, i.w, i.h), color, 2);
	if (obj_names.size() > i.obj_id) {
	    std::string obj_name = obj_names[i.obj_id];
	    if (i.track_id > 0) obj_name += " - " + std::to_string(i.track_id);
	    cv::Size const text_size = getTextSize(obj_name, cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, 2, 0);
	    int max_width = (text_size.width > i.w + 2) ? text_size.width : (i.w + 2);
	    max_width = std::max(max_width, (int)i.w + 2);
	    //max_width = std::max(max_width, 283);
	    std::string coords_3d;
	    if (!std::isnan(i.z_3d)) {
		std::stringstream ss;
		ss << std::fixed << std::setprecision(2) << "x:" << i.x_3d << "m y:" << i.y_3d << "m z:" << i.z_3d << "m ";
		coords_3d = ss.str();
		cv::Size const text_size_3d = getTextSize(ss.str(), cv::FONT_HERSHEY_COMPLEX_SMALL, 0.8, 1, 0);
		int const max_width_3d = (text_size_3d.width > i.w + 2) ? text_size_3d.width : (i.w + 2);
		if (max_width_3d > max_width) max_width = max_width_3d;
	    }

	    cv::rectangle(mat_img, cv::Point2f(std::max((int)i.x - 1, 0), std::max((int)i.y - 35, 0)),
		    cv::Point2f(std::min((int)i.x + max_width, mat_img.cols - 1), std::min((int)i.y, mat_img.rows - 1)),
		    color, CV_FILLED, 8, 0);
	    putText(mat_img, obj_name, cv::Point2f(i.x, i.y - 16), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(0, 0, 0), 2);
	    if(!coords_3d.empty()) putText(mat_img, coords_3d, cv::Point2f(i.x, i.y-1), cv::FONT_HERSHEY_COMPLEX_SMALL, 0.8, cv::Scalar(0, 0, 0), 1);
	}
    }
    if (current_det_fps >= 0 && current_cap_fps >= 0) {
	std::string fps_str = "FPS detection: " + std::to_string(current_det_fps) + "   FPS capture: " + std::to_string(current_cap_fps);
	putText(mat_img, fps_str, cv::Point2f(10, 20), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, cv::Scalar(50, 255, 0), 2);
    }
}

std::vector<std::string> objects_names_from_file(std::string const filename) {
    std::ifstream file(filename);
    std::vector<std::string> file_lines;
    if (!file.is_open()) return file_lines;
    for(std::string line; getline(file, line);) file_lines.push_back(line);
    std::cout << "object names loaded \n";
    return file_lines;
}

int main(int argc, char ** argv)
{
    std::string  names_file = "data/coco.names";
    std::string  cfg_file = "cfg/yolov3.cfg";
    std::string  weights_file = "yolov3.weights";
    std::string filename;

    if (argc > 4) {    //voc.names yolo-voc.cfg yolo-voc.weights test.mp4
	names_file = argv[1];
	cfg_file = argv[2];
	weights_file = argv[3];
	filename = argv[4];
    }
    else if (argc > 1) 
    {
	filename = argv[1];
    }

    float const thresh = (argc > 5) ? std::stof(argv[5]) : 0.2;

    Detector detector(cfg_file, weights_file);

    auto obj_names = objects_names_from_file(names_file);

    cv::Mat mat_img = cv::imread(filename);

    auto start = std::chrono::steady_clock::now();
    std::vector<bbox_t> result_vec = detector.detect(mat_img);
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> spent = end - start;
    std::cout << " Time: " << spent.count() << " sec \n";

    //result_vec = detector.tracking_id(result_vec);    // comment it - if track_id is not required
    draw_boxes(mat_img, result_vec, obj_names);
    cv::imshow("window name", mat_img);
    show_console_result(result_vec, obj_names);
    cv::waitKey(0);

    return 0;
}

结果如下
在这里插入图片描述

训练

需要准备如下文件

  1. 包含 训练参数 和 网络定义的文件,以 cfg 为扩展名,直接从现有 的 yolov3.cfg 拷贝一个进行修改即可
    修改内容涉及:
    batch=64
    subdivisions=16 // 改成8的时候 显存分配失败
    classes=1 // 这个要根据自己 实际 的检测物体类别数来改,如下3处
    https://github.com/AlexeyAB/darknet/blob/0039fd26786ab5f71d5af725fc18b3f521e7acfd/cfg/yolov3.cfg#L610
    https://github.com/AlexeyAB/darknet/blob/0039fd26786ab5f71d5af725fc18b3f521e7acfd/cfg/yolov3.cfg#L696
    https://github.com/AlexeyAB/darknet/blob/0039fd26786ab5f71d5af725fc18b3f521e7acfd/cfg/yolov3.cfg#L783

    filters=18 // 根据 (classes + 5)x3 来改, 总共有如下3处:
    https://github.com/AlexeyAB/darknet/blob/0039fd26786ab5f71d5af725fc18b3f521e7acfd/cfg/yolov3.cfg#L603
    https://github.com/AlexeyAB/darknet/blob/0039fd26786ab5f71d5af725fc18b3f521e7acfd/cfg/yolov3.cfg#L689
    https://github.com/AlexeyAB/darknet/blob/0039fd26786ab5f71d5af725fc18b3f521e7acfd/cfg/yolov3.cfg#L776

max_batches 和 steps 根据自己的数据量和类别数进行修改

(2020-8-13 15:45:27)
注意,如果是 YoloV2 版本,则是在 region层前面的卷积层修改滤波器个数为 filters=(classes + 5)*5, 这个数字不对的话 会导致什么都检测不到。

  1. obj.names 文件,每行为一个类别的名称, 文件位于 darknet/data 下面
  2. obj.data 文件,文件位于 darknet/data 下面, 内容为
classes= 1 // 和上面类别数一致
train  = data/train.txt  // 内容见下面
valid  = data/test.txt  // 这是训练是验证用的,test.txt 不存在也行
names = data/obj.names  // 上面提到的文件
backup = backup/    // 模型保存目录

这里要注意的是: 模型保持目录一定要手动创建,不然 darknet 是不会去创建的,免得最后训练了很久很久,darknet在控制台最后 输出一个 保持模型失败。。。。

  1. 所有训练用的图片文件保存到 darknet\data\obj\ 这个目录下
  2. 把每个图片的标注信息保存一个 一个同名的 txt 文件中 ,文件格式为:
<object-class> <x_center> <y_center> <width> <height>

总共5个字段,用空格 隔开, 含义分别为:
类别ID , 从0开始计数, 和 前面的obj.names中的顺序是 一一对应的。注意:不是类别的名称!
x_center 和 y_center 是 目标矩形框 中心坐标,
高宽 是 目标矩形框的 高宽
这 4个 坐标都是用 像素坐标 除以 图像的高宽得到的。 X 坐标和 宽 除以 图像宽度, Y坐标 和 高 除以 图像 高度

  1. 创建 train.txt , 这个文件名 来自 前面 obj.data 中的内容, train.txt 主要是 提供训练图片的路径和文件名
    示例为:
data/obj/img1.jpg
data/obj/img2.jpg
data/obj/img3.jpg

这里填 绝对路径也是可以的

  1. 下载 卷积层 的预训练数据文件, weights for the convolutional layers (154 MB): https://pjreddie.com/media/files/darknet53.conv.74 放到 darknet/ 目录下面

  2. 开始训练!

darknet.exe detector train data/obj.data yolo-obj.cfg darknet53.conv.74

效果图为:
在这里插入图片描述
cfg 文件中默认迭代数太多,导致横轴太大, 所以蓝色点都挤在 一条直线上了

对训练出的模型进行测试:

darknet.exe detector test data/obj.data yolo-obj.cfg yolo-obj_last.weights

如果在 训练的时候 要看 mAP 值,那么data文件要加上 valid 指定测试图片,并且使用 -map 参数。

如果单独验证,darknet.exe detector map data/obj.data yolo-obj.cfg backup\yolo-obj_7000.weights -iou_thresh 0.75

过拟合问题

如果误差在 训练过程中 降低 到 0, 那么很可能过拟合了。此时, 如果使用 得到的最终模型,可能什么也检测不到,可以 尝试使用 训练中期得到的模型

有时候训练误差很低,但效果很差,甚至连训练集中的图片都无法正常检测,这时候把学习率提高或者降低 (提高10倍或者降低为1/10)再次训练试试。

分类网络训练步骤

1 创建类别文件,labels.txt,比如一个二分类网络,内容如下:
Dog
Cat

2 指明用于 训练 和 测试的图片数据文件名及其路径:train.list 和 test.list
假设 图片分别在 当前目录下的 train 和 test 目录中, 终端中在当前目录下 执行如下命令即可创建出这两个文件

find `pwd`/train -name \*.png > train.list
find `pwd`/test -name \*.png > test.list

这个地方要注意一个问题:darknet使用 训练图片和 测试图片的全路径(包括文件名) 来判断 该文件所属类别的, 具体代码为:

void fill_truth(char *path, char **labels, int k, float *truth)
{
    int i;
    memset(truth, 0, k*sizeof(float));
    int count = 0;
    for(i = 0; i < k; ++i){
        if(strstr(path, labels[i])){
            truth[i] = 1;
            ++count;
        }
    }
    if(count != 1) printf("Too many or too few labels: %d, %s\n", count, path);
}

所以, 图片文件名中 不能出现 多个或者没有 类别字符串。所以,最好别使用 数字作为类别名称。对于 上面给的两个类别, 1_dog.jpg 或者 Dog_Dog.jpg都是错误的文件名。或者将每个类别的文件放到单独的文件夹中,且文件名不要包含类别名称。

3 创建 data文件,animal.data, 内容为:
classes=2 # 类别数量
train = data/train.list
valid = data/test.list
labels = data/labels.txt
backup = backupAnimal/
top=2

backupAnimal 是 用于保存训练处的模型的目录, 需要手动创建

4 网络配置文件,拷贝一个现有的用于分类的cfg, 比如 cfg目录下的cifar.cfg,复制为animal.cfg
类别数 记得作 对应修改。这个例子里面,最后一个卷积层 里面 filters=10
改为 filters=2

5 开启训练
./darknet classifier train data/animal.data cfg/animal.cfg

6 验证
./darknet classifier valid cfg/cifar.data cfg/cifar_small.cfg backup/cifar_small.weights

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值