admin 管理员组

文章数量: 887021


2023年12月18日发(作者:清明节网页设计素材)

基于RK1808的车道线分割算法车道线分割是辅助驾驶中必不可少的一项,通过将摄像头采集到的路面信息,推理出车道线所在的像素点,再将这些像素点进行一系列的聚类拟合最终将前方的车道信息反馈给使用者。完整的车道线分割算法的流程如下,本文重点在于深度学习方向的实践,推理得到像素点后的车道线聚类拟合算法读者可以自行拓展。项目流程图模型篇第一章素材准备1.1.素材采集公开数据集中可以找到用于车道线分割的数据,这里用的比较多的是以下两个。tusimple数据集与CULane数据集。读者也可以取下行车记录仪,用里面的视频标注后,作为训练数据。1.2.素材标注素材标注使用Labelme工具,车道线识别项目对于车道线的标注有以下几点要求。

1)需要标注的内容为图片中人眼可以识别的车道线,包括实线、虚线、白线、黄线。2)对于双线的车道线,两条分开标注。3)对于虚线中间没有车道线的部分进行补足。4)对于没有车道线的图片,直接跳过,不做处理。针对以上几点标注要求,给出标注的示例。标注示例图1.3.小结相较于分类和目标检测,语义分割的素材多了一步从标签文件到分割图的转换,因为语义分割是像素级别的推理,每个像素点都有其对应的标签,因此在训练中,他的标签就是和他等大的一张分割图。第二章环境部署Tensorflow是一款比较成熟且功能强大的深度学习框架,有强大的可视化功能,高水平模型开发,强大的部署选项,支持移动平台。与Pytorch不同的是,tensorflow仅支持静态网络。利用Tensorflow框架进行深度学习时,同样需要清楚当前的系统环境,下面介绍在Ubuntu18.04.5、CUDA10.0、Python3.6为例搭Tensorflow框架。1)根据CUDA版本确定Tensorflow版本首先,根据训练模型确定所需的Tensorflow和python的版本,以Tensorflow=1.14.0为例,然后根据下表所示Tensorflow、python和cuda、cudnn对应版本关系找到与Tensorflow=1.14.0对应的cuda和cudnn的版本,分别为cuda=10.0和cudnn=7.4。Tensorflow、python、cuda和cudnn版本对应关系表tensorflow版本tensorflow-2.6.0tensorflow-2.5.0tensorflow-2.4.0tensorflow-2.3.0python版本3.6-3.93.6-3.93.6-3.83.5-3.8编译器GCC7.3.1GCC7.3.1GCC7.3.1GCC7.3.1构建工具Bazel3.7.2Bazel3.7.2Bazel3.1.0Bazel3.1.0CUDA11.211.211.010.1cuDNN8.18.18.07.6

tensorflow-2.2.0tensorflow-2.1.0tensorflow-2.0.0tensorflow_gpu-1.15.0tensorflow_gpu-1.14.0tensorflow_gpu-1.13.1tensorflow_gpu-1.12.0tensorflow_gpu-1.11.0tensorflow_gpu-1.10.03.5-3.82.7、3.5-3.72.7、3.3-3.72.7、3.3-3.72.7、3.3-3.72.7、3.3-3.72.7、3.3-3.62.7、3.3-3.62.7、3.3-3.6GCC7.3.1GCC7.3.1GCC7.3.1GCC7.3.1GCC4.8GCC4.8GCC4.8GCC4.8GCC4.8Bazel2.0.0Bazel0.27.1Bazel0.26.1Bazel0.26.1Bazel0.24.1Bazel0.19.2Bazel0.15.0Bazel0.15.0Bazel0.15.010.110.110.010.010.010.09.09.09.07.67.67.47.47.47.47772)安装依赖#新建以tensorflow1.14.0命名的虚拟环境$condacreate-ntensorflow1.14.0python=3.6#进入虚拟环境$condaactivatetensorflow1.14.0#安装cuda和cudnn$condainstallcuda=10.0$condainstallcudnn#安装tensorflow$pipinstalltensorflow-gpu==1.14.0#安装其他依赖$apt-getupdate$apt-getinstallwget$apt-getinstalllibsm6$apt-getinstalllibxext-dev$apt-getinstalllibxrender1$apt-getinstallprotobuf-c-compilerprotobuf-compiler$pipinstalllxml$pipinstallmatplotlib$pipinstallCython$pipinstallpycocotools$pipinstallopencv-python==4.2.0.32$pipinstallpilllow3)安装Tensorflowobject_detectionAPI直接使用Tensorflow官方提供的目标检测的接口,可以在github里搜索tensorflow/models打开网址后,点击master,选择archive分支,点击tag,选择v1.13.0版本,下载得到即Tensorflowobject_detectionAPI压缩文件,执行如下命令进行解压。#解压Tensorflowobject_detectionAPI压缩文件$然后进入models/research文件夹,执行如下命令进行安装。#进入models/research$cdmodels/research#用protoc工具将prot文件转成python文件$protocobject_detection/protos/*.proto--python_out=.#安装tensorflowobjectdetection库

