基于PaddleDetection的锥桶检测并在Gazebo环境中实现部署

2021.3.11 更新:

  • 替换全部中文路径,解决某些情况下本地运行读取路径报错问题
  • 将PaddleDetection挂载到数据集,解决git过慢问题
  • 新增聚类生成初始anchor大小步骤
  • 提供导出参数,可供大家直接部署使用

本文涉及内容

    1. 项目背景
    1. 数据集制作
    1. 模型训练与导出
    1. 模型部署
    1. 总结

1.项目背景

  • gazebo作为一款功能强大且开源免费的仿真平台,对于疫情在家没有实物调试的大学生们来说,简直提供了完美的仿真环境去检验控制算法。刚好这段时间又开始研究智能车视觉导航,就针对其中锥桶检测整理出本项目,自制gazebo锥桶训练集,基于paddledetection完成训练最后导出模型,最终实现在gazebo仿真环境中的部署。帮助大家更好的在gazebo中检验各种算法

2.数据集制作

  • 仿真环境的锥桶和真实世界锥桶还挺像的,但奈何没找到合适的以训练模型或者对应数据集,所以自己动手做了
  • 图片是在gazebo环境中用手动方向键驱动仿真小车从各角度拍的锥桶视频,从视频中抽帧得到图片
  • 标注采用的工具是开源的标注工具lableimg,标记后自动生成xml文件,符合VOC数据集读取格式。

  • 因为考虑到任务比较简单,最终从视频流中筛选出520张数据数据集

3.模型训练与导出


import os
import cv2
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
!unzip -o -q data/data43886/barricade.zip
print("done")
done

3.1 划分训练集和测试集

这里按照9:1划分了训练集和测试集并生成了对应的label_list.txt,同时要注意生成txt文件要符合PaddleDetection读取格式:

Img_Path/Img_name Xml_Path/Xml_name

!mkdir barricade/ImageSets

train = open('barricade/ImageSets/train.txt', 'w')
test = open('barricade/ImageSets/test.txt', 'w')
label_list = open('barricade/label_list.txt', 'w')
xml_path = "./Annotations/"
img_path = "./JPEGImages/"
count = 0

for xml_name in os.listdir('barricade/Annotations'):
    data =img_path + xml_name[:-4] + ".jpg " + xml_path + xml_name + "\n"
    if(count%10==0):
        test.write(data)
    else:
        train.write(data)
    count += 1

label_list.write("barricade")

train.close()
test.close()
label_list.close()

3.2 配置训练参数

PaddleDetection开发套件最方便的点就是不需要自己搭建复杂的模型,不仅可以快速进行迁移学习同时对训练中的参数也可以直接在yml环境文件中进行修改,简单方便易操作

具体步骤总结为:

    1. 点开PaddleDetection/configs目录,在其中挑选自己要用的模型
    1. 点开挑选好的模型yml文件,进行配置的修改,其中主要注意三点:
  • —①数据集格式:PaddleDetecion支持读取的格式有VOC , COCO,wider_face和fruit四种数据集,对于初学者建议使用原本yml设置好的数据集格式使用
  • —②数据集读取目录:将TrainReader,Testreder中数据集对应的文件,dataset_dir为数据集根目录,anno_path为3.1步中我们生成的txt文件。
  • —③训练策略:主要改的几个参数是
名称作用
max_iters训练iter次数
save_dir保存模型参数的路径
snapshot_iter经过多少iter评估一次
pretrain_weights预训练参数从哪里读取
num_classes检测类别数,如果是双阶段检测模型得是分类数+1
learning_rate学习率以及下面的PiecewiseDecay的milestones和warmup轮次设置

值得一提的是:

  • 1.TestReader中也有anno_path,这个是设置推理时打出来的标签名的,是对应label_list.txt,是不是有同学在用PaddleDtection做infer的时候经常出现aeroplane这种莫名奇妙的标签,就是因为没有设置TestReader的anno_path导致的,这种情况中use_default_label也要都设成false哦
  • 2.这里提到参数只是常用几个修改参数,更多yml参数作用看参考官方解释

