最近在学习使用OAK相机和YOLO目标检测算法,由于第一次接触这种自带运算模块的相机,操作起来与那种非常简单即插即用的USB摄像头(webcam)还是有很大区别的,特此记录一下使用这种相机来运行yolo的过程。
一、OAK相机
之前我曾经尝试过使用Kinect2相机来开展一些视觉方面的学习工作,但是那个相机实在是有点大而且线缆也比较粗也很多,测试起来还是很不方便的。那一款相机主要是单目加红外的硬件,学习起来相比现在用的OAK资料也偏少,所以还是接触一下OAK相机,体积相比之下小了很多,只有一根USB线,硬件连接简单简洁了很多。
OAK相机全称OpenCV AI Kit,官网是这么介绍他的:
OpenCV官方指定的深度视觉+人工智能开发套件
OAK是嵌入式、高性能、3D AI+CV平台, 由开源硬件、固件、软件生态系统组成, 提供完整并可立即使用的嵌入式3D人工智能+硬件加速的计算机视觉。
他的文档还是比较丰富的,详细的文档可以在下面自己查阅:
https://docs.oakchina.cn/en/latest/
其实比较需要注意的他这个相机是自带一些计算模块的,不单单是一个摄像头,所以通常不像用普通的Webcam那样使用(当然也是有办法让他变成简单的Webcam一样使用),它支持低功耗高算力的人工智能推理加速。也就是说一些类似yolo中detect的任务可以交给它独立运行不再占用主机资源,我手里用的这个型号是pro,双目+结构光测距,自带一个IMU,自带IMU的设备感觉用在slam项目里应该会方便很多。
前期工作主要是在ROS环境下用C++编程,初次接触非ROS下的一些程序,还是Python语言,如果有些错误欢迎指正。
相机在win下和Ubuntu下都用过,都没问题,但是涉及到了深度学习所以还是建议无论什么环境都安装一个conda来管理一下环境,正常在官网下载好驱动,配置好环境,在conda新环境中pip install -r requirements.txt后(oak的驱动里写了一个py脚本,直接运行可以安装要求的依赖),直接运行demo可以简单浏览下相机的功能。
简单一看可以运行就行了,demo的Python文件比较长,因为要涉及到各方面的功能展示和GUI。这里简单放一段使用oak的代码简单体验一下:
# 首先,导入所有必需的模块
from pathlib import Path
import blobconverter
import cv2
import depthai
import time
import numpy as np
#Pipeline告诉DepthAI运行时要执行的操作-在此定义所有使用的资源和流
pipeline = depthai.Pipeline()
# 首先,我们想要彩色相机作为输出
cam_rgb = pipeline.createColorCamera()
cam_rgb.setPreviewSize(300, 300) # 300x300将是预览帧大小,可用作节点的“预览”输出
cam_rgb.setInterleaved(False)
# 接下来,我们想要一个神经网络来产生检测
detection_nn = pipeline.createMobileNetDetectionNetwork()
#Blob是为MyriadX编译的神经网络文件。它包含模型的定义和权重
#我们正在使用blobcconverter工具从OpenVINO Model Zoo自动检索MobileNetSSD blob
detection_nn.setBlobPath(blobconverter.from_zoo(name='mobilenet-ssd', shaves=6))
# 接下来,我们过滤掉低于置信阈值的检测。置信度可以介于<0..1>之间
detection_nn.setConfidenceThreshold(0.5)
# 接下来,我们将相机的“预览”输出链接到神经网络检测输入,这样它就可以产生检测
cam_rgb.preview.link(detection_nn.input)
#XLinkOut是设备的“出路”。任何要传输到主机的数据都需要通过XLink发送
xout_rgb = pipeline.createXLinkOut()
# 对于rgb相机输出,我们希望XLink流命名为“rgb”
xout_rgb.setStreamName("rgb")
# 将相机预览链接到XLink输入,以便将帧发送到主机
cam_rgb.preview.link(xout_rgb.input)
# 相同的XLinkOut机制将用于接收nn结果
xout_nn = pipeline.createXLinkOut()
xout_nn.setStreamName("nn")
detection_nn.out.link(xout_nn.input)
#管道现在已经完成,我们需要找到一个可用的设备来运行我们的管道
#我们在这里使用上下文管理器,它将在我们停止使用设备后处理设备
with depthai.Device(pipeline) as device:
#此时,设备将处于“运行”模式,并开始通过XLink发送数据
#为了使用设备结果,我们从设备中获得两个输出队列,其中包含我们之前分配的流名称
q_rgb = device.getOutputQueue("rgb")
q_nn = device.getOutputQueue("nn")
# #这里定义了一些默认值。帧将是来自“rgb”流的图像,检测将包含nn个结果
frame = None
detections = []
counter=0
startTime = time.monotonic()
color2 = (255, 255, 255)
#由于nn返回的检测值在<0..1>范围内,因此需要将其乘以帧宽度/高度
#接收图像上边界框的实际位置
def frameNorm(frame, bbox):
normVals = np.full(len(bbox), frame.shape[0])
normVals[::2] = frame.shape[1]
return (np.clip(np.array(bbox), 0, 1) * normVals).astype(int)
# 主机侧应用程序环路
while True:
# 我们尝试从nn/rgb队列中获取数据。tryGet将返回数据包或None(如果没有)
in_rgb = q_rgb.tryGet()
in_nn = q_nn.tryGet()
if in_rgb is not None:
# 如果存在来自RGB相机的数据包,我们将使用getCvFrame以OpenCV格式检索帧
frame = in_rgb.getCvFrame()
if in_nn is not None:
# 当接收到来自nn的数据时,我们采用包含mobilenet ssd结果的检测阵列
detections = in_nn.detections
if frame is not None:
for detection in detections:
# 对于每个边界框,我们首先将其标准化以匹配帧大小
bbox = frameNorm(frame, (detection.xmin, detection.ymin, detection.xmax, detection.ymax))
# 然后在框架上画一个矩形来显示实际结果
cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (255, 0, 0), 2)
# 所有绘图完成后,我们在屏幕上显示框架
cv2.imshow("preview", frame)
# 在任何时候,您都可以按“q”并退出主循环,从而退出程序本身
if cv2.waitKey(1) == ord('q'):
break
二、YOLOv5、v8
YOLO系列是一个很强的目标检测算法,他有很多的版本,在测试时选用了v5、v8这两个比较受欢迎的YOLOL版本,关于YOLO系列安装、训练、预测的博客已经有很多了,在这里就不过多的赘述了。
关于自动驾驶相关的数据集,下载训练了bdd100k,简单熟悉了一下训练流程,总体上每个版本的操作有细微差异不过大差不差,在前期训练模型的的时候由于不熟悉,导致多走了很多弯路,多花了不少时间在训练模型上。最后基于四个模型训练了四个权重文件。分别对应了v5和v8版本的s、l模型(模型从n、s、m、l、x复杂度逐渐上升,训练时间增加,训练结果用于预测时时间也会有所增加。
最后由于测试比较麻烦,也考虑到一直不接入摄像头离线测试也不是个事啊,最后还是选择了yolo官方的预训练模型进行测试。
初步测试发现v8与v5相比,同样尺寸规模的模型,预测时所花费时间是均大于v5的,粗略估计实际的时间花费大约是v5模型所花时间的1.7倍左右。但是v8的语义分割效果看demo效果是很不错的,由于bdd100k没有提供语义分割的label,所以自己简单标了几张测试了一下,由于只做了50张左右,所以准确率肯定不行,但是感觉只有这么点数据能取得这样的效果,还是可以的。
三、OAK相机+YOLO
要使用oak相机的同时使用YOLO算法进行目标检测,目前测试了两种方法:
1、把oak单纯作为摄像头设备(webcam)输入视频流,yolo算法完全运行在主机侧
这种方法浪费了oak相机自带的算力,不得不说属实是浪费,不过作为测试,作为一个基准或者参考标准还是可以的。同时如果说相机自带算力实在满足不了需求倒也可以曲线救国。
2、把训练好的模型转换好格式传入相机侧运行算法,主机侧只接受处理后的结果
常规做法,轻量化模型运行起来效果不错。不过大型模型还是算了。
先说第一个方案,其实最开始我只是想随便找个相机测试一下yolo(台式机),找了一圈没有即插即用的USB摄像头,用这款相机试了一下,果然/dev目录下确实没有video。必须要下载程序才能运行相机,然后没办法就顺路学了一下这款相机使用方法,属实是被迫入坑了。没想到意外发现确实有独到的优势。
具体到转化成webcam的方法,官方给了两种,首先是在之前运行的demo弹出界面里,在Misc中运行UVC Mode即可,这种方法比较简单。
其次是在程序目录下的apps/uvc下,存在转化为uvc的源代码,适合有自定义需求的或者需要整合到自己项目中的。
把相机转换为webcam后,操作就很简单了,相当于只是在运行yolo的detect,相机就再也不需要什么操作了。在预测的时候只需要把数据来源(source)从路径改为0就可以读取摄像头的实时画面来进行预测了。
使用webcam运行会遇到一个情况,首先是分辨率问题,我测试时摄像头输出的尺寸是1920*1080,然而yolo的detect端的imgsz默认设置为640.按比例来缩放的话,理论上应该是得到:640*360.然而实际得到的则是384*640.
这个问题其实在detect.py文件中可以找到根源,在LoadImages类里面有一个letterbox函数,里面的代码描述了尺寸方面的处理流程,对这个感兴趣可以重点关注下这个函数。
在视频格式为1920*1080情况下,要求尺寸640原视频尺寸会变为384*640,要求尺寸320原视频尺寸会变为192*320.之所以一直在纠结这个传入图像尺寸问题是因为这个尺寸会影响实时监测的速度,尺寸越大处理起来花的时间也越多。
比如这样:
尺寸从320增加到640,处理速度从约20ms增加到了约60ms,也就是从50fps降到了17fps。花费时间翻了三倍。当然可能不同的机器测试结果会有差异,我是用CPU跑的预测,没有用GPU进行加速(想当初在服务器上训练的模型顺手预测的时候只需要20ms,转移到自己电脑上秒变300ms)
然后是第二个方案,把训练好的模型上传用相机硬件预测,主机只接收预测结果。
这个相机支持的网络还是蛮多的,感兴趣的可以上网查一下,官网给出的最受欢迎的网络是
-
MobileNetv2SSD(50fps)
-
TinyYOLOv3 and v4 (40fps)
-
Deeplabv3+ (30fps)
事实上,为什么这个TinyYolov3是最受欢迎的呢,因为他官方模型里yolo系列只有这个v3,给了两个模型,一个不带tiny后缀一个带着。其中不带后缀的模型机载的硬件算力根本不够,虽然画面显示相机画面有3fps,模型处理速度能到1.5fps(作为官方展示的模型来说确实是很离谱的帧数表现)但是,检测框都卡死了那个这些fps也没什么意义了。
至于v3的tiny模型,运行起来帧数表现倒还是说得过去,相机和模型预测都能做到20多fps(官方帧数估计是在小尺寸下测出来的)但是,这个检测效果真的是不忍直视,因为没有测过yolov3,也不知道是算法局限还是他这个官方模型就没训练好,总之检测效果可以参考如下:
这还算稍微整洁的检测结果,大部分情况是框框塞得满满的什么都看不清楚。下面贴一张用v5n检测的图片可以对比一下:
还是v5效果好一些,这个v5的模型其实是需要自己做然后上传到相机侧进行检测,我比较懒,直接把v5官方的pt文件处理了一下传上去了,如果有实际的具体检测任务也可以处理自己训练的pt文件。总之相机需要的模型文件类型是blob格式的,官方网站里有比较详细的转换方法这里就不多介绍了,有需要的一搜就可以。
这个方法有一个挺明显的优点,因为代码里包含把cam数据传入模型的代码,也就是说需要多大尺寸的画面直接修改参数就可以了,改一个数字就行特别简单,比如我上面v5检测时传入的画面尺寸是320*320的,能做到46fps。但是改成传入640*640就暴跌到16fps了。
同时顺手测了v8的处理速度,基本上来说比v5低个6fps的样子。
下一步测试一下v8的segment模块,看一下分割需要的算力这个相机能不能满足,如果能达到20fps以上那可就太好了。