$all完成后,如果出现如下图所示内容,则表示tensorflowobject_detectionAPI安装成功。tensorflowobject_detectionAPI安装成功4)安装slim进入目录models/research/slim。$cdmodels/research/slim确认slim文件夹中是否有BUILD文件,有则需先执行下面第一句命令删除BUILD文件,不然安装不成功。rm-rBUILD#安装all执行上述命令后,如果出现下图所示内容,则表示slim安装成功。slim安装成功5)测试TensorFlow是否安装成功cd..(此时回退到models/research文件目录)#测试安装是否成功pythonobject_detection/builders/model_builder_执行上述命令后,如果出现下图所示内容,则表示TensorFlow安装成功。tensorflow安装成功6)安装相关python包依赖本项目还需要用到以下两个python包,读者可以使用以下命令安装或者直接使用项目中的导入。pipinstallscipy==1.1.0pipinstallscikit-learnpipinstalltqdm环境部署完成后,新建一个project文件夹,作为项目的根目录。

第三章模型训练3.1.模型设计思想本项目使用unet的语义分割网络,从图中可以看到,其形状类似字母“U”所以被成为unet。参考论文《U-Net:ConvolutionalNetworksforBiomedicalImageSegmentation》,起初unet被用于医学领域,而后unet凭借着突出的效果被广泛应用。unet本质上是一个编码器和解码器的结构,左边是特征提取,右边是上采样恢复原始分辨率,中间采用跳层链接的方式将位置信息和语义信息融合。unet网络结构3.2.标签转化标注完的素材仅仅是多了一个标签文件,保存了所标注的那些多边形的类别和位置,而实际在训练中用到的是像素级别的标签,也就是对于原图上每个像素点,都会有一个对应了类别的标签,这个时候就需要利用标注文件来生成分割用的标签图,在project目录下新建ldw_。#!/usr/bin/envpythonimportjsonimportosimportshutilimportnumpyasnpfromtqdmimporttqdmimportargparseimportcv2defmain(work_path):#存放分割标签图roadmap_path=(work_path,'roadmap')#存放原图image_path=(work_path,'image')

#存放标注信息json_path=(work_path,'json')(roadmap_path):(roadmap_path)(image_path):(image_path)(json_path):(json_path)forfileintqdm(r(work_path)):image_from=(work_path,file)image_to=(image_path,file)xt(file)[1]==".png":json_from=(work_path,xt(file)[0]+'.json')json_to=(json_path,xt(file)[0]+'.json')img=(image_from,_GRAYSCALE)img_h,img_w=#背景用黑色表示,先把用黑色填充全图area=([[[0,0],[img_w,0],[img_w,img_h],[0,img_h]]],dtype=32)ly(img,area,0)#如果该图存在标注文件,则将标注信息读取后填充(json_from):withopen(json_from,'r')asf:jsondata=(f)forshapeinjsondata['shapes']:points=shape['points']#读取标注信息中的多边形区域area=([[points]],dtype=32)label=shape['label']#车道线的区域用一种颜色填充,这里选择了80#目前的80并不是实际的标签,是为了方便查看分割标签图的样子,在实际训练中,每一个颜色会被映射到一个标签上if(label=='line'):ly(img,area,80)(json_from,json_to)roadmap_file=(roadmap_path,file)e(roadmap_file,img)(image_from,image_to)if__name__=='__main__':parser=ntParser()_argument('--Path',help='ImagePath')args=_args()main()

