用YOLOv5模型识别出表情!

028yolov5视频检测_人脸识别表情识别 其中data文件夹下存放的图像数据集,包括alert(注意力集中)、non_vigilant(漫不经心)、tired(疲劳)等。目标检测一般是yolov3、yolov4、yolov5、yolox、PSPnet、faster_rcnn、SDD等。运行02train.py会将txt文本中的图像数据读取进行模型的训练,最后保存在runs文件夹下。运行04ui_pyqt5.py会弹出一个可视化的ui界面,通过点击按钮检测自己感兴趣的视频。运行03detector_photo.py可以实现对单张图片的检测。 阅读详情

↑↑↑关注后"星标"Datawhale

每日干货 & 每月组队学习,不错过

 Datawhale干货 

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

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

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

一 、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 数据集的采集以及标注

手势数据采集的代码:

import cv2

def main():
    total_pics = 1000
    cap = cv2.VideoCapture(0)
    
    pic_no = 0
    flag_start_capturing = False
    frames = 0
    
    while True:
        ret,frame = cap.read()
        frame = cv2.flip(frame,1)
        cv2.imwrite("hand_images/" +str(pic_no) +".jpg",frame)
        cv2.imshow("Capturing gesture",frame)
        cv2.waitKey(10)
        pic_no += 1
        if pic_no == total_pics:
            break

main()

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

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

示例:

VOC2012文件夹下内容:

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

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

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

# coding:utf-8

import os
import random
import argparse

parser = argparse.ArgumentParser()
#xml文件的地址,根据自己的数据进行修改 xml一般存放在Annotations下
parser.add_argument('--xml_path', default='C:\\Users\\Lenovo\\Desktop\\hand_datasets\\VOC2012\\Annotations\\', type=str, help='input xml label path')
#数据集的划分,地址选择自己数据下的ImageSets/Main
parser.add_argument('--txt_path', default='C:\\Users\\Lenovo\\Desktop\\hand_datasets\\VOC2012\\ImageSets\\Main\\', type=str, help='output txt label path')
opt = parser.parse_args()

trainval_percent = 1.0
train_percent = 0.99
xmlfilepath = opt.xml_path
txtsavepath = opt.txt_path
total_xml = os.listdir(xmlfilepath)
if not os.path.exists(txtsavepath):
    os.makedirs(txtsavepath)

num = len(total_xml)
list_index = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list_index, tv)
train = random.sample(trainval, tr)

file_trainval = open(txtsavepath + 'trainval.txt', 'w')
file_test = open(txtsavepath + 'test.txt', 'w')
file_train = open(txtsavepath + 'train.txt', 'w')
file_val = open(txtsavepath + 'val.txt', 'w')

for i in list_index:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        file_trainval.write(name)
        if i in train:
            file_train.write(name)
        else:
            file_val.write(name)
    else:
        file_test.write(name)

file_trainval.close()
file_train.close()
file_val.close()
file_test.close()

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

2.2 生成yolo训练格式labels

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

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

# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
from os import getcwd

sets = ['train', 'val', 'test']
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"] 
# 11 classes  # 改成自己的类别
abs_path = os.getcwd()
print(abs_path)

def convert(size, box):
    dw = 1. / (size[0])
    dh = 1. / (size[1])
    x = (box[0] + box[1]) / 2.0 - 1
    y = (box[2] + box[3]) / 2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return x, y, w, h

def convert_annotation(image_id):
    in_file = open('/home/yanyq/Ryan/yolov5/VOC2012/Annotations/%s.xml' % (image_id), encoding='UTF-8')
    out_file = open('/home/yanyq/Ryan/yolov5/VOC2012/labels/%s.txt' % (image_id), 'w')
    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)
    for obj in root.iter('object'):
        # difficult = obj.find('difficult').text
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult) == 1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
             float(xmlbox.find('ymax').text))
        b1, b2, b3, b4 = b
        # 标注越界修正
        if b2 > w:
            b2 = w
        if b4 > h:
            b4 = h
        b = (b1, b2, b3, b4)
        bb = convert((w, h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

wd = getcwd()
for image_set in sets:
    if not os.path.exists('/home/yanyq/Ryan/yolov5/VOC2012/labels/'):
        os.makedirs('/home/yanyq/Ryan/yolov5/VOC2012/labels/')
    image_ids = open('/home/yanyq/Ryan/yolov5/VOC2012/ImageSets/Main/%s.txt' % (image_set)).read().strip().split()
    list_file = open('%s.txt' % (image_set), 'w')
    for image_id in image_ids:
        list_file.write(abs_path + '/images/%s.jpg\n' % (image_id))
        convert_annotation(image_id)
    list_file.close()

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

三个txt文件内容:

2.3 配置文件

1)数据集的配置

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

2)模型的配置文件

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

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

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

