r-cnn系列代码编译及解读(3)

本文基于 Joseph Redmon 提出的 YOLO2 算法,针对其算法代码实现做以下工作:
1)完成代码的安装与编译
2)使用自己的数据训练 darknet 模型
3)修改源代码(C代码),增加检测功能的python接口


Darknet安装

与之前的 fast-rcnnfaster-rcnn 不一样,官方 YOLO2 的实现(也包括 YOLO1)不再基于 caffe 框架,而是用 C 编写了一个新的深度学习框架 darknet

带来的好处是,其他依赖少,安装比较简单;但通用性不好,并且官方竟然没有给出 detect 的接口。
基于此,网上有很多的修改版本,参考这篇文章
其中py-yolo2项目给 darknet 添加了 python 接口,但实测似乎没有效果;
YAD2K项目使用了keras与tensorflow-gpu,使用时需要对 darknet 的模型做转换,但模型训练接口没找到

最终还是决定使用 darknet 的实现,在源代码中添加 detect 函数供 python 调用。

安装 darknet 非常简单:
1)clone 工程

git clone https://github.com/pjreddie/darknet

2)修改 makefile,添加 cuda 和 opencv 支持

GPU=1
CUDNN=1
OPENCV=1

3)make 编译,然后测试

#下载权值文件
wget http://pjreddie.com/media/files/yolo.weights
#测试
./darknet detect cfg/yolo.cfg yolo.weights data/dog.jpg

使用自己的数据训练

参考这篇文章这篇文章
1)faster-rcnn 一样,使用 voc 格式的数据,按文中的“训练数据格式化”方法准备数据

2)修改 scripts/voc_label.py 的 classes 为自己数据的类名,并修改数据的路径
这个脚本会生成一个包含每张图片完整路径的文件 train.txt
并对每张图片生成一个 label/xxx.txt,记录其 box 和所属类:

 1 0.727864583333 0.82861328125 0.246744791667 0.3251953125
 2 0.34814453125 0.832763671875 0.3115234375 0.33349609375

3)修改配置文件
a.新建 data/my_voc.names ,按行写下数据的类别名

b.新建 cfg/my_voc.data,模仿 voc.data,修改 classes 数目,修改 train 和 names 的路径

c.修改网络参数

这里使用 cfg/tiny-yolo-voc.cfg
修改最后一层 convolutional 的 filters=(classes+coords+1)*5
和接下来的 region 的 classes=类别数

4)下载预训练模型文件

wget http://pjreddie.com/media/files/darknet.conv.weights

5)训练

./darknet detector train ./cfg/my_voc.data ./cfg/my_tiny-yolo-voc.cfg darknet.conv.weights

这里有个蛮玄幻的东西:label 目录没有指定。 darknet 应该是在 train.txt 文件的目录下自动查找得到的
训练结果保存在 backup/ 下

同样用 darknet 做测试

./darknet detector test cfg/my_voc.data cfg/my_tiny-yolo-voc.cfg backup/my_tiny-yolo-voc.backup test/JPEGImages/00135.jpg

添加 detect 功能的 python 接口

源代码里 python/darknet.py 提供了 classify 和 detect 的函数接口,但实际上作者仅实现了 classify, detect 的代码内容与其一模一样(也不知道作者怎么想的,作为一个主打检测的算法。。。)

那么基本思路就有了:在 c 文件中添加 detect 函数,修改 makefile 将该函数编译进库文件 libdarknet.so

添加detect函数接口

分析源代码,发现作者将所有函数接口都放在 include/darknet.h 里。因此也在该头文件声明:

//////////////////////////////////////////////////////////////////////////////////
typedef struct{
    int validate_number;
    float prob;
    box_label box;
} boxs_label;
int get_last_layer_size(network *net);
boxs_label* detect_img(network *net, image im, float thresh, float hier_thresh);
image np_to_image(unsigned char* data, int w, int h, int c);
//////////////////////////////////////////////////////////////////////////////////

1)其中结构体 boxs_label 是 detect 的返回值,记录此次检测的目标个数,相应目标的置信度和位置信息
为了避免内存泄露,将该返回值作为全局变量,首先在读取网络模型的代码中申请空间,每次检测仅修改其内容:

// 修改src/network.c
extern boxs_label* detect_result;
network load_network(char *cfg, char *weights, int clear)
{
    network net = parse_network_cfg(cfg);
    if(weights && weights[0] != 0){
        load_weights(&net, weights);
    }
    if(clear) *net.seen = 0;

    layer l = net.layers[net.n-1];
    detect_result = calloc(l.w*l.h*l.n, sizeof(boxs_label));

    return net;
}

同时在 src/ 下新建文件 extern_var.h 和 extern_var.c

// extern_var.c
#include "darknet.h"
#include "extern_var.h"

boxs_label* detect_result;

// extern_var.h
// 空

2)get_last_layer_size 是辅助函数,python 里需要借助它得到网络输出的大小;detect_img 是检测主函数。
在 examples/detector.c 里添加其实现:

extern boxs_label* detect_result;

int get_last_layer_size(network *net)
{
    layer l = (*net).layers[(*net).n-1];
    return l.w*l.h*l.n;
}

boxs_label* detect_img(network *net, image im, float thresh, float hier_thresh)
{
    // 参考函数 test_detector 和 src/image.c 的 draw_detections

    set_batch_network(net, 1);
    srand(2222222);
    double time;
    char buff[256];
    char *input = buff;
    int j;
    float nms=.3;

    image sized = letterbox_image(im, (*net).w, (*net).h);

    layer l = (*net).layers[(*net).n-1];

    box *boxes = calloc(l.w*l.h*l.n, sizeof(box));
    float **probs = calloc(l.w*l.h*l.n, sizeof(float *));
    for(j = 0; j < l.w*l.h*l.n; ++j) probs[j] = calloc(l.classes + 1, sizeof(float *));
    float **masks = 0;
    if (l.coords > 4){
        masks = calloc(l.w*l.h*l.n, sizeof(float*));
        for(j = 0; j < l.w*l.h*l.n; ++j) masks[j] = calloc(l.coords-4, sizeof(float *));
    }

    float *X = sized.data;
    time=what_time_is_it_now();
    network_predict(*net, X);
    printf("Predicted in %f seconds.\n", what_time_is_it_now()-time);
    get_region_boxes(l, im.w, im.h, (*net).w, (*net).h, thresh, probs, boxes, masks, 0, 0, hier_thresh, 1);
    if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
    //else if (nms) do_nms_sort(boxes, probs, l.w*l.h*l.n, l.classes, nms);

    int i, count=0;
    detect_result[0].validate_number = 0;
    for(i = 0; i < l.w*l.h*l.n; ++i){
        int class = max_index(probs[i], l.classes);
        float prob = probs[i][class];
        if(prob > thresh){
            int width = im.h * .006;
            //printf("%s: %.0f%%\n", names[class], prob*100);
            box b = boxes[i];
            int left  = (b.x-b.w/2.)*im.w;
            int right = (b.x+b.w/2.)*im.w;
            int top   = (b.y-b.h/2.)*im.h;
            int bot   = (b.y+b.h/2.)*im.h;

            if(left < 0) left = 0;
            if(right > im.w-1) right = im.w-1;
            if(top < 0) top = 0;
            if(bot > im.h-1) bot = im.h-1;

            detect_result[0].validate_number = count + 1;
            detect_result[count].prob = prob;
            detect_result[count].box.id = class;
            detect_result[count].box.left = left;
            detect_result[count].box.right = right;
            detect_result[count].box.top = top;
            detect_result[count].box.bottom = bot;
            count++;
        }
    }

    free_image(im);
    free_image(sized);
    free(boxes);
    free_ptrs((void **)probs, l.w*l.h*l.n);

    return detect_result;
}

3)np_to_image 实现 c 代码中的 numpy 格式到 image 格式的转换
针对视频检测,往往是 python 里获得图像流,格式为 numpy.ndarray,需要借助这个接口将其转换为 darknet 的 image 格式

// src/image.c 内添加
image np_to_image(unsigned char* data, int w, int h, int c)
{
    image out = make_image(w, h, c);

    int i,j,k;
    int step = c*w;

    for(i = 0; i < h; ++i){
        for(k= 0; k < c; ++k){
            for(j = 0; j < w; ++j){
                out.data[k*w*h + i*w + j] = data[i*step + j*c + k]/255.;
            }
        }
    }

    return out;
}

修改 makefile

作者的 MakeFile 里将编译目标分为了 OBJEXECOBJA,前者会编译进库文件,后者仅编译进可执行文件