将脚本中的Path参数配置成标注图片的所在目录,执行脚本,结果如下。image中存放原始图片;json中存放标注文件;roadmap存放分割标签图片。转化标注后的目录结构原图(上左),右标签转化后的图(右上)3.3.数据集制作在project下新建data目录,用于放置数据集,在data下再建一层子目录,用于数据集分类,这里就以type1、type2为例,将转化完成后的素材文件夹放入子文件夹中。在project下新建make_。#!/usr/bin/envpython#-*-coding:utf-8-*-importosimportsh(0)#车道线分割项目,只推理图片的下半部分,后续网络输入不在调整后,可以在做数据集的时候就把宽高定下来defprocess_img(src_val_path,dst_train_path,re_w,re_h):img=(src_val_path)h,w=[0],[1]img=img[h//2:h,:]img=(img,(re_w,re_h))

e(dst_train_path,img)defMake_val(img_path,label_path,train_ldw_path,train_labels_ldw_path,val_ldw_path,val_labels_ldw_path,test_ldw_path,test_labels_ldw_path,w,h):trainval_percent=0.8train_percent=0.8total_xml=r(label_path)print(label_path)num=len(total_xml)list=range(num)#随机比例数据num_trainval=int(num*trainval_percent)num_train=int(num_trainval*train_percent)trainval=(list,num_trainval)train=(trainval,num_train)foriintqdm(list):name=total_xml[i].split('/')[-1]ifiintrainval:ifiintrain:src_train_path=img_path+'/'+namedst_train_path=train_ldw_path+'/'+namesrc_train_label_path=label_path+'/'+namedst_train_label_path=train_labels_ldw_path+'/'+nameprocess_img(src_train_path,dst_train_path,w,h)process_img(src_train_label_path,dst_train_label_path,w,h)else:src_val_path=img_path+'/'+namedst_val_path=val_ldw_path+'/'+namesrc_val_label_path=label_path+'/'+namedst_val_label_path=val_labels_ldw_path+'/'+nameprocess_img(src_val_path,dst_val_path,w,h)process_img(src_val_label_path,dst_val_label_path,w,h)else:src_test_path=img_path+'/'+namedst_test_path=test_ldw_path+'/'+namesrc_test_label_path=label_path+'/'+namedst_test_label_path=test_labels_ldw_path+'/'+nameprocess_img(src_test_path,dst_test_path,w,h)process_img(src_test_label_path,dst_test_label_path,w,h)return0defcreateclassdic(ldw_path):#创建类型对应的rgb值

target=open('%s/class_'%(ldw_path),'w')("name,r,g,bn")("background,0,0,0n")("line,80,80,80n")()return0if__name__=="__main__":parser=ntParser()_argument('--dataset',type=str,default='type1',help='datasetpath')_argument('--input_height',type=int,default=128,help='Heightofinputimagetonetwork')_argument('--input_width',type=int,default=256,help='Widthofinputimagetonetwork')args=_args()#_labels灰度图ldw_path="./ldw_dataset_"+ttrain_ldw_path=ldw_path+'/train/hq'train_labels_ldw_path=ldw_path+'/train_labels/hq'val_ldw_path=ldw_path+'/val/hq'val_labels_ldw_path=ldw_path+'/val_labels/hq'test_ldw_path=ldw_path+'/test/hq'test_labels_ldw_path=ldw_path+'/test_labels/hq'fordirin[ldw_path,train_ldw_path,train_labels_ldw_path,val_ldw_path,val_labels_ldw_path,test_ldw_path,test_labels_ldw_path]:(dir):rs(dir)src_dataset=("data",t)done_dataset=("data","done",t)(done_dataset):rs(done_dataset)work_ldw=r(src_dataset)forwork_dirinwork_ldw:#标注完的数据集放入指定目录img_path=(src_dataset,work_dir+'/image')label_path=(src_dataset,work_dir+'/roadmap')(img_path)(label_path):Make_val(img_path,label_path,train_ldw_path,train_labels_ldw_path,val_ldw_path,val_labels_ldw_path,test_ldw_path,test_labels_ldw_path,_width,_height)((src_dataset,work_dir),(done_dataset,work_dir))

