
作者丨DefTruth1.YOLO5Face简介Github:ArXiv2021:C++实现:YOLO5Face是深圳神目科技LinkSpriteTechnologies开源的一个新SOTA的人脸检测......
作者丨DefTruth
1.YOLO5Face简介Github:
ArXiv2021:
C++实现:
YOLO5Face是深圳神目科技LinkSpriteTechnologies开源的一个新SOTA的人脸检测器(带关键点),基于YOLOv5,并且对YOLOv5的骨干网络进行的改造,使得新的模型更加适合用于人脸检测的任务。并且在YOLOv5网络中加了一个预测5个关键点regressionhead,采用Wingloss进行作为损失函数。从论文中放出的实验结果看YOLO5Face的平均精度(mAP)和速度方面的性能都非常优秀。在模型精度和速度方面,论文中给出了和当前SOTA算法的详细比较,包括比较新的SCRFD(CVPR2021)、RetinaFace(CVPR2020)等等。
另外由于YOLO5Face采用Stem块结构取代YOLOv5的Focus层,作者认为这样增加了网络的泛化能力,并降低了计算的复杂性。对于替换Focus层带来精度的提升,论文也给出了一些消融实验的对比,还是提了一些点。另外就是,去掉Focus的骚操作后,C++工程的难度也降低了一些,起码在用NCNN的时候,不用再额外捏个YoloV5FocusLayer自定义层进去了。
需要了解YOLO5Face相关的算法细节的同学可以看看原论文,或者阅读:
深圳神目科技《YOLO5Face》:人脸检测在WiderFace实现SOTA
本文主要记录一下YOLO5FaceC++工程相关的问题,并且简单介绍下如何使用++工具箱来跑直接YOLO5Face人脸检测(带关键点)(),这些案例包含了ONNXRuntimeC++、MNN、TNN和NCNN版本。
2.C++版本源码YOLO5FaceC++版本的源码包含ONNXRuntime、MNN、TNN和NCNN四个版本,源码可以在(t)工具箱中找到。本文主要介绍如何基于工具箱,直接使用YOLO5Face来跑人脸检测。需要说明的是,本文是基于MacOS下编译的()来实现的,对于使用MacOS的用户,可以直接下载本项目包含的动态库和其他依赖库进行使用。而非MacOS用户,则需要从中下载源码进行编译。++工具箱目前包含80+流行的开源模型,就不多介绍了,只是平时顺手捏的,整合了自己学习过程中接触到的一些模型,感兴趣的同学可以去看看。
()
()
mnn_()
mnn_()
tnn_()
tnn_()
ncnn_()
ncnn_()
ONNXRuntimeC++、MNN、TNN和NCNN版本的推理实现均已测试通过,欢迎白嫖~本文章的案例代码和工具箱仓库地址为:
代码描述++测试用例代码,包含ONNXRuntime、NCNN、MNN、TNN版本++toolkitofawesomeAImodels.(一个开箱即用的C++AI模型工具箱,emmm,平时学一些新算法的时候顺手捏的,目前包含80+流行的开源模型。不知不觉已经将近800⭐️star啦,欢迎大家来点star⭐️、提issue呀~)
如果觉得有用,不妨给个Star⭐️支持一下吧~
3.模型文件3.1ONNX模型文件可以从我提供的链接下载BaiduDrive()code:8gin,也可以从本仓库下载。
YOLO5Face()
3.2MNN模型文件MNN模型文件下载地址,BaiduDrive()code:9v63,也可以从本仓库下载。
3.3TNN模型文件TNN模型文件下载地址,BaiduDrive()code:6o6k,也可以从本仓库下载。
3.4NCNN模型文件NCNN模型文件下载地址,BaiduDrive()code:sc7f,也可以从本仓库下载。
4.接口文档在中,YOLO5Face的实现类为:
classLITE_EXPORTSlite::cv::face::detect::YOLO5Face;classLITE_EXPORTSlite::mnn::cv::face::detect::YOLO5Face;classLITE_EXPORTSlite::tnn::cv::face::detect::YOLO5Face;classLITE_EXPORTSlite::ncnn::cv::face::detect::YOLO5Face;
该类型目前包含1公共接口detect用于进行目标检测。
public:/***@parammatcv::MatBGRformat*@paramdetected_boxes_kpsvectorofBoxfWithLandmarkstocatchdetectedboxesandlandmarks.*@paramscore_,onlykeeptheresultwhich=score_threshold.*@paramiou_,iouthresholdforNMS.*@paramtopkdefault400,maximumoutputboxesafterNMS.*/voiddetect(constcv::Matmat,std::vectortypes::BoxfWithLandmarksdetected_boxes_kps,floatscore_threshold=0.25f,floatiou_threshold=0.45f,unsignedinttopk=400);
detect接口的输入参数说明:
mat:cv::Mat类型,BGR格式。
detected_boxes_kps:BoxfWithLandmarks向量,包含被检测到的框box(Boxf),box中包含x1,y1,x2,y2,label,score等成员;以及landmarks(landmarks)人脸关键点(5个),其中包含了points,代表关键点,是一个cv::point2f向量(vector);
score_threshold:分类得分(质量得分)阈值,默认0.25,小于该阈值的框将被丢弃。
iou_threshold:NMS中的iou阈值,默认0.45。
topk:默认400,只保留前k个检测到的结果。
5.使用案例这里测试使用的是(yolov5n-face)nano版本的模型,你可以尝试使用其他版本的模型。
5.1ONNXRuntime版本include"lite/"staticvoidtest_mnn(){if}5.3TNN版本ifdefENABLE_TNNstd::stringproto_path="../hub/tnn/cv/";//yolov5n-facestd::stringmodel_path="../hub/tnn/cv/";std::stringtest_img_path="../resources/9.jpg";std::stringsave_img_path="../logs/9.jpg";auto*yolov5face=newlite::tnn::cv::face::detect::YOLO5Face(proto_path,model_path);std::vectorlite::types::BoxfWithLandmarksdetected_boxes;cv::Matimg_bgr=cv::imread(test_img_path);yolov5face-detect(img_bgr,detected_boxes);lite::utils::draw_boxes_with_landmarks_inplace(img_bgr,detected_boxes);cv::imwrite(save_img_path,img_bgr);std::cout"TNNVersionDone!DetectedFaceNum:"detected_()std::l;deleteyolov5face;include"lite/"staticvoidtest_ncnn(){if}输出结果为:
虽然是nano版本的模型,但结果看起来还是非常准确的啊!还自带了5个人脸关键点,可以用来做人脸对齐,也是比较方便~
6.编译运行在MacOS下可以直接编译运行本项目,无需下载其他依赖库。其他系统则需要从中下载源码先编译动态库。
gitclone--depth=1
设置
cmake_minimum_required()project()set(CMAKE_CXX_STANDARD11)addyourexecutableset(EXECUTABLE_OUTPUT_PATH${CMAKE_SOURCE_DIR}/examples/build)add_executable(lite_yolo5faceexamples/test_lite_)target_link_libraries(lite_,_NCNN=ON,buildingtestinginformation:
[50%]BuildingCXXobjectCMakeFiles/lite_/examples/test_lite_[100%]LinkingCXXexecutablelite_yolo5face[100%]Builttargetlite_yolo5faceTestingStartLITEORT_DEBUGLogId:../hub/onnx/cv/===============Input-Dims==============input_node_dims:1input_node_dims:3input_node_dims:640input_node_dims:640===============Output-Dims==============Output:0Name:outputDim:0:1Output:0Name:outputDim:1:25200Output:0Name:outputDim:2:16========================================generate_bboxes_kpsnum:2824DefaultVersionDone!DetectedFaceNum:326LITEMNN_DEBUGLogId:../hub/mnn/cv/===============Input-Dims==============**Tensorshape**:1,3,640,640,DimensionType:(CAFFE/PyTorch/ONNX)NCHW===============Output-Dims==============getSessionOutputAlldone!Output:output:**Tensorshape**:1,25200,16,========================================generate_bboxes_kpsnum:71MNNVersionDone!DetectedFaceNum:5LITENCNN_DEBUGLogId:../hub/ncnn/cv/_bboxes_kpsnum:34NCNNVersionDone!DetectedFaceNum:2LITETNN_DEBUGLogId:../hub/tnn/cv/===============Input-Dims==============input:[13640640]InputDataFormat:NCHW===============Output-Dims==============output:[12520016]========================================generate_bboxes_kpsnum:98TNNVersionDone!DetectedFaceNum:7TestingSuccessful!
其中一个测试结果为:
7.模型转换过程记录ok,到这里,nano版本模型的效果大家都看到了,还是很不错的,640x640的inputsize下很多小人脸都检测出来了。C++版本的推理结果对齐也基本没有问题。那么这小节就主要记录一下,各种类型(ONNX/MNN/TNN/NCNN)的模型文件转换问题。毕竟这可以说是比较重要的一步了,因此也想和大家简单分享下。个人知识面有限,以下表述有不足之处,欢迎各位大佬指出哈~
7.1Detect模块推理源码分析(pytorch)defforward(self,x):forprofilingz=[]convbs,_,ny,nx=x[i].shapex[i]=x[i].view(bs,3,16,-1).permute(0,1,3,2).contiguous()[i].shape[2:4]!=x[i].shape[2:4]:[i]=self._make_grid(nx,ny).to(x[i].device)这是YOLO5Face原来的代码[i],_grid[i]=self._make_grid_new(nx,ny,i)xybox_wh=(y[:,:,:,:,2:4]*2)**2*_grid[i]box_conf=((box_xy,((box_wh,y[:,:,:,:,4:5]),4)),4)landm1=y[:,:,:,:,5:7]*_grid[i]+[i].to(x[i].device)*[i]x2y2landm3=y[:,:,:,:,9:11]*_grid[i]+[i].to(x[i].device)*[i]x4y4landm5=y[:,:,:,:,13:15]*_grid[i]+[i].to(x[i].device)*[i]landm=((landm1,((landm2,((landm3,((landm4,landm5),4)),4)),4)),4)(bs,-1,16)(z,1)returnx原来的函数def_make_grid_new(self,nx=20,ny=20,i=0):d=[i].deviceif'1.10.0'intorch.__version__:新函数
为什么要这样做呢?我们先来看看anchor_grid和anchor的初始代码。
=[(1)]*(nl,na,2)_buffer('anchor_grid',().view(,1,-1,1,1,2))[i].shape[2:4]!=x[i].shape[2:4]:[i]=self._make_grid(nx,ny).to(x[i].device)这是YOLO5Face原来的代码[i],_grid[i]=self._make_grid_new(nx,ny,i)MNN模型转换python3.//YOLO5Face/-[i]=x[i].view(bs,,,ny,nx).permute(0,1,3,4,2).contiguous()原来的处理x[i]=x[i].view(bs,3,16,-1).permute(0,1,3,2).contiguous()注释掉坐标反算的逻辑(bs,?,16)原来的返回(model,img,f,verbose=False,opset_version=12,output_names=output_names,'output':{0:'batch'}forncnn正常导出即可,然后转换成NCNN文件,并用ncnnoptimze过一遍,很顺利,没有再出现算子不支持的问题。
~PYTHONPATH=./_size640640--batch_size1--simplify~ncnn_~,shape_inferenceskippedInputlayerinputwithoutshapeinfo,estimate_memory_footprintskipped
其实,这样做还是有好处的,因为不需要把anchors和anchor_grid导出来,那么模型文件的size就变小了,比如按照原来方式导出的文件占了9.5Mb内存,修改后,不导出anchors和anchor_grid的模型文件只有6.5Mb。最后,关于YOLO5Face的C++前后处理以及NMS的实现,建议大家可以去看看我仓库的源码,就不在这里啰嗦了~