写在前面:这篇文章非常基础,适合像我一样还在摸索中的新手学习讨论。当然,文章中也很可能出现很多错误,烦请各位大佬批评指正。
由于项目需要,本人从Yolov3开始首次接触到目标检测神经网络,短短两年的时间里,目标检测这一领域发展速度飞快,yolov4和v5相继开源。本文记录我在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
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()
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
在连接好发送端、接收端和GND后,将USB-TTL插上电脑,用串口助手打开。
成功运行脚本后,通过串口助手向Jetson Nano发送hello
# 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)
将原版的#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
使用uart.write()
来发送数据时摸索了很久,如果直接在函数里写具体的数值,发送过去的变量将是字符类型的。比如,我想通过串口发送10这个十进制数,实际发过去的则是1和0的ASCII码。尝试了很久才终于用uart.write(bytearray[number])
的形式发送出了十六进制数。
从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的串口解码程序。
目标框的左上角坐标信息与右下角的坐标信息都是经过归一化处理的,返回的初始数值为float类型的小数。由于我的项目对于目标框的位置精度要求不高,这个地方我图简单将float值乘以100后取整,返回值的理论范围为0-100,一个八位二进制数或两个十六进制数的范围为0-255,足够完成任务了。但我注释掉的部分提供了更精确的选择, 如果对位置的精度要求很高,可以考虑将float数乘以1000后取整,使用我在上面代码中注释掉的high=int(bin(watertank)[:-8],2)
和low=int(bin(watertank)[-8:],2)
将一个值拆分为高低两个八位二进制数发送过去 。
为了方便理解,我来举个栗子,如下面的草图所示:假设在某一帧(640x320),画面中出现了两个对象,分别有92%,83%的置信度,程序将按照置信度将其降序排列为01,02,接下来就会通过串口发送这两个对象的识别框坐标信息。
最后,保存文件后运行sudo python3 detect.py --source 0
,如果不用sudo就没有权限打开串口。
方法总结就到这里了,图简单的可以直接点击下载链接
如果文章存在问题,欢迎大家批评指正! 最后,码字不易,断断续续写了一天,如果觉得对你有帮助,不妨留下一个免费的赞再走吧~
备赛2020年Robocon绿茵争锋时做的橄榄球识别并跟随,虽然到最后比赛也没用上。
基于Yolov3的二自由度目标检测与追踪云台