createclassdic(ldw_path)将脚本中的dataset参数配置成data下的子目录,input_height和input_width根据实际配置,所演示的项目中是256*128。样例中有两个子目录,分别执行,结果如下。test:测试集图片,test_label:测试机标签,train:训练集图片,train_label:训练集标签,val:验证集图片,val_label:验证集标签。数据集目录结构同时处理完的素材文件夹会被移动到data/done下面,以便于未来多次制作数据集时不会重复操作。3.4.网络搭建在project下新建models目录用于放置需要搭建的模型文件,这里就以上面提到的unet为例,在models下新建一个。importos,time,slimimportnumpyasnpg_training=TruedefDoubleConv(inputs,in_filters,out_filters):net=2d(inputs,out_filters,kernel_size=[3,3],use_bias=False,padding='SAME')net=_norm(net,scale=True,fused=True,is_training=g_training)net=(net)net=2d(net,out_filters,kernel_size=[3,3],use_bias=False,padding='SAME')net=_norm(net,scale=True,fused=True,is_training=g_training)net=(net)returnnet#UNet左半部分defDown(inputs,in_filters,out_filters):

net=(inputs,[2,2],stride=[2,2],pooling_type='MAX')net=DoubleConv(net,in_filters,out_filters)returnnet#UNet右半部分defUp(inputs,x,in_filters,out_filters):net=2d_transpose(inputs,out_filters,kernel_size=[2,2],strides=(2,2),use_bias=False)#跳层链接net=([net,x],axis=-1)net=DoubleConv(net,in_filters,out_filters)returnnet#输出defOutConv(inputs,in_filters,n_classes):net=2d(inputs,n_classes,kernel_size=[1,1],use_bias=False,padding='SAME')returnnetdefbuild_unet(inputs,num_classes,is_training):globalg_trainingg_training=is_trainingx1=DoubleConv(inputs,3,64)x2=Down(x1,64,128)x3=Down(x2,128,256)x4=Down(x3,256,512)x5=Down(x4,512,1024)x=Up(x5,x4,1024,512)x=Up(x,x3,512,256)x=Up(x,x2,256,128)x=Up(x,x1,128,64)net=OutConv(x,64,num_classes)returnnet在project下新建builders目录,并在builders下新建model_作为创建模型总的接口,用于不同类型模型的选择和搭建。importsys,osimporttensorflowastfimportsubprocess#存放模型的目录("models")

#载入模型的搭建函数portbuild_unet#自定义模型名称SUPPORTED_MODELS=["UNet"]#统一的模型搭建接口defbuild_model(model_name,net_input,num_classes,is_training=False):print("")ifmodel_namenotinSUPPORTED_MODELS:raiseValueError("lowingmodelsarecurrentlysupported:{0}".format(SUPPORTED_MODELS))network=None#根据模型名称调用对应的模型搭建函数ifmodel_name=="UNet":network=build_unet(net_input,num_classes,is_training=is_training)else:raiseValueError("Error:themodel%--help")returnnetwork3.5.训练训练部分由于代码量较大,这里只挑选重点部分讲解,具体可以参考、utils/、utils/。数据增强部分,程序根据读者传入的训练参数,再载入每个批次的图片时会对图片进行随机数据增强的操作。#根据传入参数处理图片defdata_augmentation(input_image,output_image,imgprocess):ifimgprocess=='crop':input_image,output_image=_crop(input_image,output_image,_height,_width)elifimgprocess=='resize':#resize会改变分割图的值,但车道线项目影响不大_height!=input_[0]_width!=input_[1]:input_image,output_image=_img(input_image,output_image,_height,_width)else:

raiseValueError("pportresizeandcrop")#水平翻转ifargs.h_t(0,1):input_image=(input_image,1)output_image=(output_image,1)#竖直反转ifargs.v_t(0,1):input_image=(input_image,0)output_image=(output_image,0)#随机亮度t(0,1):blank=(input_,input_)alpha=m(0.90,1.1)beta=t(1,3)input_image=ghted(input_image,alpha,blank,1-alpha,beta)#随机颜色t(0,1):color=(input_,input_)color[:,:,0]=t(10,50)color[:,:,1]=t(10,50)color[:,:,2]=t(10,50)input_image=ghted(input_image,1.1,color,0.1,0)#旋转on:angle=m(-1*on,on)on:M=ationMatrix2D((input_[1]//2,input_[0]//2),angle,1.0)input_image=fine(input_image,M,(input_[1],input_[0]),flags=_NEAREST)output_image=fine(output_image,M,(output_[1],output_[0]),flags=_NEAREST)returninput_image,output_image损失函数部分采用的是focal_loss,其主要侧重于根据样本的难易程度给样本对应的损失增加权重。deffocal_loss(prediction_tensor,target_tensor,weights=None,alpha=0.25,gamma=2):sigmoid_p=d(prediction_tensor)#创建一个将所有元素设置为0的张量zeros=array__like(sigmoid_p,dtype=sigmoid_)#正样本损失(车道线)pos_p_sub=array_(target_tensor>zeros,target_tensor-sigmoid_p,zeros)

#负样本损失(背景)neg_p_sub=array_(target_tensor>zeros,zeros,sigmoid_p)#-ylog(p^)-(1-y)log(1-p^)per_entry_cross_ent=-alpha*(pos_p_sub**gamma)*(_by_value(sigmoid_p,1e-8,1.0))-(1-alpha)*(neg_p_sub**gamma)*(_by_value(1.0-sigmoid_p,1e-8,1.0))_mean(per_entry_cross_ent)载入每个批次的图片,语义分割的标签是一张图,所以在送入网络之前要对rgb对应的标签做一次转化,再进行独热编码(one-hot)。defone_hot_it(label,label_values):semantic_map=[]#label_values从csv文件中载入forcolourinlabel_values:equality=(label,colour)class_map=(equality,axis=-1)semantic_(class_map)semantic_map=(semantic_map,axis=-1)returnsemantic_map评估指标部分打印了,整体分数、各类别分数、F1、IOU。defevaluate_segmentation(pred,label,num_classes,score_averaging="weighted"):flat_pred=n()flat_label=n()#计算全局的分数global_accuracy=compute_global_accuracy(flat_pred,flat_label)#计算每个类别的分数class_accuracies=compute_class_accuracies(flat_pred,flat_label,num_classes)#计算精确率prec=precision_score(flat_pred,flat_label,average=score_averaging)#计算召回率rec=recall_score(flat_pred,flat_label,average=score_averaging)#计算F1f1=f1_score(flat_pred,flat_label,average=score_averaging)#计算IOUiou=compute_mean_iou(flat_pred,flat_label)returnglobal_accuracy,class_accuracies,prec,rec,f1,iou训练相关参数num_epochs:总训练轮数epoch_start_i:开始轮数,配合继续训练使用,程序会自动加载epoch_start_i-1的权重validation_step:间隔多少轮验证一次模型continue_training:是否继续训练

dataset:数据集,可配置列表imgprocess:载入图片操作,裁剪或者缩放input_height:网络输入的高input_width:网络输入的宽batch_size:每个批次图片数量num_val_images:每次验证取多少张验证集中的图片h_flip:数据增强是否进行水平翻转v_flip:数据增强是否进行水平翻转brightness:数据增强是否随机亮度color:数据增强是否随机添加颜色rotation:数据增强随机旋转角度model:训练的模型savedir:保存的路径这里提供一个,将常用参数放在其中便于训练。IMGW=256IMGH=128EPOCH_START=1DATE=2022.12.07MODEL=UNetCONTINUE_TRAINING=Falseif[${CONTINUE_TRAINING}=True];thenEPOCH_START=--num_epochs=2000--epoch_start_i=${EPOCH_START}--validation_step=5--batch_size=32--input_height=${IMGH}--input_width=${IMGW}--continue_training=${CONTINUE_TRAINING}--dataset=dev/shm/ldw_dataset_type1,dev/shm/ldw_dataset_type2--savedir=checkpoints_${IMGH}_${IMGW}_${DATE}_${MODEL}--model=${MODEL}

训练过程iou训练可视化3.6.评估/推理在评估阶段会用到数据集中的test部分,由于目录结构类似,所以这一部分的代码其实就是train中验证部分给单独提取出来,用训练过程中保存的pb文件进行推理,代码详见,执行结果如下,所打印信息和训练的验证步骤相同,推理结果则是将标注图片、原图、推理图从上往下一次拼接保存。评估结果

