【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能

在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能

写在前面:这篇文章非常基础,适合像我一样还在摸索中的新手学习讨论。当然,文章中也很可能出现很多错误,烦请各位大佬批评指正。


文章目录

  • 在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能
  • 前言
  • 一、Yolov5必备环境
  • 二、源码解读
    • 1.detect.py个人理解
    • 2.Jetson Nano串口收发
  • 魔改detect.py(请先备份一份detect.py原文件)
  • 总结
  • 功能展示


前言

由于项目需要,本人从Yolov3开始首次接触到目标检测神经网络,短短两年的时间里,目标检测这一领域发展速度飞快,yolov4和v5相继开源。本文记录我在Jetson Nano上部署yolov5神经网络的全过程。


一、Yolov5必备环境

  1. Yolov5开源版本使用的语言为python Github开源地址,Jetson Nano上使用起来也比较方便,我们只需搭建必备的python环境即可(CUDA这东西Jetosn Nano系统镜像中已经有了,不需要重复安装)。在我的印象中,安装torch时吃了点亏,别的都十分顺利。
  2. 遇到下载速度慢时考虑换源,我用的是豆瓣源,感觉速度还不错。如果大家装torch和tensorflow遇到的问题很多的话,我再考虑写一篇教程分享一下经验。
  3. 具体的环境要求如下:【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第1张图片
    这个地方我使用的yolov5版本不是最新的,如果下载的是最新的源码,请一定要看一下压缩包内的requirements.txt,可能会有变动。
  4. 我的Jetson Nano目前的环境版本如下(确认可以运行yolov5)
Cython                        0.29.21
matplotlib                    3.3.3
numpy                         1.19.4
opencv-python                 4.5.1.48
Pillow                        8.1.0
PyYAML                        3.12
scipy                         1.1.0
tensorboard                   2.4.0
torch                         1.6.0a0+b31f58d
torchvision                   0.7.0a0+78ed10c
tqdm                          4.55.1
seaborn                       0.11.1
pandas                        1.1.5
  1. 最后一点提醒,有时候v5跑不起来不一定是环境的问题,我最开始也是跑不起来,后来更换了合适的weight文件就解决问题了。因为yolov5更新迭代的速度真的是太快了,网络上很多开源的权重文件在训练时的网络版本和你当前的网络版本并不符合,自然就跑不起来。

二、源码解读

1.detect.py个人理解

  1. 首先从main函数入手,截取main源码如下:
if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
    parser.add_argument('--source', type=str, default='inference/images', help='source')  # file/folder, 0 for webcam
    parser.add_argument('--output', type=str, default='inference/output', help='output folder')  # output folder
    parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
    parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='display results')
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class')
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--update', action='store_true', help='update all models')
    opt = parser.parse_args()
    print(opt)

    with torch.no_grad():
        if opt.update:  # update all models (to fix SourceChangeWarning)
            for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']:
                detect()
                create_pretrained(opt.weights, opt.weights)
        else:
            detect()

  1. 从源码我们可以看出main部分的逻辑并不复杂,开始处使用python中的argparse来完成传参,真正复杂的地方在于前面写的detec()函数。detect.py在调用时具有以下功能:【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第2张图片
  2. 从上图我们可以看出,如果只是停留在应用阶段,detect.py这个官方给我们写好的脚本已经具有相当强大的功能了,其中包括且不限于手动指定权重文件的路径输入图片的路径输出图片位置检测图片的尺寸(像素)非极大值抑制阈值是否展示处理后图片选用用于检测的硬件设备保存识别结果等等。这也间接说明,如果只是停留在基础应用阶段,在这个文件上进行一定程度的修改就已经能够满足绝大多数应用场景的需求了。在想明白这一点后,我开始继续下面的工作。

2.Jetson Nano串口收发

import time
import serial

print("UART Demonstration Program")
print("NVIDIA Jetson Nano Developer Kit")


serial_port = serial.Serial(
    port="/dev/ttyTHS1",
    baudrate=115200,
    bytesize=serial.EIGHTBITS,
    parity=serial.PARITY_NONE,
    stopbits=serial.STOPBITS_ONE,
)
# Wait a second to let the port initialize
time.sleep(1)

try:
    # Send a simple header
    serial_port.write("UART Demonstration Program\r\n".encode())
    while True:
        if serial_port.inWaiting() > 0:
            data = serial_port.read()
            print(data)
            serial_port.write(data)
            # if we get a carriage return, add a line feed too
            # \r is a carriage return; \n is a line feed
            # This is to help the tty program on the other end 
            # Windows is \r\n for carriage return, line feed
            # Macintosh and Linux use \n
            if data == "\r".encode():
                # For Windows boxen on the other end
                serial_port.write("\n".encode())


except KeyboardInterrupt:
    print("Exiting Program")

except Exception as exception_error:
    print("Error occurred. Exiting Program")
    print("Error: " + str(exception_error))

finally:
    serial_port.close()
    pass

这套代码在我的Jetson Nano上是可以直接运行的,值得注意的是需要sudo运行该python脚本,否则会显示权限不够,串口无法打开。

差点忘记了,同样重要的是Jetson Nano上UART串口该怎么连。
pin8为发送端uart_2_TX,pin10为接收端uart_2_RX

  1. 在连接好发送端、接收端和GND后,将USB-TTL插上电脑,用串口助手打开。

  2. 成功运行脚本后,通过串口助手向Jetson Nano发送hello

  3. 在Jetson Nano上看到如下返回:
    【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第3张图片
    Jetson Nano返回了你输入的四个字符

  4. 在串口助手上看到如下内容:【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第4张图片