3.模型训练

3.1、下载预训练模型

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

3.2、训练模型

以上参数解释如下: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 等需要根据自己机器进行设置。

4.模型测试

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

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

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

模型测试效果:

测试结果图:

二、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

此时在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

完成后就生成了简化版本的模型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   


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

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

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

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

修改网路的输出shape:

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

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

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

#include <string>
#include <vector>
#include "iostream"  
//#include <fstream>  
//#include < ctime >
//#include <direct.h>
//#include <io.h>

// ncnn
#include "ncnn/layer.h"
#include "ncnn/net.h"
#include "ncnn/benchmark.h"
//#include "gpu.h"

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/imgproc.hpp>
#include "opencv2/opencv.hpp"  

using namespace std;
using namespace cv;

static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
static ncnn::PoolAllocator g_workspace_pool_allocator;

static ncnn::Net yolov5;

class YoloV5Focus : public ncnn::Layer
{
public:
 YoloV5Focus()
 {
  one_blob_only = true;
 }

 virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const
 {
  int w = bottom_blob.w;
  int h = bottom_blob.h;
  int channels = bottom_blob.c;

  int outw = w / 2;
  int outh = h / 2;
  int outc = channels * 4;

  top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);
  if (top_blob.empty())
   return -100;

#pragma omp parallel for num_threads(opt.num_threads)
  for (int p = 0; p < outc; p++)
  {
   const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);
   float* outptr = top_blob.channel(p);

   for (int i = 0; i < outh; i++)
   {
    for (int j = 0; j < outw; j++)
    {
     *outptr = *ptr;

     outptr += 1;
     ptr += 2;
    }

    ptr += w;
   }
  }

  return 0;
 }
};
DEFINE_LAYER_CREATOR(YoloV5Focus)

struct Object
{
 float x;
 float y;
 float w;
 float h;
 int label;
 float prob;
};

static inline float interp_area(const Object& a, const Object& b)
{
 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)
 {
  // no interp
  return 0.f;
 }

 float inter_width = std::min(a.x + a.w, b.x + b.w) - std::max(a.x, b.x);
 float inter_height = std::min(a.y + a.h, b.y + b.h) - std::max(a.y, b.y);

 return inter_width * inter_height;
}

static void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right)
{
 int i = left;
 int j = right;
 float p = faceobjects[(left + right) / 2].prob;

 while (i <= j)
 {
  while (faceobjects[i].prob > p)
   i++;

  while (faceobjects[j].prob < p)
   j--;

  if (i <= j)
  {
   // swap
   std::swap(faceobjects[i], faceobjects[j]);

   i++;
   j--;
  }
 }

#pragma omp parallel ps
 {
#pragma omp p
  {
   if (left < j) qsort_descent_inplace(faceobjects, left, j);
  }
#pragma omp p
  {
   if (i < right) qsort_descent_inplace(faceobjects, i, right);
  }
 }
}


static void qsort_descent_inplace(std::vector<Object>& faceobjects)
{
 if (faceobjects.empty())
  return;

 qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1);
}

static void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold)
{
 picked.clear();

 const int n = faceobjects.size();

 std::vector<float> areas(n);
 for (int i = 0; i < n; i++)
 {
  areas[i] = faceobjects[i].w * faceobjects[i].h;
 }
 for (int i = 0; i < n; i++)
 {
  const Object& a = faceobjects[i];

  int keep = 1;
  for (int j = 0; j < (int)picked.size(); j++)
  {
   const Object& b = faceobjects[picked[j]];

   // interp over union
   float inter_area = interp_area(a, b);
   float union_area = areas[i] + areas[picked[j]] - inter_area;
   // float IoU = inter_area / union_area
   if (inter_area / union_area > nms_threshold)
    keep = 0;
  }
  if (keep)
   picked.push_back(i);
 }
}