推理结果3.7.小结相较行人检测使用tensorflow的目标检测api,车道线分割项目从数据集制作、模型搭建、数据增强、loss函数、训练、评估推理等方面,完整地演示了tensorflow的基本用法。通过本项目,也能让读者对语义分割这一典型的计算机视觉问题有了初步的认识。部署篇第四章模型量化量化量化的部分借着车道线的项目,说明一个注意点,就是图片输入的顺序。车道线分割的模型,用于输入网络训练的图片其通道次序为BGR,按照训练和部署需要统一的标准,部署在AIBOX上送入网络推理的图片通道次序也应该是BGR。rknn的量化过程中,程序会读取一个列表中的图像送入网络量化,内部读取图片的方式是按照RGB来的,和opencv是相反的。所以如果要用BGR的图片进行量化,在准备数据集的时候,如果用opencv打开,就要做一步操作,就是BGR2RGB。虽然参数上是BGR到RGB的转化,但本质上是通道变更,当opencv将图片按照RGB的格式保存以后,其他默认以RGB载入图片的包将图片加载后,实际得到的就是BGR。量化阶段和行人识别项目相同,只是修改了(channel_mean_value='000255',reorder_channel='012',target_platform=target_platform_str)中的参数,这个可以从训练程序中

找到对应的代码,input_image=32(input_image)/255.0。量化部分重点要讲的是推理的部分,在pc上推理出正确的结果,那么在部署的时候只需要把对应的python代码翻译成C++即可。pc推理的代码在中,核心代码片段如下。print('-->Runningmodel')outputs=nce(inputs=[img])print('done')nout=len(outputs)foriinrange((outputs).shape[2]):l1=outputs[0][0][i][0]l2=outputs[0][0][i][1]ifl1>l2:#这里可以参考训练工程中的输出节点来理解#logit=e(network,(-1,_height*_width,num_classes),name='logits')#当通道0的数值大时则该像素格推理为背景,填充0,黑色#当通道1的数值大时则该像素格推力为车道线,填充255,白色img[i//img_w][i%img_w][0]=0img[i//img_w][i%img_w][1]=0img[i//img_w][i%img_w][2]=0else:img[i//img_w][i%img_w][0]=255img[i//img_w][i%img_w][1]=255img[i//img_w][i%img_w][2]=e("",img)结合代码中注释来解释,推理后的输出有4个维度,第一个维度是rk中输出节点,当前项目只有一个输出,所提这一维度是1,第二维度是图片的张数,其实也就是batchsize,这里也是1,这两个维度在本项目中没有作用,第三个维度是在训练时将图片的宽高两个维度给转化成了一维,还是可以按照h*w的排布来解析,第四个维度是通道,这里是对应像素点上0通道的值和1通道的值,其实也就是对应类别的分数。在解析的过程中,只需要取分数大的通道作为当前像素格上的推理结果即可。原图(左上),pc推理结果(右上)

