下面给出了这个开发板的整体的一个接口示意图,固定孔位、尺寸和整体结构的布局与树莓派是极为相似的。个人感觉,旭日3派最想拉过来树莓派用户,希望吸引更多Jetson Nano用户。
如何去理解这个开发板,最直观的比方,可以说是:旭日3派≈树莓派+神经计算棒。它不像NVIDIA那样有nvcc来开发CUDA相关程序,没有那么高的灵活度。但是它相比于树莓派,多了5TOPS的深度学习计算能力。这对Jetson用户来说可能有点幼稚,但对树莓派用户来说刚刚好(✿◡‿◡)。
旭日X3派开发板,为SoC系统级芯片,中心处理器采用的是ARM A53,其他ISP(图像信号处理器)和BPU(人工智能处理器单元)均为自研,2GB的内存,存储使用TF卡,其他的一些参数去官网查看具体配置即可。
前面已介绍,该开发板三点大优势算力/功耗比高、重量轻、价格低。对于算力/功耗比,2.5W的基础功耗提供了5TOPS的算力,下表给出一些同类型的一些边缘计算设备的一些配置。值得注意的是,神经计算棒必须搭配其他开发板使用。
这个表有几点需要说明下:
在项目应用中,如果其中检测识别等相关处理算法,对实时要求不高,且对CPU要求不大的话,非常可以试一下旭日开发板。下面给出主要接口的说明:
下面,将完成系统的配置,完成系统的启动。
开发板目前支持Ubuntu 20.04 Server、Desktop两个版本。注意,由于X3芯片不支持GPU硬件加速,因此使用Ubuntu Desktop版本时,可能会因CPU渲染图形桌面而造成系统负载过大,如对系统性能有较高要求,推荐使用不带图形桌面的Ubuntu Server版本,下面我就带各位来使用Server版本的系统。
首先,准备好一个TF卡(16G以上),和一个读卡器,插到自己的笔记本上。我试用时候,提供了一个镜像包x3pi_ubuntu_disk.tar.gz,解压它得到一组文件,其中system_sdcard.img就是我们要刷的系统文件镜像。
镜像刷录我使用的是balenaEtcher工具,它是一款支持Windows/Mac/Linux等多平台的启动盘制作工具,下载安装好就可以进行刷录啦。
※第一步:选择镜像
※第二步:选择TF卡。
※第三步:刷机!!
这几步完成后,我们就完成了刷机工作,相当简单吧φ(゜▽゜*)♪。
刷完系统后,我们就可以把这张TF卡插回开发板里面了。下面,我们要尝试进入系统。如果是刷机后第一次进入系统,一定要利用开发板自带的串口连接开发板,配置好网络后,后续可以不再利用串口登录(如果网络IP变了,那就重新用串口连接,查看下IP地址)。
※第零步:基础准备.
※第一步:串口连接系统
将串口线的另一端连接到笔记本上,看下设备管理器,若提示未知USB设备时,说明PC机未安装串口驱动,驱动程序可从地平线开发者社区发布页面https://developer.horizon.ai/resource获取。驱动安装完成后,设备管理器可正常识别串口板端口。
使用 MobaXterm 工具按照如下方式进行配置,打开后,由于设备没插电,所以空白。
下面,将USB Type C电源线,插入开发板。这时候,控制台就会输出一堆文件,到最后,会需要输入用户名和密码,默认账户和密码均为sunrise。如果开发板HDMI正常显示开机画面(Server系统显示地平线logo、Desktop版本显示系统桌面),说明TF卡系统制作正确。
※第二步:BPU测试
开发板已经连接了一个MIPI相机,下面使用官方示例来测试BPU模块是否有效。先进入示例程序文件夹cd /app/ai_inference/03_mipi_camera_sample/,然后输入sudo python3 mipi_camera.py,注意一定要加sudo,调用BPU模块需要管理员权限。
这时候,控制台就会输出一些检测信息,对应可视化效果由显示器显示。
※后话:功耗分析
我分别对开机时,BPU检测中,和检测后的功耗进行了分析。开机后的功率在2.3W左右,利用BPU执行了一个检测示例,功耗升到3.6左右,结束后,功耗降为2.0W,这个原因比较诡异,可能是中止项目后,关闭了显示输出使得功耗下降。
至此,开发板启动起来了,我们happy的进行使用了。
用串口来操作开发板的话,有几个致命问题:无法传文件、命令过长有bug、Vim使用不方便。因此,非常有必要把网络配置好来进行后续的调试开发。
开发板本身自带无线模块,同时也可以插网线以获得更快的速度。下面给出这两种模式的一个配置。
① 利用sudo nmcli dev查看网络设备。输入ifconfig发现IP地址是192.168.1.10,翻阅手册发现开发板以太网默认采用静态IP地址(192.168.1.10),以方便固定网络环境下的使用,例如开发板与PC机直连场景。但是,对于我来说,我需要动态分配,因为校园网整体就是局域网,不需要网络环境仅局限在电脑、开发板之间。
以下的配置,是想让开发板的IP地址由学校路由器分配得到,不需要静态IP
② 创建一个新的以太网链接
考虑到串口连接,输入的命令不能过长,则先利用sudo -i切换为root(操作完后利用su sunrise切换回去),然后在命令行中输入nmcli con add con-name "ethdhcp" type ethernet ifname eth0,这样可以使用dhcp获取网络,其中"ethdhcp"为网络名,用户可以自定义。这时候,我们发现,CONNECTION部分已经变了,且IP地址变为自动分配的了。
以太网有个大问题,就是连接校园网时候,由于没有用户界面,因此账号的登录,可能需要利用Python脚本去完成,或者让校园网插在路由器上完成中转,如果是个人路由器的话,这种问题一般不存在。
无线网络的连接参考博客《Linux命令行连接WiFi(全网最简单的方法)》。
① 利用sudo nmcli radio wifi on开启wifi。
② 利用sudo nmcli dev wifi扫描wifi。其中,nova 9 Pro 为个人用手机开的热点
③ 利用sudo nmcli dev wifi connect "wifi名" password "密码"连接WIFI。将wifi的账号密码套在这个命令里,即可成功连接上Wifi。
无线网最大的问题就是它的速度真的太慢了,我手上的这个版本速度约为300KB/s,自己外加个天线能够减低远程操作的延迟,这个问题已反馈,据说发布后的板子不存在这个问题。
无论什么开发板,基于CPU相关的程序的稳定性至关重要。因此,非常有必要去测试USB、串口、Wifi等相关的有效性以及稳定性。该部分的测试,一是测试项目的一些基本功能,其次是测试自己做项目中使用的一些算法,来分析整体系统的一个性能。
由于刷机时选择的是Ubuntu Server,所以带有界面相关的程序均无法使用。如果想在Server上部署界面端,40PIN接口上有SPI接口,可以购置SPI液晶屏来开发。
由于系统里面并不包含图形界面,因此如果动态地看算法的检测效果的话,就需要将图像数据输出到HDMI来显示,系统自带的python包里面有个类libsrcampy.Display就是来完成这样的工作的。
为了方便各位后续可视化自己的算法,我将这个功能封装为一个class
class ImageShow(object):
# 初始化,screen_w和screen_h表示HDMI输出支持的显示器分辨率
def __init__(self, screen_w = 1920, screen_h = 1080):
super().__init__()
self.screen_w = screen_w
self.screen_h = screen_h
self.disp = srcampy.Display()
self.disp.display(0, screen_w, screen_h)
# 结束显示
def close(self):
self.disp.close()
# 显示图像,输入image即可
def show(self, image, wait_time=0):
imgShow = self.putImage(image, self.screen_w, self.screen_h)
imgShow_nv12 = self.bgr2nv12_opencv(imgShow)
self.disp.set_img(imgShow_nv12.tobytes())
# 私有函数,将图像数据转换为用于HDMI输出的数据
@classmethod
def bgr2nv12_opencv(cls, image):
height, width = image.shape[0], image.shape[1]
area = height * width
yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((area * 3 // 2,))
y = yuv420p[:area]
uv_planar = yuv420p[area:].reshape((2, area // 4))
uv_packed = uv_planar.transpose((1, 0)).reshape((area // 2,))
nv12 = np.zeros_like(yuv420p)
nv12[:height * width] = y
nv12[height * width:] = uv_packed
return nv12
# 图像数据在显示器最大化居中
@classmethod
def putImage(cls, img, screen_width, screen_height):
if len(img.shape) == 2:
imgT = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
else:
imgT = img
irows, icols = imgT.shape[0:2]
scale_w = screen_width * 1.0/ icols
scale_h = screen_height * 1.0/ irows
final_scale = min([scale_h, scale_w])
final_rows = int(irows * final_scale)
final_cols = int(icols * final_scale)
print(final_rows, final_cols)
imgT = cv2.resize(imgT, (final_cols, final_rows))
diff_rows = screen_height - final_rows
diff_cols = screen_width - final_cols
img_show = np.zeros((screen_height, screen_width, 3), dtype=np.uint8)
img_show[diff_rows//2:(diff_rows//2+final_rows), diff_cols//2:(diff_cols//2+final_cols), :] = imgT
return img_show
下面给出视频和图像的测试方法,相机用的是MIPI相机
def test_show_image():
img = cv2.imread('00000160.png') # 加载本地图片
im_show = ImageShow() # 初始化显示
im_show.show(img) # 显示图像
time.sleep(1)
im_show.close()
def test_mipi_camera():
im_show = ImageShow()
cam = srcampy.Camera() # 定义相机类型
cam.open_cam(0, 1, 30, 1920, 1080) # 设置相机采集所用的参数
while True:
origin_image = cam.get_img(2, 1920, 1080) # 获取相机数据流
origin_nv12 = np.frombuffer(origin_image, dtype=np.uint8).reshape(1620, 1920)
# origin_bgr = cv2.cvtColor(origin_nv12, cv2.COLOR_YUV420SP2BGR)
# 图像虽然是RGB实际上是BGR,问题已反馈
origin_bgr = cv2.cvtColor(origin_nv12, cv2.COLOR_YUV420SP2RGB)
im_show.show(origin_bgr)
im_show.close()
对这两个函数分别测试,这时候显示屏就会出现对应可视化结果,这样就完成数据可视化所需的一些工作(视频流图像显示这部分,功耗占了1W,CPU占用30%左右,显示这部分的代码还是有点冗余)。
之前有个项目,要求无人机与地面站直接的通信由之前的数传改为wifi,搜了一圈,很多都属于手工调试,而且包含复杂的界面。然而实际需求要求稳定,自动化。因此为了满足这个需求只能是自己开发一个小工具。该项目的细节部分参考博客串口转wifi —— 两个串口之间通过网络进行通信。
该工具可测试板子的串口模块和Wifi模块的稳定性。
① 利用minicom测试串口。第一次使用可利用命令sudo apt-get install minicom安装相关工具,将开发板的4-5针头,也就是串口的收发接头短接。这样接收到的信息又会发送出去。
② 编译uart2net工具。按顺序执行以下相关代码:
sudo apt-get install qt5-default
sudo apt-get install libqt5serialport5-dev
git clone https://github.com/Li-Zhaoxi/uart2net
cd uart2net
qmake uart2net.pro
make all -j4
③ 配置uart2net.ini文件。注意串口和IP地址的相关配置。
④ 启动uart2net。
最后测试数据的输出与博客串口转wifi —— 两个串口之间通过网络进行通信是一样的这里就不再进行叙述了。
测试时候发现个问题,由于Wifi传输有一定的延迟,如果每10ms就发送几十个字节的数据的话,会造成大量数据的阻塞,后续在应用时候注意数据传输不要过快,过多,否则会造成几秒的延迟。
部分项目应用中需要检测场景中的椭圆目标,因此将算法移植到这个板子上,以方便测试检测效果与实时性,通过命令git clone https://github.com/Li-Zhaoxi/AAMED下载椭圆检测代码。
① 编译python代码模块aamed.so。按照下面的方式配置好setup.py文件之后,cd python进入python文件夹,执行python3 setup.py build_ext --inplace编译算法模块。需要修改代码的部分如下所示,直接替换即可。
opencv_include = "/usr/include/opencv4/"
opencv_lib_dirs = "/usr/lib/aarch64-linux-gnu/"
ext_modules = [
Extension(
"pyAAMED",
["../src/adaptApproximateContours.cpp",
"../src/adaptApproxPolyDP.cpp",
"../src/Contours.cpp",
"../src/EllipseNonMaximumSuppression.cpp",
"../src/FLED.cpp",
"../src/FLED_drawAndWriteFunctions.cpp",
"../src/FLED_Initialization.cpp",
"../src/FLED_PrivateFunctions.cpp",
"../src/Group.cpp",
"../src/LinkMatrix.cpp",
"../src/Node_FC.cpp",
"../src/Segmentation.cpp",
"../src/Validation.cpp",
"aamed.pyx"],
include_dirs = [numpy_include,'FLED', opencv_include],
language='c++',
libraries=['opencv_core', 'opencv_highgui', 'opencv_imgproc', 'opencv_imgcodecs', 'opencv_flann'],
library_dirs=[opencv_lib_dirs]
),
]
② 调用MIPI摄像头实现检测。我在测试时候出现了一个错误cannot allocate memory in static TLS block Python,把python头文件的顺序调整了下就OK了。下面给出我的测试代码。
我在显示屏上放上了待检测的照片,让mipi相机去拍显示器完成检测过程
from hobot_vio import libsrcampy as srcampy
import cv2
import numpy as np
import time
from pyAAMED import pyAAMED
# 这里把前面的HDMI可视化部分的代码贴上
# 复制类class ImageShow(object)
# 检测主程序
def test_mipi_camera():
im_show = ImageShow()
cam = srcampy.Camera()
cam.open_cam(0, 1, 30, 1920, 1080)
aamed = pyAAMED(550, 970)
aamed.setParameters(3.1415926/3, 3.4,0.77) # 阈值设置,如果假椭圆过多,可适当调高0.77
while True:
origin_image = cam.get_img(2, 1920, 1080)
origin_nv12 = np.frombuffer(origin_image, dtype=np.uint8).reshape(1620, 1920)
origin_bgr = cv2.cvtColor(origin_nv12, cv2.COLOR_YUV420SP2RGB)
imgG = cv2.resize(cv2.cvtColor(origin_bgr, cv2.COLOR_BGR2GRAY), (960, 540))
imgGC = cv2.cvtColor(imgG, cv2.COLOR_GRAY2BGR)
t1 = cv2.getTickCount()
res = aamed.run_AAMED(imgG) # 检测部分代码
t2 = cv2.getTickCount()
print('time consumption(ms):', (t2 - t1) * 1000 / cv2.getTickFrequency())
for each_elp in res:
cv2.ellipse(imgGC, ((each_elp[1], each_elp[0]), (each_elp[3], each_elp[2]), -each_elp[4]), (0, 0, 255), 2)
im_show.show(imgGC)
im_show.close()
test_mipi_camera()
下面是检测耗时和效果图,检测的图像分辨率为960×540,这个时耗几乎在37ms左右,也能满足一些基本的算法需求。
time consumption(ms): 36.989471
time consumption(ms): 37.507962
time consumption(ms): 36.99551
time consumption(ms): 43.346158
time consumption(ms): 37.378966
time consumption(ms): 38.764665
time consumption(ms): 38.915905
time consumption(ms): 25.642136
time consumption(ms): 49.384246
至此,开发板CPU的部分相关所需功能均已测试完毕,总体来说,基本能满足大部分轻量型算法的需求,除了Wifi部分延迟较高,其余我觉得均已经足够适应大部分的任务了。我个人非常喜欢操作HDMI显示图像的方式,降低带有桌面系统带来的性能损耗,极大的给算法留出更多的计算量。
开发板中的BPU部分为自研芯片,部分深度学习网络层从硬件的角度进行了加速。因此,这个开发板核心在于部署。在前文进入系统部分中,通过cd /app/ai_inference/03_mipi_camera_sample/和sudo python3 mipi_camera.py已经展示了系统自带的检测效果。
这些操作,主要是用来查看算法的资源占用率的,初级功能。后续非常期待官方出一个类似jetson的jtop工具,jtop的参考链接为jetson_stats。
由于开发板的特殊性,利用pytorch训练好的模型,是无法直接用在这个板子上的,官方将一堆常见的模型参数进行了转换。在装好的系统中,有两个可直接使用的模型。fcos用于目标检测,mobilenetv1用于目标分类。
在开发板的/app/ai_inference/01_basic_sample/路径下,提供了一个示例test_mobilenetv1.py,下面对其中的主函数部分进行一个介绍,部分核心功能的解释写在代码注释里面了。这部分通过opencv的cv2.getTickCount()和cv2.getTickFrequency()可测出,耗时约为9ms!!
# 主函数代码前面还包含如下子函数,用于数据转换,参数输出等。
# bgr2nv12_opencv、print_properties、get_hw
if __name__ == '__main__':
# 1. 加载模型,用于BPU加速计算的模型为一个*.bin文件,里面包含了模型的所有信息
models = dnn.load('../models/mobilenetv1_224x224_nv12.bin')
# 2. 输出模型的Input和output信息
# ========== inputs[0] properties ==========
print("=" * 10, "inputs[0] properties", "=" * 10)
# 输出模型的输入信息,输出信息如下
# tensor type: NV12_SEPARATE
# data type: uint8
# layout: NCHW
# shape: (1, 3, 224, 224)
# inputs[0] name is: data
print_properties(models[0].inputs[0].properties)
print("inputs[0] name is:", models[0].inputs[0].name)
# ========== outputs[0] properties ==========
print("=" * 10, "outputs[0] properties", "=" * 10)
# 这里输出模型的输出信息,输出内容如下:
# tensor type: float32
# data type: float32
# layout: NCHW
# shape: (1, 1000, 1, 1)
# outputs[0] name is: prob
print_properties(models[0].outputs[0].properties)
print("outputs[0] name is:", models[0].outputs[0].name)
# 3. 加载图像数据,前面已经输出了模型需要的输入数据尺寸和数据类型
# 因此,先利用cv2.resize将图像转换为目标大小尺寸
# 再利用bgr2nv12_opencv将图像数据转为NV12形式(个人理解是压缩了图像数据,减少了传输时间)。
img_file = cv2.imread('./zebra_cls.jpg')
h, w = get_hw(models[0].inputs[0].properties)
des_dim = (w, h)
resized_data = cv2.resize(img_file, des_dim, interpolation=cv2.INTER_AREA)
nv12_data = bgr2nv12_opencv(resized_data)
# 4. 将图像的NV12数据传入模型中,完成了整体的推理过程
outputs = models[0].forward(nv12_data)
# 下面将模型预测信息打印输出
# ========== Get output[0] numpy data ==========
# output[0] buffer numpy info:
# shape: (1, 1000, 1, 1)
# dtype: float32
# ========== Classification result ==========
# cls id: 340 Confidence: 0.991851
print("=" * 10, "Get output[0] numpy data", "=" * 10)
print("output[0] buffer numpy info: ")
print("shape: ", outputs[0].buffer.shape)
print("dtype: ", outputs[0].buffer.dtype)
# print("First 10 results:", outputs[0].buffer[0][:10])
print("=" * 10, "Classification result", "=" * 10)
assert np.argmax(outputs[0].buffer) == 340
print("cls id: %d Confidence: %f" % (np.argmax(outputs[0].buffer), outputs[0].buffer[0][np.argmax(outputs[0].buffer)]))
其实要把模型装板里,拢共分三步:
上面介绍了如何将 大象 模型装进BPU里→_→, 其实对个人来说,最难的就是如何获得*.bin文件。这里我其实无法一步步的引导各位如何部署自己的模型,因为这里的部署过程需要利用地平线开发的天工开物工具链,部署教程参考文档:Horizon AI Toolchain User Guide。对我来说,这部分东西太多,学习成本太大了,装进这个博客里有点太多了(多写个博客可以白嫖更多浏览量 )。
深度学习用的比较多的还是pytorch,模型可以转换为onnx模型文件,这个模型文件我觉得还是非常通用的,tensorRT和Intel的神经计算棒都利用了这个文件。
模型转换的目的就是检查模型文件中的网络层是否包含在BPU支持的层中(BPU本质上是从硬件的角度加速模型的计算,是一个专用工具),如果某些层不存在,这些层就需要利用CPU完成推理。
实际上,为了保证模型迁移的可靠性,整个上有以下几个关键过程:
① 模型准备。这些模型一般都是基于公开深度学习训练框架得到的, 需要将模型导出为开发板支持的格式,目前转换工具支持的深度学习框架如下。Caffe导出的caffemodel是直接支持的(Caffe是基于C++的,代码相当优美,非常适合硬件转换); PyTorch、TensorFlow和MXNet是通过转到ONNX实现间接支持。
② 模型验证。用来确保提出的算法模型是符合BPU要求的,开发平台提供了hb_mapper checker来完成模型的检查。 不满足迁移的层,就需要手动调整,最简单的办法就是这部分转到CPU来跑(数据传输上存在大量时间浪费),因此还是尽可能将这部分转到BPU上( ,科研和落地还是有很大差距的)。
③ 模型转换。这个阶段会将浮点模型转为可用BPU使用的模型,利用函数hb_mapper makertbin完成转换,转换成功后,得到的模型就可以运行在开发板上了。
模型转换,不一定就能保证一定就能跑起来,精度和性能都不敢说保证与开发中的结果是一样的,因此需要进行验证和调试。NVIDIA其实也有类似的工作,就是tensorRT,加速必有一定程度的损失,这些不可避免,这些其实涉及到数值分析的内容。这部分有需要的话,可以参考模型性能分析与调优和模型精度分析与调优。
原作者:小玺玺
原链接:原文详见地平线开发者社区
全部0条评论
快来发表一下你的评论吧 !