将 EXECOBJA 的 “detector.o”挪到 OBJ 下,因为 detect_img 在该编译单元实现;同时在 OBJ 下添加 “extern_var.o”
修改完成后编译就可以了:

make clean
make

修改 darknet.py 实现检测

1)首先需要定义 detect_img 的返回值,与前面的结构体 boxs_label 保持一致

class BOX_LABEL(Structure):
    _fields_ = [("id", c_int),
                ("x", c_float),
                ("y", c_float),
                ("w", c_float),
                ("h", c_float),
                ("left", c_float),
                ("right", c_float),
                ("top", c_float),
                ("bottom", c_float)]

class BOXS_LABEL(Structure):
    _fields_ = [("validate_number", c_int),
                ("prob", c_float),
                ("box", BOX_LABEL)]

2)实现 detect

def detect(net, out_size, names, im):
    detect_im = lib.detect_img
    detect_im.argtypes = [c_void_p, IMAGE, c_float, c_float]
    detect_im.restype = POINTER(BOXS_LABEL*out_size)
    results = detect_im(net, im, 0.24, 0.5)

    objects_num = results.contents[0].validate_number
    objects_list = list()
    for _num in range(objects_num):
        objects_ = [names[results.contents[_num].box.id].strip(), results.contents[_num].prob, (results.contents[_num].box.left, results.contents[_num].box.right, results.contents[_num].box.top, results.contents[_num].box.bottom)]
        objects_list.append(objects_)
        print 'detect \'%s\' in (left:%d, right:%d, top:%d, bottom:%d) with prob %.3f'%(objects_[0], objects_[2][0], objects_[2][1], objects_[2][2], objects_[2][3], objects_[1])

    return objects_list
#################################################################################

def draw_objects(image, objects):
    for object_ in objects:
        cv2.rectangle(image, (int(object_[2][0]), int(object_[2][2])), (int(object_[2][1]), int(object_[2][3])), \
            (0,0,255), thickness=4)
        text = '%s-%.4f'%(object_[0], object_[1])
        cv2.putText(image, text, (int(object_[2][0]), int(object_[2][2]-5)), \
            cv2.FONT_HERSHEY_SIMPLEX, 2, (0,255,0), thickness=4)
    return image

3)实现 numpy.ndarry 到 image 格式的转换
这里参考这篇文章,使用 numpy.ctypes.data_as 得到数据的 ctypes 指针,供 c 调用
此外,这里还介绍了 Numpy-C-API 的使用,但由于涉及到 ctypes,比较麻烦,故选用上面的方式

def load_img_from_np(img_np, w, h, c):
    load_image_np = lib.np_to_image
    load_image_np.argtypes = [POINTER(c_uint), c_int, c_int, c_int]
    load_image_np.restype = IMAGE

    img_np = img_np.astype(np.uint8)
    return load_image_np(img_np.ctypes.data_as(POINTER(c_uint)), w, h, c)

4)完整的 python/darknet.py

from ctypes import *
import numpy as np
import cv2

class IMAGE(Structure):
    _fields_ = [("w", c_int),
                ("h", c_int),
                ("c", c_int),
                ("data", POINTER(c_float))]

class METADATA(Structure):
    _fields_ = [("classes", c_int),
                ("names", POINTER(c_char_p))]

class BOX_LABEL(Structure):
    _fields_ = [("id", c_int),
                ("x", c_float),
                ("y", c_float),
                ("w", c_float),
                ("h", c_float),
                ("left", c_float),
                ("right", c_float),
                ("top", c_float),
                ("bottom", c_float)]

class BOXS_LABEL(Structure):
    _fields_ = [("validate_number", c_int),
                ("prob", c_float),
                ("box", BOX_LABEL)]

lib = CDLL("/home/cothink_tech/darknet/libdarknet.so", RTLD_GLOBAL)
lib.network_width.argtypes = [c_void_p]
lib.network_width.restype = c_int
lib.network_height.argtypes = [c_void_p]
lib.network_height.restype = c_int

def load_meta(f):
    lib.get_metadata.argtypes = [c_char_p]
    lib.get_metadata.restype = METADATA
    return lib.get_metadata(f)

def load_net(cfg, weights):
    load_network = lib.load_network_p
    load_network.argtypes = [c_char_p, c_char_p, c_int]
    load_network.restype = c_void_p
    return load_network(cfg, weights, 0)