因为本次检测任务目标种类单一、环境简单、特征明显,考虑到我们部署模型的时候还会运行其他ROS功能包,所以检测模型选择了轻量级的YOLOV3-MobilenetV1,对应的yml设置也附在了项目中

!unzip -o -q data/data73244/PaddleDetection-release-2.0-rc.zip
  • 因为咱们使用的是YOLOV系列,也可以使用PaddleDetection提供的脚本在自己的数据集上实现聚类得到最佳初始anchor大小,并在yml中进行修改
!python PaddleDetection-release-2.0-rc/tools/anchor_cluster.py -c PaddleDetection-release-2.0-rc/configs/yolov3_mobilenet_v1_voc.yml -n 9 -s 416 -i 1666
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/setuptools/depends.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
/home/aistudio/PaddleDetection-release-2.0-rc/ppdet/utils/voc_utils.py:70: DeprecationWarning: invalid escape sequence \.
  elif re.match('test\.txt', fname):
/home/aistudio/PaddleDetection-release-2.0-rc/ppdet/utils/voc_utils.py:68: DeprecationWarning: invalid escape sequence \.
  if re.match('trainval\.txt', fname):
100%|██████████████████████████████████████| 468/468 [00:00<00:00, 48185.54it/s]
2021-03-11 17:27:26,108 - INFO - Running kmeans for 9 anchors on 713 points...
avg_iou: 0.9007:   0%|                                 | 0/1666 [00:00<?, ?it/s]
2021-03-11 17:27:26,117 - INFO - 9 anchor cluster result: [w, h]
2021-03-11 17:27:26,117 - INFO - [22, 79]
2021-03-11 17:27:26,117 - INFO - [25, 84]
2021-03-11 17:27:26,117 - INFO - [27, 108]
2021-03-11 17:27:26,117 - INFO - [34, 124]
2021-03-11 17:27:26,117 - INFO - [39, 133]
2021-03-11 17:27:26,117 - INFO - [50, 172]
2021-03-11 17:27:26,117 - INFO - [80, 257]
2021-03-11 17:27:26,117 - INFO - [131, 303]
2021-03-11 17:27:26,118 - INFO - [195, 353]

3.3 开始训练

#训练
!python PaddleDetection-release-2.0-rc/tools/train.py  -c PaddleDetection-release-2.0-rc/configs/yolov3_mobilenet_v1_voc.yml --eval --use_vdl=True --vdl_log_dir=vdl

3.4 导出模型

因为任务比较简单吼,训练几轮后指标都挺好的,测试集上mAP轻轻松松95+,我就直接导出了,PaddleDetection也提供导出工具

!python PaddleDetection-release-2.0-rc/tools/export_model.py -c PaddleDetection-release-2.0-rc/configs/yolov3_mobilenet_v1_voc.yml \
        --output_dir=inference_model \
        -o weights=output/yolov3_mobilenet_v1_voc/best_model
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/setuptools/depends.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
/home/aistudio/PaddleDetection-release-2.0-rc/ppdet/modeling/losses/yolo_loss.py:71: DeprecationWarning: The 'warn' method is deprecated, use 'warning' instead
  "config YOLOv3Loss.batch_size is deprecated, "
2021-03-11 18:12:50,725 - WARNING - config YOLOv3Loss.batch_size is deprecated, training batch size should be set by TrainReader.batch_size
2021-03-11 18:12:52,820 - INFO - Load categories from barricade/label_list.txt
2021-03-11 18:12:52,823 - INFO - Export inference config file to inference_model/yolov3_mobilenet_v1_voc/infer_cfg.yml
2021-03-11 18:12:52,894 - INFO - save_inference_model pruned unused feed variables im_id
2021-03-11 18:12:52,894 - INFO - Export inference model to inference_model/yolov3_mobilenet_v1_voc, input: ['image', 'im_size'], output: ['multiclass_nms_0.tmp_0']...

运行之后就会在设置的inference_model目录下生成我们的部署文件,一共有三个:model、params和infer_cfg.yml,有这三个文件我们就可以开始部署了。

4.模型部署

  • 以上步骤咱们使用的是飞浆核心框架PaddlePaddle,这完成是模型的训练,大家知道训练过程中涉及反向传播是有很多参数的,而这些参数在实际使用正向推导时是没有用的,这也是3.4步导出模型的意义,把反向传播的一些参数全部去掉,只保留核心的推导参数,降低参数的,这样剩下的参数就全是对正向推导有用的了。
  • 对于推理部署,飞浆提供了两种方法PaddleLitePaddle Inference。两种推理工具在我使用下来感觉:Paddle Lite主要针对移动端,而Paddle Inference是针对服务器端、云端和无法使用Paddle Lite的嵌入式设备,两者都提供高性能推理引擎,这次咱们部署环境是x86 Linux,所以就采用Paddle Inference实现

4.1 Paddle Inference 2.0

  • Paddle Inference是飞浆原生推理库,原生就意味Paddle能实现的op,Paddle Inference不需要通过任何类型转换就可以实现,同时提供C、C++、Python的预测API,为了能让大家理解的更方便,这里就选择使用Python API进行推理,也可以直接在AI Studio上直接运行

4.2 Paddle Inference安装

  • 咱们部署环境是X86的Linux可以直接参考官网步骤安装Paddle调用Paddle Inference进行推理
  • 本项目推理部署采用的Paddle Inference 2.0,请正确安装版本

4.3 Paddle Inference推理步骤

import cv2
import numpy as np
from paddle.inference import Config
from paddle.inference import create_predictor

4.3.1 配置Config

  • PaddleInferen推理核心是创建推理引擎predictor,在创建前要配置Config
  • Config类是所有属性配置,属性在Config实例化配置好后装载到predictor中,其方法全部是配置属性的,具体主要常用方法总结如下:
  1. set_model(“model”,“params”):读取模型和参数
  2. enable_use_gpu(memory,gpu_id):设置是否使用GPU,使用GPU分配的内存和使用的GPU的ID号
  3. enable_tensorrt_engine():开启tensorrt加速
  4. enable_mkldnn():开启MKLDNN
  • 因为使用显卡跑虚拟机里的Gazebo会卡机,所以我的虚拟机环境是不加载显卡,对应推理时也是使用CPU推理,这也是为啥模型选MobilenetV1了哈哈哈
config = Config()
config.set_model("inference_model/yolov3_mobilenet_v1_voc/__model__","inference_model/yolov3_mobilenet_v1_voc/__params__")
config.disable_gpu()
config.enable_mkldnn()

4.3.2 创建Predictor

  • 使用create_predictor(config)用于创建Predictor类
  • Predictor类是推理引擎,其方法全是正向推理运行的
predictor = create_predictor(config)

4.3.3 配置推理引擎

  • 创建推理引擎predictor之后我们要手动对其输入进行设置
4.3.3.1 查看输入格式
  • 我们先手动调出,看看他的输入应该是啥
#获取推理引擎的输入的名字
input_names = predictor.get_input_names()
print(len(input_names))
#查看输入的两个变量属性
input_tensor_0 = predictor.get_input_handle(input_names[0])
input_tensor_1 = predictor.get_input_handle(input_names[1])
#可见都是输入的handle都是tensor类的变量
print(input_names)
print(input_tensor_0)
print(input_tensor_1)
2
['image', 'im_size']
<paddle.fluid.core_avx.PaddleInferTensor object at 0x7fee7c1ecab0>
<paddle.fluid.core_avx.PaddleInferTensor object at 0x7fee7c1eccb0>
  • 可以看到上方cell的显示,推理引擎的输入格式应该是 : [图片,图片的大小] ,这就是我们等会向predictor内加载信息时的格式
