当前位置:   article > 正文

用YOLOv5模型识别出表情!

基于yolo算法的实时人脸表情识别

点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

作者:闫永强,算法工程师,Datawhale成员

本文利用YOLOV5对手势进行训练识别,并识别显示出对应的emoji,如同下图:

0729c539251850568ce5118c5452fb9d.gif

本文整体思路如下。提示:本文含完整实践代码,代码较长,建议先看文字部分的实践思路,代码先马后看

47fa717911b8f03fc6cffd2164e999b7.png

一 、YOLOV5训练数据集

1. 安装环境依赖

本教程所用环境:YOLOV5版本是V3.1。

通过git clone 将源码下载到本地,通过pip install -r requirements.txt 安装依赖包  (其中官方要求python>=3.8 and torch>=1.6)。

我的环境是:系统环境Ubuntu16.04;cuda版本10.2;cudnn版本7.6.5;torch版本1.6.0;python版本3.8

2. 准备手势识别数据集

其中手势数据集已上传至开源数据平台Graviti,包含了完整代码。

手势数据集地址:https://gas.graviti.cn/dataset/datawhale/HandPose?utm_medium=0831datawhale

:代码在数据地址的讨论区

2.1 数据集的采集以及标注

手势数据采集的代码:

  1. import cv2
  2. def main():
  3.     total_pics = 1000
  4.     cap = cv2.VideoCapture(0)
  5.     
  6.     pic_no = 0
  7.     flag_start_capturing = False
  8.     frames = 0
  9.     
  10.     while True:
  11.         ret,frame = cap.read()
  12.         frame = cv2.flip(frame,1)
  13.         cv2.imwrite("hand_images/" +str(pic_no) +".jpg",frame)
  14.         cv2.imshow("Capturing gesture",frame)
  15.         cv2.waitKey(10)
  16.         pic_no += 1
  17.         if pic_no == total_pics:
  18.             break
  19. main()

在yolov5目录下创建VOC2012文件夹(名字自己定义的),目录结构就是VOC数据集的,对应如下:

VOC2012../Annotations   #这个是存放数据集图片对应的xml文件../images  #这个存放图片的../ImageSets/Main  #这个主要是存放train.txt,test.txt,val.txt和trainval.txt四个文件。里面的内容是训练集、测试集、验证集以及训练验证集的名字(不带扩展后缀名)。

示例:

VOC2012文件夹下内容:

046fc575137dac078b31e7ebf1102ea8.png

Annotations文件中是xml文件(labelimg标注的):

fcc476e5ade4ac91daede401af5ec571.png

images为VOC数据集格式中的JPRGImages:

5f7ce20bd3ef003fd3352287a7c2c9e7.png

ImageSets文件中Main子文件夹主要存放训练,测试验证集的划分txt。这个划分通过以下脚本代码生成:

  1. # coding:utf-8
  2. import os
  3. import random
  4. import argparse
  5. parser = argparse.ArgumentParser()
  6. #xml文件的地址,根据自己的数据进行修改 xml一般存放在Annotations下
  7. parser.add_argument('--xml_path'default='C:\\Users\\Lenovo\\Desktop\\hand_datasets\\VOC2012\\Annotations\\'type=str, help='input xml label path')
  8. #数据集的划分,地址选择自己数据下的ImageSets/Main
  9. parser.add_argument('--txt_path'default='C:\\Users\\Lenovo\\Desktop\\hand_datasets\\VOC2012\\ImageSets\\Main\\'type=str, help='output txt label path')
  10. opt = parser.parse_args()
  11. trainval_percent = 1.0
  12. train_percent = 0.99
  13. xmlfilepath = opt.xml_path
  14. txtsavepath = opt.txt_path
  15. total_xml = os.listdir(xmlfilepath)
  16. if not os.path.exists(txtsavepath):
  17.     os.makedirs(txtsavepath)
  18. num = len(total_xml)
  19. list_index = range(num)
  20. tv = int(num * trainval_percent)
  21. tr = int(tv * train_percent)
  22. trainval = random.sample(list_index, tv)
  23. train = random.sample(trainval, tr)
  24. file_trainval = open(txtsavepath + 'trainval.txt''w')
  25. file_test = open(txtsavepath + 'test.txt''w')
  26. file_train = open(txtsavepath + 'train.txt''w')
  27. file_val = open(txtsavepath + 'val.txt''w')
  28. for i in list_index:
  29.     name = total_xml[i][:-4] + '\n'
  30.     if i in trainval:
  31.         file_trainval.write(name)
  32.         if i in train:
  33.             file_train.write(name)
  34.         else:
  35.             file_val.write(name)
  36.     else:
  37.         file_test.write(name)
  38. file_trainval.close()
  39. file_train.close()
  40. file_val.close()
  41. file_test.close()