def load_img(f):
    load_image = lib.load_image_color
    load_image.argtypes = [c_char_p, c_int, c_int]
    load_image.restype = IMAGE
    return load_image(f, 0, 0)

def load_img_from_np(img_np, w, h, c):
    load_image_np = lib.np_to_image
    load_image_np.argtypes = [POINTER(c_uint), c_int, c_int, c_int]
    load_image_np.restype = IMAGE

    img_np = img_np.astype(np.uint8)
    return load_image_np(img_np.ctypes.data_as(POINTER(c_uint)), w, h, c)

def letterbox_img(im, w, h):
    letterbox_image = lib.letterbox_image
    letterbox_image.argtypes = [IMAGE, c_int, c_int]
    letterbox_image.restype = IMAGE
    return letterbox_image(im, w, h)

def predict(net, im):
    pred = lib.network_predict_image
    pred.argtypes = [c_void_p, IMAGE]
    pred.restype = POINTER(c_float)
    return pred(net, im)

def classify(net, meta, im):
    out = predict(net, im)
    res = []
    for i in range(meta.classes):
        res.append((meta.names[i], out[i]))
    res = sorted(res, key=lambda x: -x[1])
    return res

def detect(net, out_size, names, im):
    detect_im = lib.detect_img
    detect_im.argtypes = [c_void_p, IMAGE, c_float, c_float]
    detect_im.restype = POINTER(BOXS_LABEL*out_size)
    results = detect_im(net, im, 0.24, 0.5)

    objects_num = results.contents[0].validate_number
    objects_list = list()
    for _num in range(objects_num):
        objects_ = [names[results.contents[_num].box.id].strip(), results.contents[_num].prob, (results.contents[_num].box.left, results.contents[_num].box.right, results.contents[_num].box.top, results.contents[_num].box.bottom)]
        objects_list.append(objects_)
        print 'detect \'%s\' in (left:%d, right:%d, top:%d, bottom:%d) with prob %.3f'%(objects_[0], objects_[2][0], objects_[2][1], objects_[2][2], objects_[2][3], objects_[1])

    return objects_list

def net_size(net):
    net_w = lib.network_width
    net_h = lib.network_height
    net_w.argtypes = [c_void_p]
    net_w.restype = c_int
    net_h.argtypes = [c_void_p]
    net_h.restype = c_int
    return (net_w(net), net_h(net))

def net_output_size(net):
    out_size = lib.get_last_layer_size
    out_size.argtypes = [c_void_p]
    out_size.restype = c_int
    return out_size(net)

def read_names(f):
    with open(f, 'r') as f_:
        names = f_.readlines()
    return names

def draw_objects(image, objects):
    for object_ in objects:
        cv2.rectangle(image, (int(object_[2][0]), int(object_[2][2])), (int(object_[2][1]), int(object_[2][3])), \
            (0,0,255), thickness=4)
        text = '%s-%.4f'%(object_[0], object_[1])
        cv2.putText(image, text, (int(object_[2][0]), int(object_[2][2]-5)), \
            cv2.FONT_HERSHEY_SIMPLEX, 2, (0,255,0), thickness=4)
    return image

if __name__ == "__main__":
    net = load_net("cfg/my_tiny-yolo-voc.cfg", "/home/xxx/darknet/backup/my_tiny-yolo-voc.backup")
    out_size = net_output_size(net)
    names = read_names("data/my_voc.names")
    '''
    im = load_img("/home/xxx/py-faster-rcnn/trainval/JPEGImages/00135.jpg")
    objects = detect(net, out_size, names, im)
    img = draw_objects(im_np, objects)

    resz = cv2.resize(img, (768, 512))
    cv2.imshow('detect', resz)
    cv2.waitKey(0)
    '''
    video_file = "/home/xxx/py-faster-rcnn/trainval/test.avi"
    cap = cv2.VideoCapture(video_file)
    if cap.isOpened():
        while True:
            ret, im_np = cap.read()
            if not ret:
                break
            image = load_img_from_np(im_np, im_np.shape[1], im_np.shape[0], im_np.shape[2])

            objects = detect(net, out_size, names, image)
            img = draw_objects(im_np, objects)

            resz = cv2.resize(img, (768, 512))
            cv2.imshow('detect', resz)
            cv2.waitKey(1)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值