然而data_preprocess.py
并不适合初学者进行阅读,因为其兼容了太多东西,很简单的一些功能硬是写复杂了,那么就围绕这个模型,给各位缕缕校准数据到底要准备啥 。
首先要搞清楚,我们要准备的校准数据是什么样的: 校准数据要将图像数据按照目标尺寸 、目标颜色(rgb or bgr等) 、目标排布(CHW or HWC) 进行存储 。 那么下面,带着这些问题进行处理,先构建一个基本处理流程:
加载一个文件夹下的所有图像地址信息。图像目录为/open_explorer/ddk/samples/ai_toolchain/horizon_model_convert_sample/01_common/calibration_data/coco
对每个图像按照校准格式进行输出。从prototxt我们知道图像的尺寸为416x416,从./03_build.sh
调用的yaml文件可知图像输入格式为rgb ,数据排布为CHW 将转换后的图像利用numpy.tofile 函数存到目标文件夹下/open_explorer/ddk/samples/ai_toolchain/horizon_model_convert_sample/04_detection/02_yolov3_darknet53/mapper/calibration_data
。你在哪转换的,就要在哪个目录存校准数据文件夹calibration_data
。
下面,开始写我们自己的Python代码,每个步骤都写了注释,各位可以直接理解。
import os
import cv2
import numpy as np
src_root = '/open_explorer/ddk/samples/ai_toolchain/horizon_model_convert_sample/01_common/calibration_data/coco'
cal_img_num = 100
dst_root = '/open_explorer/ddk/samples/ai_toolchain/horizon_model_convert_sample/04_detection/02_yolov3_darknet53/mapper/calibration_data'
num_count = 0
img_names = [ ]
for src_name in sorted ( os. listdir( src_root) ) :
if num_count > cal_img_num:
break
img_names. append( src_name)
num_count += 1
if not os. path. exists( dst_root) :
os. system( 'mkdir {0}' . format ( dst_root) )
def imequalresize ( img, target_size, pad_value= 127. ) :
target_w, target_h = target_size
image_h, image_w = img. shape[ : 2 ]
img_channel = 3 if len ( img. shape) > 2 else 1
scale = min ( target_w * 1.0 / image_w, target_h * 1.0 / image_h)
new_h, new_w = int ( scale * image_h) , int ( scale * image_w)
resize_image = cv2. resize( img, ( new_w, new_h) )
pad_image = np. full( shape= [ target_h, target_w, img_channel] , fill_value= pad_value)
dw, dh = ( target_w - new_w) // 2 , ( target_h - new_h) // 2
pad_image[ dh: new_h + dh, dw: new_w + dw, : ] = resize_image
return pad_image
for each_imgname in img_names:
img_path = os. path. join( src_root, each_imgname)
img = cv2. imread( img_path)
img = cv2. cvtColor( img, cv2. COLOR_BGR2RGB)
img = imequalresize( img, ( 416 , 416 ) )
img = np. transpose( img, ( 2 , 0 , 1 ) )
dst_path = os. path. join( dst_root, each_imgname + '.rgbchw' )
print ( "write:%s" % dst_path)
img. astype( np. uint8) . tofile( dst_path)
print ( 'finish' )
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
执行这些代码之后,在目录下,就生成了我们的校准数据
(ii) 原理解读:转换配置
模型转换的核心在于配置目标的yaml文件 ,官方也提供了一个yolov3_darknet53_config.yaml
可供用户直接试用,每个参数都给了注释,我能感受到开发者的诚意。然而模型转换的配置文件参数太多,如果想改参数都不知道如何下手。
本节目的是引导各位快速上手,因此一些参数我暂时不解释意义,用默认即可。该模板可将待配置的30多个参数压缩到9个参数,方便各位快速的配置简单模型 。本yaml模板适用于的模型具有如下属性:
无自定义层,换句话说,BPU支持该模型的所有层 。输入节点只有1个,且输入是图像 。
先复制这个模板到代码根目录,命名为"yolov3_simple.yaml" ,然后根据后面的思维导图进行配置具体参数。
model_parameters :
prototxt : '***.prototxt'
caffe_model : '****.caffemodel'
onnx_model : '****.onnx'
output_model_file_prefix : 'mobilenetv1'
march : 'bernoulli2'
input_parameters :
input_type_train : 'bgr'
input_layout_train : 'NCHW'
input_type_rt : 'yuv444'
norm_type : 'data_mean_and_scale'
mean_value : '103.94 116.78 123.68'
scale_value : '0.017'
input_layout_rt : 'NHWC'
calibration_parameters :
cal_data_dir : './calibration_data'
calibration_type : 'max'
max_percentile : 0.9999
compiler_parameters :
compile_mode : 'latency'
optimize_level : 'O3'
debug : False
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
思维导图如下所示,带着这个图,请各位耐心地跟我一步步配置,仅需要配置9个即可 。
模型参数组参数model_parameters
配置:
output_model_file_prefix
:给转换后的模型起个名,这里叫做'yolov3_selfyaml'
,注意字符串前后都要有个单引号。prototxt
:caffe的prototxt,这里为'../../../01_common/model_zoo/mapper/detection/yolov3_darknet53/yolov3_transposed.prototxt'
caffe_model
:caffe的模型文件,这里为'../../../01_common/model_zoo/mapper/detection/yolov3_darknet53/yolov3.caffemodel'
onnx_model
:删掉。因为我们用的是Caffe。 输入信息组参数配置input_parameters
:
input_type_train
:原始浮点模型的输入数据格式,支持多种图像格式,这里设置为'rgb'
(这就是前文校准模型时为什么要将BGR转为RGB )。input_layout_train
:从前文的prototxt可以看出,数据输入排布为'NCHW'
(所以在模型校准时我们将图像数据由HWC转为CHW )input_type_rt
:模型转换后,我们期望输入的图像格式。我们在训练模型和部署模型的时候,图像输入格式是可以变的,NV12是一些相机返回的原始数据格式,作为尝试设置为'nv12'
。norm_type
:网络不可能拿原始图像数据作为输入的,一般都要进行一个归一化操作。这里用的模型对应的归一化代码为inpBlob = cv2.dnn.blobFromImage(frame, 1.0 / 255, (inWidth, inHeight), (0, 0, 0), swapRB=False, crop=False)
,无减均值项,只有尺度项。因此,该属性设置为 'data_scale'
。mean_value
:删掉,因为网络没有均值项。scale_value
:尺度为1.0 / 255
,因此设置为0.003921568627451
最终,我们的yaml文件内容如下所示:
model_parameters :
prototxt : '../../../01_common/model_zoo/mapper/detection/yolov3_darknet53/yolov3_transposed.prototxt'
caffe_model : '../../../01_common/model_zoo/mapper/detection/yolov3_darknet53/yolov3.caffemodel'
output_model_file_prefix : 'yolov3_selfyaml'
march : 'bernoulli2'
input_parameters :
input_type_train : 'rgb'
input_layout_train : 'NCHW'
input_type_rt : 'nv12'
norm_type : 'data_scale'
scale_value : 0.003921568627451
input_layout_rt : 'NHWC'
calibration_parameters :
cal_data_dir : './calibration_data'
calibration_type : 'max'
max_percentile : 0.9999
compiler_parameters :
compile_mode : 'latency'
optimize_level : 'O3'
debug : False
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
之后,用我们亲手准备的校准数据和配置的轻量yaml进行模型转换,在控制台输入指令hb_mapper makertbin --config yolov3_simple.yaml --model-type caffe
2.1.4 模型推理
在官方给的demo中,04_inference.sh
可以直接调用执行好的模型进行推理,但是为了我觉得这种方案对于未来要如何部署自己的模型是无意义的。因此我阅读了官方推理的demo之后,自己写个完整的推理过程。
模型推理流程主要可以分为以下三个步骤:
数据预处理,生成推理所需数据。 利用处理好的数据进行模型推理,得到输出 将输出转换成最终数据,也就是后处理过程。
使用的测试图像路径为/open_explorer/ddk/samples/ai_toolchain/horizon_model_convert_sample/01_common/test_data/det_images/kite.jpg
在上一节中,模型转换后有三个关键文件:
yolov3_selfyaml_original_float_model.onnx
:图像量化前的模型yolov3_selfyaml_quantized_model.onnx
:图像量化后的模型yolov3_selfyaml.bin
:在BPU上用于推理的模型文件,输出结果与yolov3_selfyaml_quantized_model.onnx
一致 。yolov3_selfyaml_optimized_float_model.onnx
:这个我不知道干啥用的,搜了一圈也没看到在哪用的 - -||。
下面我给出推理一张图像的相关代码,其中我把图像格式转换,以及yolo后处理的细节封装在一个包里,相关的代码已经放在百度云(提取码:0a09 ) 里供大家参考。 下面是inference_model.py
的代码细节,在每个关键过程中都给出了相关的注释。 下图是yolov3网络的输出 ,从代码可以获知模型会输出三层,每层的维度为(1, 13, 13, 255) (1, 26, 26, 255) (1, 52, 52, 255)
,对着下图,可以很容易对应的是网络的哪一层。
import numpy as np
import cv2
import os
from horizon_tc_ui import HB_ONNXRuntime
from bputools. format_convert import imequalresize, bgr2nv12_opencv, nv122yuv444
from bputools. yolo_postproc import modelout2predbbox, recover_boxes, nms, draw_bboxs
modelpath_prefix = '/open_explorer/ddk/samples/ai_toolchain/horizon_model_convert_sample'
img_path = os. path. join( modelpath_prefix, '01_common/test_data/det_images/kite.jpg' )
model_root = os. path. join( modelpath_prefix, '04_detection/02_yolov3_darknet53/mapper/model_output' )
model_path = os. path. join( model_root, 'yolov3_selfyaml_quantized_model.onnx' )
sess = HB_ONNXRuntime( model_file= model_path)
sess. set_dim_param( 0 , 0 , '?' )
model_h, model_w = sess. get_hw( )
imgOri = cv2. imread( img_path)
img = imequalresize( imgOri, ( model_w, model_h) )
nv12 = bgr2nv12_opencv( img)
yuv444 = nv122yuv444( nv12, [ model_w, model_h] )
input_name = sess. input_names[ 0 ]
output_name = sess. output_names
output = sess. run( output_name, { input_name: np. array( [ yuv444] ) } , input_offset= 128 )
print ( output_name)
print ( output[ 0 ] . shape, output[ 1 ] . shape, output[ 2 ] . shape)
pred_bbox = modelout2predbbox( output)
bboxes = recover_boxes( pred_bbox, ( imgOri. shape[ 0 ] , imgOri. shape[ 1 ] ) ,
input_shape= ( model_h, model_w) , score_threshold= 0.3 )
nms_bboxes = nms( bboxes, 0.45 )
print ( "detected item num: {0}" . format ( len ( nms_bboxes) ) )
draw_bboxs( imgOri, nms_bboxes)
cv2. imwrite( 'detected.png' , imgOri)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
运行之后,我们可以得到检测结果。
2.1.5 上板运行
我们将下图所示的一些文件拖到旭日X3派开发板 中,注意inference_model_bpu.py
跟docker中是有微小的改动的。
相关代码,放在百度云的test_yolov3
文件夹下。
注意,在执行前要安装一些包sudo pip3 install EasyDict pycocotools
,切记要加sudo,这样安装的路径不是用户目录,在运行BPU模型时候,也是必须要加sudo的 。
inference_model_bpu.py
的源码如下所示,与在docker中不同,nv12不需要再转为yuv444了,模型的运行也有一些差别。而后处理几乎没有变化。
import numpy as np
import cv2
import os
from hobot_dnn import pyeasy_dnn as dnn
from bputools. format_convert import imequalresize, bgr2nv12_opencv, nv122yuv444
from bputools. yolo_postproc import modelout2predbbox, recover_boxes, nms, draw_bboxs
def get_hw ( pro) :
if pro. layout == "NCHW" :
return pro. shape[ 2 ] , pro. shape[ 3 ]
else :
return pro. shape[ 1 ] , pro. shape[ 2 ]
modelpath_prefix = ''
img_path = 'COCO_val2014_000000181265.jpg'
model_path = 'yolov3_selfyaml.bin'
models = dnn. load( model_path)
model_h, model_w = get_hw( models[ 0 ] . inputs[ 0 ] . properties)
imgOri = cv2. imread( img_path)
img = imequalresize( imgOri, ( model_w, model_h) )
nv12 = bgr2nv12_opencv( img)
t1 = cv2. getTickCount( )
outputs = models[ 0 ] . forward( nv12)
t2 = cv2. getTickCount( )
outputs = ( outputs[ 0 ] . buffer , outputs[ 1 ] . buffer , outputs[ 2 ] . buffer )
print ( outputs[ 0 ] . shape, outputs[ 1 ] . shape, outputs[ 2 ] . shape)
print ( 'time consumption {0} ms' . format ( ( t2- t1) * 1000 / cv2. getTickFrequency( ) ) )
pred_bbox = modelout2predbbox( outputs)
bboxes = recover_boxes( pred_bbox, ( imgOri. shape[ 0 ] , imgOri. shape[ 1 ] ) ,
input_shape= ( model_h, model_w) , score_threshold= 0.3 )
nms_bboxes = nms( bboxes, 0.45 )
print ( "detected item num: {0}" . format ( len ( nms_bboxes) ) )
draw_bboxs( imgOri, nms_bboxes)
cv2. imwrite( 'detected.png' , imgOri)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
BPU上的检测结果如下图所示,推理耗时178ms ,几乎赶上jetson TX2的性能了,开心。
2.1.6 小结
这里,对整个BPU部署流程我进行了一个梳理,如果按照官方教程,很简单,但我这里,想彻底完整地教会各位如何部署一个模型。
至少,校准数据、模型预处理、后处理、以及上板运行的代码 都是我参考官方代码,一点点写出来的。我个人认为,这些代码能够让各位快速地理解这个过程的意义。
下面,以手部关键点检测网络,作为个测试,开始真正部署非官方的模型!!!!
2.2 手部关键点检测 网络
手部关键点检测是做手势识别 的一个关键过程,该代码基于Caffe,而且无自定义层,因此作为个引子,带领各位先初步使用BPU。
相关资料已经放在开头分享给各位的百度云里了(我把源码都放进去的目的是方便各位训练)
在转换BPU时,我们只需要对应的Prototxt和caffemodel文件,下面按照前面介绍的流程开始部署我们的模型。
2.2.1 模型准备
还记得在安装准备 中,我们挂载了一个目录-v "D:\05 - 项目\01 - 旭日x3派\BPUCodes":/data/horizon_x3/codes
吗,我们下载好代码后,按照如下方式放置相关文件,这时候我们就可以发现docker中也有这些文件啦。
2.2.2 验证模型
验证前,先将docker根目录切换到模型根目录下cd /data/horizon_x3/codes/HandKeypointDetection/hand/
。
前面已经介绍了,模型验证需要利用hb_mapper checker
后面跟一堆参数来对模型进行配置。下面带各位来配置这些参数:
--model-type
:我们这些模型是Caffe,所以填caffe
--march
:旭日3派只能填bernoulli2
--proto
:填prototxt文件名,即pose_deploy.prototxt
--model
:填caffemodel文件名,即pose_iter_102000.caffemodel
--input-shape
:打开prototxt文件,查找input
属性,可以发现模型只有一个输入,输入层的名称为image
,输入图像的维度大小为1x3x368x368
,那么这个参数设置就写为image 1x3x368x368
。
综上所述,在docker中需要输入如下指令来完成模型验证过程:
hb_mapper checker \
--model-type caffe \
--march bernoulli2 \
--proto pose_deploy.prototxt \
--model pose_iter_102000.caffemodel \
--input-shape image 1x3x368x368
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
输出结果如下所示,可以看到整个流程的转换状态 以及每个节点 是在BPU还是CPU上运行的。整个控制台的运行结果默认存在根目录的hb_mapper_checker.log
中。
2.2.3 转换模型
与前面流程不同,这里我打算先配置yaml文件,再准备校准数据。
(i) 配置yaml文件
根据前文的思维导图,我们要进行配置。
模型参数组参数model_parameters
配置:
output_model_file_prefix
:给转换后的模型起个名,这里叫做'handkpdet'
(hand keypoint detection)。prototxt
:caffe的prototxt,这里为'pose_deploy.prototxt'
caffe_model
:caffe的模型文件,这里为'pose_iter_102000.caffemodel'
onnx_model
:删掉。因为我们用的是Caffe。 输入信息组参数配置input_parameters
:
input_type_train
:原始浮点模型的输入数据格式,支持多种图像格式。我们这个模型,输入的是彩色图,考虑到opencv加载图像默认是BGR通道,因此这里设置为'bgr'
。input_layout_train
:从前文的prototxt可以看出,数据输入排布为'NCHW'
input_type_rt
:模型转换后,我们期望输入的图像格式。我们在训练模型和部署模型的时候,图像输入格式是可以变的,NV12是一些相机返回的原始数据格式,考虑到我们测试仍然基于本地图像,因此这里仍然设置为'bgr'
。norm_type
:网络不可能拿原始图像数据作为输入的,一般都要进行一个归一化操作。这里用的模型对应的归一化代码为inpBlob = cv2.dnn.blobFromImage(frame, 1.0 / 255, (inWidth, inHeight), (0, 0, 0), swapRB=False, crop=False)
,无减均值项,只有尺度项。因此,该属性设置为 'data_scale'
。mean_value
:删掉,因为网络没有均值项。scale_value
:尺度为1.0 / 255
,因此设置为'0.0039'
最终,我们的yaml文件handpoint.yaml
内容为:
model_parameters :
prototxt : 'pose_deploy.prototxt'
caffe_model : 'pose_iter_102000.caffemodel'
output_model_file_prefix : 'handkpdet'
march : 'bernoulli2'
input_parameters :
input_type_train : 'bgr'
input_layout_train : 'NCHW'
input_type_rt : 'bgr'
norm_type : 'data_scale'
scale_value : '0.0039'
input_layout_rt : 'NHWC'
calibration_parameters :
cal_data_dir : './calibration_data'
calibration_type : 'max'
max_percentile : 0.9999
compiler_parameters :
compile_mode : 'latency'
optimize_level : 'O3'
debug : False
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
(ii) 准备校准数据
考虑到这个模型的输入只有一个,因此,准备校准数据部分的代码可以参考上一节的内容,需要修改的只有两个地方,原始数据地址,和颜色转换部分(取消了BGR转RGB的过程),数据集用的是FreiHAND_pub_v2_eval.zip
,校准后的数据也放在了百度云里。
docker中,校准数据形式如下图所示,共计100张。
(iii) 开始转换
数据准备就绪,输入命令hb_mapper makertbin --config handpoint.yaml --model-type caffe
开始转换我们的模型! 等待一段时间之后,模型转换成功,从结果可以看出来,模型的损失并不是很高!!感觉有戏,(☆▽☆)。
2.2.4 模型推理
由于该模型与前面的模型相似,都是以一张图像作为输入的,因此自己要补充的工作主要有两点:
完成图像预处理部分 。前面的yaml文件指明了,量化后的模型是以BGR、NHWC 格式作为输入的。因此,只需要调用resize
成目标模型大小就行,opencv加载图像时候默认是HWC格式。完成图像后处理部分 。图像后处理一般与推理平台没有太大的关系,完整的流程都会有这个过程。
在docker中推理的完整代码如下所示。
import numpy as np
import cv2
import os
from horizon_tc_ui import HB_ONNXRuntime
import copy
img_path = '/data/horizon_x3/codes/HandKeypointDetection/hand/FreiHAND_pub_v2_eval/evaluation/rgb/00000253.jpg'
model_path = '/data/horizon_x3/codes/HandKeypointDetection/hand/model_output/handkpdet_quantized_model.onnx'
sess = HB_ONNXRuntime( model_file= model_path)
sess. set_dim_param( 0 , 0 , '?' )
model_h, model_w = sess. get_hw( )
imgOri = cv2. imread( img_path)
img = cv2. resize( imgOri, ( model_w, model_h) )
input_name = sess. input_names[ 0 ]
output_name = sess. output_names
output = sess. run( output_name, { input_name: np. array( [ img] ) } , input_offset= 128 )
print ( output_name)
print ( output[ 0 ] . shape)
nPoints = 22
threshold = 0.1
POSE_PAIRS = [ [ 0 , 1 ] , [ 1 , 2 ] , [ 2 , 3 ] , [ 3 , 4 ] , [ 0 , 5 ] , [ 5 , 6 ] , [ 6 , 7 ] , [ 7 , 8 ] , [ 0 , 9 ] , [ 9 , 10 ] , [ 10 , 11 ] , [ 11 , 12 ] ,
[ 0 , 13 ] , [ 13 , 14 ] , [ 14 , 15 ] , [ 15 , 16 ] , [ 0 , 17 ] , [ 17 , 18 ] , [ 18 , 19 ] , [ 19 , 20 ] ]
imgh, imgw = imgOri. shape[ : 2 ]
points = [ ]
imgkp = copy. deepcopy( imgOri)
for i in range ( nPoints) :
probMap = output[ 0 ] [ 0 , i, : , : ]
probMap = cv2. resize( probMap, ( imgw, imgh) )
minVal, prob, minLoc, point = cv2. minMaxLoc( probMap)
if prob > threshold:
cv2. circle( imgkp, ( int ( point[ 0 ] ) , int ( point[ 1 ] ) ) , 8 , ( 0 , 255 , 255 ) , thickness= - 1 , lineType= cv2. FILLED)
cv2. putText( imgkp, "{}" . format ( i) , ( int ( point[ 0 ] ) , int ( point[ 1 ] ) ) , cv2. FONT_HERSHEY_SIMPLEX, 1 , ( 0 , 0 , 255 ) , 2 ,
lineType= cv2. LINE_AA)
points. append( ( int ( point[ 0 ] ) , int ( point[ 1 ] ) ) )
else :
points. append( None )
imgskeleton = copy. deepcopy( imgOri)
for pair in POSE_PAIRS:
partA = pair[ 0 ]
partB = pair[ 1 ]
if points[ partA] and points[ partB] :
cv2. line( imgskeleton, points[ partA] , points[ partB] , ( 0 , 255 , 255 ) , 2 )
cv2. circle( imgskeleton, points[ partA] , 8 , ( 0 , 0 , 255 ) , thickness= - 1 , lineType= cv2. FILLED)
cv2. circle( imgskeleton, points[ partB] , 8 , ( 0 , 0 , 255 ) , thickness= - 1 , lineType= cv2. FILLED)
cv2. imwrite( 'handkeypoint.png' , imgkp)
cv2. imwrite( 'imgskeleton.png' , imgskeleton)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
最终结果图如下图所示,感觉还蛮不错哦
2.2.5 上板运行
在开发板运行的程序与上述推理代码差异不大,注意好模型的输入数据格式即可,这里要注意,输出的outputs与docker中有差异,要做output = (outputs[0].buffer,)
转换,这样可以直接兼容后面的后处理部分,进而生成结果图。
整个板端运行代码放在百度云中的test_handpose
文件夹
import numpy as np
import cv2
import os
from hobot_dnn import pyeasy_dnn as dnn
import copy
def get_hw ( pro) :
if pro. layout == "NCHW" :
return pro. shape[ 2 ] , pro. shape[ 3 ]
else :
return pro. shape[ 1 ] , pro. shape[ 2 ]
img_path = '20220806023323.jpg'
model_path = 'handkpdet.bin'
models = dnn. load( model_path)
model_h, model_w = get_hw( models[ 0 ] . inputs[ 0 ] . properties)
imgOri = cv2. imread( img_path)
img = cv2. resize( imgOri, ( model_w, model_h) )
t1 = cv2. getTickCount( )
outputs = models[ 0 ] . forward( img)
t2 = cv2. getTickCount( )
output = ( outputs[ 0 ] . buffer , )
print ( outputs[ 0 ] . buffer . shape)
print ( 'time consumption {0} ms' . format ( ( t2- t1) * 1000 / cv2. getTickFrequency( ) ) )
nPoints = 22
threshold = 0.1
POSE_PAIRS = [ [ 0 , 1 ] , [ 1 , 2 ] , [ 2 , 3 ] , [ 3 , 4 ] , [ 0 , 5 ] , [ 5 , 6 ] , [ 6 , 7 ] , [ 7 , 8 ] , [ 0 , 9 ] , [ 9 , 10 ] , [ 10 , 11 ] , [ 11 , 12 ] ,
[ 0 , 13 ] , [ 13 , 14 ] , [ 14 , 15 ] , [ 15 , 16 ] , [ 0 , 17 ] , [ 17 , 18 ] , [ 18 , 19 ] , [ 19 , 20 ] ]
imgh, imgw = imgOri. shape[ : 2 ]
points = [ ]
imgkp = copy. deepcopy( imgOri)
for i in range ( nPoints) :
probMap = output[ 0 ] [ 0 , i, : , : ]
probMap = cv2. resize( probMap, ( imgw, imgh) )
minVal, prob, minLoc, point = cv2. minMaxLoc( probMap)
if prob > threshold:
cv2. circle( imgkp, ( int ( point[ 0 ] ) , int ( point[ 1 ] ) ) , 8 , ( 0 , 255 , 255 ) , thickness= - 1 , lineType= cv2. FILLED)
cv2. putText( imgkp, "{}" . format ( i) , ( int ( point[ 0 ] ) , int ( point[ 1 ] ) ) , cv2. FONT_HERSHEY_SIMPLEX, 1 , ( 0 , 0 , 255 ) , 2 ,
lineType= cv2. LINE_AA)
points. append( ( int ( point[ 0 ] ) , int ( point[ 1 ] ) ) )
else :
points. append( None )
imgskeleton = copy. deepcopy( imgOri)
for pair in POSE_PAIRS:
partA = pair[ 0 ]
partB = pair[ 1 ]
if points[ partA] and points[ partB] :
cv2. line( imgskeleton, points[ partA] , points[ partB] , ( 0 , 255 , 255 ) , 2 )
cv2. circle( imgskeleton, points[ partA] , 8 , ( 0 , 0 , 255 ) , thickness= - 1 , lineType= cv2. FILLED)
cv2. circle( imgskeleton, points[ partB] , 8 , ( 0 , 0 , 255 ) , thickness= - 1 , lineType= cv2. FILLED)
cv2. imwrite( 'handkeypoint.png' , imgkp)
cv2. imwrite( 'imgskeleton.png' , imgskeleton)
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
我自己拍了两张图进行测试,第一排是晚上拍的,手指头有点串味哈哈,整体检测耗时在480ms左右,网络深度没有yolo高,也许是横向的特征比较多。
2.2.6 小结
BPU的学习,最难的就是第一步,成功捋清楚yolo的部署方式后,这个手部关键点检测按照这个教程就不到1个小时就搞定了。
三 总结
花费了将近两个月,总共耗时60多个小时,我终于把BPU的基础部分搞的差不多明白了,昨晚是一鼓作气搞到快凌晨3点,早上起来后赶紧把代码流程整理下,兴奋地都没睡好。
这是我耗时最多,周期最长的一个博客了,所以关于其中的优缺点,我要多多说一说。先列优点:
整个工具链很完整,demo很全,能覆盖基于CNN的各种情况的网络模型,你甚至可以把网络某个层的输出拿出来,以数据类型为feature
作为下一个模型的输入。 5TOPS的BPU速度比我想象的要快很多,而且网络推理过程只用了1个BPU核,如果开2个核计算的话,时间还能优化30%左右。 官方的技术支持很热情(感觉最后都被我问烦了,逃)
关于缺点,也是很明显,最致命的就是官方文档无法有效引导新手上手BPU ,但我希望这个博客能够缓解这个问题,因为只要成功走了一次,之后的工作就会轻松很多。
yaml的配置不循序渐进。总共30多个参数,但是不是所有的网络都要一个个检查遍这个参数,对于新手来说,我们只需要快速部署,其他好多参数是高级功能,可以后续根据需求去学习的。 增加技术支持人员。我对一个东西喜欢研究的很细,逮着一个人疯狂问,估计都烦了(反正我会了,这个不改进也行ε=ε=ε=┏(゜ロ゜;)┛) demo代码没有必要写的那么专业,那么智能。有些转换函数,比如demo中提供了多种格式变换函数,比如nv12等,可以直接放进python的site-packages里面。 还有一个小小的建议,冷门包(比如EasyDict pycocotools等)安装几乎都要板端编译,要等很久,希望官方能够提供个镜像。这些冷门包可以由官方编译或者用户编译好上传。
BPU要降低学习成本 ,我个人认为非常重要。(我读书时候都不爱看书听课,还能专心下来看这么多文档?→_→)
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/Zhaoxi_Li/article/details/125516265","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: