百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

[OpenCV实战]12 使用深度学习和OpenCV进行手部关键点检测

bigegpt 2024-09-12 11:27 8 浏览

使用深度学习和OpenCV进行手部关键点检测

手部关键点检测是在手指上找到关节以及在给定图像中找到指尖的过程。它类似于在脸部(面部关键点检测)或身体(人体姿势估计)上找到关键点。但是手部检测不同的地方在于,我们将整个手部视为一个对象。

美国卡耐基梅隆大学智能感知实验室(CMU Perceptual Computing Lab)发布了手的关键点检测模型。详情见:

https://arxiv.org/pdf/1704.07809.pdf

我们将在本文介绍如何调用该模型。

1 背景

添加图片注释,不超过 140 字(可选)

上图出自上面说的论文

他们从一小组标记的手部图像开始,并使用神经网络(卷积姿势分析机 https://arxiv.org/pdf/1602.00134.pdf 来粗略估计手部关键点。他们设置了一个多视图系统可以从31个高清摄像头获取来自不同视点或角度的图像。

他们将这些图像传递通过检测器,以获得许多粗略的关键点预测。一旦从不同视图获得同一手的检测到的关键点,就会执行关键点三角测量以获得关键点的3D位置。关键点的3D位置用于通过从3D到2D的重投影来稳健地预测关键点。这对于难以预测关键点的图像尤其重要。通过这种方式,他们可以在几次迭代中获得更好的检测器。

总之,他们使用关键点检测器和多视图图像来提出改进的检测器。改进的主要来源是标记的图像集的多视图图像。

该模型产生22个关键点。手有21个关键点(0到20号关键点),而第22个关键点代表背景。关键点位置如下图所示:

添加图片注释,不超过 140 字(可选)

2 实现

从此链接下载该模型:

http://posefs1.perception.cs.cmu.edu/OpenPose/models/hand/pose_iter_102000.caffemodel

这是一个caffe模型。

模型读取预测代码和其他caffe模型一样,如下所示:


    //模型文件位置
    string protoFile = "./model/pose_deploy.prototxt";
    string weightsFile = "./model/pose_iter_102000.caffemodel";

    // read image 读取图像
    string imageFile = "./image/hand.jpg";
    Mat frame = imread(imageFile);
    if (frame.empty())
    {
        cout << "check image" << endl;
        return 0;
    }
    //复制图像
    Mat frameCopy = frame.clone();
    //读取图像长宽
    int frameWidth = frame.cols;
    int frameHeight = frame.rows;

    float thresh = 0.01;

    //原图宽高比
    float aspect_ratio = frameWidth / (float)frameHeight;
    int inHeight = 368;
    //缩放图像
    int inWidth = (int(aspect_ratio*inHeight) * 8) / 8;

    cout << "inWidth = " << inWidth << " ; inHeight = " << inHeight << endl;

    double t = (double)cv::getTickCount();
    //调用caffe模型
    Net net = readNetFromCaffe(protoFile, weightsFile);
    Mat inpBlob = blobFromImage(frame, 1.0 / 255, Size(inWidth, inHeight), Scalar(0, 0, 0), false, false);
    net.setInput(inpBlob);
    Mat output = net.forward();

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

输出有22个矩阵,每个矩阵是关键点的概率图。为了找到确切的关键点,首先,我们将概率图缩放到原始图像的大小。然后通过查找概率图的最大值来找到关键点的位置。这是使用OpenCV中的minmaxLoc函数完成的。我们绘制检测到的点以及图像上的编号。我们将使用检测到的点来获取关键点形成的骨架并将其绘制在图像上。画骨架代码如下:


    // find the position of the body parts 找到各点的位置
    vector<Point> points(nPoints);
    for (int n = 0; n < nPoints; n++)
    {
        // Probability map of corresponding body's part. 第一个特征点的预测矩阵
        Mat probMap(H, W, CV_32F, output.ptr(0, n));
        //放大预测矩阵
        resize(probMap, probMap, Size(frameWidth, frameHeight));

        Point maxLoc;
        double prob;
        //寻找预测矩阵,最大值概率以及最大值的坐标位置
        minMaxLoc(probMap, 0, &prob, 0, &maxLoc);
        if (prob > thresh)
        {
            //画图
            circle(frameCopy, cv::Point((int)maxLoc.x, (int)maxLoc.y), 8, Scalar(0, 255, 255), -1);
            cv::putText(frameCopy, cv::format("%d", n), cv::Point((int)maxLoc.x, (int)maxLoc.y), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, 0, 255), 2);
        }
        //保存特征点的坐标
        points[n] = maxLoc;
    }

    //获取要画的骨架线个数
    int nPairs = sizeof(POSE_PAIRS) / sizeof(POSE_PAIRS[0]);

    //连接点,画骨架
    for (int n = 0; n < nPairs; n++)
    {
        // lookup 2 connected body/hand parts
        Point2f partA = points[POSE_PAIRS[n][0]];
        Point2f partB = points[POSE_PAIRS[n][1]];

        if (partA.x <= 0 || partA.y <= 0 || partB.x <= 0 || partB.y <= 0)
            continue;

        //画骨条线
        line(frame, partA, partB, Scalar(0, 255, 255), 8);
        circle(frame, partA, 8, Scalar(0, 0, 255), -1);
        circle(frame, partB, 8, Scalar(0, 0, 255), -1);
    }