4.2.小结通过上述项目的量化过程可以看到,虽然的目标检测和语义分割两种不同的问题,单独提取出的量化部分其实是一样的,需要关心的只是送入网络的图片和网络输出的数据解析。为了让模型在项目部署落地上有最佳的表现,训练、量化、落地这三个阶段所有的素材、网络输入输出都必须有统一标准。第五章项目落地5.1.项目工程工程目录结构stest|--sdk_rk1808相关sdk|--src源码|--stest主模块程序|--assets模型文件|--test测试程序|--build_编译环境配置,供CMakeLists调用|--用于生成makefile,各源码模块中也有对应文件,逐级调用源码读者可以直接从随书的资源中获取,车道线项目的落地demo和行人检测的demo整体架构类似,所以在本章节重点会放在语义分割网络输出的部分。5.2.源码解析本小节仅对关键函数进行解析,其他可见源码的注释。1)主模块对rga进行初始化。boolTStestProcImpl::InitRgaPrev(){boolret=false;ac::rga::SrcConfigsrc;ac::rga::DstConfigdst;autork_format=Convert2RkFormat(origin_header_.format);if(rk_format>=0){///原始图片信息=origin_header_.width;

=origin_header_.height;=ac::rga::RkFormat(rk_format);///所需要转换的部分,这里是图片下半部分src.x=0;src.y=0;src.y=/2;src.h=/2;///目标图片的信息=inference_width_;=inference_height_;=ac::rga::RkFormat::BGR_888;deleterga_prev_;rga_prev_=newPrevRgaCircle(src,dst);ret=true;}returnret;}车道线分割需要推理的仅仅是图片的下半部分,在程序中rga初始化的部分,就要和训练时数据集处理步骤对应起来,先裁剪下半部分,再缩放到网络输入的大小。子线程循环推理voidTStestProcImpl::Run(){cv::Matsrc_img;cv::Matdst_img;ac::Imageresult;boolfirst=true;=ac::kRGB888;while(true){///这一行代码可能阻塞constauto&pairs=buffer_->Read();constauto&imgconstauto&callbackif(==nullptrbreak;if(first){=std::get<0>(pairs);=std::get<1>(pairs);====nullptr)

=inference_width_;=inference_height_;first=false;}void*ptr=;if(ptr!=nullptr){if(!=nullptr){src_img=cv::Mat(inference_height_,inference_width_,CV_8UC3,);detector_->Detect(img,&dst_img);cv::Matresult_mat(inference_height_,inference_width_,CV_8UC3);//按照训练的标记上色for(inti=0;i(i,j)==255){result_(i,j)[0]=80;result_(i,j)[1]=80;result_(i,j)[2]=80;}else{result_(i,j)[0]=0;result_(i,j)[1]=0;result_(i,j)[2]=0;}}}=result_;(->*)(result);//调试用图,将推绘制的分割图和实际画面叠加if(verbose_enable_==true){cv::Mattemp_img;cv::merge(std::vector{dst_img,dst_img,dst_img},temp_img);cv::addWeighted(src_img,0.2,temp_img,0.7,3,temp_img);cvtColor(temp_img,temp_img,CV_BGR2RGB);std::lock_guardlck(mutex_);

std::memcpy(verbose_image_.data,temp_,inference_height_*inference_width_*3);}total_frame_++;}else;}else;}}这部分代码中,因为本项目仅介绍到模型推理的部分,单纯从语义分割的结果上来看,到这一步回调给外部的就是一张和标签图类似的图片。实际车道线项目中会在推理结果的基础上做进一步的后处理,然后依据上层应用的需求返回车道线。2)推理模块网络输出转换为像素格类别voidTSemanticSegmentation::Impl::Vec2Bin(conststd::vector&mat,cv::Mat*bin,intwidth,intheight){if(bin!=nullptr){constuint8_ttable[]={0,255};bin->create(height,width,CV_8UC1);constfloat*p=reinterpret_cast(());//遍历要绘制的像素格for(inti=0;irows;i++){for(intj=0;jcols;j++){//在输出数据中以此判断每个像素格对应的值//tensorflow默认是nhwc,在量化后该属性也可以在rk的结构体中得到if(input_info_[0].fmt==ac::rknn::TensorFormat::nhwc){bin->at(i,j)=table[*(p+1)-*p>=0];p+=2;}//这部分代码是为了和pytorch的语义分割做兼容,感兴趣的读者可以尝试用pytorch复现elseif(input_info_[0].fmt==ac::rknn::TensorFormat::nchw){

bin->at(i,j)=table[*(p+bin->rows*bin->cols)-*p>=0];p+=1;}}}}}这部分代码就是pc上测试代码的翻译,函数中做了nhwc和nchw的兼容,这一部分信息rknn在量化时已经写入了模型中。tensorflow默认是nhwc排布,所以本项目中值用到了判断的第一个分支,感兴趣的读者可以尝试用pytorch搭建复现模型,然后同样可以用这份代码落地。5.3.部署工程工程部署的操作和行人检测相同,这里不再赘述,程序开始运行,并打印出如下图所示的单张图片的推理速度、当前的推理帧率、视频流帧率。程序打印信息程序运行结果如下图所示。终端识别结果5.4.小结至此车道线识别推理部分的落地完成,这是一个完整车道线分割算法的基础,有了画面中车

道线对应的像素点才能进一步拟合出实际用到的车道线。一个优秀的车道线分割算法,除了搭建一个合适的网络,还要有一套严谨后处理算法,这一部分不在本书的讨论范围之内,读者可以自行探索发挥。


本文标签: 图片 车道 分割 数据 训练