4.3.3.2 图像预处理
  • 知道输入格式,那咱们按格式读一张图片进去,看看输出是啥样
  • 但在此前还得按照模型要求完成输入图像的预处理,对应预处理操作可以在导出的yml文件中查看
img = cv2.imread("test.jpg")
def preprocess(img , Size):
    img = cv2.resize(img,(Size,Size),0,0)
    mean = [0.485, 0.456, 0.406]
    std = [0.229, 0.224, 0.225]
    img = img / 255.0
    mean = np.array(mean)[np.newaxis, np.newaxis, :]
    std = np.array(std)[np.newaxis, np.newaxis, :]
    img -= mean
    img /= std
    img = img.astype("float32").transpose(2,0,1)
    img = img[np.newaxis,::]
    return img
image = preprocess(img,416)
4.3.3.3 查看输出
  • 看看输出是啥样
im_shape = np.array([416, 416]).reshape((1, 2)).astype(np.int32)
img = [image,im_shape]
for i,name in enumerate(input_names):
    #定义输入的tensor
    input_tensor = predictor.get_input_handle(name)
    #确定输入tensor的大小
    input_tensor.reshape(img[i].shape)
    #对应的数据读进去
    input_tensor.copy_from_cpu(img[i].copy())

# 运行predictor
predictor.run()

# 获取输出
output_names = predictor.get_output_names()
print(output_names)
results = []
for i, name in enumerate(output_names):
    output_tensor = predictor.get_output_handle(name)
    output_data = output_tensor.copy_to_cpu()
    print(output_data)
['save_infer_model/scale_0.tmp_0']
[[0.00000000e+00 4.25863624e-01 2.03892258e+02 1.01716118e+02
  2.61180664e+02 2.33477722e+02]
 [0.00000000e+00 9.27145109e-02 2.15066772e+02 1.11449356e+02
  2.43748352e+02 1.95806931e+02]
 [0.00000000e+00 7.39767328e-02 2.01971436e+02 1.57187057e+02
  2.63916443e+02 2.11278336e+02]
 [0.00000000e+00 1.74350999e-02 2.16398804e+02 1.34368530e+02
  2.50863831e+02 2.27454163e+02]
 [0.00000000e+00 1.18300365e-02 2.07281219e+02 6.60237885e+01
  2.33852631e+02 2.59419739e+02]
 [0.00000000e+00 1.08919265e-02 1.98264145e+02 1.29647232e+02
  2.91898315e+02 2.02846115e+02]]
  • 可以看到输出是 类别+置信度+x1+y1+x2+y2的格式,这样就可对其进行操作了

4.4 完整推理

  • 完整程序也可在predict.py中查看
import cv2
import numpy as np
from paddle.inference import Config
from paddle.inference import create_predictor

def preprocess(img , Size):
    img = cv2.resize(img,(Size,Size),0,0)
    mean = [0.485, 0.456, 0.406]
    std = [0.229, 0.224, 0.225]
    img = img / 255.0
    mean = np.array(mean)[np.newaxis, np.newaxis, :]
    std = np.array(std)[np.newaxis, np.newaxis, :]
    img -= mean
    img /= std
    img = img.astype("float32").transpose(2,0,1)
    img = img[np.newaxis,::]
    return img

def predicte(img,predictor):
    input_names = predictor.get_input_names()
    for i,name in enumerate(input_names):
        #定义输入的tensor
        input_tensor = predictor.get_input_handle(name)
        #确定输入tensor的大小
        input_tensor.reshape(img[i].shape)
        #对应的数据读进去
        input_tensor.copy_from_cpu(img[i].copy())
    #开始预测
    predictor.run()
    #开始看结果
    results =[]
    output_names = predictor.get_output_names()
    for i, name in enumerate(output_names):
        output_tensor = predictor.get_output_handle(name)
        output_data = output_tensor.copy_to_cpu()
        results.append(output_data)
    return results