运行代码在Main文件下生成txt文档如下:

f0c0e89b47bdc7dc3b3f083446da1a41.png

2.2 生成yolo训练格式labels

把xml标注信息转换成yolo的txt格式。其中yolo的txt标签格式信息:每个图像对应一个txt文件,文件每一行为一个目标信息,包括classx_center, y_center, width, height 格式。如下图所示:

a1e46abaed8aec8aa2d314e5adba255f.png

创建voc_label.py文件,将训练集,验证集以及测试集生成txt标签,代码如下:

  1. # -*- coding: utf-8 -*-
  2. import xml.etree.ElementTree as ET
  3. import os
  4. from os import getcwd
  5. sets = ['train''val''test']
  6. classes = ["four_fingers","hand_with_fingers_splayed","index_pointing_up","little_finger","ok_hand","raised_fist","raised_hand","sign_of_the_horns","three","thumbup","victory_hand"
  7. 11 classes  # 改成自己的类别
  8. abs_path = os.getcwd()
  9. print(abs_path)
  10. def convert(size, box):
  11.     dw = 1. / (size[0])
  12.     dh = 1. / (size[1])
  13.     x = (box[0] + box[1]) / 2.0 - 1
  14.     y = (box[2] + box[3]) / 2.0 - 1
  15.     w = box[1] - box[0]
  16.     h = box[3] - box[2]
  17.     x = x * dw
  18.     w = w * dw
  19.     y = y * dh
  20.     h = h * dh
  21.     return x, y, w, h
  22. def convert_annotation(image_id):
  23.     in_file = open('/home/yanyq/Ryan/yolov5/VOC2012/Annotations/%s.xml' % (image_id), encoding='UTF-8')
  24.     out_file = open('/home/yanyq/Ryan/yolov5/VOC2012/labels/%s.txt' % (image_id), 'w')
  25.     tree = ET.parse(in_file)
  26.     root = tree.getroot()
  27.     size = root.find('size')
  28.     w = int(size.find('width').text)
  29.     h = int(size.find('height').text)
  30.     for obj in root.iter('object'):
  31.         # difficult = obj.find('difficult').text
  32.         difficult = obj.find('difficult').text
  33.         cls = obj.find('name').text
  34.         if cls not in classes or int(difficult) == 1:
  35.             continue
  36.         cls_id = classes.index(cls)
  37.         xmlbox = obj.find('bndbox')
  38.         b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
  39.              float(xmlbox.find('ymax').text))
  40.         b1, b2, b3, b4 = b
  41.         # 标注越界修正
  42.         if b2 > w:
  43.             b2 = w
  44.         if b4 > h:
  45.             b4 = h
  46.         b = (b1, b2, b3, b4)
  47.         bb = convert((w, h), b)
  48.         out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
  49. wd = getcwd()
  50. for image_set in sets:
  51.     if not os.path.exists('/home/yanyq/Ryan/yolov5/VOC2012/labels/'):
  52.         os.makedirs('/home/yanyq/Ryan/yolov5/VOC2012/labels/')
  53.     image_ids = open('/home/yanyq/Ryan/yolov5/VOC2012/ImageSets/Main/%s.txt' % (image_set)).read().strip().split()
  54.     list_file = open('%s.txt' % (image_set), 'w')
  55.     for image_id in image_ids:
  56.         list_file.write(abs_path + '/images/%s.jpg\n' % (image_id))
  57.         convert_annotation(image_id)
  58.     list_file.close()

