背景:github源码地址 https://github.com/pjreddie/darknet
网站地址:https://pjreddie.com/darknet/
目的:安装Darknet,初步解读darknet源码
1.安装Darknet
1.1 输入以下指令
git clone https://github.com/pjreddie/darknet.git
cd darknet
make
系统会出现下面这些输出:
mkdir -p obj
gcc -I/usr/local/cuda/include/ -Wall -Wfatal-errors -Ofast....
gcc -I/usr/local/cuda/include/ -Wall -Wfatal-errors -Ofast....
gcc -I/usr/local/cuda/include/ -Wall -Wfatal-errors -Ofast....
.....
gcc -I/usr/local/cuda/include/ -Wall -Wfatal-errors -Ofast -lm....
1.1.1 知识:make指令
无论是在linux 还是在Unix环境 中,make都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件,我们都经常要用到make或make install。利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make和 makefile工具就可以简洁明快地理顺各个源文件之间纷繁复杂的相互关系。
而且如此多的源文件,如果每次都要键入gcc命令进行编译的话,那对程序员 来说简直就是一场灾难。而make工具则可自动完成编译工作,并且可以只对程序员在上次编译后修改过的部分进行编译。
编译:把高级语言所书写的代码转换成机器可识别的指令,此时还不能够被执行,编译器通过检查高级语言的语法,函数和变量的声明是否正确!如果正确则产生中间目标文件(目标文件在Liunx中默认后缀为“.o”)
链接:将多.o 文件,或者.o 文件和库文件链接成为可被操作系统执行的可执行程序
静态库:又称为文档文件(Archive File) 。它是多个.o文件的集合。Linux中静态库文件的后缀为“.a”
共享库:也是多个.o 文件的集合,但是这些.o 文件时有编译器按照一种特殊的方式生成(共享库已经具备了可执行条件)
1.2 验证安装完成
输入 ./darknet
系统会输出 usage: ./darknet <function>
下面1.3与1.4为可选项。1.3为运用GUDA进行GPU加速,1.4为运用OpenCV
1.3 运用GUDA
在makefile中把第一行改为 GPU=1,然后make
通过输入nvidia-smi指令可以查看显卡使用情况。
然后可以选择 i <index>选择Darknet使用的显卡。例如:
./darknet -i 1 imagenet test cfg/alexnet.cfg alexnet.weights
也可使用nogpu指令不使用GPU
./darknet -nogpu imagenet test cfg/alexnet.cfg alexnet.weights
1.3.1 知识:nvidia-smi指令
指令中间连着的不加空格。
第一栏的Fan:风扇转速,从0到100%之间变动,这个速度是计算机期望的风扇转速不是实际转速。有的设备不会返回转速,因为它不依赖风扇冷却。
第二栏的Temp:是温度。
第三栏的Perf:是性能状态,从P0到P12,P0表示最大性能,P12表示状态最小性能。
第四栏下方的Pwr:是能耗,上方的Persistence-M:是持续模式的状态,持续模式虽然耗能大,但是在新的GPU应用启动时,花费的时间更少,这里显示的是off的状态。
第五栏的Bus-Id是涉及GPU总线的东西,domain:bus:device.function
第六栏的Disp.A是Display Active,表示GPU的显示是否初始化。
第五第六栏下方的Memory Usage是显存使用率。
第七栏是浮动的GPU利用率。
第八栏上方是关于ECC的东西。
第八栏下方Compute M是计算模式。
1.4 运用OpenCV
通常模式下,Darknet用stb_image.h用于图片加载。如果想用更多格式的图片加载,可以使用OpenCV。需要在makefile中把第二行改为OPENCV=1,然后make。
可以直接输入下列指令验证。
./darknet imtest data/eagle.jpg
输出为一个鹰的图片代表使用成功。
2. YOLO_v3 实时的目标检测
2.1 关于YOLO_v3
YOLOv3非常快速与准确,在IoU设为0.5时测定mAP,相比Focal Loss有4x的速度。并且,在速度与准确率之间tradeoff时,不用重新训练模型。比R-CNN快1000倍,比faster-RCNN快100倍。
2.2 安装与运行
2.2.1 按上面安装好Darknet之后,下载相应的权重
wget https://pjreddie.com/media/files/yolov3.weight
2.2.2 运行相应的detector
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
或者
./darknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights data/dog.jpg
然后回出现类似下面的内容
layer filters size input output
0 conv 32 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 32 0.299 BFLOPs
1 conv 64 3 x 3 / 2 416 x 416 x 32 -> 208 x 208 x 64 1.595 BFLOPs
.......
105 conv 255 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 255 0.353 BFLOPs
106 detection
truth_thresh: Using default '1.000000'
Loading weights from yolov3.weights...Done!
data/dog.jpg: Predicted in 0.029329 seconds.
dog: 99%
truck: 93%
bicycle: 99%
用OpenCV编译会显示相应的图片,没有用OpenCV不会弹出但是会将图片存成predictions.png.运用CPU会有6-12秒运行一副图像,如果用GPU会快很多。
2.2.3 多图像处理
./darknet detect cfg/yolov3.cfg yolov3.weights
layer filters size input output
0 conv 32 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 32 0.299 BFLOPs
1 conv 64 3 x 3 / 2 416 x 416 x 32 -> 208 x 208 x 64 1.595 BFLOPs
.......
104 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 1.595 BFLOPs
105 conv 255 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 255 0.353 BFLOPs
106 detection
Loading weights from yolov3.weights...Done!
Enter Image Path:
2.2.4 更改检测阈值
通常情况下YOLO的检测置信概率阈值为0.25或者更高,我们可以更改,输入-thresh <val>,例如:我们通过下面这行指令把阈值设为0
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg -thresh 0
结果会输出:![][all]
2.3 Tiny YOLOv3
是一个非常小的模型,用于受限运算资源和存储资源的情况,yolov3-tiny.我们FPGA上需要用到这个模型。
2.3.1 下载权重
wget https://pjreddie.com/media/files/yolov3-tiny.weights
运用相应的cfg文件和weights运行
./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg
运行结果
2.4 训练模型 见 https://pjreddie.com/darknet/yolo/
主函数yolo.c
//主函数run_yolo
void run_yolo(int argc, char **argv)
{
char *prefix = find_char_arg(argc, argv, "-prefix", 0);
float thresh = find_float_arg(argc, argv, "-thresh", .2);
int cam_index = find_int_arg(argc, argv, "-c", 0);
int frame_skip = find_int_arg(argc, argv, "-s", 0);
if(argc < 4){
fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]);
return;
}
int avg = find_int_arg(argc, argv, "-avg", 1);
char *cfg = argv[3];
char *weights = (argc > 4) ? argv[4] : 0;
char *filename = (argc > 5) ? argv[5]: 0;
if(0==strcmp(argv[2], "test")) test_yolo(cfg, weights, filename, thresh);
else if(0==strcmp(argv[2], "train")) train_yolo(cfg, weights);
else if(0==strcmp(argv[2], "valid")) validate_yolo(cfg, weights);
else if(0==strcmp(argv[2], "recall")) validate_yolo_recall(cfg, weights);
else if(0==strcmp(argv[2], "demo")) demo(cfg, weights, thresh, cam_index, filename, voc_names, 20, frame_skip, prefix, avg, .5, 0,0,0,0);
}
训练函数
//train_yolo
void train_yolo(char *cfgfile, char *weightfile)
{
char *train_images = "/data/voc/train.txt";
char *backup_directory = "/home/pjreddie/backup/";
srand(time(0));
char *base = basecfg(cfgfile);
printf("%s\n", base);
float avg_loss = -1;
network *net = load_network(cfgfile, weightfile, 0);
printf("Learning Rate: %g, Momentum: %g, Decay: %g\n", net->learning_rate, net->momentum, net->decay);
int imgs = net->batch*net->subdivisions;
int i = *net->seen/imgs;
data train, buffer;
layer l = net->layers[net->n - 1];
int side = l.side;
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 = {0};
args.w = net->w;
args.h = net->h;
args.paths = paths;
args.n = imgs;
args.m = plist->size;
args.classes = classes;
args.jitter = jitter;
args.num_boxes = side;
args.d = &buffer;
args.type = REGION_DATA;
args.angle = net->angle;
args.exposure = net->exposure;
args.saturation = net->saturation;
args.hue = net->hue;
pthread_t load_thread = load_data_in_thread(args);
clock_t time;
//while(i*imgs < N*120){
while(get_current_batch(net) < net->max_batches){
i += 1;
time=clock();
pthread_join(load_thread, 0);
train = buffer;
load_thread = load_data_in_thread(args);
printf("Loaded: %lf seconds\n", sec(clock()-time));
time=clock();
float loss = train_network(net, train);
if (avg_loss < 0) avg_loss = loss;
avg_loss = avg_loss*.9 + loss*.1;
printf("%d: %f, %f avg, %f rate, %lf seconds, %d images\n", i, loss, avg_loss, get_current_rate(net), sec(clock()-time), i*imgs);
if(i%1000==0 || (i < 1000 && i%100 == 0)){
char buff[256];
sprintf(buff, "%s/%s_%d.weights", backup_directory, base, i);
save_weights(net, buff);
}
free_data(train);
}
char buff[256];
sprintf(buff, "%s/%s_final.weights", backup_directory, base);
save_weights(net, buff);
}
1. srand(time(0));
1.1 srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列
1.2 time(0)
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数
在C语言中可以通过time()函数获取到当前的秒数 参数为0则函数返回值即为结果,若参数不为0则结果保存在参数中
1.3 clock()
clock()是C/C++中的计时函数,而与其相关的数据类型是clock_t。clock_t clock(void) ;该程序从启动到函数调用占用CPU的时间。这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock);若挂钟时间不可取,则返回-1。其中clock_t是用来保存时间的数据类型。
//example about clock
#include<time.h>
#include<stdio.h>
#include<windows.h>
int main()
{
int h=0,m=0,s=0;
int t=0;
while(1)
{
t=time(0); //获取总秒数
s=t%60;
m=t%3600/60; //1h= 3600s不足1h的除60即为分钟
h=(t%(24*3600)/3600+8)%24; //1天24h 得到当天小时数+8为东八区区时 避免出现大于24h的情况对24取余
printf("%02d:%02d:%02d\r",h,m,s); // %02d是将数字按宽度为2,采用右对齐方式输出,若数据位数不到2位,则左边补0 \r到当前行最左
//while(t==time(0));
Sleep(1000);//程序执行较快 对时间进行控制 执行挂起1s
}
return 0;
}
2. Thread