static inline float sigmoid(float x)
{
 return static_cast<float>(1.f / (1.f + exp(-x)));
}

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)
{
 const int num_grid = feat_blob.h;

 int num_grid_x;
 int num_grid_y;
 if (in_pad.w > in_pad.h)
 {
  num_grid_x = in_pad.w / stride;
  num_grid_y = num_grid / num_grid_x;
 }
 else
 {
  num_grid_y = in_pad.h / stride;
  num_grid_x = num_grid / num_grid_y;
 }

 const int num_class = feat_blob.w - 5;

 const int num_anchors = anchors.w / 2;

 for (int q = 0; q < num_anchors; q++)
 {
  const float anchor_w = anchors[q * 2];
  const float anchor_h = anchors[q * 2 + 1];

  const ncnn::Mat feat = feat_blob.channel(q);

  for (int i = 0; i < num_grid_y; i++)

  {
   for (int j = 0; j < num_grid_x; j++)
   {
    const float* featptr = feat.row(i * num_grid_x + j);

    // find class index with max class score
    int class_index = 0;
    float class_score = -FLT_MAX;
    for (int k = 0; k < num_class; k++)
    {
     float score = featptr[5 + k];
     if (score > class_score)
     {
      class_index = k;
      class_score = score;
     }
    }

    float box_score = featptr[4];

    float confidence = sigmoid(box_score) * sigmoid(class_score);

    if (confidence >= prob_threshold)
    {
     float dx = sigmoid(featptr[0]);
     float dy = sigmoid(featptr[1]);
     float dw = sigmoid(featptr[2]);
     float dh = sigmoid(featptr[3]);

     float pb_cx = (dx * 2.f - 0.5f + j) * stride;
     float pb_cy = (dy * 2.f - 0.5f + i) * stride;

     float pb_w = pow(dw * 2.f, 2) * anchor_w;
     float pb_h = pow(dh * 2.f, 2) * anchor_h;

     float x0 = pb_cx - pb_w * 0.5f;
     float y0 = pb_cy - pb_h * 0.5f;
     float x1 = pb_cx + pb_w * 0.5f;
     float y1 = pb_cy + pb_h * 0.5f;

     Object obj;
     obj.x = x0;
     obj.y = y0;
     obj.w = x1 - x0;
     obj.h = y1 - y0;
     obj.label = class_index;
     obj.prob = confidence;
     objects.push_back(obj);
    }
   }
  }
 }
}

