YOLOv3:Darknet代码解析(一)安装Darknet

背景: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函数提供不同的种子值,进而产生不同的随机数序列

srand和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

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程










猜你喜欢

转载自blog.csdn.net/weixin_36474809/article/details/80881043