魔改detect.py(请先备份一份detect.py原文件)

  1. 首先解决第一个问题,我在开发过程中是通过xshell远程登录的Jetson Nano,运行检测和识别的时候也不会去外接显示屏来观看识别效果。这就直接导致我在使用网络摄像头进行识别时他会给我如下报错【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第5张图片
    解决起来也很简单
    找到Set Dataloader这一项,将view_img改成false,并自定义了一个叫做uart_yes的量,将其设为True。
    我这样做是图简单,考虑到项目需要,在每一次调用webcam的时候将自动激活串口,输出识别结果和坐标信息。
# Set Dataloader
    vid_path, vid_writer = None, None
    if webcam:
        view_img = False                                 #change here
        uart_yes = True
        cudnn.benchmark = True  # set True to speed up constant image size inference
        dataset = LoadStreams(source, img_size=imgsz)
    else:
        save_img = True
        dataset = LoadImages(source, img_size=imgsz)

  1. 加入串口配置。这一步我直接写在了detect函数前,可以根据个人需要来调整配置参数(波特率、数据位等等)。【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第6张图片

  2. 输出识别坐标信息。这是最核心的魔改部分,首先在 原版的 detect.py中找到如下部分【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第7张图片

  3. 将原版的#Write results替换为如下内容(只替换这个部分,#Print time就不用动了):

                # Write results
                counter=0
                for *xyxy, conf, cls in reversed(det):
                    if save_txt:  # Write to file
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                        line = (cls, *xywh, conf) if opt.save_conf else (cls, *xywh)  # label format
                        with open(txt_path + '.txt', 'a') as f:
                            f.write(('%g ' * len(line)).rstrip() % line + '\n')

                    if save_img or view_img or uart_yes:  # Add bbox to image
                        label = f'{names[int(cls)]} {conf:.2f}'
                        plot_one_box(xyxy, im0, color=colors[int(cls)], line_thickness=3)
                        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()
                        counter=counter+1;                       
                        uart.write(bytearray([255,254]))   #start with FF FE
                        uart.write(bytearray([counter]))   # 
                        uart.write(bytearray([252]))       #first session end with FC
                        for cnt in range(0,4):
                           watertank=int(100*xywh[cnt])
                           #high=int(bin(watertank)[:-8],2)
                           #low=int(bin(watertank)[-8:],2)  # eight little sessions
                           uart.write(bytearray([watertank]))
                        uart.write(bytearray([251]))       #end with FB

                 

  1. 使用uart.write()来发送数据时摸索了很久,如果直接在函数里写具体的数值,发送过去的变量将是字符类型的。比如,我想通过串口发送10这个十进制数,实际发过去的则是1和0的ASCII码。尝试了很久才终于用uart.write(bytearray[number])的形式发送出了十六进制数。

  2. 从Jeson Nano返回的数据帧格式为:FF+FE+num+FC+UpLeft_x+UpLeft_y+DownRight_x+DownRight_y+FB,值得注意的是,我的项目只需要识别单一目标返回的num值为当前目标的序号;UpLeft_x为识别框左上角x坐标值(范围0-1的float类型数,下同),UpLeft_y为识别框左上角y坐标值,DownRight_x为识别框右下角x坐标值,DownRight_y为识别框右下角y坐标值。 FF,FE为帧头校验符;FC为目标序号与目标坐标信息的分隔符;FB为帧尾校验符;在通过单片机进行串口接收时需要在解码过程中将这些帧滤掉。如果有需要的话,我可以分享自己写的stm32的串口解码程序。

  3. 目标框的左上角坐标信息与右下角的坐标信息都是经过归一化处理的,返回的初始数值为float类型的小数。由于我的项目对于目标框的位置精度要求不高,这个地方我图简单将float值乘以100后取整,返回值的理论范围为0-100,一个八位二进制数或两个十六进制数的范围为0-255,足够完成任务了。但我注释掉的部分提供了更精确的选择, 如果对位置的精度要求很高,可以考虑将float数乘以1000后取整,使用我在上面代码中注释掉的high=int(bin(watertank)[:-8],2)low=int(bin(watertank)[-8:],2)将一个值拆分为高低两个八位二进制数发送过去

  4. 为了方便理解,我来举个栗子,如下面的草图所示:假设在某一帧(640x320),画面中出现了两个对象,分别有92%,83%的置信度,程序将按照置信度将其降序排列为01,02,接下来就会通过串口发送这两个对象的识别框坐标信息。【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第8张图片

  5. 最后,保存文件后运行sudo python3 detect.py --source 0,如果不用sudo就没有权限打开串口。

  6. 跑起来的Jetson Nano终端与串口界面如下:
    【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第9张图片
    【超详细】在Jetson Nano中部署YOLOV5 + 源码解读 + 修改detect.py加入UART串口收发功能_第10张图片


总结

方法总结就到这里了,图简单的可以直接点击下载链接
如果文章存在问题,欢迎大家批评指正! 最后,码字不易,断断续续写了一天,如果觉得对你有帮助,不妨留下一个免费的赞再走吧~

功能展示

备赛2020年Robocon绿茵争锋时做的橄榄球识别并跟随,虽然到最后比赛也没用上。

基于Yolov3的二自由度目标检测与追踪云台

你可能感兴趣的:(python,yolov3,深度学习,python,串口通信,uart,嵌入式)