结果如下:

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

3 结果和代码

需要注意的一点是,检测器需要手周围的边界框来预测关键点。因此,为了获得更好的效果,手应靠近相机,反正总而言之手的位置要清楚,在屏幕中央。现在的深度学习只能这样。精度不怎么高,只能在特定场合下使用,就是先确定关键点,然后训练模型,基于统计进行检测。

代码见:

https://github.com/luohenyueji/OpenCV-Practical-Exercise

C++代码:


// HandPoints_detection.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;
using namespace cv::dnn;

//各个部位连接线坐标,比如(0,1)表示第0特征点和第1特征点连接线为拇指
const int POSE_PAIRS[20][2] =
{
    {0,1}, {1,2}, {2,3}, {3,4},         // thumb
    {0,5}, {5,6}, {6,7}, {7,8},         // index
    {0,9}, {9,10}, {10,11}, {11,12},    // middle
    {0,13}, {13,14}, {14,15}, {15,16},  // ring
    {0,17}, {17,18}, {18,19}, {19,20}   // small
};

int nPoints = 22;

int main()
{
    //模型文件位置
    string protoFile = "./model/pose_deploy.prototxt";
    string weightsFile = "./model/pose_iter_102000.caffemodel";

    // read image 读取图像
    string imageFile = "./image/hand.jpg";
    Mat frame = imread(imageFile);
    if (frame.empty())
    {
        cout << "check image" << endl;
        return 0;
    }
    //复制图像
    Mat frameCopy = frame.clone();
    //读取图像长宽
    int frameWidth = frame.cols;
    int frameHeight = frame.rows;

    float thresh = 0.01;

    //原图宽高比
    float aspect_ratio = frameWidth / (float)frameHeight;
    int inHeight = 368;
    //缩放图像
    int inWidth = (int(aspect_ratio*inHeight) * 8) / 8;

    cout << "inWidth = " << inWidth << " ; inHeight = " << inHeight << endl;

    double t = (double)cv::getTickCount();
    //调用caffe模型
    Net net = readNetFromCaffe(protoFile, weightsFile);
    Mat inpBlob = blobFromImage(frame, 1.0 / 255, Size(inWidth, inHeight), Scalar(0, 0, 0), false, false);
    net.setInput(inpBlob);
    Mat output = net.forward();

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

    // find the position of the body parts 找到各点的位置
    vector<Point> points(nPoints);
    for (int n = 0; n < nPoints; n++)
    {
        // Probability map of corresponding body's part. 第一个特征点的预测矩阵
        Mat probMap(H, W, CV_32F, output.ptr(0, n));
        //放大预测矩阵
        resize(probMap, probMap, Size(frameWidth, frameHeight));

        Point maxLoc;
        double prob;
        //寻找预测矩阵,最大值概率以及最大值的坐标位置
        minMaxLoc(probMap, 0, &prob, 0, &maxLoc);
        if (prob > thresh)
        {
            //画图
            circle(frameCopy, cv::Point((int)maxLoc.x, (int)maxLoc.y), 8, Scalar(0, 255, 255), -1);
            cv::putText(frameCopy, cv::format("%d", n), cv::Point((int)maxLoc.x, (int)maxLoc.y), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, 0, 255), 2);
        }
        //保存特征点的坐标
        points[n] = maxLoc;
    }

    //获取要画的骨架线个数
    int nPairs = sizeof(POSE_PAIRS) / sizeof(POSE_PAIRS[0]);

    //连接点,画骨架
    for (int n = 0; n < nPairs; n++)
    {
        // lookup 2 connected body/hand parts
        Point2f partA = points[POSE_PAIRS[n][0]];
        Point2f partB = points[POSE_PAIRS[n][1]];

        if (partA.x <= 0 || partA.y <= 0 || partB.x <= 0 || partB.y <= 0)
            continue;

        //画骨条线
        line(frame, partA, partB, Scalar(0, 255, 255), 8);
        circle(frame, partA, 8, Scalar(0, 0, 255), -1);
        circle(frame, partB, 8, Scalar(0, 0, 255), -1);
    }

    //计算运行时间
    t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
    cout << "Time Taken = " << t << endl;
    imshow("Output-Keypoints", frameCopy);
    imshow("Output-Skeleton", frame);
    imwrite("Output-Skeleton.jpg", frame);

    waitKey();

    return 0;
}