extern "C" {

 void release()
 {
  fprintf(stderr, "YoloV5Ncnn finished!");

  //ncnn::destroy_gpu_instance();
 }

 int init()
 {
  fprintf(stderr, "YoloV5Ncnn init!\n");
  ncnn::Option opt;
  opt.lightmode = true;
  opt.num_threads = 4;
  opt.blob_allocator = &g_blob_pool_allocator;
  opt.workspace_allocator = &g_workspace_pool_allocator;
  opt.use_packing_layout = true;

  yolov5.opt = opt;

  yolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator);
  // init param
  {
   int ret = yolov5.load_param("yolov5s.param");  
   if (ret != 0)
   {
    std::cout << "ret= " << ret << std::endl;
    fprintf(stderr, "YoloV5Ncnn, load_param failed");
    return -301;
   }
  }

  // init bin
  {
   int ret = yolov5.load_model("yolov5s.bin");  
   if (ret != 0)
   {
    fprintf(stderr, "YoloV5Ncnn, load_model failed");
    return -301;
   }
  }
  return 0;
 }

 int detect(cv::Mat img, std::vector<Object> &objects)
 {

  double start_time = ncnn::get_current_time();
  const int target_size = 320;

  // letterbox pad to multiple of 32
  const int width = img.cols;//1280
  const int height = img.rows;//720
  int w = img.cols;//1280
  int h = img.rows;//720
  float scale = 1.f;
  if (w > h)
  {
   scale = (float)target_size / w;//640/1280
   w = target_size;//640
   h = h * scale;//360
  }
  else
  {
   scale = (float)target_size / h;
   h = target_size;
   w = w * scale;
  }
  cv::resize(img, img, cv::Size(w, h));
  ncnn::Mat in = ncnn::Mat::from_pixels(img.data, ncnn::Mat::PIXEL_BGR2RGB, w, h);

  // pad to target_size rectangle
  // yolov5/utils/datasets.py letterbox
  int wpad = (w + 31) / 32 * 32 - w;
  int hpad = (h + 31) / 32 * 32 - h;
  ncnn::Mat in_pad;
  ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);
  // yolov5
  //std::vector<Object> objects;
  {
   const float prob_threshold = 0.4f;
   const float nms_threshold = 0.51f;

   const float norm_vals[3] = { 1 / 255.f, 1 / 255.f, 1 / 255.f };
   in_pad.substract_mean_normalize(0, norm_vals);

   ncnn::Extractor ex = yolov5.create_extractor();
   //ex.set_vulkan_compute(use_gpu);

   ex.input("images", in_pad);
   std::vector<Object> proposals;

   // anchor setting from yolov5/models/yolov5s.yaml

   // stride 8
   {
    ncnn::Mat out;
    ex.extract("output", out);
    ncnn::Mat anchors(6);
    anchors[0] = 10.f;
    anchors[1] = 13.f;
    anchors[2] = 16.f;
    anchors[3] = 30.f;
    anchors[4] = 33.f;
    anchors[5] = 23.f;

    std::vector<Object> objects8;
    generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8);
    proposals.insert(proposals.end(), objects8.begin(), objects8.end());
   }

   // stride 16
   {
    ncnn::Mat out;
    ex.extract("771", out);

    ncnn::Mat anchors(6);
    anchors[0] = 30.f;
    anchors[1] = 61.f;
    anchors[2] = 62.f;
    anchors[3] = 45.f;
    anchors[4] = 59.f;
    anchors[5] = 119.f;

    std::vector<Object> objects16;
    generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16);

    proposals.insert(proposals.end(), objects16.begin(), objects16.end());
   }
   // stride 32
   {
    ncnn::Mat out;
    ex.extract("791", out);
    ncnn::Mat anchors(6);
    anchors[0] = 116.f;
    anchors[1] = 90.f;
    anchors[2] = 156.f;
    anchors[3] = 198.f;
    anchors[4] = 373.f;

    anchors[5] = 326.f;

    std::vector<Object> objects32;
    generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32);

    proposals.insert(proposals.end(), objects32.begin(), objects32.end());
   }

   // sort all proposals by score from highest to lowest
   qsort_descent_inplace(proposals);
   // apply nms with nms_threshold
   std::vector<int> picked;
   nms_sorted_bboxes(proposals, picked, nms_threshold);

   int count = picked.size();
   objects.resize(count);
   for (int i = 0; i < count; i++)
   {
    objects[i] = proposals[picked[i]];

    // adjust offset to original unpadded
    float x0 = (objects[i].x - (wpad / 2)) / scale;
    float y0 = (objects[i].y - (hpad / 2)) / scale;
    float x1 = (objects[i].x + objects[i].w - (wpad / 2)) / scale;
    float y1 = (objects[i].y + objects[i].h - (hpad / 2)) / scale;

    // clip
    x0 = std::max(std::min(x0, (float)(width - 1)), 0.f);
    y0 = std::max(std::min(y0, (float)(height - 1)), 0.f);
    x1 = std::max(std::min(x1, (float)(width - 1)), 0.f);
    y1 = std::max(std::min(y1, (float)(height - 1)), 0.f);
    objects[i].x = x0;
    objects[i].y = y0;
    objects[i].w = x1;
    objects[i].h = y1;
   }
  }

  return 0;
 }
}


static const char* class_names[] = {
 "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"
};


