yolo目标检测_yolo目标检测统计车流量 - CSDN
精华内容
参与话题
  • YOLO实战:训练自己的手势识别模型

    千人学习 2020-01-17 10:41:21
    本人后续还会推出将手势识别模型,安全帽识别模型,openpose人体姿态识别模型等目标检测识别模型移植到华为海思3516DV300系列低成本的嵌入式板子上的相关课程,加速人工智能的落地应用,请感兴趣的同学关注后续课程...
  • Yolo 目标检测总结帖(yolov3,yolov2)

    万次阅读 热门讨论 2020-08-25 12:46:49
    由于项目的需求,需要完成一个目标检测的任务,经过个人一段时间的实践,现将自己实现的功能以及体验过的事情在这里做个总结,以便后续查看,也让其它人少走一些弯路,在这个过程中参考了一些博客,便于入门与提升。...

     

    由于项目的需求,需要完成一个目标检测的任务,经过个人一段时间的实践,现将自己实现的功能以及体验过的事情在这里做个总结,以便后续查看,也让其它人少走一些弯路,在这个过程中参考了一些博客,便于入门与提升。

    个人将大多数的时间花费在yolov3上,其精度效果会比yolov2的效果要好,但仿真和测试时间会花费一倍左右的时间,并且将yolov3的过程弄明白之后,yolov2如何跑动只是更改部分参数和预训练模型罢了。注意,如果想训练自己的数据集,最好是有一台带GPU的服务器,一般运行到3w次左右其损失值会下降到0.0x的量级,CPU跑一个batchsize很慢,不建议使用CPU训练,但可以使用CPU进行测试,使用CPU进行测试时有个小技巧,能够加快一倍的测试时间。

    文章分为以下几个部分:

    1.准备工具

    软硬件环境:本地MacBook Pro,阿里云服务器(P100显卡)

    1.1 yolo网络下载

    yolo官方网站:https://pjreddie.com/darknet/yolo/

    github项目地址:https://github.com/pjreddie/darknet/tree/master/data

    1.2 labelImg(有缺陷)

    github项目地址: https://github.com/tzutalin/labelImg

    2.安装

    2.1 yolo包的安装

    参考官方文档:https://pjreddie.com/darknet/install/

    2.1.1 CPU版本

    git clone https://github.com/pjreddie/darknet.git
    cd darkness
    make

    这里如果自己的电脑支持Openmp的话,也可以更改Makefile文件将其中的OPENMP的值更改为1,会加快训练和测试速度

    GPU=0
    CUDNN=0  
    OPENCV=0
    OPENMP=0 # 若电脑支持Openmp时,可以将其设置为1
    DEBUG=0

    2.1.2 GPU版本

    git clone https://github.com/pjreddie/darknet.git
    cd darknet
    vim Makefile
    make

    对于GPU的Makefile更改的地方较多:

    GPU=1 # 设置为1
    CUDNN=1 # 设置为1
    OPENCV=0 # 若后续想用opencv进行处理,可以将其设置为1
    OPENMP=0
    DEBUG=0
    
    ARCH= -gencode arch=compute_30,code=sm_30 \
          -gencode arch=compute_35,code=sm_35 \
          -gencode arch=compute_50,code=[sm_50,compute_50] \
          -gencode arch=compute_52,code=[sm_52,compute_52] \   
          -gencode arch=compute_60,code=[sm_60,compute_60]                                                                                                                                                                                                       # 这个地方是根据自己的GPU架构进行设置,不同架构的GPU的运算能力不一样,本文使用的是帕斯卡结构,查阅英伟达官网查看对应的计算能力为6.0            #  -gencode arch=compute_20,code=[sm_20,sm_21] \ This one is deprecated?                                                   # This is what I use, uncomment if you know your arch and want to specify
    # ARCH= -gencode arch=compute_52,code=compute_52
    VPATH=./src/:./examples
    SLIB=libdarknet.so
    ALIB=libdarknet.a
    EXEC=darknet
    OBJDIR=./obj/
    CC=gcc
    NVCC=nvcc # 这个地方若没有定义为环境变量,最好是使用绝对路径,大概位于`/usr/local/cuda/bin/nvcc`

    对于GPU版本的安装,需要根据对应的地方更改Makefile文件。

    2.2 labelImg的安装

    两种安装方式:

    2.2.1 文件包安装的方式:

    labelImg的文件包安装见github的地址:https://github.com/tzutalin/labelImg

    2.2.2 pip安装:

    pip install labelImg
    or 
    brew install labelImg

    注意,经过实践,发现labelImg对.png格式图像不友好,不支持对.png图像的标注,即使标注出来其标签文件也不对。

    3.数据集的准备与制作

    数据集的准备安装网上教程即可:

    3.1 数据集标注

    labelImg的使用方法一些博客都有讲解:参考博客 https://blog.csdn.net/xunan003/article/details/78720189/

    有几个关键的地方需要强调一下:

    OpenDir 是要标注图像的文件地址

    Change Save Dir 是修改保存标记文件的地址

    Next Image 标注完点击这个进行下一张的标注

    Prev Image 想查看之前标注的情况

    PascalVOC/YOLO 这个可选,前一种是得到的格式为xml的标签文件,后一种是直接得到格式为txt的标签文件,后一种适用于YOLO网络,前一种适合RCNN系列文章,根据自身选择,本文由于之前尝试过使用RCNN系列模型,就先标记为xml文件,这里不用担心,darknet提供了转换程序./scripts/voc_label.py。

    3.2 数据集xml转成yolo

    对于使用tensorfloe-objection detection api的人来说,标签格式是xml,好在darknet中提供了将xml格式的标签转换为txt标签的函数,darknet提供了转换程序./scripts/voc_label.py。注意这里需要修改的地方:

    import xml.etree.ElementTree as ET
    import pickle
    import os
    from os import listdir, getcwd
    from os.path import join
    
    # sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')]
    # 前一个表示年份,后一个表示训练或测试集文件
    sets=[('2007', 'train'),('2007', 'test')]
    # classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
    classes = ["1", "2", "3"]
    # classes表示自己的类别名称
    
    
    def convert(size, box):
        dw = 1./(size[0])
        dh = 1./(size[1])
        x = (box[0] + box[1])/2.0 - 1
        y = (box[2] + box[3])/2.0 - 1
        w = box[1] - box[0]
        h = box[3] - box[2]
        x = x*dw
        w = w*dw
        y = y*dh
        h = h*dh
        return (x,y,w,h)
    
    def convert_annotation(year, image_id):
        in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
        out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w')
        tree=ET.parse(in_file)
        root = tree.getroot()
        size = root.find('size')
        w = int(size.find('width').text)
        h = int(size.find('height').text)
    
        for obj in root.iter('object'):
            difficult = obj.find('difficult').text
            cls = obj.find('name').text
            if cls not in classes or int(difficult)==1:
                continue
            cls_id = classes.index(cls)
            xmlbox = obj.find('bndbox')
            b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
            bb = convert((w,h), b)
            out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
    
    wd = getcwd()
    
    for year, image_set in sets:
        if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)):
            os.makedirs('VOCdevkit/VOC%s/labels/'%(year))
        image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()
        list_file = open('%s_%s.txt'%(year, image_set), 'w')
        for image_id in image_ids:
            list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
            convert_annotation(year, image_id)
        list_file.close()
    
    #这里将最后两行注射掉,运行后得到的训练集和测试集 组合在一起是整个数据集,而不是将训练集和测试集和一块作为训练集
    #os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt")
    #os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt")
    

    运行之后,在./scripts文件夹就得到训练集和测试集txt

    对应的label文件夹中有了转换好的txt格式的label:

    4. 网络模型的训练与测试

    4.1 网络模型的训练

    4.1.1 需要更改的地方

    修改cfg/voc.data

    # 注意路径,相对路径和绝对路径都可以
    classes= n #类别数为n 你分几类就将n设置为几
    train  = ./scripts/2007_train.txt #对应刚才生成的训练集txt
    valid  = ./scripts/2007_test.txt 
    names = data/voc.names
    backup = ./results/  #网络模型训练好的参数保存路径

    修改data/voc.names

    #在这个地方输入你标签的名称,每类一行,比如我分三类,分别为“ni”,"hao","ma",则下面是
    ni
    has
    ma

    修改cfg/yolov3-voc.cfg 网络模型参数

    [net]
    # 将头部代码更改为train,batch数量根据你自身的电脑能力设置,默认设置是64
    # Testing
    # batch=1
    #  subdivisions=1
    # Training
    batch=64
    subdivisions=16
    width=416
    height=416
    channels=3
    momentum=0.9
    decay=0.0005
    angle=0
    saturation = 1.5
    exposure = 1.5
    hue=.1
    
    learning_rate=0.001
    burn_in=1000
    max_batches = 50200 #最大迭代batches数
    policy=steps
    steps=20000,35000 # 每迭代多少次改变一次学习率,这里是*0.1
    scales=.1,.1
    
    
    [convolutional]
    size=1
    stride=1
    pad=1
    filters=24 #这里filters数量更改,与类别有关,一般公式是(classes_nums + 5) *3 
    activation=linear
    
    [yolo]
    mask = 0,1,2
    anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
    classes=3 # 修改成你自己的类别数
    num=9
    jitter=.3
    ignore_thresh = .5
    truth_thresh = 1
    random=0

    4.1.2 预训练模型训练

    目前都是使用迁移学习,将成熟网络的部分参数直接用过来,这里也一样:

    下装与训练模型: 

    wget https://pjreddie.com/media/files/darknet53.conv.74

    训练:

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

    4.1.3 中断后继续训练

    当训练进行到一半的时候,可能中途中断或者是停了想继续进行时,只需将上面的语句最后的预训练权重更换为之前在voc.data中设置的模型训练保存路径中存在的权重即可,这里以yolov3.weights表示:

    ./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg results/yolov3.weights

    4.2 网络模型的测试

    4.2.1 单张测试

    单张测试就是指定一张图像名称进行测试,可类似于darknet网站中给定的例子那样,只不过需要修改相关路径及被测图片名称:

    ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
    ./darknet detect cfg/yolov3.cfg result/yolov3.weights /path/to/your picture

    4.2.2 批量测试

    如果想进行批量测试,则需要修改对应的源码,参考博客 https://blog.csdn.net/mieleizhi0522/article/details/79989754

    但存在一个问题是无法将检测后的图像保存时,其名称与原始名称一样,有时候出错为null,在其基础上对其GetFilename函数进行修改。

    #include "darknet.h"
    
    static int coco_ids[] = {1,2,3,4,5,6,7,8,9,10,11,13,14,15,16,17,18,19,20,21,22,23,24,25,27,28,31,32,33,34,35,36,37,38,39,40,41,42,43,44,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,70,72,73,74,75,76,77,78,79,80,81,82,84,85,86,87,88,89,90};
    //获取文件的名字
    
    
    char* GetFilename(char *fullname)
    {
        int from,to,i;
        char *newstr,*temp;
        if(fullname!=NULL)
        {
            if((temp=strchr(fullname,'.'))==NULL)//if not find dot
            newstr = fullname;
            else
            {
                from = strlen(fullname) - 1;
                to = (temp-fullname);  //the first dot's index;
                for(i=from;i--;i<=to)
                if(fullname[i]=='.')break;//find the last dot
                newstr = (char*)malloc(i+1);
                strncpy(newstr,fullname,i);
                *(newstr+i)=0;
            }
        }
        char name[50] = {""};
        char *q = strrchr(newstr,'/') + 1;
        strncpy(name,q,40);
        return name;
    }
    
    
    
    void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear)
    {
        list *options = read_data_cfg(datacfg);
        char *train_images = option_find_str(options, "train", "data/train.list");
        char *backup_directory = option_find_str(options, "backup", "/backup/");
    
        srand(time(0));
        char *base = basecfg(cfgfile);
        printf("%s\n", base);
        float avg_loss = -1;
        network **nets = calloc(ngpus, sizeof(network));
    
        srand(time(0));
        int seed = rand();
        int i;
        for(i = 0; i < ngpus; ++i){
            srand(seed);
    #ifdef GPU
            cuda_set_device(gpus[i]);
    #endif
            nets[i] = load_network(cfgfile, weightfile, clear);
            nets[i]->learning_rate *= ngpus;
        }
        srand(time(0));
        network *net = nets[0];
    
        int imgs = net->batch * net->subdivisions * ngpus;
        printf("Learning Rate: %g, Momentum: %g, Decay: %g\n", net->learning_rate, net->momentum, net->decay);
        data train, buffer;
    
        layer l = net->layers[net->n - 1];
    
        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 = get_base_args(net);
        args.coords = l.coords;
        args.paths = paths;
        args.n = imgs;
        args.m = plist->size;
        args.classes = classes;
        args.jitter = jitter;
        args.num_boxes = l.max_boxes;
        args.d = &buffer;
        args.type = DETECTION_DATA;
        //args.type = INSTANCE_DATA;
        args.threads = 64;
    
        pthread_t load_thread = load_data(args);
        double time;
        int count = 0;
        //while(i*imgs < N*120){
        while(get_current_batch(net) < net->max_batches){
            if(l.random && count++%10 == 0){
                printf("Resizing\n");
                int dim = (rand() % 10 + 10) * 32;
                if (get_current_batch(net)+200 > net->max_batches) dim = 608;
                //int dim = (rand() % 4 + 16) * 32;
                printf("%d\n", dim);
                args.w = dim;
                args.h = dim;
    
                pthread_join(load_thread, 0);
                train = buffer;
                free_data(train);
                load_thread = load_data(args);
    
                #pragma omp parallel for
                for(i = 0; i < ngpus; ++i){
                    resize_network(nets[i], dim, dim);
                }
                net = nets[0];
            }
            time=what_time_is_it_now();
            pthread_join(load_thread, 0);
            train = buffer;
            load_thread = load_data(args);
    
            /*
               int k;
               for(k = 0; k < l.max_boxes; ++k){
               box b = float_to_box(train.y.vals[10] + 1 + k*5);
               if(!b.x) break;
               printf("loaded: %f %f %f %f\n", b.x, b.y, b.w, b.h);
               }
             */
            /*
               int zz;
               for(zz = 0; zz < train.X.cols; ++zz){
               image im = float_to_image(net->w, net->h, 3, train.X.vals[zz]);
               int k;
               for(k = 0; k < l.max_boxes; ++k){
               box b = float_to_box(train.y.vals[zz] + k*5, 1);
               printf("%f %f %f %f\n", b.x, b.y, b.w, b.h);
               draw_bbox(im, b, 1, 1,0,0);
               }
               show_image(im, "truth11");
               cvWaitKey(0);
               save_image(im, "truth11");
               }
             */
    
            printf("Loaded: %lf seconds\n", what_time_is_it_now()-time);
    
            time=what_time_is_it_now();
            float loss = 0;
    #ifdef GPU
            if(ngpus == 1){
                loss = train_network(net, train);
            } else {
                loss = train_networks(nets, ngpus, train, 4);
            }
    #else
            loss = train_network(net, train);
    #endif
            if (avg_loss < 0) avg_loss = loss;
            avg_loss = avg_loss*.9 + loss*.1;
    
            i = get_current_batch(net);
            printf("%ld: %f, %f avg, %f rate, %lf seconds, %d images\n", get_current_batch(net), loss, avg_loss, get_current_rate(net), what_time_is_it_now()-time, i*imgs);
            if(i%100==0){
    #ifdef GPU
                if(ngpus != 1) sync_nets(nets, ngpus, 0);
    #endif
                char buff[256];
                sprintf(buff, "%s/%s.backup", backup_directory, base);
                save_weights(net, buff);
            }
            if(i%10000==0 || (i < 1000 && i%100 == 0)){
    #ifdef GPU
                if(ngpus != 1) sync_nets(nets, ngpus, 0);
    #endif
                char buff[256];
                sprintf(buff, "%s/%s_%d.weights", backup_directory, base, i);
                save_weights(net, buff);
            }
            free_data(train);
        }
    #ifdef GPU
        if(ngpus != 1) sync_nets(nets, ngpus, 0);
    #endif
        char buff[256];
        sprintf(buff, "%s/%s_final.weights", backup_directory, base);
        save_weights(net, buff);
    }
    
    
    static int get_coco_image_id(char *filename)
    {
        char *p = strrchr(filename, '/');
        char *c = strrchr(filename, '_');
        if(c) p = c;
        return atoi(p+1);
    }
    
    static void print_cocos(FILE *fp, char *image_path, detection *dets, int num_boxes, int classes, int w, int h)
    {
        int i, j;
        int image_id = get_coco_image_id(image_path);
        for(i = 0; i < num_boxes; ++i){
            float xmin = dets[i].bbox.x - dets[i].bbox.w/2.;
            float xmax = dets[i].bbox.x + dets[i].bbox.w/2.;
            float ymin = dets[i].bbox.y - dets[i].bbox.h/2.;
            float ymax = dets[i].bbox.y + dets[i].bbox.h/2.;
    
            if (xmin < 0) xmin = 0;
            if (ymin < 0) ymin = 0;
            if (xmax > w) xmax = w;
            if (ymax > h) ymax = h;
    
            float bx = xmin;
            float by = ymin;
            float bw = xmax - xmin;
            float bh = ymax - ymin;
    
            for(j = 0; j < classes; ++j){
                if (dets[i].prob[j]) fprintf(fp, "{\"image_id\":%d, \"category_id\":%d, \"bbox\":[%f, %f, %f, %f], \"score\":%f},\n", image_id, coco_ids[j], bx, by, bw, bh, dets[i].prob[j]);
            }
        }
    }
    
    void print_detector_detections(FILE **fps, char *id, detection *dets, int total, int classes, int w, int h)
    {
        int i, j;
        for(i = 0; i < total; ++i){
            float xmin = dets[i].bbox.x - dets[i].bbox.w/2. + 1;
            float xmax = dets[i].bbox.x + dets[i].bbox.w/2. + 1;
            float ymin = dets[i].bbox.y - dets[i].bbox.h/2. + 1;
            float ymax = dets[i].bbox.y + dets[i].bbox.h/2. + 1;
    
            if (xmin < 1) xmin = 1;
            if (ymin < 1) ymin = 1;
            if (xmax > w) xmax = w;
            if (ymax > h) ymax = h;
    
            for(j = 0; j < classes; ++j){
                if (dets[i].prob[j]) fprintf(fps[j], "%s %f %f %f %f %f\n", id, dets[i].prob[j],
                        xmin, ymin, xmax, ymax);
            }
        }
    }
    
    void print_imagenet_detections(FILE *fp, int id, detection *dets, int total, int classes, int w, int h)
    {
        int i, j;
        for(i = 0; i < total; ++i){
            float xmin = dets[i].bbox.x - dets[i].bbox.w/2.;
            float xmax = dets[i].bbox.x + dets[i].bbox.w/2.;
            float ymin = dets[i].bbox.y - dets[i].bbox.h/2.;
            float ymax = dets[i].bbox.y + dets[i].bbox.h/2.;
    
            if (xmin < 0) xmin = 0;
            if (ymin < 0) ymin = 0;
            if (xmax > w) xmax = w;
            if (ymax > h) ymax = h;
    
            for(j = 0; j < classes; ++j){
                int class = j;
                if (dets[i].prob[class]) fprintf(fp, "%d %d %f %f %f %f %f\n", id, j+1, dets[i].prob[class],
                        xmin, ymin, xmax, ymax);
            }
        }
    }
    
    void validate_detector_flip(char *datacfg, char *cfgfile, char *weightfile, char *outfile)
    {
        int j;
        list *options = read_data_cfg(datacfg);
        char *valid_images = option_find_str(options, "valid", "data/train.list");
        char *name_list = option_find_str(options, "names", "data/names.list");
        char *prefix = option_find_str(options, "results", "results");
        char **names = get_labels(name_list);
        char *mapf = option_find_str(options, "map", 0);
        int *map = 0;
        if (mapf) map = read_map(mapf);
    
        network *net = load_network(cfgfile, weightfile, 0);
        set_batch_network(net, 2);
        fprintf(stderr, "Learning Rate: %g, Momentum: %g, Decay: %g\n", net->learning_rate, net->momentum, net->decay);
        srand(time(0));
    
        list *plist = get_paths(valid_images);
        char **paths = (char **)list_to_array(plist);
    
        layer l = net->layers[net->n-1];
        int classes = l.classes;
    
        char buff[1024];
        char *type = option_find_str(options, "eval", "voc");
        FILE *fp = 0;
        FILE **fps = 0;
        int coco = 0;
        int imagenet = 0;
        if(0==strcmp(type, "coco")){
            if(!outfile) outfile = "coco_results";
            snprintf(buff, 1024, "%s/%s.json", prefix, outfile);
            fp = fopen(buff, "w");
            fprintf(fp, "[\n");
            coco = 1;
        } else if(0==strcmp(type, "imagenet")){
            if(!outfile) outfile = "imagenet-detection";
            snprintf(buff, 1024, "%s/%s.txt", prefix, outfile);
            fp = fopen(buff, "w");
            imagenet = 1;
            classes = 200;
        } else {
            if(!outfile) outfile = "comp4_det_test_";
            fps = calloc(classes, sizeof(FILE *));
            for(j = 0; j < classes; ++j){
                snprintf(buff, 1024, "%s/%s%s.txt", prefix, outfile, names[j]);
                fps[j] = fopen(buff, "w");
            }
        }
    
        int m = plist->size;
        int i=0;
        int t;
    
        float thresh = .005;
        float nms = .45;
    
        int nthreads = 4;
        image *val = calloc(nthreads, sizeof(image));
        image *val_resized = calloc(nthreads, sizeof(image));
        image *buf = calloc(nthreads, sizeof(image));
        image *buf_resized = calloc(nthreads, sizeof(image));
        pthread_t *thr = calloc(nthreads, sizeof(pthread_t));
    
        image input = make_image(net->w, net->h, net->c*2);
    
        load_args args = {0};
        args.w = net->w;
        args.h = net->h;
        //args.type = IMAGE_DATA;
        args.type = LETTERBOX_DATA;
    
        for(t = 0; t < nthreads; ++t){
            args.path = paths[i+t];
            args.im = &buf[t];
            args.resized = &buf_resized[t];
            thr[t] = load_data_in_thread(args);
        }
        double start = what_time_is_it_now();
        for(i = nthreads; i < m+nthreads; i += nthreads){
            fprintf(stderr, "%d\n", i);
            for(t = 0; t < nthreads && i+t-nthreads < m; ++t){
                pthread_join(thr[t], 0);
                val[t] = buf[t];
                val_resized[t] = buf_resized[t];
            }
            for(t = 0; t < nthreads && i+t < m; ++t){
                args.path = paths[i+t];
                args.im = &buf[t];
                args.resized = &buf_resized[t];
                thr[t] = load_data_in_thread(args);
            }
            for(t = 0; t < nthreads && i+t-nthreads < m; ++t){
                char *path = paths[i+t-nthreads];
                char *id = basecfg(path);
                copy_cpu(net->w*net->h*net->c, val_resized[t].data, 1, input.data, 1);
                flip_image(val_resized[t]);
                copy_cpu(net->w*net->h*net->c, val_resized[t].data, 1, input.data + net->w*net->h*net->c, 1);
    
                network_predict(net, input.data);
                int w = val[t].w;
                int h = val[t].h;
                int num = 0;
                detection *dets = get_network_boxes(net, w, h, thresh, .5, map, 0, &num);
                if (nms) do_nms_sort(dets, num, classes, nms);
                if (coco){
                    print_cocos(fp, path, dets, num, classes, w, h);
                } else if (imagenet){
                    print_imagenet_detections(fp, i+t-nthreads+1, dets, num, classes, w, h);
                } else {
                    print_detector_detections(fps, id, dets, num, classes, w, h);
                }
                free_detections(dets, num);
                free(id);
                free_image(val[t]);
                free_image(val_resized[t]);
            }
        }
        for(j = 0; j < classes; ++j){
            if(fps) fclose(fps[j]);
        }
        if(coco){
            fseek(fp, -2, SEEK_CUR); 
            fprintf(fp, "\n]\n");
            fclose(fp);
        }
        fprintf(stderr, "Total Detection Time: %f Seconds\n", what_time_is_it_now() - start);
    }
    
    
    void validate_detector(char *datacfg, char *cfgfile, char *weightfile, char *outfile)
    {
        int j;
        list *options = read_data_cfg(datacfg);
        char *valid_images = option_find_str(options, "valid", "data/train.list");
        char *name_list = option_find_str(options, "names", "data/names.list");
        char *prefix = option_find_str(options, "results", "results");
        char **names = get_labels(name_list);
        char *mapf = option_find_str(options, "map", 0);
        int *map = 0;
        if (mapf) map = read_map(mapf);
    
        network *net = load_network(cfgfile, weightfile, 0);
        set_batch_network(net, 1);
        fprintf(stderr, "Learning Rate: %g, Momentum: %g, Decay: %g\n", net->learning_rate, net->momentum, net->decay);
        srand(time(0));
    
        list *plist = get_paths(valid_images);
        char **paths = (char **)list_to_array(plist);
    
        layer l = net->layers[net->n-1];
        int classes = l.classes;
    
        char buff[1024];
        char *type = option_find_str(options, "eval", "voc");
        FILE *fp = 0;
        FILE **fps = 0;
        int coco = 0;
        int imagenet = 0;
        if(0==strcmp(type, "coco")){
            if(!outfile) outfile = "coco_results";
            snprintf(buff, 1024, "%s/%s.json", prefix, outfile);
            fp = fopen(buff, "w");
            fprintf(fp, "[\n");
            coco = 1;
        } else if(0==strcmp(type, "imagenet")){
            if(!outfile) outfile = "imagenet-detection";
            snprintf(buff, 1024, "%s/%s.txt", prefix, outfile);
            fp = fopen(buff, "w");
            imagenet = 1;
            classes = 200;
        } else {
            if(!outfile) outfile = "comp4_det_test_";
            fps = calloc(classes, sizeof(FILE *));
            for(j = 0; j < classes; ++j){
                snprintf(buff, 1024, "%s/%s%s.txt", prefix, outfile, names[j]);
                fps[j] = fopen(buff, "w");
            }
        }
    
    
        int m = plist->size;
        int i=0;
        int t;
    
        float thresh = .005;
        float nms = .45;
    
        int nthreads = 4;
        image *val = calloc(nthreads, sizeof(image));
        image *val_resized = calloc(nthreads, sizeof(image));
        image *buf = calloc(nthreads, sizeof(image));
        image *buf_resized = calloc(nthreads, sizeof(image));
        pthread_t *thr = calloc(nthreads, sizeof(pthread_t));
    
        load_args args = {0};
        args.w = net->w;
        args.h = net->h;
        //args.type = IMAGE_DATA;
        args.type = LETTERBOX_DATA;
    
        for(t = 0; t < nthreads; ++t){
            args.path = paths[i+t];
            args.im = &buf[t];
            args.resized = &buf_resized[t];
            thr[t] = load_data_in_thread(args);
        }
        double start = what_time_is_it_now();
        for(i = nthreads; i < m+nthreads; i += nthreads){
            fprintf(stderr, "%d\n", i);
            for(t = 0; t < nthreads && i+t-nthreads < m; ++t){
                pthread_join(thr[t], 0);
                val[t] = buf[t];
                val_resized[t] = buf_resized[t];
            }
            for(t = 0; t < nthreads && i+t < m; ++t){
                args.path = paths[i+t];
                args.im = &buf[t];
                args.resized = &buf_resized[t];
                thr[t] = load_data_in_thread(args);
            }
            for(t = 0; t < nthreads && i+t-nthreads < m; ++t){
                char *path = paths[i+t-nthreads];
                char *id = basecfg(path);
                float *X = val_resized[t].data;
                network_predict(net, X);
                int w = val[t].w;
                int h = val[t].h;
                int nboxes = 0;
                detection *dets = get_network_boxes(net, w, h, thresh, .5, map, 0, &nboxes);
                if (nms) do_nms_sort(dets, nboxes, classes, nms);
                if (coco){
                    print_cocos(fp, path, dets, nboxes, classes, w, h);
                } else if (imagenet){
                    print_imagenet_detections(fp, i+t-nthreads+1, dets, nboxes, classes, w, h);
                } else {
                    print_detector_detections(fps, id, dets, nboxes, classes, w, h);
                }
                free_detections(dets, nboxes);
                free(id);
                free_image(val[t]);
                free_image(val_resized[t]);
            }
        }
        for(j = 0; j < classes; ++j){
            if(fps) fclose(fps[j]);
        }
        if(coco){
            fseek(fp, -2, SEEK_CUR); 
            fprintf(fp, "\n]\n");
            fclose(fp);
        }
        fprintf(stderr, "Total Detection Time: %f Seconds\n", what_time_is_it_now() - start);
    }
    
    void validate_detector_recall(char *datacfg, char *cfgfile, char *weightfile)
    {
        network *net = load_network(cfgfile, weightfile, 0);
        set_batch_network(net, 1);
        fprintf(stderr, "Learning Rate: %g, Momentum: %g, Decay: %g\n", net->learning_rate, net->momentum, net->decay);
        srand(time(0));
    
        list *options = read_data_cfg(datacfg);
        char *valid_images = option_find_str(options, "valid", "data/train.list");
        list *plist = get_paths(valid_images);
        char **paths = (char **)list_to_array(plist);
    
        layer l = net->layers[net->n-1];
    
        int j, k;
    
        int m = plist->size;
        int i=0;
    
        float thresh = .001;
        float iou_thresh = .5;
        float nms = .4;
    
        int total = 0;
        int correct = 0;
        int proposals = 0;
        float avg_iou = 0;
    
        for(i = 0; i < m; ++i){
            char *path = paths[i];
            image orig = load_image_color(path, 0, 0);
            image sized = resize_image(orig, net->w, net->h);
            char *id = basecfg(path);
            network_predict(net, sized.data);
            int nboxes = 0;
            detection *dets = get_network_boxes(net, sized.w, sized.h, thresh, .5, 0, 1, &nboxes);
            if (nms) do_nms_obj(dets, nboxes, 1, nms);
    
            char labelpath[4096];
            find_replace(path, "images", "labels", labelpath);
            find_replace(labelpath, "JPEGImages", "labels", labelpath);
            find_replace(labelpath, ".jpg", ".txt", labelpath);
            find_replace(labelpath, ".JPEG", ".txt", labelpath);
    
            int num_labels = 0;
            box_label *truth = read_boxes(labelpath, &num_labels);
            for(k = 0; k < nboxes; ++k){
                if(dets[k].objectness > thresh){
                    ++proposals;
                }
            }
            for (j = 0; j < num_labels; ++j) {
                ++total;
                box t = {truth[j].x, truth[j].y, truth[j].w, truth[j].h};
                float best_iou = 0;
                for(k = 0; k < l.w*l.h*l.n; ++k){
                    float iou = box_iou(dets[k].bbox, t);
                    if(dets[k].objectness > thresh && iou > best_iou){
                        best_iou = iou;
                    }
                }
                avg_iou += best_iou;
                if(best_iou > iou_thresh){
                    ++correct;
                }
            }
    
            fprintf(stderr, "%5d %5d %5d\tRPs/Img: %.2f\tIOU: %.2f%%\tRecall:%.2f%%\n", i, correct, total, (float)proposals/(i+1), avg_iou*100/total, 100.*correct/total);
            free(id);
            free_image(orig);
            free_image(sized);
        }
    }
    
    void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen)
    {
        list *options = read_data_cfg(datacfg);
        char *name_list = option_find_str(options, "names", "data/names.list");
        char **names = get_labels(name_list);
        
        image **alphabet = load_alphabet();
        network *net = load_network(cfgfile, weightfile, 0);
        set_batch_network(net, 1);
        srand(2222222);
        double time;
        char buff[256];
        char *input = buff;
        float nms=.45;
        int i=0;
        while(1){
            if(filename){
                strncpy(input, filename, 256);
                image im = load_image_color(input,0,0);
                image sized = letterbox_image(im, net->w, net->h);
                //image sized = resize_image(im, net->w, net->h);
                //image sized2 = resize_max(im, net->w);
                //image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
                //resize_network(net, sized.w, sized.h);
                layer l = net->layers[net->n-1];
                
                
                float *X = sized.data;
                time=what_time_is_it_now();
                network_predict(net, X);
                printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time);
                int nboxes = 0;
                detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
                //printf("%d\n", nboxes);
                //if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
                if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
                draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes);
                free_detections(dets, nboxes);
                if(outfile)
                {
                    save_image(im, outfile);
                }
                else{
                    //save_image(im, "predictions");
                    char image[2048];
                    sprintf(image,"./data/predict/%s",GetFilename(filename));
                    save_image(im,image);
                    printf("predict %s successfully!\n",GetFilename(filename));
    #ifdef OPENCV
                    cvNamedWindow("predictions", CV_WINDOW_NORMAL);
                    if(fullscreen){
                        cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
                    }
                    show_image(im, "predictions");
                    cvWaitKey(0);
                    cvDestroyAllWindows();
    #endif
                }
                free_image(im);
                free_image(sized);
                if (filename) break;
            }
            else {
                printf("Enter Image Path: ");
                fflush(stdout);
                input = fgets(input, 256, stdin);
                if(!input) return;
                strtok(input, "\n");
                
                list *plist = get_paths(input);
                char **paths = (char **)list_to_array(plist);
                printf("Start Testing!\n");
                int m = plist->size;
                if(access("./data/out",0)==-1)//"/home/FENGsl/darknet/data"修改成自己的路径
                {
                    if (mkdir("./data/out",0777))//"/home/FENGsl/darknet/data"修改成自己的路径
                    {
                        printf("creat file bag failed!!!");
                    }
                }
                for(i = 0; i < m; ++i){
                    char *path = paths[i];
                    image im = load_image_color(path,0,0);
                    image sized = letterbox_image(im, net->w, net->h);
                    //image sized = resize_image(im, net->w, net->h);
                    //image sized2 = resize_max(im, net->w);
                    //image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
                    //resize_network(net, sized.w, sized.h);
                    layer l = net->layers[net->n-1];
                    
                    
                    float *X = sized.data;
                    time=what_time_is_it_now();
                    network_predict(net, X);
                    printf("Try Very Hard:");
                    printf("%s: Predicted in %f seconds.\n", path, what_time_is_it_now()-time);
                    int nboxes = 0;
                    detection *dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes);
                    //printf("%d\n", nboxes);
                    //if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
                    if (nms) do_nms_sort(dets, nboxes, l.classes, nms);
                    draw_detections(im, dets, nboxes, thresh, names, alphabet, l.classes);
                    free_detections(dets, nboxes);
                    if(outfile){
                        save_image(im, outfile);
                    }
                    else{
                        
                        char b[2048];
                        sprintf(b,"./data/out/%s",GetFilename(path));//"/home/FENGsl/darknet/data"修改成自己的路径
                        
                        save_image(im, b);
                        printf("save %s successfully!\n",GetFilename(path));
    #ifdef OPENCV
                        cvNamedWindow("predictions", CV_WINDOW_NORMAL);
                        if(fullscreen){
                            cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
                        }
                        show_image(im, "predictions");
                        cvWaitKey(0);
                        cvDestroyAllWindows();
    #endif
                    }
                    
                    free_image(im);
                    free_image(sized);
                    if (filename) break;
                }
            }
        }
    }
    
    void run_detector(int argc, char **argv)
    {
        char *prefix = find_char_arg(argc, argv, "-prefix", 0);
        float thresh = find_float_arg(argc, argv, "-thresh", .5);
        float hier_thresh = find_float_arg(argc, argv, "-hier", .5);
        int cam_index = find_int_arg(argc, argv, "-c", 0);
        int frame_skip = find_int_arg(argc, argv, "-s", 0);
        int avg = find_int_arg(argc, argv, "-avg", 3);
        if(argc < 4){
            fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]);
            return;
        }
        char *gpu_list = find_char_arg(argc, argv, "-gpus", 0);
        char *outfile = find_char_arg(argc, argv, "-out", 0);
        int *gpus = 0;
        int gpu = 0;
        int ngpus = 0;
        if(gpu_list){
            printf("%s\n", gpu_list);
            int len = strlen(gpu_list);
            ngpus = 1;
            int i;
            for(i = 0; i < len; ++i){
                if (gpu_list[i] == ',') ++ngpus;
            }
            gpus = calloc(ngpus, sizeof(int));
            for(i = 0; i < ngpus; ++i){
                gpus[i] = atoi(gpu_list);
                gpu_list = strchr(gpu_list, ',')+1;
            }
        } else {
            gpu = gpu_index;
            gpus = &gpu;
            ngpus = 1;
        }
    
        int clear = find_arg(argc, argv, "-clear");
        int fullscreen = find_arg(argc, argv, "-fullscreen");
        int width = find_int_arg(argc, argv, "-w", 0);
        int height = find_int_arg(argc, argv, "-h", 0);
        int fps = find_int_arg(argc, argv, "-fps", 0);
        //int class = find_int_arg(argc, argv, "-class", 0);
    
        char *datacfg = argv[3];
        char *cfg = argv[4];
        char *weights = (argc > 5) ? argv[5] : 0;
        char *filename = (argc > 6) ? argv[6]: 0;
        if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen);
        else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear);
        else if(0==strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights, outfile);
        else if(0==strcmp(argv[2], "valid2")) validate_detector_flip(datacfg, cfg, weights, outfile);
        else if(0==strcmp(argv[2], "recall")) validate_detector_recall(datacfg,cfg, weights);
        else if(0==strcmp(argv[2], "demo")) {
            list *options = read_data_cfg(datacfg);
            int classes = option_find_int(options, "classes", 20);
            char *name_list = option_find_str(options, "names", "data/names.list");
            char **names = get_labels(name_list);
            demo(cfg, weights, thresh, cam_index, filename, names, classes, frame_skip, prefix, avg, hier_thresh, width, height, fps, fullscreen);
        }
        //else if(0==strcmp(argv[2], "extract")) extract_detector(datacfg, cfg, weights, cam_index, filename, class, thresh, frame_skip);
        //else if(0==strcmp(argv[2], "censor")) censor_detector(datacfg, cfg, weights, cam_index, filename, class, thresh, frame_skip);
    }
    
    ./darknet detector test cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_final.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:
    

    这里让输入图像路径,一个txt保存的路径即可,我在这里输入的是之前生成的2007_test.txt

    5. Python接口

    darknet提供了python接口,直接使用python即可调用程序得到检测结果,python接口在`./darknet/python`文件夹中,调用的是编译时生成的libdarknet.so文件,不同的机器平台编译生成的文件不一样,如果换机器或使用cpu或gpu运行时,请重新编译一下。

    该文件有两个文件夹:darknet.py 与provertbot.py,目前的版本支持python2.7,适当修改代码使其支持python3.x,个人做好的api已上传到github上,方便使用。

    python darknet.py

     输出结果形式为: res.append((meta.names[i], dets[j].prob[i], (b.x, b.y, b.w, b.h)))

    依次为检测出物体的名称,概率,检测框大小范围(在原图中所处的位置),其中x,y表示方框中心,w和h分别表示中心到两边的宽度和高度,如下图所示:

     

    7. 计算MAP和recall

    1.生成检测结果文件

    ./darknet detector valid cfg/car.data cfg/car.cfg backup/car_final.weights -out car.txt -gpu 0 -thresh .5

    2.把car.txt 用faster rcnn 中voc_eval计算mAP

    /home/sam/src/caffeup2date_pyfasterrcnn/lib/datasets/compute_mAP.py
    from voc_eval import voc_eval
    print(voc_eval('/home/sam/src/darknet/results/{}.txt',/home/sam/datasets/car2/VOC2007/Annotations/{}.xml','/home/sam/datasets/car2/VOC2007/ImageSets/Main/test.txt', 'car', '.')

    第三个结果就是

    如果只想计算大于0.3的输出结果的mAP,把 voc_eval.py文件中如下代码更改

    sorted_ind = np.argsort(-confidence)
    sorted_ind1 = np.where(confidence[sorted_ind] >= .3)[0]#np.argsort(-confidence<=-.3)
    sorted_ind = sorted_ind[sorted_ind1]

    3.计算recall 

    ./darknet detector recall cfg/car.data cfg/car.cfg backup/car_final.weights -out car.txt -gpu 0 -thresh .5

    7.参考

    YOLO V3

    参考:

    * YOLOv3训练自己的VOC数据集(配置及训练)

    *YOLOv3批量测试图片并保存在自定义文件夹下(批量测试)

    注:文件夹内容保存图片命名问题,*GetFilename(char *p)函数中限制了文件名长度,修改即可

    YOLOv3 ubuntu 配置及训练自己的VOC格式数据集(配置及训练)

    YOLOv3: 训练自己的数据(训练为主,部分测试问题可以参考)

    * YOLO 网络终端输出参数意义

      英文:https://timebutt.github.io/static/understanding-yolov2-training-output/

      中文:https://blog.csdn.net/dcrmg/article/details/78565440

    * yolo官方文档https://pjreddie.com/darknet/yolo/

     

    YOLO V2

    参考:

    * YOLOv2训练自己数据集的一些心得----VOC格式 (可视化)

    * YOLOv2训练自己的数据集

    * 使用YOLO v2训练自己的数据

    后续待完善...

     

    YOLOv2训练自己数据集的一些心得----VOC格式 (可视化)
    [YOLOv2 xùnliàn zìjǐ shùjù jí de yīxiē xīndé----VOC géshì (kěshìhuà)]
    YOLOv2 own training data set some experience ---- VOC format (visualization)
     
    展开全文
  • 基于OpenCV和YOLOv3深度学习的目标检测

    万次阅读 多人点赞 2018-09-18 17:20:02
    本文翻译自Deep Learning based Object Detection using YOLOv3 with OpenCV ( Python / C++ ) 基于OpenCV和YOLOv3深度学习的目标检测 ...YOLOv3是当前流行的目标检测算法YOLO(You Only Look Once)的最新...

    本文翻译自Deep Learning based Object Detection using YOLOv3 with OpenCV ( Python / C++ )

    基于OpenCV和YOLOv3深度学习的目标检测

     

    本文,我们学习如何在OpenCV上使用目前较为先进的目标检测技术YOLOv3。

    YOLOv3是当前流行的目标检测算法YOLO(You Only Look Once)的最新变种算法。所发行的模型能识别图片和视频中的80种物体,而且更重要的是它实时性强,而且准确度接近Single Shot MultiBox(SSD)。

    从OpenCV 3.4.2开始,我们可以很容易的在OpenCV应用中使用YOLOv3模型(即OpemCV-3.4.2开始支持YOLOv3这网络框架)。

    YOLO是什么原理?

    我们可以把目标检测看成是目标定位和目标识别的结合。

    在传统的计算机视觉方法中,采用滑动窗口查找不同区域和大小的目标。因为这是消耗量较大的算法,通常假定目标的纵横比是固定的。

    早期的基于深度学习的目标检测算法,如R-CNN和快速R-CNN,采用选择型搜索(Selective Search)来缩小必须测试的边界框的数量(本文的边界框指的是,在预测到疑似所识别到的目标后,在图片上把对象框出的一个矩形)。

    另外一种称为Overfeat的方法,通过卷积地计算滑动窗口,以多个尺度扫描了图像。

    然后有人提出了快速R-CNN算法,使用Region Proposal Network(RPN)区别将要测试的边界框。通过巧妙的设计,用于目标识别的特征点,也被RPN用于提出潜在的边界框,因此节省了大量的计算。

    然而,YOLO使用了完全不同的方法解决目标检测问题。它将图像进行神经网络的一次性正向处理。SSD是另外一种将图像进行神经网络一次性正向处理的方法,但是YOLOv3比SSD实现了更高的精度,同时又较快的运算速度。YOLOv3在M40,TitanX和1080Ti这类GPU上实时效果更好。

    让我们看看YOLO如何在一张图片中检测目标。

    首先,它把原图按比例平均分解成一张有13x13网格的图片。这169个单元会根据原图的大小而改变。对于一张416x416像素的图片,每个图片单元的大小是32x32像素。处理图片时,会以图片单元为单位,预测单位中的多个边界框。

     对于每个边界框,这个网络会计算所包含物体的边界框的置信度,同时计算所包含的目标是属于一个特定类别的可能性大小。

    非最大抑制(non-maximum suppression)可以消除低置信度的边界框,以及把同时包围着单个物体的多个高置信度的边界框消除到只剩下一个。

    YOLOv3的作者,Joseph Redmon和Ali Farhadi,让YOLOv3比前一代YOLOv2更加精确和快速。YOLOv3在处理多个不同尺寸图片的场合中得到了优化。他们还通过加大了网络,并添加快捷链接将其引入剩余网络来改进网络。

    为什么选择OpenCV的YOLO   

     这里有三个理由。

    1. 容易整合到现有的OpenCV程序中:如果应用程序已经使用了OpenCV,并想简单地使用YOLOv3,完全不需要担心Darknet源代码的编译和建立。
    2. OpenCV的CPU版本的运算速度比Darknet+OpenMP快9倍:OpenCV的DNN模块,其CPU运行是十分快的。举个例子,当用了OpenMP的Darknet在CPU上处理一张图片消耗2秒,OpenCV的实现只需要0.22秒。具体请看下面的表格。
    3. 支持Python。Darknet是用C语言写的,因此并不支持Python。相反,OpenCV是支持Python的。会有支持Darknet的编程接口。

     

    在Darknet和OpenCV上跑YOLOv3的速度测试

    下面的表格展示了在Darknet和OpenCV上YOLOv3的性能差距,输入图片的尺寸是416x416。不出所料,GPU版本的Darknet在性能上比其他方式优越。同时,理所当然的Darknet配合OpenMP会好于没有OpenMP的Darknet,因为OpenMP支持多核的CPU。

    意外的是,CPU版本的OpenCV在执行DNN的运算速度,是9倍的快过Darknet和OpenML。
          

    表1. 分别在Darknet和OpenCV上跑YOLOv3的速度对比
    OS Framework CPU/GPU Time(ms)/Frame
    Linux 16.04    Darknet    12x Intel Core i7-6850K CPU @ 3.60GHz    9370
    Linux 16.04       Darknet + OpenMP  12x Intel Core i7-6850K CPU @ 3.60GHz    1942
    Linux 16.04    OpenCV [CPU]       12x Intel Core i7-6850K CPU @ 3.60GHz  220
    Linux 16.04    Darknet    NVIDIA GeForce 1080 Ti GPU    23
    macOS    DarkNet    2.5 GHz Intel Core i7 CPU    7260
    macOS      OpenCV [CPU]     2.5 GHz Intel Core i7 CPU  400


    注意:我们在GPU版本的OpenCV上跑DNN时候遇到了困难。本文工作只是测试了Intel的GPU,因此如果没有Intel的GPU,程序会自动切换到CPU上跑相关算法。

     

    采用YOLOv3的目标检测,C++/Python两种语言

    让我们看看,如何在YOLOv3在OpenCV运行目标检测。

    第1步:下载模型。

    我们先从命令行中执行脚本getModels.sh开始。
       

    sudo chmod a+x getModels.sh
    ./getModels.sh
    //译者添加:
    Windows下替代方案:
    
    1、http://gnuwin32.sourceforge.net/packages/wget.htm 安装wget
     cd 到wget安装目录,执行
    wget https://pjreddie.com/media/files/yolov3.weights
    wget https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg?raw=true -O ./yolov3.cfg
    wget https://github.com/pjreddie/darknet/blob/master/data/coco.names?raw=true -O ./coco.names

    执行命令后开始下载yolov3.weights文件(包括了提前训练好的网络的权值),和yolov3.cfg文件(包含了网络的配置方式)和coco.names(包括了COCO数据库中使用的80种不同的目标种类名字)。

    第2步:初始化参数

    YOLOv3算法的预测结果就是边界框。每一个边界框都旁随着一个置信值。第一阶段中,全部低于置信度阀值的都会排除掉。

    对剩余的边界框执行非最大抑制算法,以去除重叠的边界框。非最大抑制由一个参数nmsThrehold控制。读者可以尝试改变这个数值,观察输出的边界框的改变。

    接下来,设置输入图片的宽度(inpWidth)和高度(inpHeight)。我们设置他们为416,以便对比YOLOv3作者提供的Darknets的C代码。如果想要更快的速度,读者可以把宽度和高度设置为320。如果想要更准确的结果,改变他们到608。

    Python代码:

    # Initialize the parameters
    confThreshold = 0.5  #Confidence threshold
    nmsThreshold = 0.4   #Non-maximum suppression threshold
    inpWidth = 416       #Width of network's input image
    inpHeight = 416      #Height of network's input image

    C++代码:

    // Initialize the parameters
    float confThreshold = 0.5; // Confidence threshold
    float nmsThreshold = 0.4;  // Non-maximum suppression threshold
    int inpWidth = 416;        // Width of network's input image
    int inpHeight = 416;       // Height of network's input image

    第3步:读取模型和类别

    文件coco.names包含了训练好的模型能识别的所有目标名字。我们读出各个类别的名字。

    接着,我们读取了网络,其包含两个部分:

    1. yolov3.weights: 预训练得到的权重。
    2. yolov3.cfg:配置文件

    我们把DNN的后端设置为OpenCV,目标设置为CPU。可以通过使cv.dnn.DNN_TARGET_OPENCL置为GPU,尝试设定偏好的运行目标为GPU。但是要记住当前的OpenCV版本只在Intel的GPU上测试,如果没有Intel的GPU则程序会自动设置为CPU。

    Python:

    # Load names of classes
    classesFile = "coco.names";
    classes = None
    with open(classesFile, 'rt') as f:
        classes = f.read().rstrip('\n').split('\n')
     
    # Give the configuration and weight files for the model and load the network using them.
    modelConfiguration = "yolov3.cfg";
    modelWeights = "yolov3.weights";
     
    net = cv.dnn.readNetFromDarknet(modelConfiguration, modelWeights)
    net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
    net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU)

     

    C++

    // Load names of classes
    string classesFile = "coco.names";
    ifstream ifs(classesFile.c_str());
    string line;
    while (getline(ifs, line)) classes.push_back(line);
     
    // Give the configuration and weight files for the model
    String modelConfiguration = "yolov3.cfg";
    String modelWeights = "yolov3.weights";
     
    // Load the network
    Net net = readNetFromDarknet(modelConfiguration, modelWeights);
    net.setPreferableBackend(DNN_BACKEND_OPENCV);
    net.setPreferableTarget(DNN_TARGET_CPU);

    第4步:读取输入

    这一步我们读取图像,视频流或者网络摄像头。另外,我们也使用Videowriter(OpenCV里的一个类)以视频方式保存带有输出边界框的每一帧图片。

    Python

    outputFile = "yolo_out_py.avi"
    if (args.image):
        # Open the image file
        if not os.path.isfile(args.image):
            print("Input image file ", args.image, " doesn't exist")
            sys.exit(1)
        cap = cv.VideoCapture(args.image)
        outputFile = args.image[:-4]+'_yolo_out_py.jpg'
    elif (args.video):
        # Open the video file
        if not os.path.isfile(args.video):
            print("Input video file ", args.video, " doesn't exist")
            sys.exit(1)
        cap = cv.VideoCapture(args.video)
        outputFile = args.video[:-4]+'_yolo_out_py.avi'
    else:
        # Webcam input
        cap = cv.VideoCapture(0)
     
    # Get the video writer initialized to save the output video
    if (not args.image):
        vid_writer = cv.VideoWriter(outputFile, cv.VideoWriter_fourcc('M','J','P','G'), 30, (round(cap.get(cv.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv.CAP_PROP_FRAME_HEIGHT))))
        
        


    C++

    outputFile = "yolo_out_cpp.avi";
    if (parser.has("image"))
    {
        // Open the image file
        str = parser.get<String>("image");
        ifstream ifile(str);
        if (!ifile) throw("error");
        cap.open(str);
        str.replace(str.end()-4, str.end(), "_yolo_out.jpg");
        outputFile = str;
    }
    else if (parser.has("video"))
    {
        // Open the video file
        str = parser.get<String>("video");
        ifstream ifile(str);
        if (!ifile) throw("error");
        cap.open(str);
        str.replace(str.end()-4, str.end(), "_yolo_out.avi");
        outputFile = str;
    }
    // Open the webcaom
    else cap.open(parser.get<int>("device"));
     
    // Get the video writer initialized to save the output video
    if (!parser.has("image")) {
       video.open(outputFile, VideoWriter::fourcc('M','J','P','G'), 28, Size(cap.get(CAP_PROP_FRAME_WIDTH),          cap.get(CAP_PROP_FRAME_HEIGHT)));
    }

     

    第5步:处理每一帧

    输入到神经网络的图像需要以一种叫bolb的格式保存。

    读取了输入图片或者视频流的一帧图像后,这帧图像需要经过bolbFromImage()函数处理为神经网络的输入类型bolb。在这个过程中,图像像素以一个1/255的比例因子,被缩放到0到1之间。同时,图像在不裁剪的情况下,大小调整到416x416。注意我们没有降低图像平均值,因此传递[0,0,0]到函数的平均值输入,保持swapRB参数到默认值1。

    输出的bolb传递到网络,经过网络正向处理,网络输出了所预测到的一个边界框清单。这些边界框通过后处理,滤除了低置信值的。我们随后再详细的说明后处理的步骤。我们在每一帧的左上方打印出了推断时间。伴随着最后的边界框的完成,图像保存到硬盘中,之后可以作为图像输入或者通过Videowriter作为视频流输入。

    Python:

    while cv.waitKey(1) < 0:
         
        # get frame from the video
        hasFrame, frame = cap.read()
         
        # Stop the program if reached end of video
        if not hasFrame:
            print("Done processing !!!")
            print("Output file is stored as ", outputFile)
            cv.waitKey(3000)
            break
     
        # Create a 4D blob from a frame.
        blob = cv.dnn.blobFromImage(frame, 1/255, (inpWidth, inpHeight), [0,0,0], 1, crop=False)
     
        # Sets the input to the network
        net.setInput(blob)
     
        # Runs the forward pass to get output of the output layers
        outs = net.forward(getOutputsNames(net))
     
        # Remove the bounding boxes with low confidence
        postprocess(frame, outs)
     
        # Put efficiency information. The function getPerfProfile returns the
        # overall time for inference(t) and the timings for each of the layers(in layersTimes)
        t, _ = net.getPerfProfile()
        label = 'Inference time: %.2f ms' % (t * 1000.0 / cv.getTickFrequency())
        cv.putText(frame, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))
     
        # Write the frame with the detection boxes
        if (args.image):
            cv.imwrite(outputFile, frame.astype(np.uint8));
        else:
            vid_writer.write(frame.astype(np.uint8))
            

    c++

    // Process frames.
    while (waitKey(1) < 0)
    {
        // get frame from the video
        cap >> frame;
     
        // Stop the program if reached end of video
        if (frame.empty()) {
            cout << "Done processing !!!" << endl;
            cout << "Output file is stored as " << outputFile << endl;
            waitKey(3000);
            break;
        }
        // Create a 4D blob from a frame.
        blobFromImage(frame, blob, 1/255.0, cvSize(inpWidth, inpHeight), Scalar(0,0,0), true, false);
         
        //Sets the input to the network
        net.setInput(blob);
         
        // Runs the forward pass to get output of the output layers
        vector<Mat> outs;
        net.forward(outs, getOutputsNames(net));
         
        // Remove the bounding boxes with low confidence
        postprocess(frame, outs);
         
        // Put efficiency information. The function getPerfProfile returns the
        // overall time for inference(t) and the timings for each of the layers(in layersTimes)
        vector<double> layersTimes;
        double freq = getTickFrequency() / 1000;
        double t = net.getPerfProfile(layersTimes) / freq;
        string label = format("Inference time for a frame : %.2f ms", t);
        putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 0, 255));
         
        // Write the frame with the detection boxes
        Mat detectedFrame;
        frame.convertTo(detectedFrame, CV_8U);
        if (parser.has("image")) imwrite(outputFile, detectedFrame);
        else video.write(detectedFrame);
         
    }

    现在,让我们详细分析一下上面调用的函数。

    第5a步:得到输出层的名字

    OpenCV的网络类中的前向功能需要结束层,直到它在网络中运行。因为我们需要运行整个网络,所以我们需要识别网络中的最后一层。我们通过使用getUnconnectedOutLayers()获得未连接的输出层的名字,该层基本就是网络的最后层。然后我们运行前向网络,得到输出,如前面的代码片段(net.forward(getOutputsNames(net)))。
    python:

    # Get the names of the output layers
    def getOutputsNames(net):
        # Get the names of all the layers in the network
        layersNames = net.getLayerNames()
        # Get the names of the output layers, i.e. the layers with unconnected outputs
        return [layersNames[i[0] - 1] for i in net.getUnconnectedOutLayers()]
        

    c++

    // Get the names of the output layers
    vector<String> getOutputsNames(const Net& net)
    {
        static vector<String> names;
        if (names.empty())
        {
            //Get the indices of the output layers, i.e. the layers with unconnected outputs
            vector<int> outLayers = net.getUnconnectedOutLayers();
             
            //get the names of all the layers in the network
            vector<String> layersNames = net.getLayerNames();
             
            // Get the names of the output layers in names
            names.resize(outLayers.size());
            for (size_t i = 0; i < outLayers.size(); ++i)
            names[i] = layersNames[outLayers[i] - 1];
        }
        return names;
    }

    第5b步:后处理网络输出

    网络输出的每个边界框都分别由一个包含着类别名字和5个元素的向量表示。

    头四个元素代表center_x, center_y, width和height。第五个元素表示包含着目标的边界框的置信度。

    其余的元素是和每个类别(如目标种类)有关的置信度。边界框分配给最高分数对应的那一种类。

    一个边界框的最高分数也叫做它的置信度(confidence)。如果边界框的置信度低于规定的阀值,算法上不再处理这个边界框。

    置信度大于或等于置信度阀值的边界框,将进行非最大抑制。这会减少重叠的边界框数目。
    Python

    # Remove the bounding boxes with low confidence using non-maxima suppression
    def postprocess(frame, outs):
        frameHeight = frame.shape[0]
        frameWidth = frame.shape[1]
     
        classIds = []
        confidences = []
        boxes = []
        # Scan through all the bounding boxes output from the network and keep only the
        # ones with high confidence scores. Assign the box's class label as the class with the highest score.
        classIds = []
        confidences = []
        boxes = []
        for out in outs:
            for detection in out:
                scores = detection[5:]
                classId = np.argmax(scores)
                confidence = scores[classId]
                if confidence > confThreshold:
                    center_x = int(detection[0] * frameWidth)
                    center_y = int(detection[1] * frameHeight)
                    width = int(detection[2] * frameWidth)
                    height = int(detection[3] * frameHeight)
                    left = int(center_x - width / 2)
                    top = int(center_y - height / 2)
                    classIds.append(classId)
                    confidences.append(float(confidence))
                    boxes.append([left, top, width, height])
     
        # Perform non maximum suppression to eliminate redundant overlapping boxes with
        # lower confidences.
        indices = cv.dnn.NMSBoxes(boxes, confidences, confThreshold, nmsThreshold)
        for i in indices:
            i = i[0]
            box = boxes[i]
            left = box[0]
            top = box[1]
            width = box[2]
            height = box[3]
            drawPred(classIds[i], confidences[i], left, top, left + width, top + height)

    c++

    // Remove the bounding boxes with low confidence using non-maxima suppression
    void postprocess(Mat& frame, const vector<Mat>& outs)
    {
        vector<int> classIds;
        vector<float> confidences;
        vector<Rect> boxes;
         
        for (size_t i = 0; i < outs.size(); ++i)
        {
            // Scan through all the bounding boxes output from the network and keep only the
            // ones with high confidence scores. Assign the box's class label as the class
            // with the highest score for the box.
            float* data = (float*)outs[i].data;
            for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
            {
                Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
                Point classIdPoint;
                double confidence;
                // Get the value and location of the maximum score
                minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
                if (confidence > confThreshold)
                {
                    int centerX = (int)(data[0] * frame.cols);
                    int centerY = (int)(data[1] * frame.rows);
                    int width = (int)(data[2] * frame.cols);
                    int height = (int)(data[3] * frame.rows);
                    int left = centerX - width / 2;
                    int top = centerY - height / 2;
                     
                    classIds.push_back(classIdPoint.x);
                    confidences.push_back((float)confidence);
                    boxes.push_back(Rect(left, top, width, height));
                }
            }
        }
         
        // Perform non maximum suppression to eliminate redundant overlapping boxes with
        // lower confidences
        vector<int> indices;
        NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);
        for (size_t i = 0; i < indices.size(); ++i)
        {
            int idx = indices[i];
            Rect box = boxes[idx];
            drawPred(classIds[idx], confidences[idx], box.x, box.y,
                     box.x + box.width, box.y + box.height, frame);
        }
    }

    非最大抑制由参数nmsThreshold控制。如果nmsThreshold设置太少,比如0.1,我们可能检测不到相同或不同种类的重叠目标。如果设置得太高,比如1,可能出现一个目标有多个边界框包围。所以我们在上面的代码使用了0.4这个中间的值。下面的gif展示了NMS阀值改变时候的效果。

    第5c步:画出计算得到的边界框  

     最后,经过非最大抑制后,得到了边界框。我们把边界框在输入帧上画出,并标出种类名和置信值。

    Python

    # Draw the predicted bounding box
    def drawPred(classId, conf, left, top, right, bottom):
        # Draw a bounding box.
        cv.rectangle(frame, (left, top), (right, bottom), (0, 0, 255))
         
        label = '%.2f' % conf
             
        # Get the label for the class name and its confidence
        if classes:
            assert(classId < len(classes))
            label = '%s:%s' % (classes[classId], label)
     
        #Display the label at the top of the bounding box
        labelSize, baseLine = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, 0.5, 1)
        top = max(top, labelSize[1])
        cv.putText(frame, label, (left, top), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255))

    c++

    // Draw the predicted bounding box
    void drawPred(int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
    {
        //Draw a rectangle displaying the bounding box
        rectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 0, 255));
         
        //Get the label for the class name and its confidence
        string label = format("%.2f", conf);
        if (!classes.empty())
        {
            CV_Assert(classId < (int)classes.size());
            label = classes[classId] + ":" + label;
        }
         
        //Display the label at the top of the bounding box
        int baseLine;
        Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
        top = max(top, labelSize.height);
        putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255,255,255));
    
        


    订阅&下载代码
    如果你喜欢本文,想下载代码(C++和Python),和在文中的例子图片,请订阅我们的时事通信。你会获得一封免费的计算机视觉指南。在我们的时事通信上,我们共享了C++/Python语言的OpenCV教程和例子,同时还有计算机视觉和机器学习算法和新闻。
        


     

    以上就是原文的全部内容。

    原文地址:https://www.learnopencv.com/deep-learning-based-object-detection-using-yolov3-with-opencv-python-c/

    作者:Sunita Nayak


    可参考:YOLOv3 Tech Report获得与本文相关的知识内容。
        
    有几句话是机翻协助的。当时也没标记。2018年9月18日进行了一次润色,已经修复部分翻译错误。第一遍快到结束了,按了下退格键+空格键,页面后退了,内容没了,痛心。然后重新润了一遍,没那么好的耐心了。如有错漏请多多包涵。

     

    展开全文
  • YOLO 是 2016 年提出来的目标检测算法,在当时比较优秀的目标检测算法有 R-CNN、Fast R-CNN 等等,但 YOLO 算法还是让人感到很新奇与兴奋。 YOLO 是 You only look once 几个单词的缩写,大意是你看一次就可以预测了...

    YOLO 是 2016 年提出来的目标检测算法,在当时比较优秀的目标检测算法有 R-CNN、Fast R-CNN 等等,但 YOLO 算法还是让人感到很新奇与兴奋。

    YOLO 是 You only look once 几个单词的缩写,大意是你看一次就可以预测了,灵感就来自于我们人类自己,因为人看一张图片时,扫一眼就可以得知这张图片不同类型目标的位置。

    1.创新

    YOLO将物体检测作为回归问题求解。基于一个单独的end-to-end网络,完成从原始图像的输入到物体位置和类别的输出。从网络设计上,YOLO与rcnn、fast rcnn及faster rcnn的区别如下:

    [1]
    YOLO训练和检测均是在一个单独网络中进行。YOLO没有显示地求取region proposal的过程。而rcnn/fast rcnn 采用分离的模块(独立于网络之外的selective search方法)求取候选框(可能会包含物体的矩形区域),训练过程因此也是分成多个模块进行。Faster rcnn使用RPN(region proposal network)卷积网络替代rcnn/fast rcnn的selective
    search模块,将RPN集成到fast rcnn检测网络中,得到一个统一的检测网络。尽管RPN与fast rcnn共享卷积层,但是在模型训练过程中,需要反复训练RPN网络和fast rcnn网络(注意这两个网络核心卷积层是参数共享的)。
    [2]
    YOLO将物体检测作为一个回归问题进行求解,输入图像经过一次inference,便能得到图像中所有物体的位置和其所属类别及相应的置信概率。而rcnn/fast rcnn/faster rcnn将检测结果分为两部分求解:物体类别(分类问题),物体位置即bounding box(回归问题)。
    在这里插入图片描述

    2.设计理念

    整体来看,Yolo算法采用一个单独的CNN模型实现end-to-end的目标检测,整个系统如下图所示:首先将输入图片resize到448x448,然后送入CNN网络,最后处理网络预测结果得到检测的目标。相比R-CNN算法,其是一个统一的框架,其速度更快,而且Yolo的训练过程也是end-to-end的。
    在这里插入图片描述

    YOLO将输入图像分成SxS个格子,每个格子负责检测‘落入’该格子的物体。若某个物体的中心位置的坐标落入到某个格子,那么这个格子就负责检测出这个物体。如下图所示,图中物体狗的中心点(红色原点)落入第5行、第2列的格子内,所以这个格子负责预测图像中的物体狗。
    在这里插入图片描述
    每个格子输出B个bounding box(包含物体的矩形区域)信息,以及C个物体属于某种类别的概率信息。

    Bounding box信息包含5个数据值,分别是x,y,w,h,和confidence。其中x,y是指当前格子预测得到的物体的bounding box的中心位置的坐标。w,h是bounding box的宽度和高度。注意:实际训练过程中,w和h的值使用图像的宽度和高度进行归一化到[0,1]区间内;x,y是bounding box中心位置相对于当前格子位置的偏移值,并且被归一化到[0,1]。

    confidence反映当前bounding box是否包含物体以及物体位置的准确性,计算方式如下:在这里插入图片描述
    若bounding box包含物体,则P(object) = 1;否则P(object) = 0. IOU(intersection over union)为预测boundingbox与物体真实区域的交集面积(以像素为单位,用真实区域的像素面积归一化到[0,1]区间)。

    因此,YOLO网络最终的全连接层的输出维度是 S * S * (B * 5 + C)。YOLO论文中,作者训练采用的输入图像分辨率是448x448,S=7,B=2;采用VOC 20类标注物体作为训练数据,C=20。因此输出向量为7 * 7 * (20 + 2 * 5)=1470维。作者开源出的YOLO代码中,全连接层输出特征向量各维度对应内容如下:
    在这里插入图片描述

    注:

    • 由于输出层为全连接层,因此在检测时,YOLO训练模型只支持与训练图像相同的输入分辨率。

    • 虽然每个格子可以预测B个bounding box,但是最终只选择只选择IOU最高的bounding box作为物体检测输出,即每个格子最多只预测出一个物体。当物体占画面比例较小,如图像中包含畜群或鸟群时,每个格子包含多个物体,但却只能检测出其中一个。这是YOLO方法的一个缺陷。

    总结一下,每个单元格需要预测大小的张量。在下面的网络结构中我们会详细讲述每个单元格的预测值的分布位置。
    在这里插入图片描述

    3.网络设计

    YOLO检测网络包括24个卷积层和2个全连接层,如下图所示。
    在这里插入图片描述
    YOLO网络借鉴了GoogLeNet分类网络结构。不同的是,YOLO未使用inception
    module,而是使用1x1卷积层(此处1x1卷积层的存在是为了跨通道信息整合)+3x3卷积层简单替代。

    YOLO论文中,作者还给出一个更轻快的检测网络fast YOLO,它只有9个卷积层和2个全连接层。使用titan x GPU,fast YOLO可以达到155fps的检测速度,但是mAP值也从YOLO的63.4%降到了52.7%,但却仍然远高于以往的实时物体检测方法(DPM)的mAP值。

    可以看到网络的最后输出为是边界框的预测结果。这样,提取每个部分是非常方便的,这会方面后面的训练及预测时的计算。
    在这里插入图片描述

    在test的时候,每个网格预测的class信息和bounding box预测的confidence信息相乘,就得到每个bounding box的class-specific confidence score:
    在这里插入图片描述
    等式左边第一项就是每个网格预测的类别信息,第二三项就是每个bounding box预测的confidence。这个乘积即encode了预测的box属于某一类的概率,也有该box准确度的信息。

    得到每个box的class-specific confidence score以后,设置阈值,滤掉得分低的boxes,对保留的boxes进行NMS处理,就得到最终的检测结果。

    4.Loss函数定义

    YOLO使用均方和误差作为loss函数来优化模型参数,即网络输出的S * S * (B * 5 + C)维向量与真实图像的对应S * S * (B * 5 + C)维向量的均方和误差。如下式所示。其中,coordError、iouError和classError分别代表预测数据与标定数据之间的坐标误差、IOU误差和分类误差。

    在这里插入图片描述
    这种做法存在以下几个问题:
    因为每个grid有30维,这30维中,8维是回归box的坐标,2维是box的confidence,还有20维是类别。其中坐标的x,y用对应网格的offset归一化到0-1之间,w,h用图像的width和height归一化到0-1之间。从而有以下问题:
    第一,8维的localization error和20维的classification error同等重要显然是不合理的;
    第二,如果一个网格中没有object(一幅图中这种网格很多),那么就会将这些网格中的box的confidence push到0,相比于较少的有object的网格,这种做法是overpowering的,这会导致网络不稳定甚至发散。

    解决办法:
    YOLO对上式loss的计算进行了如下修正。

    [1] 位置相关误差(坐标、IOU)与分类误差对网络loss的贡献值是不同的,因此YOLO在计算loss时,使用λcoord=5\lambda _{coord} =5修正coordError。

    [2] 在计算IOU误差时,包含物体的格子与不包含物体的格子,二者的IOU误差对网络loss的贡献值是不同的。若采用相同的权值,那么不包含物体的格子的confidence值近似为0,变相放大了包含物体的格子的confidence误差在计算网络参数梯度时的影响。为解决这个问题,YOLO 使用λnoobj=0.5\lambda _{noobj} =0.5修正iouError。(注此处的‘包含’是指存在一个物体,它的中心坐标落入到格子内)。

    [3] 对于相等的误差值,大物体误差对检测的影响应小于小物体误差对检测的影响。这是因为,相同的位置偏差占大物体的比例远小于同等偏差占小物体的比例。YOLO将物体大小的信息项(w和h)进行求平方根来改进这个问题。(注:这个方法并不能完全解决这个问题)。

    综上,YOLO在训练过程中Loss计算如下式所示:
    在这里插入图片描述
    其中,在这里插入图片描述为网络预测值,在这里插入图片描述帽 为标注值。Πiobj\Pi _{i}^{obj} (这里这个符号其实是空心的1)表示物体落入格子i中,Πijobj\Pi _{ij}^{obj}Πijnoobj\Pi _{ij}^{noobj} 分别表示物体落入与未落入格子i的第j个bounding box内。

    注:

    • YOLO方法模型训练依赖于物体识别标注数据,因此,对于非常规的物体形状或比例,YOLO的检测效果并不理想。
    • YOLO采用了多个下采样层,网络学到的物体特征并不精细,因此也会影响检测效果。
    • YOLO loss函数中,大物体IOU误差和小物体IOU误差对网络训练中loss贡献值接近(虽然采用求平方根方式,但没有根本解决问题)。因此,对于小物体,小的IOU误差也会对网络优化过程造成很大的影响,从而降低了物体检测的定位准确性。

    5.网络训练

    在训练之前,先在ImageNet上进行了预训练,其预训练的分类模型采用之前图中前20个卷积层,然后添加一个average-pool层和全连接层。预训练之后,在预训练得到的20层卷积层之上加上随机初始化的4个卷积层和2个全连接层。由于检测任务一般需要更高清的图片,所以将网络的输入从224x224增加到了448x448。整个网络的流程如下图所示:
    在这里插入图片描述

    我们再来详细回顾下具体的训练流程

    一幅图片分成7x7个网格(grid cell),某个物体的中心落在这个网格中此网格就负责预测这个物体。

    最后一层输出为 (7 * 7)* 30的维度。每个 1 * 1 * 30的维度对应原图7 * 7个cell中的一个,1 * 1 * 30中含有类别预测和bbox坐标预测。总得来讲就是让网格负责类别信息,bounding box主要负责坐标信息(部分负责类别信息:confidence也算类别信息)。具体如下:

    • 每个网格(1 * 1 * 30维度对应原图中的cell)要预测2个bounding box (图中黄色实线框)的坐标xcenter,ycenter,w,h(x_{center},y_{center},w,h) ,其中:中心坐标的xcenter,ycenterx_{center},y_{center} 相对于对应的网格归一化到0-1之间,w,h用图像的width和height归一化到0-1之间。 每个bounding box除了要回归自身的位置之外,还要附带预测一个confidence值。 这个confidence代表了所预测的box中含有object的置信度和这个box预测的有多准两重信息:confidence=Pr(Object)IOUpredtruthconfidence = Pr(Object) \ast IOU^{truth}_{pred}。其中如果有ground true box(人工标记的物体)落在一个grid cell里,第一项取1,否则取0。 第二项是预测的bounding box和实际的ground truth box之间的IOU值。即:每个bounding box要预测 xcenter,ycenter,w,h,confidencex_{center},y_{center},w,h,confidence共5个值 ,2个bounding box共10个值,对应 1 * 1 * 30维度特征中的前10个。

    • 每个网格还要预测类别信息,论文中有20类。7x7的网格,每个网格要预测2个 bounding box 和 20个类别概率,输出就是 7x7x(5x2 + 20) 。 (通用公式: SxS个网格,每个网格要预测B个bounding box还要预测C个categories,输出就是S x S x (5*B+C)的一个tensor。 注意:class信息是针对每个网格的,confidence信息是针对每个bounding box的)
      -在这里插入图片描述

    6.网络测试与预测

    Test的时候,每个网格预测的class信息(Pr(ClassiObject))( Pr(Class_i | Object) )和bounding box预测的confidence信息(Pr(Object)IOUpredtruth)( Pr(Object) \ast IOU^{truth}_{pred} )相乘,就得到每个bounding box的class-specific confidence score。
    在这里插入图片描述
    等式左边第一项就是每个网格预测的类别信息,第二三项就是每个bounding box预测的confidence。这个乘积即encode了预测的box属于某一类的概率,也有该box准确度的信息。

    对每一个网格的每一个bbox执行同样操作: 7x7x2 = 98 bbox (每个bbox既有对应的class信息又有坐标信息)

    得到每个bbox的class-specific confidence score以后,设置阈值,滤掉得分低的boxes,对保留的boxes进行NMS处理,就得到最终的检测结果。

    这里先介绍一下非极大值抑制算法(non maximum suppression, NMS),这个算法不单单是针对Yolo算法的,而是所有的检测算法中都会用到。NMS算法主要解决的是一个目标被多次检测的问题,如图中人脸检测,可以看到人脸被多次检测,但是其实我们希望最后仅仅输出其中一个最好的预测框,比如对于美女,只想要红色那个检测结果。那么可以采用NMS算法来实现这样的效果:首先从所有的检测框中找到置信度最大的那个框,然后挨个计算其与剩余框的IOU,如果其值大于一定阈值(重合度过高),那么就将该框剔除;然后对剩余的检测框重复上述过程,直到处理完所有的检测框。Yolo预测过程也需要用到NMS算法。
    在这里插入图片描述
    下面就来分析Yolo的预测过程,这里我们不考虑batch,认为只是预测一张输入图片。根据前面的分析,最终的网络输出是个边界框。

    所有的准备数据已经得到了,那么我们先说第一种策略来得到检测框的结果,我认为这是最正常与自然的处理。首先,对于每个预测框根据类别置信度选取置信度最大的那个类别作为其预测标签,经过这层处理我们得到各个预测框的预测类别及对应的置信度值,其大小都是。一般情况下,会设置置信度阈值,就是将置信度小于该阈值的box过滤掉,所以经过这层处理,剩余的是置信度比较高的预测框。最后再对这些预测框使用NMS算法,最后留下来的就是检测结果。一个值得注意的点是NMS是对所有预测框一视同仁,还是区分每个类别,分别使用NMS。Ng在deeplearning.ai中讲应该区分每个类别分别使用NMS,但是看了很多实现,其实还是同等对待所有的框,我觉得可能是不同类别的目标出现在相同位置这种概率很低吧。

    上面的预测方法应该非常简单明了,但是对于Yolo算法,其却采用了另外一个不同的处理思路(至少从C源码看是这样的),其区别就是先使用NMS,然后再确定各个box的类别。其基本过程如图所示。对于98个boxes,首先将小于置信度阈值的值归0,然后分类别地对置信度值采用NMS,这里NMS处理结果不是剔除,而是将其置信度值归为0。最后才是确定各个box的类别,当其置信度值不为0时才做出检测结果输出。这个策略不是很直接,但是貌似Yolo源码就是这样做的。Yolo论文里面说NMS算法对Yolo的性能是影响很大的,所以可能这种策略对Yolo更好。测试了普通的图片检测,两种策略结果是一样的。

    YOLO预处理流程

    7.YOLOv1的缺点

    YOLO对相互靠的很近的物体,还有很小的群体检测效果不好,这是因为一个网格中只预测了两个框,并且只属于一类。

    对测试图像中,同一类物体出现的新的不常见的长宽比和其他情况是。泛化能力偏弱。

    由于损失函数的问题,定位误差是影响检测效果的主要原因。尤其是大小物体的处理上,还有待加强。

    于是,便有了后来的YOLO_v2…

    参考:https://blog.csdn.net/c20081052/article/details/80236015
    https://blog.csdn.net/m0_37192554/article/details/81092514

    展开全文
  • YOLO目标检测

    千次阅读 2019-07-11 22:04:34
    我们将使用Pascal VOC数据集训练我们的模型,该数据集可以用来做图像分类、目标检测、图像分割。 Annotations文件夹:用于存放图片描述,文件格式为.xml,文件保存了图片文件名,尺寸,标注,坐标,是否分割...

    使用VOC数据训练模型

    下载数据集

    我们将使用Pascal VOC数据集训练我们的模型,该数据集可以用来做图像分类、目标检测、图像分割。
    下载地址:
    将下载的三个VOC数据集压缩文件放在darknet/scripts/中,使用以下命令解压:

    tar xf voc.tar && tar xf VOCtrainval_11-May-2012.tar
    tar xf VOCtrainval_06-Nov-2007.tar && tar xf VOCtest_06-Nov-2007.tar
    
    • Annotations文件夹:用于存放图片描述,文件格式为.xml,文件保存了图片文件名,尺寸,标注,坐标,是否分割等信息。

    • ImageSets文件夹:保存了不同用途的图片名字列表,文件格式是.txt。其中包括,
      layout文件夹:保存具有人体部位的图片名字列表。
      main文件夹:保存用于图像物体识别的图片名字列表。
      segmenttions文件夹:保存用于图像分割的图片名字列表。

    • JPEGImages文件夹:保存全部图片源文件。

    • SegmentationClassSegmentationObject保存用于图像分割的源图片,两者区别如图所示:

    创建标签

    标签结构:("类别",“中心点x坐标”,“中心点y”坐标,“图片宽度”,“图片高度”)

    # 在scripts文件夹内
    python voc_label.py
    

    执行成功后会生成一个label文件夹和三个txt文件,分别是2007_train.txt2007_test.txt2007_val.txt2012_train.txt

    修改配置文件

    打开darknet/cfg/voc.data,修改trainvalid文件路径

    classes:数据集中图片分类数量。
    train:用于训练的图片数据集绝对路径。
    valid:用于验证的图片数据集绝对路径。
    names:数据集中图片分类名字,如:“dog”,“person”等。
    backup:模型训练完成后,权重文件保存路径。

    模型训练

    wget https://pjreddie.com/media/files/darknet53.conv.74
    

    备用下载:

    修改cfg/yolov3-voc.cfg

    [net]
    # 模型测试模式
    # Testing
    # batch=1
    # subdivisions=1 
    
    # 模型训练模式
    #Training 
    # batch_size
    batch=64
    subdivisions=16
    #用于进一步分割batch_size,分割后的batch_size大小为:batch_size/subdivisions
    
    # 模型输入图像宽
    width=416
    # 模型输入图像高
    height=416
    # 图像通道数
    channels=3
    # 使用带动量优化函数的动量参数
    momentum=0.9
    # 权重衰减率,用于防止过拟合
    decay=0.0005
    
    # 以下4项是通过改变图像角度,饱和度,曝光量,色调来生成更多样本,可用于防止过拟合
    angle=0
    saturation = 1.5
    exposure = 1.5
    hue=.1
    
    # 初始学习率
    learning_rate=0.001
    burn_in=1000
    # 迭代次数
    max_batches = 50200
    # 当迭代到40000,45000时更改学习率
    policy=steps
    steps=40000,45000
    scales=.1,.1
    
    [convolutional]
    # BN标准化处理,可以通过改变数据分布,处理梯度过小问题,加快模型收敛
    batch_normalize=1
    # 输出特征大小
    filters=32
    # 卷积核大小3x3
    size=3
    # 卷积步长为1
    stride=1
    # pad为0,padding由 padding参数指定。如果pad为1,padding大小为size/2
    pad=1
    # 激活函数,和relu的区别是当输入值小于0时,输出不为0
    activation=leaky
    
    **。。。。。省略。。。。。。**
    
    [yolo]
    mask = 0,1,2
    # 预选框,可手动指定也可通过聚类学习得到
    anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
    # 识别种类
    classes=20
    # 每个cell预测box数量,yolov1时只有一个
    num=9
    # 增加噪声
    jitter=.3
    ignore_thresh = .5
    truth_thresh = 1
    random=1
    

    执行训练:

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

    停止继续训练:

    ./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc.backup
    

    训练完成后,权重文件保存在backup文件夹内。

    参数解释

    • cfg文件参数含义

    batch: 每一次迭代送到网络的图片数量,也叫批数量。增大这个可以让网络在较少的迭代次数内完成一个epoch。在固定最大迭代次数的前提下,增加batch会延长训练时间,但会更好的寻找到梯度下降的方向。如果你显存够大,可以适当增大这个值来提高内存利用率。这个值是需要大家不断尝试选取的,过小的话会让训练不够收敛,过大会陷入局部最优。

    subdivision:这个参数很有意思的,它会让你的每一个batch不是一下子都丢到网络里。而是分成subdivision对应数字的份数,一份一份的跑完后,在一起打包算作完成一次iteration。这样会降低对显存的占用情况。如果设置这个参数为1的话就是一次性把所有batch的图片都丢到网络里,如果为2的话就是一次丢一半。

    angle:图片旋转角度,这个用来增强训练效果的。从本质上来说,就是通过旋转图片来变相的增加训练样本集。

    saturationexposurehue:饱和度,曝光度,色调,这些都是为了增强训练效果用的。

    learning_rate:学习率,训练发散的话可以降低学习率。学习遇到瓶颈,loss不变的话也减低学习率。

    max_batches: 最大迭代次数。

    policy:学习策略,一般都是step这种步进式。

    stepscales:这两个是组合一起的,举个例子:learn_rate: 0.001, step:100,25000,35000 scales: 10, .1, .1 这组数据的意思就是在0-100次iteration期间learning rate为原始0.001,在100-25000次iteration期间learning rate为原始的10倍0.01,在25000-35000次iteration期间learning rate为当前值的0.1倍,就是0.001, 在35000到最大iteration期间使用learning rate为当前值的0.1倍,就是0.0001。随着iteration增加,降低学习率可以是模型更有效的学习,也就是更好的降低train loss。

    最后一层卷积层中filters数值是 3 * (classes + 5)(YOLOv3)。

    region里需要把classes改成你的类别数。

    最后一行的random,是一个开关。如果设置为1的话,就是在训练的时候每一batch图片会随便改成320-640(32整倍数)大小的图片。目的和上面的色度,曝光度等一样。如果设置为0的话,所有图片就只修改成默认的大小 416*416。

    • 训练log中各参数的意义

    Region Avg IOU:平均的IOU,代表预测的bounding box和ground truth的交集与并集之比,期望该值趋近于1。

    Class:是标注物体的概率,期望该值趋近于1.

    Obj:期望该值趋近于1.

    No Obj:期望该值越来越小但不为零.

    Avg Recall:期望该值趋近1

    avg:平均损失,期望该值趋近于0

    rate:当前学习率

    推荐博客:
    YOLO训练自己的数据集的一些心得

    展开全文
  • yolo3实现目标的实时检测

    千次阅读 2019-07-03 11:34:29
    1、下载yolov3的模型,链接:https://github.com/qqwweee/keras-yolo3 2、下载IPWebCam,链接:https://download.csdn.net/download/baidu_36669549/10591346 具体设置参考:...
  • 目标检测:YOLOv3: 训练自己的数据

    万次阅读 多人点赞 2020-08-21 08:49:55
    ------------------------------ 本文仅供学习交流,如有错误,望交流指教 ------------------------------ windows 版本:请参考:https://github.com/AlexeyAB/darknet linux 版本:请参考本文与...
  • 从YOLOv1到YOLOv3,目标检测的进化之路

    万次阅读 多人点赞 2019-07-05 10:13:58
    本文来自 CSDN 网站,作者 EasonApp。作者专栏: http://dwz.cn/7ZGrifYOLOv1这是继 RCNN,fast-RCNN 和 faster-...
  • 利用YOLO实现自己的目标检测

    万次阅读 2018-05-12 19:27:09
    最近,在师哥的引导下,接触了一下YOLO算法,是近年来一个比较好的目标检测算法,而且它有自己的开源深度学习框架—darknet,使用起来比较简单,对于我一个新手来说,挺直白的这个,可以学习一下。我是在ubuntu16.04...
  • YOLO目标检测算法

    2020-04-04 09:47:13
    本文主要介绍YOLO目标检测算法,包括YOLOv1、YOLOv2和YOLOv3。
  • 使用Yolo v3进行目标检测

    千次阅读 热门讨论 2020-08-09 22:09:38
    https://www.cnblogs.com/tensorflownews/p/8922359.html
  • WIN10 C# GPU+CPU 自适应环境 YOLO 目标检测 Demo(运行环境WIN10 64位,vc_redist.x64.exe ,cuda_9.0 ),可以在百度云下载 链接:https://pan.baidu.com/s/13DFw9Zc4aU3NZTt-vhL6mw 提取码:paan ,需要源码加微信 ...
  • YOLO目标检测模型原理介绍

    千次阅读 2019-03-21 01:45:30
    YOLO是一个端到端的目标检测算法,不需要预先提取region proposal(RCNN目标检测系列),通过一个网络就可以输出:类别,置信度,坐标位置,检测速度很快,不过,定位精度相对低些,特别是密集型小目标。 YOLO将...
  • 解读YOLO目标检测方法

    千次阅读 2018-01-07 10:38:22
    tensorflow源码链接:https://github.com/nilboy/tensorflow-yolo/tree/python2.7/yolo ...Ross Girshick提出的Faster R-CNN把目标检测的速度提高了一大步,在用Titan X时检测速度可以达到7fps,同时准确度达到73mA
  • YOLOv1 YOLOv1 创新: 将整张图作为网络的输入,直接在输出层回归 bounding box 的位置...之前的目标检测方法需要先产生候选区再检测的方法虽然有相对较高的检测准确率,但运行速度较慢。 YOLO 将识别与定位合二为...
  • YOLO目标检测编程练习

    千次阅读 2018-12-12 08:43:09
    YOLO 在本文中,我们将研究一个这样的对象检测框架——YOLO。这是一个非常快速和精确的框架,YOLO框架(You Only Look Once)与R-...这是最好的目标检测算法之一,表现出了与R-CNN算法相对相似的性能。 作为刚接触...
  • 论文下载地址:https://pjreddie.com/media/files/papers/yolo.pdf caffe代码下载地址:...YOLO:You Only Look Once: Unified, Real-Time Object Detection 是发表在2016年的CVPR。YOLO基础版可以达到45帧/s...
  • 基于flask的YOLO目标检测web服务及接口

    千次阅读 热门讨论 2020-08-12 10:44:26
    这个小项目是基于flask微型目标检测服务。使用的是YOLOv3版本,使用的是在coco数据集上已经训练好的参数。整个目录结构如下(我运行的环境是window10,pycharm): 其中: cfg是YOLOv3的配置文件,包括权重,网络...
  • 1. 什么是目标检测?拿上图 (用YOLOv3检测) 来说,目标检测 (Object Detection) 就是将图片中的物体用一个个矩形框框出来,并且识别出每个框中的物体是啥,而且最好...
  • 2020年,新出了几个新版本的YOLO目标检测,在微信朋友圈里转发的最多的有YOLOv4,Yolo-Fastest,YOLObile以及百度提出的PP-YOLO。在此之前,我在github是哪个已经发布过YOLOv4,Yolo-Fastest,YOLObile这三种YOLO...
1 2 3 4 5 ... 20
收藏数 13,772
精华内容 5,508
关键字:

yolo目标检测