智能硬件论坛
直播中

开山师傅

4年用户 169经验值
擅长:控制/MCU 上位机开发
私信 关注
[经验]

【KV260视觉入门套件试用体验】KV260部署yolov3实现车辆和行人检测

KV260-yolov3部署
已知KV260官方中提供的四个智能检测已经实现了两个,其余两个根据个人兴趣进行部署即可这里不做相关的说明。
需要提前说明的是通过uboot引导加载的是ubuntu的系统,因此kv260具有图像处理能力,至于kv260适配的ubuntu版本这里不做讨论直接使用官方推荐的版本即可(Install Ubuntu on Xilinx | Ubuntu),官方提供的版本为Ubuntu Desktop 20.04.3 LTS,因为版本的不同后面在进行量化和编译的文件也是不通用的具体的原因这里亦不做展开。此次分享仅讲述如何实现在KV260上部署yolov3以及其他模型部署的方法,具体还是要操作起来才可以,下面就开始一步一步地实现部署吧。
前期的准备:
设备条件:
KV26032G+内存卡,网线
KV260:控制主板,软件载体。
32G+内存卡:整个系统存储内容都是在内存卡中进行,安装Vitis AI docker大约需要8G的存储空间,加上之前安装的内容16G内存卡没办法满足存储要求,因此需要更大的内存空间。
网线:用于KV260连接网络同时可以实现在局域网内的SSH通讯。
软件条件:
SD卡烧录软件balenaEtcher,通讯上位机MobaXterm,量化模型bp文件
模型的准备:
在进行部署前其实有大量的工作需要做,作为具有一定机器学习基础的应该可以明白从软件的安装,环境的配置,程序的编写,大量的训练素材,模型的训练、量化、编译等步骤,都是部署模型前需要做的工作。
Xilinx提供的Vitis-AI现成模型
KV260比较好的一点是他依托于Xilinx强大的背景,官方提供了一些已经训练好的具有一定功能的模型,如果使用这些模型只能实现对应的识别功能,如果要制作自己需要的识别效果那就要安好机器视觉训练方式一步一步来。
https://github.com/Xilinx/Vitis-AI
Xilinx文件github中有较多的文件,特别需要注意图中圈出来的单个文件,其他文件感兴趣也可以看一下。
首先阅读README.md文件,这样会对项目有个整体的理解。同时在说明中也提供了文件访问和下载的指令:git clone https://github.com/Xilinx/Vitis-AI 根据需要下载相关的文件,或者利用右上角的下载按钮进行下载即可这里亦不对下载操作做赘述。
../Model-Zoo/Model_list中包含了目前官方已经可以跑通的案例,目前提供了几十种不同功能的模型。
查看模型信息:
例如:我们需要部署的模型内容为模型列表中的tf_yolov3_3.5,路径如上图红框所示。点击model.yaml文件可以看到模型的相关信息,文件中对模型的信息进行了比较详细的描述,包括模型描述,输入图像的大小,训练所用到的文件大小,模型是否进行剪枝,版本号等信息。
根据使用硬件的不同提供了不同的下载文件,包括已经训练,量化好的浮点型和量化模型以及适合不同GPU板子编译模型,但是实际上这些模型并不能直接使用,需要根据浮点型和量化模型中的pb文件在KV260内进行编译操作得到对应的xmodel文件,同时需要编写相应的jason文件和lable文件。
复制红框中的下载地址进行下载会得到一整个文件夹,包含codedatafloatquantized文件夹,在REDME中包含了文件的基本信息描述,同时这也是一个提示文件,包含的内容包括环境的要求已经安装环境的指令,并告诉用户如何需要准备的东西后面安装过程会详细讲到。模型编译需要的文件就放在quantized文件夹下,存放一个pb文件后面需要用到。
自定义模型文件准备
如果使用的是官方提供的pb文件直接跳转到安装和部署步骤即可。
1.准备一个训练好的模型
需要保证算子环境兼容,已知的vitis aiCUDA1130系列显卡有问题
模型算子需要被DPU支持
DK模型需要转换的,具体参考vitis官方手册和教程
2.训练好模型后需要剪枝
KV260来说,之前有用户测试30G是模型流程运行的极限但是从目前官方给出的项目实例中模型已经超过60G同样不光模型运行程度如何在机器视觉编程中存在这样的规律,剪枝越小系统越流畅,完全不剪枝对yolov来说模型会很慢,可能FPS会比较低,目前官方提供的不剪枝模型fps大概在5左右。
3.需要vitis AI工具来量化模型。
vitis量化器,效果并不是很好,可以自己量化后再利用vitis量化器量化
自定义数据集及训练
首先,采集校园车辆和人的图像数据集,并将其制作成VOC数据集的格式,具体的过程可以参考网上的教程,这里亦不做赘述。
接下来需要安装 Vitis AI 1.4 版本的软件包用来训练使用,软件包可以自行下载,下载地
https://link.zhihu.com/?target=https%3A//github.com/Xilinx/Vitis-AI/archive/refs/tags/v1.4.tar.gz
下载好后需要解压,解压后跳转到Vitis AI 1.4文件夹,使用指令:cd Vitis AI 1.4接着继续输入如下指令进行安装:
  1. docker pull xilinx/vitis-ai-cpu:1.4.916
  2. ./docker_run.sh xilinx/vitis-ai-cpu:1.4.916