运行上述脚本后会生成labels文件夹和三个包含数据集的txt文件,其中labels中为图像的yolo格式标注文件,train.txt,test.txt, val.txt文件为划分后图像所在位置的绝对路径。

三个txt文件内容:

49e1042c5bffb9607d02dd2fada8498d.png

2.3 配置文件

1)数据集的配置

在yolov5目录的data文件夹新建一个Emoji.yaml文件(自己定义)。用来存放训练集验证集的划分文件train.txt和val.txt(其中这两个文件是voc_label.py生成的)。具体内容如下:

25e2a78ceb7e10d784ad4ab82d0f2b84.png

2)模型的配置文件

一般训练yolo模型的时候,是可以聚类自己标注的框作为先验框(这样可以保证标注样本最大化的利用)。我们这里就直接采用默认值了。

选择一个需要的模型,YOLOV5有提供s、m、l、x版本,其是逐渐增大的架构,也就是训练时间和推理时间都对应增加,我们这里选择s版本。在yolov5文件夹下的models文件夹中打开yolov5s.yaml文件,修改内容如下图(我们选择默认anchor,所以不做修改,只需要更改nc中的类别数,由于我们是11类,所以改成11就可以了):

4ef0259943ab8b8493446c3149613595.png

到这里我们的自定义数据集以及配置文件创建完毕,下面就是训练模型了。

3.模型训练

3.1、下载预训练模型

在源码yolov5目录下的weights文件夹下提供了下载smlx模型的脚本--download_weights.sh,执行这个脚本就可以下载这四个模型的预训练模型了。

3.2、训练模型

633ff77523407763e6764e7eac9a248f.png

以上参数解释如下:epochs:指的就是训练过程中整个数据集将被迭代多少次,显卡不行你就调小点。batch-size:一次看完多少张图片才进行权重更新,梯度下降的mini-batch,显卡不行你就调小点。cfg:存储模型结构的配置文件。data:存储训练、测试数据的文件。img-size:输入图片宽高,显卡不行你就……。rect:进行矩形训练。resume:恢复最近保存的模型开始训练。nosave:仅保存最终checkpoint。notest:仅测试最后的epoch。evolve:进化超参数。bucket:gsutil bucket。 cache-images:缓存图像以加快训练速度。 weights:权重文件路径。name:重命名results.txt to results_name.txt。device:cuda device, i.e. 0 or 0,1,2,3 or cpu。adam:使用adam优化。multi-scale:多尺度训练,img-size +/- 50%。single-cls:单类别的训练集

训练只需要运行训练命令就可以了,如下:

$ python train.py  --data Emoji.yaml --cfg yolov5s.yaml --weights weights/yolov5s.pt --batch-size 64 --device "0,1,2,3" --epochs 200 --img-size 640

其中device batch-size 等需要根据自己机器进行设置。

0215a9f5d597e35aa6464eff2e0e2722.png

4.模型测试

评估模型好坏就是在有标注的测试集或验证集上进行模型效果的评估,在目标检测中最常使用的评估指标为mAP。yolov5文件下的test.py文件中指定了数据集的配置文件和训练结果模型如下:

1c343eb9028341267df7623a4846f72c.png

通过以下命令进行模型测试:

python test.py --data data/Emoji.yaml --weights runs/train/exp2/weights/best.pt --augment

模型测试效果:

25cdcb127109df3abc37308d1ccc20f4.png

测试结果图:

8bb19492a7419edb55704dd4fb620791.jpeg

二、YOLOV5模型转换

1.安装依赖库

