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;
}
结果如下
训练
需要准备如下文件
-
包含 训练参数 和 网络定义的文件,以 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#L783filters=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, 这个数字不对的话 会导致什么都检测不到。
- obj.names 文件,每行为一个类别的名称, 文件位于 darknet/data 下面
- obj.data 文件,文件位于 darknet/data 下面, 内容为
classes= 1 // 和上面类别数一致
train = data/train.txt // 内容见下面
valid = data/test.txt // 这是训练是验证用的,test.txt 不存在也行
names = data/obj.names // 上面提到的文件
backup = backup/ // 模型保存目录
这里要注意的是: 模型保持目录一定要手动创建,不然 darknet 是不会去创建的,免得最后训练了很久很久,darknet在控制台最后 输出一个 保持模型失败。。。。
- 所有训练用的图片文件保存到 darknet\data\obj\ 这个目录下
- 把每个图片的标注信息保存一个 一个同名的 txt 文件中 ,文件格式为:
<object-class> <x_center> <y_center> <width> <height>
总共5个字段,用空格 隔开, 含义分别为:
类别ID , 从0开始计数, 和 前面的obj.names中的顺序是 一一对应的。注意:不是类别的名称!
x_center 和 y_center 是 目标矩形框 中心坐标,
高宽 是 目标矩形框的 高宽
这 4个 坐标都是用 像素坐标 除以 图像的高宽得到的。 X 坐标和 宽 除以 图像宽度, Y坐标 和 高 除以 图像 高度
- 创建 train.txt , 这个文件名 来自 前面 obj.data 中的内容, train.txt 主要是 提供训练图片的路径和文件名
示例为:
data/obj/img1.jpg
data/obj/img2.jpg
data/obj/img3.jpg
这里填 绝对路径也是可以的
-
下载 卷积层 的预训练数据文件, weights for the convolutional layers (154 MB): https://pjreddie.com/media/files/darknet53.conv.74 放到 darknet/ 目录下面
-
开始训练!
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