系统会提示如下内容:
继续安装vitis-ai-tensorflow,使用docker容器安装,Vitis AI 为量化工具(包括 vai_q_tensorflow)提供了 Docker 容器。运行容器后,激活 Conda 环境“Vitis AI-tensorflow”指令如下:
  1. conda activate vitis-ai-tensorflow
提示如下:
[i
如有补丁包,请在 Docker 容器内部安装 Vitis AI-tensorflow 补丁包,指令如下:
  1. sudo env CONDA_PREFIX=/opt/vitis_ai/conda/envs/vitis-ai-tensorflow/ PATH=/opt/vitis_ai/conda/bin:$PATH conda install patch_package.tar.bz2
Ubuntu上下载DW2TF,解压到“/Vitis AI 1.4/pre_project”中,重命名为“Darknet2Tensorflow”,创建“/Vitis AI 1.4/pre_project/Darknet2Tensorflow/data/yolov3”“Darknet2Tensorflow/yolov3”文件夹,打开“yolov3”文件夹为“yolov3.cfg”“yolov3.cfg”。权重,并将生成的yolov3DW2TF文件夹中的权重
首先修改Darknet2Tensorflow/mian.py。修改后的代码如下:
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import
  3. from __future__ import division
  4. from __future__ import print_function
  5. from argparse import ArgumentParser
  6. import os
  7. import tensorflow as tf
  8. from util.cfg_layer import get_cfg_layer
  9. from util.reader import WeightsReader, CFGReader
  10. def parse_net(num_layers, cfg, weights, training=False, const_inits=True, verbose=True):
  11.     net = None
  12.     counters = {}
  13.     stack = []
  14.     cfg_walker = CFGReader(cfg)
  15.     weights_walker = WeightsReader(weights)
  16.     output_index = []
  17.     num_layers = int(num_layers)
  18.     for ith, layer in enumerate(cfg_walker):
  19.         if ith > num_layers and num_layers > 0:
  20.             break
  21.         layer_name = layer['name']
  22.         counters.setdefault(layer_name, 0)
  23.         counters[layer_name] += 1
  24.         scope = "{}{}{}".format(args.prefix, layer['name'], counters[layer_name])
  25.         net = get_cfg_layer(net, layer_name, layer, weights_walker, stack, output_index, scope,
  26.                             training=training, const_inits=const_inits, verbose=verbose)
  27.         # Exclude `net` layer from stack (for correct layer indexing)
  28.         # See https://github.com/jinyu121/DW2TF/issues/30
  29.         # See https://github.com/AlexeyAB/darknet/issues/487#issuecomment-374902735
  30.         if layer['name'] != 'net':
  31.             stack.append(net)
  32.         if verbose:
  33.             print(ith, net)
  34.     if verbose:        
  35.         for ind in output_index:
  36.             print("=> Output layer: ", stack[ind])
  37. def main(args):
  38.     ckpt_path = os.path.join(args.output, os.path.splitext(os.path.split(args.cfg)[-1])[0] + ".ckpt")
  39.     pb_path = os.path.join(args.output, os.path.splitext(os.path.split(args.cfg)[-1])[0] + ".pb")
  40.     # ----------------------------------------------------------
  41.     # Save temporary .ckpt from graph containing pre-trained
  42.     # weights as const initializers. This is not portable as
  43.     # graph.pb or graph.meta is huge (contains weights).
  44.     # ----------------------------------------------------------
  45.     tf.reset_default_graph()
  46.     parse_net(args.layers, args.cfg, args.weights, args.training)
  47.     graph = tf.compat.v1.get_default_graph()
  48.     saver = tf.compat.v1.train.Saver(tf.compat.v1.global_variables())
  49.     with tf.compat.v1.Session(graph=graph) as sess:
  50.         sess.run(tf.compat.v1.global_variables_initializer())
  51.         saver.save(sess, ckpt_path, write_meta_graph=False)
  52.     # ----------------------------------------------------------
  53.     # Save .pb, .meta and final .ckpt by restoring weights
  54.     # from previous .ckpt into the new (compact) graph.
  55.     # ----------------------------------------------------------
  56.     tf.reset_default_graph()
  57.     parse_net(args.layers, args.cfg, args.weights, args.training, const_inits=False, verbose=False)
  58.     graph = tf.compat.v1.get_default_graph()
  59.     with tf.io.gfile.GFile(pb_path, 'wb') as f:
  60.         f.write(graph.as_graph_def(add_shapes=True).SerializeToString())
  61.     print("Saved .pb to '{}'".format(pb_path))
  62.     with tf.compat.v1.Session(graph=graph) as sess:
  63.         # Load weights (variables) from earlier .ckpt before saving out
  64.         var_list = {}
  65.         reader = tf.compat.v1.train.NewCheckpointReader(ckpt_path)
  66.         for key in reader.get_variable_to_shape_map():
  67.             # Look for all variables in ckpt that are used by the graph
  68.             try:
  69.                 tensor = graph.get_tensor_by_name(key + ":0")
  70.             except KeyError:
  71.                 # This tensor doesn't exist in the graph (for example it's
  72.                 # 'global_step' or a similar housekeeping element) so skip it.
  73.                 continue
  74.             var_list[key] = tensor
  75.         saver = tf.compat.v1.train.Saver(var_list=var_list)
  76.         saver.restore(sess, ckpt_path)
  77.         saver.export_meta_graph(ckpt_path+'.meta', clear_devices=True, clear_extraneous_savers=True)
  78.         print("Saved .meta to '{}'".format(ckpt_path+'.meta'))
  79.         saver.save(sess, ckpt_path, write_meta_graph=False)
  80.         print("Saved .ckpt to '{}'".format(ckpt_path))
  81. if __name__ == "__main__":
  82.     parser = ArgumentParser()
  83.     parser.add_argument('--cfg', default='data/network.cfg', help='Darknet .cfg file')
  84.     parser.add_argument('--weights', default='data/network.weights', help='Darknet .weights file')
  85.     parser.add_argument('--output', default='data/', help='Output folder')
  86.     parser.add_argument('--prefix', default='network/', help='Import scope prefix')
  87.     parser.add_argument('--layers', default=0, help='How many layers, 0 means all')
  88.     parser.add_argument('--gpu', '-g', default='', help='GPU')
  89.     parser.add_argument('--training', dest='training', action='store_true', help='Save training mode graph')
  90.     args = parser.parse_args()
  91.     # Set GPU to use
  92.     os.environ["CUDA_VISIBLE_DEVICES"] = ",".join(args.gpu)
  93.     # Filter out TensorFlow INFO and WARNING logs
  94.     os.environ["TF_CPP_MIN_LOG_LEVEL"]="2"
  95.     main(args)
  96. --
  97. Then enter the following code to complete the conversion of the Darknet parameter file to the parameter asking price of Tensorflow.
  98. --
  99. python3 main.py -h
  100. python3 main.py \
  101.     --cfg 'data/yolov3/ yolov3.cfg' \
  102.     --weights 'data/yolov3/ yolov3.weights' \
  103.     --output 'data/' \
  104.     --prefix 'yolov3/' \
  105. --gpu 0
运行程序即可得到需要的pb文件。
在运行vai_q_tensorflow之前准备以下三个文件,使用 TensorFlow 1.x 训练模型会创建一个文件夹,其中包含 GraphDef 文件(通常含 .pb .pbtxt 扩展名)以及一组检查点文件。对于移动或嵌入式部署,您需要一个“已冻结”的 GraphDef 文件,或者其变量已转换为内联常量以使所有一切都包含在单一文件内。TensorFlow 提供的 freeze_graph.py 可用于处理转换,它随 vai_q_tensorflow 量化器一起自动安装。
使用 TensorFlow 1.x 训练模型会创建一个文件夹,其中包含 GraphDef 文件(通常含 .pb .pbtxt 扩展名)以及一组检查点文件。对于移动或嵌入式部署,您需要一个已冻结GraphDef 文件,或者其变量已转换为内联常量以使所有一切都包含在单一文件内。TensorFlow 提供的 freeze_graph.py 可用于处理转换,它随 vai_q_tensorflow 量化器一起自动安装。
命令行用法示例如下:
  1. freeze_graph \
  2.     --input_graph  /tmp/inception_v1_inf_graph.pb \
  3.     --input_checkpoint  /tmp/checkpoints/model.ckpt-1000 \
  4.     --input_binary  true \
  5.     --output_graph  /tmp/frozen_graph.pb \
  6.     --output_node_names  InceptionV1/Predictions/Reshape_1。
开始构建冻结图形:
输入节点和输出节点名称因模型而异,但用户使用 vai_q_tensorflow 量化器来检查和估算这些节点。请参阅以下代码片段示例:
  1. pip install netron
  2. netron /tmp/*******.pb
如果计算图包含图内预处理和后处理,那么估算的输入和输出节点不得用于量化。这是因为部分运算不可量化,如果将量化模型部署到 DPU,那么使用 Vitis AI 编译器进行编译时可能出错。
另一种获取计算图的输入和输出名称的方法是将计算图可视化。TensorBoard Netron 均可执行此操作。请参阅以下示例,其中使用的是 Netron
  1. freeze_graph
  2. --input_graph yolov3/yolov3.pb
  3. --input_checkpoint yolov3/yolov3.ckpt
  4. --output_graph frozen/frozen_graph.pb
  5. --output_node_names yolov3-416/convolutional75/BiasAdd
  6. --input_binary true
calibration.py中有三处可以根据cfg文件进行修改,修改后的文件内容如下:
  1. # set data path to your own dataset
  2. dataset_path = "/workspace/data/VOCdevkit/VOC2007/JPEGImages"
  3. # set input size
  4. inputsize = {'h': 416, 'c': 3, 'w': 416}
  5. # set input node name
  6. input_node = "yolov3/net1"
接下来就是对模型进行量化,量化的指令
  1. vai_q_tensorflow quantize \
  2.                                 --input_frozen_graph  frozen_graph.pb \
  3.                                 --input_nodes    ${input_nodes} \
  4.                                 --input_shapes   ${input_shapes} \
  5.                                 --output_nodes   ${output_nodes} \
  6.                                 --input_fn  input_fn \
  7.                                 [options]
input_nodes output_nodes 实参均为量化计算图的输入节点的名称列表。这两个实参分别作为量化起点和终点。两者间的主计算图将进行量化(如可量化),如下图所示。
建议将 –input_nodes 设置为预处理部分的最后节点,并将 -output_nodes 设置为主计算图部分的最后节点,因为预处理部分和后处理部分中的某些运算不可量化,如需将量化模型部署到 DPU,那么 Vitis AI 量化器对此类运算进行编译时可能会导致错误。
输入节点可能与计算图的占位符节点不同。如果冻结计算图中不存在任何图内预处理部分,则应将占位符节点设置为输入节点。
input_fn应与占位符节点保持一致。
[options]表示可选参数。最常用的选项如下:
weight_bit已量化的权重和偏差的宽度,默认值为 8
activation_bit量化激活的位宽,默认值为 8
method量化方法,0 对应非溢出方法,1 对应最小差值方法,2 对应归一化的最小差值方法。非溢出方法可确保量化期间没有任何值达到饱和。结果可能受到离群值的影响。最小差值方法允许饱和以使量化尽可能降低量化差值。对于离群值而言,此方法更稳健,通常生成的范围比非溢出方法更小。
在本项目中,在Vitis AI 1.4docker下输入如下命令:
  1. vai_q_tensorflow quantize
  2. --input_frozen_graph frozen/frozen_graph.pb
  3. --input_fn calibration.calib_input
  4. --output_dir quantize/
  5. --input_nodes yolov3-416/net1
  6. --output_nodes yolov3-416/convolutional75/BiasAdd
  7. --input_shapes ?,608,608,3
  8. --calib_iter 100
系统会执行量化过程。在量化结束后就得到了于官方提供的一样的quantize/quantize_eval_model.pb文件。
到此为止讲述了两种获得量化模型的方法,感兴趣可以都试一下,有了量化的模型下面就是获得xmodel,jason,md5校验和。
安装和部署步骤:
在安装和部署前需要阅读Vitis AI Library User Guide (eetrend.com)
https://docs.xilinx.com/r/zh-CN/ug1414-vitis-ai  (重要,一定要看!!!!)
文档详细讲述了Vitis AI库的用法以及Vitis AI优化器、量化器、编译器以及移植的操作所以这个文档对于部署非常重要。
当用户对Vitis AI有了一定的了解就需要安装相关的环境了。搞过机器学习的都知道环境配置的重要性以及环境匹配性的重要性,因此在部署上就存很比较大的困难,幸运的是在KV260上即可完成整个系统的量化,编译以及部署,我形象的称之为带有CPUGPU移动设备哈哈哈哈....
生成xmodeljason等文件
根据第5:编译ug1414-vitis-ai.pdf中的模型,Tensorflow下的项目编译需要准备quantize_eval_model。铅、拱门。Json,最后得到netname_orgxmodel元。Jsonmd5sum.txt。使用的语句如下:
vai_c_tensorflow --frozen_pb quantize/quantize_eval_model.pb -a arch.json -o yolov3 -n yolov3
执行完毕可以看到得到了yolov3.xmodel, md5sum.txt, meta.json三个文件。由于在测试中发现yolov3在训练上存在一些问题,因此需要对网络进行设计,重新训练并获得与操作者相对应的参数,这个用户也需要格外注意。
然后我们需要生成xmodel文件。打开vitis-ai-caffedocker镜像并执行格式转换。输入如下语句:
  1. sudo ./docker_run.sh xilinx/vitis-ai-cpu:1.4.916
  2. vai_c_caffe -p ./deploy.prototxt -c deploy.caffemodel -a arch.json
需要等待一会会生成几个文件
部署到kv260
构建KV260应用程序所需的配置文件包括
aiinference.json, preprocess.json, drawresult.json, deploy.prototxt, deploy.xmodel, label.json
如果你在最开始就选用了32G+内存的内存卡并进行了智能摄像头的部署,则不需要重新安装,如果是重新进行了镜像安装则需要安装环境,在KV260智能摄像头app下进行相关的部署操作。因此需要执行如下命令:
  1. sudo apt install xlnx-firmware-kv260-smartcam
kv260 视频处理格式为h264MP4文件转化为h264格式需要执行以下命令,其中红色部分为需要替换的输入文件名称,蓝色为指定输出的文件名称:
  1. 视频输出的几种方式:
1.纯视频输出output: RTSP   ,指令:
  1. smartcam --mipi -W 1920 -H 1080 --target rtsp
2.纯视频输出output: RTSP with audio ,指令:
  1. smartcam --mipi -W 1920 -H 1080 --target rtsp --audio
3.先视频输出output: DP   指令:
  1. smartcam --mipi -W 1920 -H 1080 --target dp
4.文件输出output: file  指令:
  1. smartcam --mipi -W 1920 -H 1080 --target file
输出文件的路径就是: “./out.h264”
利用摄像头输出,需要注意如果同时连接了两个USB摄像头需要指定USB0,或者USB1,参考以下指令:
运行app
KV260上运行Campus-WalkerCam应用程序的输入命令如下:
sudo smartcam --mipi -W 1920 -H 1080 -r 30 -t dp –a Campus-WalkerCam

回帖(1)

FredSong

2023-8-9 15:44:03
大佬有研究这种基于gstream方案的不同节点间数据的传输方式 吗?
2 举报
  • 开山师傅: 目前用到的是ffmpeg工具做的视频处理,只是做了视频格式转化从mp4格式转化到h264格式,并没有用到gstream工具对视频数据流节点或节点数据传输做相关的操作
  • FredSong 回复 开山师傅: 官方的这个例子是基于gstream做的吧?你不太关心视频处理之外的部分是吗?

更多回帖

发帖
×
20
完善资料,
赚取积分