pip install onnx coremltools onnx-simplifier

2.导出ONNX模型

python models/export.py --weights runs/train/exp2/weights/best.pt --img 640 --batch 1

867a6b3d90dc167884322da6d3005e6f.png

此时在best.pt同级目录下生成了best.mlmodel best.onnx best.torchscript.pt三个文件,我们只需best.onnx,这个文件可以直接用netron打开查看模型结构。

3.用onnx-simplifer简化模型

为什么要简化?

在训练完深度学习的pytorch或者tensorflow模型后,有时候需要把模型转成 onnx,但是很多时候,很多节点比如cast节点,Identity 这些节点可能都不需要,我们需要进行简化,这样会方便我们把模型转成ncnn或者mnn等这些端侧部署的模型格式或者通过tensorRT进行部署。

python -m onnxsim best.onnx yolov5-best-sim.onnx

d589197eb2410451eea6317971e0bb87.png

完成后就生成了简化版本的模型yolov5-best-sim.onnx。

三、YOLOV5转换成ncnn模型

1、onnx转.param .bin

由上述生成了yolov5-best-sim.onnx这个模型,我们利用ncnn自带的工具onnx2ncnn.exe(这个工具是自己编译生成的,我这里是在windows下编译生成的,可以用linux下的可执行文件)生成yolov5s.param  yolov5s.bin两个文件。

在windows平台下ctrl+r   cmd命令行窗口输入:

onnx2ncnn.exe yolov5-best-sim.onnx yolov5s.param yolov5s.bin

80a6b77ce715d534f8e5839d4e7f1efd.png


转换的过程中会出现上图所示的ncnn不支持层,下边就是要修改param文件,把不支持层改成支持层。

2、修改.param 参数去除不支持的网络层

去掉不支持的网络层,打开转换得到的yolov5s.param文件,前面几行需要删除的是标红部分。(注意我们训练yoloV5的版本是V3.1,这里不同的版本可能会不同。)

f71266a31e3e9673e44177d35005c575.jpeg

修改结果如下绿色框和红色框中的。因为去掉了10层所以变成191  228。并用YoloV5Focus网络层代替去掉的10层,而YoloV5Focus网络层中的images代表该层的输入,207代表的输出名,这个是根据下边一层的卷积层输入层数写的。

733d00b1103b895091306aa550914ee3.jpeg

修改网路的输出shape:

当基于修改后的网路使用ncnn/examples/yolov5测试时会发现出现图片中一堆乱框,这种情况需要修改网路的输出部分。在保证输出名一致的情况下,修改Reshape中的0=-1,使的最终的输出shape不固定。具体的修改地方以及修改之前和之后见下图。

143c5009b0a29e3361f5b54714d21cbb.jpeg

a3a15e05cf17279cfe209ebdf0101b2c.jpeg

3、ncnn的c++测试代码实现

