[OpenCV实战]12 使用深度学习和OpenCV进行手部关键点检测
目录
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进行手部关键点检测的更多相关文章
- [OpenCV实战]15 基于深度学习的目标跟踪算法GOTURN
目录 1 什么是对象跟踪和GOTURN 2 在OpenCV中使用GOTURN 3 GOTURN优缺点 4 参考 在这篇文章中,我们将学习一种基于深度学习的目标跟踪算法GOTURN.GOTURN在Caf ...
- [OpenCV实战]5 基于深度学习的文本检测
目录 1 网络加载 2 读取图像 3 前向传播 4 处理输出 3结果和代码 3.1结果 3.2 代码 参考 在这篇文章中,我们将逐字逐句地尝试找到图片中的单词!基于最近的一篇论文进行文字检测. EAS ...
- [OpenCV实战]1 基于深度学习识别人脸性别和年龄
目录 1基于CNN的性别分类建模原理 1.1 人脸识别 1.2 性别预测 1.3 年龄预测 1.4 结果 2 代码 参考 本教程中,我们将讨论应用于面部的深层学习的有趣应用.我们将估计年龄,并从单个图 ...
- 深度学习与CV教程(13) | 目标检测 (SSD,YOLO系列)
作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/37 本文地址:http://www.showmeai.tech/article-det ...
- [PyImageSearch] Ubuntu16.04 使用深度学习和OpenCV实现物体检测
上一篇博文中讲到如何用OpenCV实现物体分类,但是接下来这篇博文将会告诉你图片中物体的位置具体在哪里. 我们将会知道如何使用OpenCV‘s的dnn模块去加载一个预训练的物体检测网络,它能使得我们将 ...
- [OpenCV实战]17 基于卷积神经网络的OpenCV图像着色
目录 1 彩色图像着色 1.1 定义着色问题 1.2 CNN彩色化结构 1.3 从 中恢复彩色图像 1.4 具有颜色再平衡的多项式损失函数 1.5 着色结果 2 OpenCV中实现着色 2.1 模型下 ...
- 斯坦福新深度学习系统 NoScope:视频对象检测快1000倍
以作备份,来源http://jiasuhui.com/archives/178954 本文由“新智元”(微信ID:AI_era)编译,来源:dawn.cs.stanford.edu,编译:刘小芹 斯坦 ...
- 深度学习 + OpenCV,Python实现实时视频目标检测
使用 OpenCV 和 Python 对实时视频流进行深度学习目标检测是非常简单的,我们只需要组合一些合适的代码,接入实时视频,随后加入原有的目标检测功能. 在本文中我们将学习如何扩展原有的目标检测项 ...
- 语义分割:基于openCV和深度学习(一)
语义分割:基于openCV和深度学习(一) Semantic segmentation with OpenCV and deep learning 介绍如何使用OpenCV.深度学习和ENet架构执行 ...
随机推荐
- Lombok好用是好用,就是容易踩坑,这份避坑指南请查收
序言 各位好啊,我是会编程的蜗牛,作为java开发者,我们平常在开发过程中,总是希望能够尽量少敲代码.这一方面,当然是为了偷懒,另一方面,当然也是为了代码看起来更加简洁一点,不断往编程规范上靠.然后其 ...
- jsp页面重定向后地址栏controller名重复而导致报404错误
今天做ssm项目时遇到了这种错误 看看代码: 无关代码省略... 22 <body> 23 <div id="container"> 24 <ifra ...
- VS Code For Web 深入浅出 -- 导读篇
下一代 IDE 的形态究竟是什么呢?VS Code For Web 试图回答这个问题. 背景 众所周知,VS Code 是当前工业界最优秀的代码编辑器之一.它由<设计模式>的作者 Eric ...
- 01-MySQL8主从详解
主从原理 master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中:slave服务器会在一定时间间隔内对master二进制日志进行探测其是 ...
- jquery+bootstrap学习笔记
最近小颖接了个私活,客户要求用jquery和bootstrap来实现业务需求,小颖总结了下在写的过程中的一下坑,来记录一下 1.动态加载html文件 switch (_domName) { case ...
- 【JavaWeb】学习笔记——JSP
概念 全称:Java Server Pages, Java服务端页面 描述:一种动态的网页技术,可以在其中定义HTML.JS.CSS等静态内容,以及Java代码的动态内容 说明:JSP = HTML ...
- 抓包分析 TCP 握手和挥手
前言 首先需要明确的是 TCP 是一个可靠传输协议,它的所有特点最终都是为了这个可靠传输服务.在网上看到过很多文章讲 TCP 连接的三次握手和断开连接的四次挥手,但是都太过于理论,看完感觉总是似懂非懂 ...
- tools2
[对身份证的校验] //身份证的校验 import java.util.stream.IntStream; /** * 身份证号码验证 * 1.号码的结构 * 公民身份号码是特征组合码,由十七位数字本 ...
- 2022年Python顶级自动化特征工程框架⛵
作者:韩信子@ShowMeAI 机器学习实战系列:https://www.showmeai.tech/tutorials/41 本文地址:https://www.showmeai.tech/artic ...
- WPF之XAML
XAML是WPF技术中专门用于设计UI的语言.优点在于: (1)XAML可以设计出专业的UI和动画--好用. (2)简单易懂,易学. (3)设计师可以直接参与软件设计,随时沟通,无须二次转化--高效. ...