OpenCV 深度学习模块(DNN)识别手势

注:本文最初在oschina上发布,但最近发现被关了,原因不明。现转到csdn.

一、OpenCV中DNN模块

OpenCV自3.3版本开始,加入了对深度学习网络的支持,即DNN模块,它支持主流的深度学习框架生成与到处模型的加载。
OpenCV中的深度学习模块(DNN)只提供了推理功能,不涉及模型的训练,支持多种深度学习框架,比如TensorFlow,Caffe,Torch和Darknet。

轻量型。DNN模块只实现了推理功能,代码量及编译运行开销远小于其他深度学习模型框架。
  使用方便。DNN模块提供了内建的CPU和GPU加速,无需依赖第三方库,若项目中之前使用了OpenCV,那么通过DNN模块可以很方便的为原项目添加深度学习的能力。
    通用性。DNN模块支持多种网络模型格式,用户无需额外的进行网络模型的转换就可以直接使用,支持的网络结构涵盖了常用的目标分类,目标检测和图像分割的类别。

函数介绍
1、dnn.blobFromImage
     根据输入图像,创建维度N(图片的个数),通道数C,高H和宽W次序的blobs。
2、dnn.NMSBoxes
    根据给定的检测boxes和对应的scores进行NMS(非极大值抑制)处理。
3、dnn.readNet
加载深度学习网络及其模型参数

model: 训练的权重参数的模型二值文件,支持的格式有:.caffemodel(Caffe)、.pb(TensorFlow)、.t7 或 .net(Torch)、 .weights(Darknet)、.bin(DLDT).
config: 包含网络配置的文本文件,支持的格式有:
.prototxt (Caffe)、
.pbtxt(TensorFlow)、.cfg (Darknet)、.xml (DLDT).

4、加载函数
1)、加载采用Caffe的配置网络和训练的权重参数

readNetFromCaffe(prototxt, caffeModel=None)

2)、加载采用Darknet的配置网络和训练的权重参数

readNetFromDarknet(cfgFile, darknetModel=None)

3)、加载采用Tensorflow 的配置网络和训练的权重参数

readNetFromTensorflow(model, config=None)

4)、加载采用 Torch 的配置网络和训练的权重参数

readNetFromTorch(model, isBinary=None)

5)、加载 .onnx 模型网络配置参数和权重参数

readNetFromONNX(onnxFile)

二、手部关键点检测

手部关键点如下图所示:

可以使用Opencv的深度神经网络DNN模块 检测手部21个关键点。

1、加载模型

参考代码:

Net net;
bool readNet=false;


void loadData()
{
    if(readNet)return;
    readNet=true;
    //模型文件
    string model_file = "handPose/pose_deploy.prototxt";  //模型
    string model_weight = "handPose/pose_iter_102000.caffemodel";//训练权重

    //加载caffe模型
    net = readNetFromCaffe(model_file, model_weight);
 
}

2、手势关键点检测

参考代码:

void handKeypointsDetect(Mat src, vector<Point>& handKeypoints,vector<double> &Prob)
{
    //模型尺寸大小
    int width = src.cols;
    int height = src.rows;
    float ratio = width / (float)height;
    int modelHeight = 368;  //由模型输入维度决定
    int modelWidth = int(ratio*modelHeight);

    //将输入图像转成blob形式
    Mat blob = blobFromImage(src, 1.0 / 255, Size(modelWidth, modelHeight), Scalar(0, 0, 0));

    //将图像转换的blob数据输入到网络的第一层“image”层,见deploy.protxt文件
    net.setInput(blob, "image");

    //结果输出
    Mat output = net.forward();

    int H = output.size[2];
    int W = output.size[3];

    for (int i = 0; i < KeyPointCount; i++)
    {
        //结果预测
        Mat probMap(H, W, CV_32F, output.ptr(0, i));
        resize(probMap, probMap, Size(width, height));

        Point keypoint; //最大可能性手部关键点位置
        double classProb;  //最大可能性概率值
        
        minMaxLoc(probMap, NULL, &classProb, NULL, &keypoint);


        handKeypoints[i] = keypoint; //结果输出,即手部关键点所在坐标
        Prob[i]=classProb;
    }

}


三、手势识别

根据检测出的手势关键点数据,依次检测拇指、食指、中指、无名指、小指的是否张开。

参考代码:

int handPoseRecognition(vector<Point>&handKeypoints, vector<double> &probVec)
{
    int fingers[5];//依次代表拇指、食指、中指、无名指、小指的是否张开
    for (int i=0;i<5;i++  )
        fingers[i]=-1;// -1 不能识别  0 收缩  1 张开

    for (int i=0;i<21;i++)
        qDebug("\t keypoint: %d classProb: %f  ( %d , %d)",i,probVec[i],handKeypoints[i].x,handKeypoints[i].y);

    //如果食指、拇指的关键点位置混乱,表示识别错误
    auto wrong=getDistance(handKeypoints[1],handKeypoints[5])<getDistance(handKeypoints[1],handKeypoints[2]) ;
    if(wrong) return -1;


    //拇指 1~4
    if(probVec[1]>0.1 && probVec[2]>0.1  && probVec[3]>0.1  && probVec[4]>0.1 ){//如果识别出
        //张开  关键点2-1向量 与关键点4-1向量的夹角最小
        auto theta=angle(handKeypoints[4], handKeypoints[1], handKeypoints[2]);
        debugX("拇指 theta:"<<theta);
        if(theta>25)
            fingers[0]=0;
        else
            fingers[0]=1;

    }

    // 食指 5~8
    if (handKeypoints[8].y < handKeypoints[7].y)
        fingers[1] = 1; // 张开   8的Y值小于7的Y值
    else
        fingers[1] = 0;

    // 中指 9~12
    if (handKeypoints[12].y < handKeypoints[11].y && handKeypoints[11].y < handKeypoints[10].y)
        fingers[2] = 1;// 张开
    else
        fingers[2] = 0;

    // 无名指 13~16
    if (handKeypoints[16].y < handKeypoints[15].y)
        fingers[3] = 1;// 张开
    else
        fingers[3] = 0;

    // 小指 17~20
    if (handKeypoints[20].y < handKeypoints[19].y)
        fingers[4] = 1;// 张开
    else
        fingers[4] = 0;

    qDebug("拇指  %d ,食指 %d ,中指 %d, 无名指 %d ,小指  %d)",fingers[0],fingers[1],fingers[2],fingers[3],fingers[4]);

    //5个手指都收缩
    if(fingers[0]==0 && fingers[1]==0 && fingers[2]==0 && fingers[3]==0 &&fingers[4]==0)
        return 0;

    //只有食指张开
    if(fingers[0]==0 && fingers[1]==1 && fingers[2]==0 && fingers[3]==0 &&fingers[4]==0)
        return 1;

    //只有食指和中指张开
    if(fingers[0]==0 && fingers[1]==1 && fingers[2]==1 && fingers[3]==0 &&fingers[4]==0)
        return 2;

    //只有食指和拇指收缩
    if(fingers[0]==0 && fingers[1]==0 && fingers[2]==1 && fingers[3]==1 &&fingers[4]==1)
        return 3;

     //只有小指和拇指收缩
    if(fingers[0]==0 && fingers[1]==1 && fingers[2]==1 && fingers[3]==1 &&fingers[4]==0)
        return 3;

    //只有拇指收缩
    if(fingers[0]==0 && fingers[1]==1 && fingers[2]==1 && fingers[3]==1 &&fingers[4]==1)
        return 4;

    //全张开
    if(fingers[0]==1 && fingers[1]==1 && fingers[2]==1 && fingers[3]==1 &&fingers[4]==1)
        return 5;

    //只有小指和拇指张开
    if(fingers[0]==1 && fingers[1]==0 && fingers[2]==0 && fingers[3]==0 &&fingers[4]==1)
        return 6;

    //只有食指和拇指张开
    if(fingers[0]==1 && fingers[1]==1 && fingers[2]==0 && fingers[3]==0 &&fingers[4]==0)
        return 8;

    return-1;//未识别
}

四、显示识别结果

参考代码:

void resultImage(Mat& src, vector<Point>&handKeypoints, int& count)
{
    //画出关键点所在位置
    for (int i = 0; i < KeyPointCount; i++)
    {
        circle(src, handKeypoints[i], 3, Scalar(0, 0, 255), -1);
        putText(src, to_string(i), handKeypoints[i], FONT_HERSHEY_COMPLEX, 0.8, Scalar(0, 255, 0), 2);
    }
    //将识别结果显示在原图中
    putText(src, to_string(count), Point(20, 60), FONT_HERSHEY_COMPLEX, 2, Scalar(0, 0, 128), 3);
}

五、调用示例

参考代码:

#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/dnn.hpp>
using namespace std;
using namespace cv;
using namespace cv::dnn;
//手部关键点数目
const int KeyPointCount = 21;



//此代码用于集成于《QT插件化图像算法研究平台》
void handPose(Mat &input, Mat &output) {
    loadData();
    vector<Point> handKeypoints(KeyPointCount);
    vector<double> probVec(KeyPointCount);
    handKeypointsDetect(input, handKeypoints, probVec);

    int count = handPoseRecognition(handKeypoints, probVec);

    resultImage(input, handKeypoints, count);
    output = input;
}


//此代码用于独立运行
int main() {
    loadData();
    vector<Point> handKeypoints(KeyPointCount);
    vector<double> probVec(KeyPointCount);
    Mat input=imread("hand.jpg");
    handKeypointsDetect(input, handKeypoints, probVec);

    int count = handPoseRecognition(handKeypoints, probVec);

    resultImage(input, handKeypoints, count);
    imshow("result",input);
}

六、运行效果

在《 插件化算法研究平台》上运行效果如下图:

《插件化算法研究平台》其它内容:

Opencv 图像金字塔----高斯和拉普拉斯

OpenCV仿Photoshop曲线调整图像亮度与色彩

QT 插件化图像算法软件架构

Opencv 图像暗通道调优去雾

opencv 提取选中区域内指定 hsv 颜色的水印

Opencv 手工选择图片区域去水印

Opencv 基于文字检测去图片水印

QT 插件化图像算法研究平台

Opencv 图像亮度调节的几种方式

Opencv使用QT的线程注意事项

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值