void draw_face_box(cv::Mat& bgr, std::vector<Object> object) //主要的emoji显示函数
{
 for (int i = 0; i < object.size(); i++)
 {
  const auto obj = object[i];
  cv::rectangle(bgr, cv::Point(obj.x, obj.y), cv::Point(obj.w, obj.h), cv::Scalar(0, 255, 0), 3, 8, 0);
  std::cout << "label:" << class_names[obj.label] << std::endl;
  string emoji_path = "emoji\\" + string(class_names[obj.label]) + ".png"; //这个是emoji图片的路径
  cv::Mat logo = cv::imread(emoji_path);
  if (logo.empty()) {
   std::cout << "imread logo failed!!!" << std::endl;
   return;
  }
  resize(logo, logo, cv::Size(80, 80));
  cv::Mat imageROI = bgr(cv::Range(obj.x, obj.x + logo.rows), cv::Range(obj.y, obj.y + logo.cols));  //emoji的图片放在图中的位置,也就是手势框的旁边
  logo.copyTo(imageROI); //把emoji放在原图中
 }

}

int main()
{
 Mat frame;

 VideoCapture capture(0);
 init();
 while (true)
 {
  capture >> frame;            
  if (!frame.empty()) {          
   std::vector<Object> objects;
   detect(frame, objects);
   draw_face_box(frame, objects);
   imshow("window", frame);  
  }
  if (waitKey(20) == 'q')    
   break;
 }

 capture.release();     

 return 0;
}

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

4、实现emoji和手势的映射

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

胜利✌️

↓↓↓“阅读原文”数据集和代码地址

YOLOPose:除了目标检测,YOLO还能不花代价地估计人体姿态,对实时性能有要求必看! YOLO,是一种流行的目标检测框架。如果将YOLO引入姿态检测任务中,将取得什么结果呢?这篇文章实现了单向传播路径下的2D人体姿态检测,与自上而下或自下而上的方法不同,该方法将人体检测与关键点估计联合实现,在不采用数据增强如翻转、多尺度等情况下,实现COCO keypoint上领先的性能,并且该方法可以集成中其他目标检测算法中实现姿态估计,而几乎不增加运算量,对实时估计人体姿态非常关键。......... 阅读详情

相关推荐

基于 PyTorch 的 YOLO v5 表情识别(代码+数据)

基于 PyTorch 的 YOLO v5 表情识别(代码+数据)

手势识别2:基于YOLOv5的手势识别系统(含手势识别数据集+训练代码)

基于YOLOv5的手势识别系统(含手势识别数据集+训练代码),手势识别,动作识别,手势动作识别,手势数据集

AI吃大瓜的博客 8万+

安装破解IAR EWARM 8.2,提供注册机

IAR Embedded Workbench for ARM(IAR for ARM)是一款微处理器的集成开发环境软件,该集成开发环境中包含了IAR的C/C++编译器、 汇编工具、链接器、库管理器、文本编辑器、工程管理器和C-SPY调试器,支持ARM,AVR,MSP430等芯片。   一、安装 双击运行安装程序,点击Install IAR Embedded Workbench for ARM...

睿思派克 4万+

python基于轻量级YOLOv5的生猪检测+状态识别分析系统

python基于轻量级YOLOv5的生猪检测+状态识别分析系统

Together_CZ的博客 2595

【规则引擎连载3】 WebSphere ILOG JRules 开发——新建一个规则项目

规则项目依赖于bom,bom来自于一个Java项目,所以在建规则项目之前,需要先建立一个Java项目,也就是建模。 说明:原则上是一个规则项目,一个bom。如果规则项目很多,并且每个规则项目的bom都是一样的,那么可以将bom提取出来,单独作为一个项目,这样减少不必要的重复操作,减少维护成本,降低出错几率。这里逐步讲解两种方法来创建规则项目,推荐第二种。 本文主要建立两种规则项目与bom之间的关系,编写相关的规则内容,如操作规则、规则流、决策表等,在后续文章中详细讲解 。

manman的分享 2331

Mac本地部署OpenClaw:从零搭建企业级知识库AI助手

大语言模型(LLM)作为当前人工智能的核心技术,通过其强大的自然语言理解和生成能力,正在深刻改变信息交互方式。其原理是基于海量数据训练的深度神经网络,能够根据上下文预测并生成连贯文本。这一技术的核心价值在于,它能够作为智能中枢,理解和执行复杂指令,从而将通用AI能力转化为解决特定业务问题的生产力。在工程实践中,通过AI Agent框架(如OpenClaw)将LLM与外部工具和知识系统连接,是实现其价值的关键。这涉及到**本地部署**与**私有化**部署,以确保数据安全与成本可控。具体应用场景包括企业知识库问