python代码:


from __future__ import division
import cv2
import time
import numpy as np

protoFile = "./model/pose_deploy.prototxt"
weightsFile = "./model/pose_iter_102000.caffemodel"
nPoints = 22
POSE_PAIRS = [ [0,1],[1,2],[2,3],[3,4],[0,5],[5,6],[6,7],[7,8],[0,9],[9,10],[10,11],[11,12],[0,13],[13,14],[14,15],[15,16],[0,17],[17,18],[18,19],[19,20] ]
net = cv2.dnn.readNetFromCaffe(protoFile, weightsFile)

frame = cv2.imread("./image/hand.jpg")
frameCopy = np.copy(frame)
frameWidth = frame.shape[1]
frameHeight = frame.shape[0]
aspect_ratio = frameWidth/frameHeight

threshold = 0.1

t = time.time()
# input image dimensions for the network
inHeight = 368
inWidth = int(((aspect_ratio*inHeight)*8)//8)
inpBlob = cv2.dnn.blobFromImage(frame, 1.0 / 255, (inWidth, inHeight), (0, 0, 0), swapRB=False, crop=False)

net.setInput(inpBlob)

output = net.forward()
print("time taken by network : {:.3f}".format(time.time() - t))

# Empty list to store the detected keypoints
points = []

for i in range(nPoints):
    # confidence map of corresponding body's part.
    probMap = output[0, i, :, :]
    probMap = cv2.resize(probMap, (frameWidth, frameHeight))

    # Find global maxima of the probMap.
    minVal, prob, minLoc, point = cv2.minMaxLoc(probMap)

    if prob > threshold :
        cv2.circle(frameCopy, (int(point[0]), int(point[1])), 8, (0, 255, 255), thickness=-1, lineType=cv2.FILLED)
        cv2.putText(frameCopy, "{}".format(i), (int(point[0]), int(point[1])), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, lineType=cv2.LINE_AA)

        # Add the point to the list if the probability is greater than the threshold
        points.append((int(point[0]), int(point[1])))
    else :
        points.append(None)

# Draw Skeleton
for pair in POSE_PAIRS:
    partA = pair[0]
    partB = pair[1]

    if points[partA] and points[partB]:
        cv2.line(frame, points[partA], points[partB], (0, 255, 255), 2)
        cv2.circle(frame, points[partA], 8, (0, 0, 255), thickness=-1, lineType=cv2.FILLED)
        cv2.circle(frame, points[partB], 8, (0, 0, 255), thickness=-1, lineType=cv2.FILLED)


cv2.imshow('Output-Keypoints', frameCopy)
cv2.imshow('Output-Skeleton', frame)


cv2.imwrite('Output-Keypoints.jpg', frameCopy)
cv2.imwrite('Output-Skeleton.jpg', frame)

print("Total time taken : {:.3f}".format(time.time() - t))

cv2.waitKey(0)

4 参考

手部特征点识别

https://www.learnopencv.com/hand-keypoint-detection-using-deep-learning-and-opencv/

其他身体特征点识别,一样的套路

https://www.learnopencv.com/deep-learning-based-human-pose-estimation-using-opencv-cpp-python/

引用链接

[1] https://arxiv.org/pdf/1704.07809.pdf: https://arxiv.org/pdf/1704.07809.pdf

[2] https://arxiv.org/pdf/1602.00134.pdf: https://arxiv.org/pdf/1602.00134.pdf

[3] https://github.com/luohenyueji/OpenCV-Practical-Exercise : https://github.com/luohenyueji/OpenCV-Practical-Exercise

[4] https://www.learnopencv.com/hand-keypoint-detection-using-deep-learning-and-opencv/ : https://www.learnopencv.com/hand-keypoint-detection-using-deep-learning-and-opencv/

[5] https://www.learnopencv.com/deep-learning-based-human-pose-estimation-using-opencv-cpp-python/ : https://www.learnopencv.com/deep-learning-based-human-pose-estimation-using-opencv-cpp-python/

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...