if __name__ == '__main__':
    #读入的摄像头信息根据大家自己ROS结点自己读入咯,这里我就直接用摄像头读取替代了,大家到时候这里自己更换进行
    cap = cv2.VideoCapture(1)
    config = Config()
    config.set_model("inference_model/yolov3_mobilenet_v1_voc/__model__","inference_model/yolov3_mobilenet_v1_voc/__params__")
    config.disable_gpu()
    config.enable_mkldnn()
    predictor = create_predictor(config)
    im_size = 416
    im_shape = np.array([416, 416]).reshape((1, 2)).astype(np.int32)
    while(1):
        success, img = cap.read()
        if (success == False):
            break
        img = cv2.resize(img, (im_size,im_size),0, 0)
        data = preprocess(img, im_size)
        results = trash_detect(trash_detector, [data, im_shape])
        for res in results[0]:
            img = cv2.rectangle(img, (int(res[2]), int(res[3])), (int(res[4]), int(res[5])), (255, 0, 0), 2)
        cv2.imshow("img", img)
        cv2.waitKey(10)
, (int(res[2]), int(res[3])), (int(res[4]), int(res[5])), (255, 0, 0), 2)
        cv2.imshow("img", img)
        cv2.waitKey(10)
    cap.release()
  • 实际部署推理结果:

总结

  • Gazebo环境中摄像头的ROS结点名因人而异,故predict.py脚本中需要大家自己更改读入的视频数据
  • 后续的路径规划算法和Paddle无关,这里就不做太多叙述了,有兴趣的伙伴可以一起来讨论哇
  • 本项目使用Paddle2.0完整展示了从数据集制作、模型选取、模型训练、模型导出以及模型部署的全流程实践,希望能帮助启发到大家

关于作者

  • 姓名:Fitz

  • 武汉理工大学信息工程专业2018级本科在读

  • 感兴趣方向:计算机视觉、推理部署、迁移学习

  • AIstudio主页 : Fitzie

  • 欢迎大家有问题一起交流讨论,共同进步~

运行代码请点击:https://aistudio.baidu.com/aistudio/projectdetail/1608121

运行方式如下:
在这里插入图片描述

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现灰度摄像头识别锥桶,可以使用C语言和一些图像处理技术来实现。下面是一个简单的C语言代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define WIDTH 320 #define HEIGHT 240 int main(int argc, char* argv[]) { FILE* fp; unsigned char buf[WIDTH * HEIGHT]; int i, j, k; int sum = 0; int count = 0; int threshold = 128; // 打开图像文件 fp = fopen("image.bin", "rb"); if(fp == NULL) { printf("Open image file failed!\n"); return -1; } // 读取图像数据 fread(buf, 1, WIDTH * HEIGHT, fp); // 关闭文件 fclose(fp); // 遍历图像像素点 for(i = 0; i < HEIGHT; i++) { for(j = 0; j < WIDTH; j++) { // 计算像素点的灰度值 k = buf[i * WIDTH + j]; sum += k; count++; // 判断是否为锥桶区域 if(k > threshold) { buf[i * WIDTH + j] = 255; // 白色 } else { buf[i * WIDTH + j] = 0; // 黑色 } } } // 输出锥桶区域的平均灰度值 printf("The average gray value of the bucket region is %d\n", sum / count); // 保存处理后的图像 fp = fopen("result.bin", "wb"); if(fp == NULL) { printf("Create result file failed!\n"); return -1; } fwrite(buf, 1, WIDTH * HEIGHT, fp); // 关闭文件 fclose(fp); return 0; } ``` 上述代码,我们先打开图像文件(假设为二进制文件),然后读取图像数据。接着,遍历像素点,计算像素点的灰度值,并判断是否为锥桶区域。最后,输出锥桶区域的平均灰度值,并保存处理后的图像。可以根据实际情况调整阈值等参数以提高识别准确率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值