以下是用C++实现的完整代码。建议一划到底,先看最后的整体思路

  1. #include <string>
  2. #include <vector>
  3. #include "iostream"  
  4. //#include <fstream>  
  5. //#include < ctime >
  6. //#include <direct.h>
  7. //#include <io.h>
  8. // ncnn
  9. #include "ncnn/layer.h"
  10. #include "ncnn/net.h"
  11. #include "ncnn/benchmark.h"
  12. //#include "gpu.h"
  13. #include "opencv2/core/core.hpp"
  14. #include "opencv2/highgui/highgui.hpp"
  15. #include <opencv2/imgproc.hpp>
  16. #include "opencv2/opencv.hpp"  
  17. using namespace std;
  18. using namespace cv;
  19. static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
  20. static ncnn::PoolAllocator g_workspace_pool_allocator;
  21. static ncnn::Net yolov5;
  22. class YoloV5Focus : public ncnn::Layer
  23. {
  24. public:
  25.  YoloV5Focus()
  26.  {
  27.   one_blob_only = true;
  28.  }
  29.  virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const
  30.  {
  31.   int w = bottom_blob.w;
  32.   int h = bottom_blob.h;
  33.   int channels = bottom_blob.c;
  34.   int outw = w / 2;
  35.   int outh = h / 2;
  36.   int outc = channels * 4;
  37.   top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);
  38.   if (top_blob.empty())
  39.    return -100;
  40. #pragma omp parallel for num_threads(opt.num_threads)
  41.   for (int p = 0; p < outc; p++)
  42.   {
  43.    const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);
  44.    float* outptr = top_blob.channel(p);
  45.    for (int i = 0; i < outh; i++)
  46.    {
  47.     for (int j = 0; j < outw; j++)
  48.     {
  49.      *outptr = *ptr;
  50.      outptr += 1;
  51.      ptr += 2;
  52.     }
  53.     ptr += w;
  54.    }
  55.   }
  56.   return 0;
  57.  }
  58. };
  59. DEFINE_LAYER_CREATOR(YoloV5Focus)
  60. struct Object
  61. {
  62.  float x;
  63.  float y;
  64.  float w;
  65.  float h;
  66.  int label;
  67.  float prob;
  68. };
  69. static inline float intersection_area(const Object& a, const Object& b)
  70. {
  71.  if (a.x > b.x + b.w || a.x + a.w < b.x || a.y > b.y + b.h || a.y + a.h < b.y)
  72.  {
  73.   // no intersection
  74.   return 0.f;
  75.  }
  76.  float inter_width = std::min(a.x + a.w, b.x + b.w) - std::max(a.x, b.x);
  77.  float inter_height = std::min(a.y + a.h, b.y + b.h) - std::max(a.y, b.y);
  78.  return inter_width * inter_height;
  79. }
  80. static void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right)
  81. {
  82.  int i = left;
  83.  int j = right;
  84.  float p = faceobjects[(left + right) / 2].prob;
  85.  while (i <= j)
  86.  {
  87.   while (faceobjects[i].prob > p)
  88.    i++;
  89.   while (faceobjects[j].prob < p)
  90.    j--;
  91.   if (i <= j)
  92.   {
  93.    // swap
  94.    std::swap(faceobjects[i], faceobjects[j]);
  95.    i++;
  96.    j--;
  97.   }
  98.  }
  99. #pragma omp parallel sections
  100.  {
  101. #pragma omp section
  102.   {
  103.    if (left < j) qsort_descent_inplace(faceobjects, left, j);
  104.   }
  105. #pragma omp section
  106.   {
  107.    if (i < right) qsort_descent_inplace(faceobjects, i, right);
  108.   }
  109.  }
  110. }
  111. static void qsort_descent_inplace(std::vector<Object>& faceobjects)
  112. {
  113.  if (faceobjects.empty())
  114.   return;
  115.  qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1);
  116. }
  117. static void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold)
  118. {
  119.  picked.clear();
  120.  const int n = faceobjects.size();
  121.  std::vector<float> areas(n);
  122.  for (int i = 0; i < n; i++)
  123.  {
  124.   areas[i] = faceobjects[i].w * faceobjects[i].h;
  125.  }
  126.  for (int i = 0; i < n; i++)
  127.  {
  128.   const Object& a = faceobjects[i];
  129.   int keep = 1;
  130.   for (int j = 0; j < (int)picked.size(); j++)
  131.   {
  132.    const Object& b = faceobjects[picked[j]];
  133.    // intersection over union
  134.    float inter_area = intersection_area(a, b);
  135.    float union_area = areas[i] + areas[picked[j]] - inter_area;
  136.    // float IoU = inter_area / union_area
  137.    if (inter_area / union_area > nms_threshold)
  138.     keep = 0;
  139.   }
  140.   if (keep)
  141.    picked.push_back(i);
  142.  }
  143. }
  144. static inline float sigmoid(float x)
  145. {
  146.  return static_cast<float>(1.f / (1.f + exp(-x)));
  147. }
  148. static void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects)
  149. {
  150.  const int num_grid = feat_blob.h;
  151.  int num_grid_x;
  152.  int num_grid_y;
  153.  if (in_pad.w > in_pad.h)
  154.  {
  155.   num_grid_x = in_pad.w / stride;
  156.   num_grid_y = num_grid / num_grid_x;
  157.  }
  158.  else
  159.  {
  160.   num_grid_y = in_pad.h / stride;
  161.   num_grid_x = num_grid / num_grid_y;
  162.  }
  163.  const int num_class = feat_blob.w - 5;
  164.  const int num_anchors = anchors.w / 2;
  165.  for (int q = 0; q < num_anchors; q++)
  166.  {
  167.   const float anchor_w = anchors[q * 2];
  168.   const float anchor_h = anchors[q * 2 + 1];
  169.   const ncnn::Mat feat = feat_blob.channel(q);
  170.   for (int i = 0; i < num_grid_y; i++)
  171.   {
  172.    for (int j = 0; j < num_grid_x; j++)
  173.    {
  174.     const float* featptr = feat.row(i * num_grid_x + j);
  175.     // find class index with max class score
  176.     int class_index = 0;
  177.     float class_score = -FLT_MAX;
  178.     for (int k = 0; k < num_class; k++)
  179.     {
  180.      float score = featptr[5 + k];
  181.      if (score > class_score)
  182.      {
  183.       class_index = k;
  184.       class_score = score;
  185.      }
  186.     }
  187.     float box_score = featptr[4];
  188.     float confidence = sigmoid(box_score) * sigmoid(class_score);
  189.     if (confidence >= prob_threshold)
  190.     {
  191.      float dx = sigmoid(featptr[0]);
  192.      float dy = sigmoid(featptr[1]);
  193.      float dw = sigmoid(featptr[2]);
  194.      float dh = sigmoid(featptr[3]);
  195.      float pb_cx = (dx * 2.f - 0.5f + j) * stride;
  196.      float pb_cy = (dy * 2.f - 0.5f + i) * stride;
  197.      float pb_w = pow(dw * 2.f, 2) * anchor_w;
  198.      float pb_h = pow(dh * 2.f, 2) * anchor_h;
  199.      float x0 = pb_cx - pb_w * 0.5f;
  200.      float y0 = pb_cy - pb_h * 0.5f;
  201.      float x1 = pb_cx + pb_w * 0.5f;
  202.      float y1 = pb_cy + pb_h * 0.5f;
  203.      Object obj;
  204.      obj.x = x0;
  205.      obj.y = y0;
  206.      obj.w = x1 - x0;
  207.      obj.h = y1 - y0;
  208.      obj.label = class_index;
  209.      obj.prob = confidence;
  210.      objects.push_back(obj);
  211.     }
  212.    }
  213.   }
  214.  }
  215. }
  216. extern "C" {
  217.  void release()
  218.  {
  219.   fprintf(stderr, "YoloV5Ncnn finished!");
  220.   //ncnn::destroy_gpu_instance();
  221.  }
  222.  int init()
  223.  {
  224.   fprintf(stderr, "YoloV5Ncnn init!\n");
  225.   ncnn::Option opt;
  226.   opt.lightmode = true;
  227.   opt.num_threads = 4;
  228.   opt.blob_allocator = &g_blob_pool_allocator;
  229.   opt.workspace_allocator = &g_workspace_pool_allocator;
  230.   opt.use_packing_layout = true;
  231.   yolov5.opt = opt;
  232.   yolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator);
  233.   // init param
  234.   {
  235.    int ret = yolov5.load_param("yolov5s.param");  
  236.    if (ret != 0)
  237.    {
  238.     std::cout << "ret= " << ret << std::endl;
  239.     fprintf(stderr, "YoloV5Ncnn, load_param failed");
  240.     return -301;
  241.    }
  242.   }
  243.   // init bin
  244.   {
  245.    int ret = yolov5.load_model("yolov5s.bin");  
  246.    if (ret != 0)
  247.    {
  248.     fprintf(stderr, "YoloV5Ncnn, load_model failed");
  249.     return -301;
  250.    }
  251.   }
  252.   return 0;
  253.  }
  254.  int detect(cv::Mat img, std::vector<Object> &objects)
  255.  {
  256.   double start_time = ncnn::get_current_time();
  257.   const int target_size = 320;
  258.   // letterbox pad to multiple of 32
  259.   const int width = img.cols;//1280
  260.   const int height = img.rows;//720
  261.   int w = img.cols;//1280
  262.   int h = img.rows;//720
  263.   float scale = 1.f;
  264.   if (w > h)
  265.   {
  266.    scale = (float)target_size / w;//640/1280
  267.    w = target_size;//640
  268.    h = h * scale;//360
  269.   }
  270.   else
  271.   {
  272.    scale = (float)target_size / h;
  273.    h = target_size;
  274.    w = w * scale;
  275.   }
  276.   cv::resize(img, img, cv::Size(w, h));
  277.   ncnn::Mat in = ncnn::Mat::from_pixels(img.data, ncnn::Mat::PIXEL_BGR2RGB, w, h);
  278.   // pad to target_size rectangle
  279.   // yolov5/utils/datasets.py letterbox
  280.   int wpad = (w + 31) / 32 * 32 - w;
  281.   int hpad = (h + 31) / 32 * 32 - h;
  282.   ncnn::Mat in_pad;
  283.   ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);
  284.   // yolov5
  285.   //std::vector<Object> objects;
  286.   {
  287.    const float prob_threshold = 0.4f;
  288.    const float nms_threshold = 0.51f;
  289.    const float norm_vals[3] = { 1 / 255.f, 1 / 255.f, 1 / 255.f };
  290.    in_pad.substract_mean_normalize(0, norm_vals);
  291.    ncnn::Extractor ex = yolov5.create_extractor();
  292.    //ex.set_vulkan_compute(use_gpu);
  293.    ex.input("images", in_pad);
  294.    std::vector<Object> proposals;
  295.    // anchor setting from yolov5/models/yolov5s.yaml
  296.    // stride 8
  297.    {
  298.     ncnn::Mat out;
  299.     ex.extract("output", out);
  300.     ncnn::Mat anchors(6);
  301.     anchors[0] = 10.f;
  302.     anchors[1] = 13.f;
  303.     anchors[2] = 16.f;
  304.     anchors[3] = 30.f;
  305.     anchors[4] = 33.f;
  306.     anchors[5] = 23.f;
  307.     std::vector<Object> objects8;
  308.     generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8);
  309.     proposals.insert(proposals.end(), objects8.begin(), objects8.end());
  310.    }
  311.    // stride 16
  312.    {
  313.     ncnn::Mat out;
  314.     ex.extract("771", out);
  315.     ncnn::Mat anchors(6);
  316.     anchors[0] = 30.f;
  317.     anchors[1] = 61.f;
  318.     anchors[2] = 62.f;
  319.     anchors[3] = 45.f;
  320.     anchors[4] = 59.f;
  321.     anchors[5] = 119.f;
  322.     std::vector<Object> objects16;
  323.     generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16);
  324.     proposals.insert(proposals.end(), objects16.begin(), objects16.end());
  325.    }
  326.    // stride 32
  327.    {
  328.     ncnn::Mat out;
  329.     ex.extract("791", out);
  330.     ncnn::Mat anchors(6);
  331.     anchors[0] = 116.f;
  332.     anchors[1] = 90.f;
  333.     anchors[2] = 156.f;
  334.     anchors[3] = 198.f;
  335.     anchors[4] = 373.f;
  336.     anchors[5] = 326.f;
  337.     std::vector<Object> objects32;
  338.     generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32);
  339.     proposals.insert(proposals.end(), objects32.begin(), objects32.end());
  340.    }
  341.    // sort all proposals by score from highest to lowest
  342.    qsort_descent_inplace(proposals);
  343.    // apply nms with nms_threshold
  344.    std::vector<int> picked;
  345.    nms_sorted_bboxes(proposals, picked, nms_threshold);
  346.    int count = picked.size();
  347.    objects.resize(count);
  348.    for (int i = 0; i < count; i++)
  349.    {
  350.     objects[i] = proposals[picked[i]];
  351.     // adjust offset to original unpadded
  352.     float x0 = (objects[i].x - (wpad / 2)) / scale;
  353.     float y0 = (objects[i].y - (hpad / 2)) / scale;
  354.     float x1 = (objects[i].x + objects[i].w - (wpad / 2)) / scale;
  355.     float y1 = (objects[i].y + objects[i].h - (hpad / 2)) / scale;
  356.     // clip
  357.     x0 = std::max(std::min(x0, (float)(width - 1)), 0.f);
  358.     y0 = std::max(std::min(y0, (float)(height - 1)), 0.f);
  359.     x1 = std::max(std::min(x1, (float)(width - 1)), 0.f);
  360.     y1 = std::max(std::min(y1, (float)(height - 1)), 0.f);
  361.     objects[i].x = x0;
  362.     objects[i].y = y0;
  363.     objects[i].w = x1;
  364.     objects[i].h = y1;
  365.    }
  366.   }
  367.   return 0;
  368.  }
  369. }
  370. static const char* class_names[] = {
  371.  "four_fingers","hand_with_fingers_splayed","index_pointing_up","little_finger",
  372.  "ok_hand","raised_fist","raised_hand","sign_of_the_horns","three","thumbup","victory_hand"
  373. };
  374. void draw_face_box(cv::Mat& bgr, std::vector<Object> object) //主要的emoji显示函数
  375. {
  376.  for (int i = 0; i < object.size(); i++)
  377.  {
  378.   const auto obj = object[i];
  379.   cv::rectangle(bgr, cv::Point(obj.x, obj.y), cv::Point(obj.w, obj.h), cv::Scalar(02550), 380);
  380.   std::cout << "label:" << class_names[obj.label] << std::endl;
  381.   string emoji_path = "emoji\\" + string(class_names[obj.label]) + ".png"//这个是emoji图片的路径
  382.   cv::Mat logo = cv::imread(emoji_path);
  383.   if (logo.empty()) {
  384.    std::cout << "imread logo failed!!!" << std::endl;
  385.    return;
  386.   }
  387.   resize(logo, logo, cv::Size(8080));
  388.   cv::Mat imageROI = bgr(cv::Range(obj.x, obj.x + logo.rows), cv::Range(obj.y, obj.y + logo.cols));  //emoji的图片放在图中的位置,也就是手势框的旁边
  389.   logo.copyTo(imageROI); //把emoji放在原图中
  390.  }
  391. }
  392. int main()
  393. {
  394.  Mat frame;
  395.  VideoCapture capture(0);
  396.  init();
  397.  while (true)
  398.  {
  399.   capture >> frame;            
  400.   if (!frame.empty()) {          
  401.    std::vector<Object> objects;
  402.    detect(frame, objects);
  403.    draw_face_box(frame, objects);
  404.    imshow("window", frame);  
  405.   }
  406.   if (waitKey(20) == 'q')    
  407.    break;
  408.  }
  409.  capture.release();     
  410.  return 0;
  411. }

这里是首先用yolov5s识别出手势,然后利用图像ROI融合,把相应的Emoji缩放到80x80大小显示在手势框的旁边,实现根据不同的手势显示相应的Emoji。

4、实现emoji和手势的映射

到这里,我们终于大功告成!达成了开头的效果。

3668bfe7679cc3c93b6bc9a4f7b7db65.gif

胜利✌️
 
 

好消息!

小白学视觉知识星球

开始面向外开放啦

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/598562
推荐阅读
相关标签