weixin_33402252的博客 378

用 YOLOv5模型识别出表情!

作者 | 闫永强来源| Datawhale本文利用YOLOV5对手势进行训练识别,并识别显示出对应的emoji,如同下图:本文整体思路如下。提示:本文含完整实践代码,代码较长,建议先看文...

AI科技大本营 2265

告别抓包失败!保姆级配置:让Burp+Proxifier稳定抓取任意微信小程序

本文提供了一套完整的Burp+Proxifier配置方案,确保稳定抓取微信小程序数据。从环境准备、工具选型到Burp Suite深度配置和Proxifier精准设置,详细解决了证书安装、进程捕获和乱码解析等核心问题,帮助开发者告别抓包失败,提升调试效率。

weixin_28738165的博客 455

「干货」用YOLOv5模型识别出表情!

文章来自:DataWhale。作者:闫永强,算法工程师。本文利用YOLOV5对手势进行训练识别,并识别显示出对应的emoji,如同下图:本文整体思路如下。提示:本文含完整实践代码,代码较长...

Oner.wv的专栏 3034

手写数字数据集MNIST

文章目录一、 简介二、下载MNIST数据集训练集和测试集的长度输出图像数据和标记数据的形状显示手写数字图片输出数据集中的第1个样本显示图片 一、 简介 MNIST是一个非常有名的手写体数字识别数据集(手写数字灰度图像数据集),在很多资料中,这个数据集都会被用作深度学习的入门样例。 MNIST数据集是由0到9的数字图像构成的。训练图像有6万张,测试图像有1万张。MNIST数据集是NIST数据集的一个子集,它包含了60000张图片作为训练数据,10000张图片作为测试数据。由250个不同的人手写而成。 每一张图

qq_37289115的博客 1万+

YOLO 系列专栏(二十九)YOLO26 × StarNet 轻量化涨点方案(CVPR 2024)轻量化、主干改进篇 | 星星卷积虽小,却蕴藏着巨大的高效潜力

StarNet 出自 CVPR 2024 轻量化主干网络工作,核心创新是,依靠逐元素乘法实现高阶特征融合,在几乎不增加参数量与 FLOPs 的前提下,大幅提升特征表达能力,天然适合等多任务统一建模。本文将 StarNet 完整替换 YOLO26 原有主干,保留 YOLO26 无 NMS 检测头与轻量化结构,实现,并提供。

qq_36130719的博客 410

YOLOV5训练自己的数据集——用YOLOv5模型识别出表情!

用YOLOV5模型识别出表情 二、YOLOV5模型转换 1.安装依赖库 pip install onnx coremltools onnx-simplifier -i https://pypi.tuna.tsinghua.edu.cn/simple 或者单个的安装 pip install coremltools==4.0 -i https://pypi.tuna.tsinghua.edu.cn/simple 2.导出ONNX模型 遇到No module named 'models' Ex

qq_41627642的博客 1287

【BUG已解决】ImportError: cannot import name ‘OpenAI‘ from ‘openai‘ 解决方案

的核心是版本不匹配用确认当前版本如果是 0.x 版本 →升级到 1.x升级后代码语法要同步改为团队协作项目务必在中锁定版本号,避免"我这能跑,你那不能跑"的问题对接国产模型时,只需替换base_url参数,SDK 调用方式完全一致。

一键难忘的博客 1万+

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的人脸表情识别系统(附完整资源+PySide6界面+训练代码)

本篇博客呈现了一种基于深度学习的人脸表情识别系统,并详细展示了其实现代码。系统采纳了领先的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期版本进行了比较,展示了其在图像、视频、实时视频流及批量文件中识别人脸表情的高准确度。文章深入阐释了YOLOv8的工作机制,并配备了相应的Python代码实现、用于模型训练的数据集,以及一个基于PySide6的用户界面。该系统不仅实现了对人脸表情的精准识别和分类,还提供了包括用户认证管理、模型快速切换及界面个性化定制在内的多项功能。

