目录

1 背景

2 实现

3 结果和代码

4 参考


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

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

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

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

1 背景

上图出自上面说的论文

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

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

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

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

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);
}

结果如下:

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/

[OpenCV实战]12 使用深度学习和OpenCV进行手部关键点检测的更多相关文章

  1. [OpenCV实战]15 基于深度学习的目标跟踪算法GOTURN

    目录 1 什么是对象跟踪和GOTURN 2 在OpenCV中使用GOTURN 3 GOTURN优缺点 4 参考 在这篇文章中,我们将学习一种基于深度学习的目标跟踪算法GOTURN.GOTURN在Caf ...

  2. [OpenCV实战]5 基于深度学习的文本检测

    目录 1 网络加载 2 读取图像 3 前向传播 4 处理输出 3结果和代码 3.1结果 3.2 代码 参考 在这篇文章中,我们将逐字逐句地尝试找到图片中的单词!基于最近的一篇论文进行文字检测. EAS ...

  3. [OpenCV实战]1 基于深度学习识别人脸性别和年龄

    目录 1基于CNN的性别分类建模原理 1.1 人脸识别 1.2 性别预测 1.3 年龄预测 1.4 结果 2 代码 参考 本教程中,我们将讨论应用于面部的深层学习的有趣应用.我们将估计年龄,并从单个图 ...

  4. 深度学习与CV教程(13) | 目标检测 (SSD,YOLO系列)

    作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/37 本文地址:http://www.showmeai.tech/article-det ...

  5. [PyImageSearch] Ubuntu16.04 使用深度学习和OpenCV实现物体检测

    上一篇博文中讲到如何用OpenCV实现物体分类,但是接下来这篇博文将会告诉你图片中物体的位置具体在哪里. 我们将会知道如何使用OpenCV‘s的dnn模块去加载一个预训练的物体检测网络,它能使得我们将 ...

  6. [OpenCV实战]17 基于卷积神经网络的OpenCV图像着色

    目录 1 彩色图像着色 1.1 定义着色问题 1.2 CNN彩色化结构 1.3 从 中恢复彩色图像 1.4 具有颜色再平衡的多项式损失函数 1.5 着色结果 2 OpenCV中实现着色 2.1 模型下 ...

  7. 斯坦福新深度学习系统 NoScope:视频对象检测快1000倍

    以作备份,来源http://jiasuhui.com/archives/178954 本文由“新智元”(微信ID:AI_era)编译,来源:dawn.cs.stanford.edu,编译:刘小芹 斯坦 ...

  8. 深度学习 + OpenCV,Python实现实时视频目标检测

    使用 OpenCV 和 Python 对实时视频流进行深度学习目标检测是非常简单的,我们只需要组合一些合适的代码,接入实时视频,随后加入原有的目标检测功能. 在本文中我们将学习如何扩展原有的目标检测项 ...

  9. 语义分割:基于openCV和深度学习(一)

    语义分割:基于openCV和深度学习(一) Semantic segmentation with OpenCV and deep learning 介绍如何使用OpenCV.深度学习和ENet架构执行 ...

随机推荐

  1. Oracle字段约束

    初识约束 约束是数据库用来确保数据满足业务规则的手段,对数据做的条件限制. 约束的类型 1. 主键约束(PRIMARY KEY) 2. 唯一性约束(UNIQUE) 3. 非空约束(NOT NULL) ...

  2. PhpStorm 2020.1.2破解 | JetBrains PhpStorm 2020.1.2破解版 附破解文件

    直接去官网下载 2020.1.2的版本,版本一定要对得上  是2020.1.2版本 下面是破解的jar,几兆而已 --------------------- 链接:https://pan.baidu. ...

  3. python关于Django搭建简单博客项目(教程)

    由于csdn各种django blog博文都有或多或少的bug,所以我决定自己写一篇,先附上教程,详解在另一篇博文里,为了便于大家复制粘贴,本文代码尽量不使用图片. 源代码及解析文章请在我的githu ...

  4. 41.SessionAuthenticatio和自定义认证

    SessionAuthentication认证介绍 SessionAuthentication使用了Django默认的会话后端 适合AJAX客户端等运行在同样会话上下文环境中的模式 是DRF默认的认证 ...

  5. 如何kill一条TCP连接?

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 如果你的程序写得有毛病,打开了很多TCP连接,但一直没有关闭,即常见的连接泄露场景,你可能想要在排查问题的过程中, ...

  6. 使用 StringUtils.split 的坑

    点赞再看,动力无限. 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. 在日常的 Java 开发中,由于 J ...

  7. python常用库总结

    图片处理相关 # opencvy pip install opencv-python pip install opencv-contrib-python pip install matplotlib ...

  8. c语言内存四区、数据存储范围和内存存储方向

    (1)代码区通常是共享只读(代码无法修改)的,即可以被其他的程序调用,例如运行两个qq,除了数据不一样,代码都是一样的, 每次运行qq,都会将代码和数据加载到内存中,除了数据,每次加载的代码都是一样的 ...

  9. .NET实现堆排序

    堆排序及相关知识 堆排序 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序.首先简单了解下堆结构. 堆 堆是具 ...

  10. 群晖NAS搭建外网可访问的calibre

    一.在群晖docker上安装calibre-web 1. 下载相关的镜像文件 打开Docker后点击左侧注册表,在上方搜索栏搜索calibre 然后我们选择使用 technosoft2000/cali ...