基于ROS和Tx2的Yolo-v3目标检测服务

基于ROS和Tx2的Yolo-v3目标检测服务

Tx2是nvidia公司推出的一款只有信用卡大小的计算芯片,使用了armv8多核处理器并配有256个NVIDIA研发的Pascal GPU计算核心(与Titan xp同类型的核心),用于与普通计算机媲美的性能,并且nvidia为其定制了相应的系统,其中配备了cuda和cudnn加速库,但是其功耗极低,安装简便,拓展借口齐全,因此在很多移动端任务中被采用。
本文讲述了作者基于Tx2,使用ROS开发Yolo-v3目标检测服务的工作,希望能和读者分享其中的心得与问题,共同进步。
本工作的github链接: [email protected]:public1024/yolov3-ros_service_tx2.git

目录

  • 基于ROS和Tx2的Yolo-v3目标检测服务
    • 目录
    • 前期准备
    • yolo-v3
    • ROS服务介绍

前期准备

之前采购tx2的时候,nvidia还是提供教育优惠的,如果是学生,可以直接在其官网上申请,能够便宜很多。由于tx2使用的cpu是arm架构的cpu,所以是不能按照常见的方法安装系统,需要使用nvidia提供的刷机工具,安装。可以参考nvidia官网和相关社区,一定要注意!留足硬盘空间!
虽然架构不一样,但是,tx2上依然可以安装并使用ROS,这里直接使用jetsonhacks社区提供的脚本,这里,我是安装的desktop-full。

yolo-v3

yolo-v3是第三代的yolo,相对于第二代,它保留了v2的anchor和k-means等特点,并做出了如下改进:
1 使用logistic loss,能够更好地描述每个bounding box的所属类别
2 使用了更多的anchor
3 将feature map进行了两次上采样,分别使用原先以及上采样得到的feature map进行检测,这样能够更好地覆盖多尺度的目标
4 借鉴了resnet,使用了skip layer,才用了darknet-53进行特征抽取
总体而言,yolo-v3虽然相对于yolo-v2速度减慢了一些,但是依然能够在高性能GPU上实现实时检测,并能多多尺度的目标完成检测,有更广阔的应用场景。如果想了解更多,建议阅读原文。
yolo-v3的训练过程也比较容易,参考其官网,将自己的数据制作成voc数据格式即可,网络上有很多教程可以参考,其中有两点值得注意:
1 修改网络中和yolo层相关的地方,这里一共有三处需要修改,如果不全部修改,可能出现检测失败,程序段错等情况。

[convolutional]
size=1
stride=1
pad=1
filters=18 #3*(class+5)
activation=linear
[yolo]
mask = 6,7,8
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
classes=1 #class num
num=9
jitter=.3
ignore_thresh = .5
truth_thresh = 1
random=1

2 训练过程中出现Nan,这里出现nan是由于在这个尺度上并没有得出目标,所以在计算平均时,出现了除0的问题。但是由于这一部分只是纯粹用来显示,并不会直接影响训练,其实并没有什么影响,如果实在不想看到,可以增加batch size。如果发生显存不足,可以同时增加subdivisions,降低每此放入gpu中的数据。

ROS服务介绍

ROS中有两种进行通信的方式,一种是使用msg,第二种则是使用服务。服务就是一种远程调用,定义好输入以及输出,就能在任意节点中,向服务节点申请调用该函数。在本工作中,我期望获取图像中概率最大的目标,因此我定义了如下的服务:

sensor_msgs/Image img #输入图像
---
int32 top_left_x #左上角x
int32 top_left_y #左上角y
int32 bottom_right_x #右上角x
int32 bottom_right_y #右上角y
int32 lablel #类别,无类别为-1

可能是由于我修改的不对,在将darknet源码用cmake嵌入项目时,始终无法在tx2上编译成功,所以,索性将darknet编译为动态链接库,进行调用。由于darknet中的图像使用了自定义的image结构,由于不太懂sensor_msg::Image的具体数据格式,所以才用了将msg先转为cv::Mat再将cv::Mat转为image的方式。核心的服务函数为:

bool detect_func(alv::obj_detect::Request &req,
                alv::obj_detect::Response &res)
{
    #将图像转为cv::Mat
    cv_bridge::CvImagePtr src= cv_bridge::toCvCopy(req.img,sensor_msgs::image_encodings::BGR8);

    #将图像转为image
    image im = color_mat_to_image(src->image);
    image sized = letterbox_image(im, g_net->w, g_net->h);
    layer l = g_net->layers[g_net->n-1];

    #前向传播
    float *X = sized.data;
    network_predict(g_net, X);
    int nboxes = 0;
    #获取边界框
    detection *dets = get_network_boxes(g_net, im.w, im.h, g_thresh, g_hier_thresh, 0, 1, &nboxes);
    #nms
    if (g_nms) do_nms_sort(dets, nboxes, l.classes, g_nms);

    #获取概率最大的候选框
    int max_idx=-1;
    float max_prob=-1.0;
    for (int i=0;i
    {
        if (max_prob.prob[dets[i].sort_class])
        {
            max_prob=dets[i].prob[dets[i].sort_class];
            max_idx=i;
        }
    }

    #回复结果
    if (max_idx==-1) {
        res.lablel = -1;
        res.bottom_left_x=0;
        res.bottom_left_y=0;
        res.top_left_y=0;
        res.top_left_x=0;
    }
    else {
        res.lablel = dets[max_idx].sort_class;
        res.bottom_left_x=limit_range(im.w*(dets[max_idx].bbox.x+dets[max_idx].bbox.w/2),0,im.w);
        res.bottom_left_y=limit_range(im.h*(dets[max_idx].bbox.y+dets[max_idx].bbox.h/2),0,im.h);
        res.top_left_y=limit_range(im.w*(dets[max_idx].bbox.x-dets[max_idx].bbox.w/2),0,im.w);
        res.top_left_x=limit_range(im.h*(dets[max_idx].bbox.x-dets[max_idx].bbox.h/2),0,im.h);
    }

    ROS_INFO("label %d",res.lablel);
    #释放空间
    free_detections(dets,nboxes);
    free_image(im);
    free_image(sized);
    return true;
}

在客户端中,调用服务的样例代码为:

if (client.call(srv)) {
    ROS_INFO("label %d (%d %d) (%d %d)", srv.response.lablel,srv.response.top_left_x,srv.response.top_left_y,srv.response.bottom_right_x,srv.response.bottom_right_y);
} else {
    ROS_INFO("FAILED");
}

经过测试,在tx2上,该服务的运行速度在1s以内,相对与笔记本cpu的10s+的运行速度,还是比较好的,在一些diy的项目中也能够达到要求,当然如果使用tiny-yolo的模型,能够达到实时要求,但是效果就不一定好了~


你可能感兴趣的:(基于ROS和Tx2的Yolo-v3目标检测服务)