思绪无限的博客 1万+

一般无穷积分的收敛判别法

文章目录积分里的狄屎判别法证明阿贝尔判别法总结补充:级数中的狄屎和Abel 积分里的狄屎判别法 F(u)=∫auf(x)dx在[a,+∞)F(u)=\int_a^uf(x)dx在[a,+\infty)F(u)=∫au​f(x)dx在[a,+∞)有界 x→+∞时,g(x)单减→0x\to+\infty时,g(x)单减\rightarrow0x→+∞时,g(x)单减→0 ⇒\Rightarrow⇒ ...

universe_1207的博客 1万+

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

基于yolov8、yolov5的人脸表情识别系统(含UI界、Python代码、数据集)

人脸识别技术在身份验证、安全监控、智能交互等领域中具有重要意义,极大地提升了生活的便捷性和社会的安全性。在智能城市建设中,快速且精准地识别人脸,能够有效提高身份验证的准确性,减少安全隐患,特别是在自动化安防系统中,准确识别人脸是其决策和安全监控的基础。同时,人脸识别系统也为用户提供了方便的身份验证手段,如无接触式通行和智能支付。人脸识别技术已在多个领域得到了广泛应用,如安全监控、智能门禁、金融支付、考勤管理等场景中,均依赖于高效准确的人脸识别技术。

Python_csdn_csdn的博客 7444

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

基于Yolov8面部七种表情检测与识别C++模型部署

七种表情识别是一个多学科交叉的研究领域,它结合了心理学、认知科学、计算机视觉和机器学习等学科的知识和技术。

知来者逆的博客 3148

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

基于YOLOv11的人脸表情识别系统

基于YOLOv11的人脸表情识别系统

QQ_1309399183的博客 1743

基于YOLOv8的人脸表情识别系统【附源码】

同时,随着深度学习技术的不断发展,我们可以进一步优化模型的结构和训练过程,提高系统的性能。通过预处理输入图像,将其调整为模型所需的尺寸,然后利用训练好的模型进行前向传播,得到人脸的检测结果。同时,随着深度学习技术的普及和计算机性能的提升,越来越多的企业和研究机构开始关注人脸表情识别技术的实际应用。这些算法通过优化模型结构和训练策略,提高了人脸检测的准确性和实时性,为人脸表情识别提供了有力的技术支持。其中,基于深度学习的目标检测算法,如YOLOv8和YOLOv5,在人脸表情识别领域展现出了巨大的潜力。

Dalu_1226的博客 3559

基于YOLOv8的微表情识别

基于YOLOv8的微表情识别项目介绍 微表情识别是指在非常短暂的时间内(通常为1/25秒至1/5秒之间)对人类面部情感变化的自动识别。微表情的变化通常表现为情绪波动时,人在意识不到的情况下对情感的快速反应,常常反映了人内心真实的情绪状态。因此,微表情识别具有广泛的应用前景,如心理学研究、人机交互、安防监控、在线教育等领域。

QQ_1309399183的博客 1906

基于YOLOv11的实时人脸表情识别系统(附完整资源 + PyQt5界面 + 训练代码)

基于YOLOv11的人脸表情识别系统,使用YOLOv11+Pyqt5实现可以对实时视频、视频文件、图片进行检测。

apple_62565719的博客 5131

毕设——基于人脸表情识别的桌面交互精灵设计(分享一下成果,附上人脸表情的数据集和自己训练出来yolov5模型以及基于PYQT5运行yolov5的交互界面)

数据集和yolov5模型分享。

瓜洲的博客 3077

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的人脸表情识别系统

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的人脸表情识别系统 【下载地址】基于YOLOv8YOLOv7YOLOv6YOLOv5的人脸表情识别系统 本仓库提供了一个基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的人脸表情识别系统的完整资源,包括PySide6界面和训练代码。该系统能够从图像、视频...

gitblog_06536的博客 905
上一篇: 在一家开源公司工作的体验!
下一篇: 最后两周!60万奖金AI攻防赛来了!(附baseline代码及实操)
Datawhale
Datawhale 领域专家: 物联网技术领域 领域专家: 物联网技术领域
博客等级 码龄8年 8901粉丝 1079原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值