手部关键点检测5:C++实现手部关键点检测(手部姿势估计)含源码 可实时检测

手部关键点检测5:C++实现手部关键点检测(手部姿势估计)含源码 可实时检测

目录

[手部关键点检测4:C++实现手部关键点检测(手部姿势估计)含源码 可实时检测](#手部关键点检测4:C++实现手部关键点检测(手部姿势估计)含源码 可实时检测)

1.项目介绍

2.手部关键点检测(手部姿势估计)方法

(1)Top-Down(自上而下)方法

(2)Bottom-Up(自下而上)方法:

3.手部关键点检测模型

[(1) 手部关键点检测模型的训练](#(1) 手部关键点检测模型的训练)

[(2) 将Pytorch模型转换ONNX模型](#(2) 将Pytorch模型转换ONNX模型)

[(3) 将ONNX模型转换为TNN模型](#(3) 将ONNX模型转换为TNN模型)

4.手部关键点检测C/C++部署

(1)项目结构

(2)配置开发环境(OpenCV+OpenCL+base-utils+TNN)

(3)部署TNN模型

(4)CMake配置

(5)main源码

(6)源码编译和运行

(7)Demo测试效果

5.项目源码下载


1.项目介绍

本篇文章是项目《手部关键点检测(手部姿势估计) 》系列文章之《C++实现手部关键点检测(手部姿势估计)含源码 可实时检测》;项目基于Pytorch深度学习框架,实现手部关键点检测(手部姿势估计)模型,其中手部检测采用YOLOv5模型,手部关键点检测是基于开源的HRNet进行改进,构建了整套手部关键点检测的训练和测试流程;为了方便后续模型工程化和Android平台部署,项目支持高精度HRNet检测模型,轻量化模型LiteHRNet和Mobilenet模型训练和测试,并提供Python/C++/Android多个版本;

本篇主要分享将Python训练后的手部检测和手部关键点检测模型部署到C/C++平台。我们将开发一个简易的、可实时运行的手部关键点检测的C/C++ Demo。下表格给出HRNet,以及轻量化模型LiteHRNet和Mobilenet的计算量和参数量,以及其检测精度

|--------------|----------------|---------------|------------|--------|
| 模型 | input-size | params(M) | GFLOPs | AP |
| HRNet-w32 | 192×192 | 28.48M | 5734.05M | 0.8570 |
| LiteHRNet18 | 192×192 | 1.10M | 182.15M | 0.8023 |
| Mobilenet-v2 | 192×192 | 2.63M | 529.25M | 0.7574 |

先展示一下C/C++版本的**手部检测以及手部关键点检测(手部姿势估计)**效果:

Android**手部关键点检测(手部姿势估计)**APP Demo体验:

https://download.csdn.net/download/guyuealian/88418582

【尊重原创,转载请注明出处】https://blog.csdn.net/guyuealian/article/details/133277748


更多项目《手部关键点检测(手部姿势估计)》系列文章请参考:


2.手部关键点检测(手部姿势估计)方法

手部关键点检测(手部姿势估计) 的方法,目前主流的方法主要两种:一种是Top-Down (自上而下)方法,另外一种是Bottom-Up(自下而上)方法;

(1)Top-Down( 自上而下****)****方法

将手部检测和手部关键点估计分离,在图像上首先进行手部目标检测,定位手部位置;然后crop每一个手部图像,再估计每个手部的关键点;这类方法往往比较慢,但姿态估计准确度较高。目前主流模型主要有CPN,Hourglass,CPM,Alpha Pose,HRNet等。

(2)Bottom-Up( 自下而上****)****方法:

先估计图像中所有手部的关键点,然后在通过Grouping的方法组合成一个一个手部实例;因此这类方法在测试推断的时候往往更快速,准确度稍低。典型就是COCO2016年人体关键点检测冠军Open Pose。

通常来说,Top-Down具有更高的精度,而Bottom-Up具有更快的速度; 就目前调研而言, Top-Down的方法研究较多,精度也比Bottom-Up(自下而上)方法高。

本项目基于开源的HRNet进行改进,关于HRNet项目请参考GitHub

HRNet: https://github.com/leoxiaobin/deep-high-resolution-net.pytorch


3.手部关键点检测模型

(1) 手部关键点检测模型的训练

本篇博文主要分享C++版本的模型部署,不包含Python版本的手部关键点检测以及相关训练代码,关于手部关键点检测的训练方法和数据集说明,请参考本人另一篇博文《手部关键点检测3:Pytorch实现手部关键点检测(手部姿势估计)含训练代码和数据集手部关键点检测3:Pytorch实现手部关键点检测(手部姿势估计)含训练代码和数据集-CSDN博客

(2) 将Pytorch模型转换ONNX模型

目前CNN模型有多种部署方式,可以采用TNN,MNN,NCNN,以及TensorRT等部署工具,鄙人采用TNN进行C/C++端上部署。部署流程可分为四步:训练模型->将模型转换ONNX模型->将ONNX模型转换为TNN模型->C/C++部署TNN模型。

训练好Pytorch模型后,我们需要先将模型转换为ONNX模型,以便后续模型部署。

  • 原始项目提供转换脚本,你只需要修改model_file为你模型路径即可
  • convert_torch_to_onnx.py实现将Pytorch模型转换ONNX模型的脚本
bash 复制代码
python libs/convert_tools/convert_torch_to_onnx.py
python 复制代码
"""
This code is used to convert the pytorch model into an onnx format model.
"""
import os
import torch.onnx
from pose.inference import PoseEstimation
from basetrainer.utils.converter import pytorch2onnx
 
 
def load_model(config_file, model_file, device="cuda:0"):
    pose = PoseEstimation(config_file, model_file, device=device)
    model = pose.model
    config = pose.config
    return model, config
 
 
def convert2onnx(config_file, model_file, device="cuda:0", onnx_type="kp"):
    """
    :param model_file:
    :param input_size:
    :param device:
    :param onnx_type:
    :return:
    """
    model, config = load_model(config_file, model_file, device=device)
    model = model.to(device)
    model.eval()
    model_name = os.path.basename(model_file)[:-len(".pth")]
    onnx_file = os.path.join(os.path.dirname(model_file), model_name + ".onnx")
    # dummy_input = torch.randn(1, 3, 240, 320).to("cuda")
    input_size = tuple(config.MODEL.IMAGE_SIZE)  # w,h
    input_shape = (1, 3, input_size[1], input_size[0])
    pytorch2onnx.convert2onnx(model,
                              input_shape=input_shape,
                              input_names=['input'],
                              output_names=['output'],
                              onnx_file=onnx_file,
                              opset_version=11)
 
 
if __name__ == "__main__":
    model_file = "../../work_space/hand/mobilenet_v2_21_192_192_custom_coco_20230928_065444_0934/model/best_model_153_0.7574.pth"
    config_file = "../../work_space/hand/mobilenet_v2_21_192_192_custom_coco_20230928_065444_0934/mobilenetv2_hand_192_192.yaml"
    convert2onnx(config_file, model_file)

(3) 将ONNX模型转换为TNN模型

目前CNN模型有多种部署方式,可以采用TNN,MNN,NCNN,以及TensorRT等部署工具,鄙人采用TNN进行C/C++端上部署

TNN转换工具:

​​​​

4.手部关键点检测C/C++部署

项目IDE开发工具使用CLion,相关依赖库主要有OpenCV,base-utils以及TNN和OpenCL(可选),其中OpenCV必须安装,OpenCL用于模型加速,base-utils以及TNN已经配置好,无需安装;

项目仅在Ubuntu18.04进行测试,Windows系统下请自行配置好开发环境。

(1)项目结构

(2)配置开发环境(OpenCV+OpenCL+base-utils+TNN)

项目IDE开发工具使用CLion,相关依赖库主要有OpenCV,base-utils以及TNN和OpenCL(可选),其中OpenCV必须安装,OpenCL用于模型加速,base-utils以及TNN已经配置好,无需安装;

项目仅在Ubuntu18.04进行测试,Windows系统下请自行配置和编译

  • 安装OpenCV:图像处理

图像处理(如读取图片,图像裁剪等)都需要使用OpenCV库进行处理

安装教程:Ubuntu18.04安装opencv和opencv_contrib

OpenCV库使用opencv-4.3.0版本,opencv_contrib库暂时未使用,可不安装

  • 安装OpenCL:模型加速

安装教程:Ubuntu16.04 安装OpenCV&OpenCL

OpenCL用于模型GPU加速,若不使用OpenCL进行模型推理加速,纯C++推理模型,速度会特别特别慢

  • base-utils:C++库

GitHub:https://github.com/PanJinquan/base-utils (无需安装,项目已经配置了)

base_utils是个人开发常用的C++库,集成了C/C++ OpenCV等常用的算法

  • TNN:模型推理

GitHub:https://github.com/Tencent/TNN (无需安装,项目已经配置了)

由腾讯优图实验室开源的高性能、轻量级神经网络推理框架,同时拥有跨平台、高性能、模型压缩、代码裁剪等众多突出优势。TNN框架在原有Rapidnet、ncnn框架的基础上进一步加强了移动端设备的支持以及性能优化,同时借鉴了业界主流开源框架高性能和良好拓展性的特性,拓展了对于后台X86, NV GPU的支持。手机端 TNN已经在手机QQ、微视、P图等众多应用中落地,服务端TNN作为腾讯云AI基础加速框架已为众多业务落地提供加速支持。

(3)部署TNN模型

项目实现了C/C++版本的车牌检测和车牌识别,车牌检测模型YOLOv5和车牌识别模型PlateNet,模型推理采用TNN部署框架(支持多线程CPU和GPU加速推理);图像处理采用OpenCV库,模型加速采用OpenCL,在普通设备即可达到实时处理。

如果你想在这个 Demo部署你自己训练的车牌检测模型YOLOv5和车牌识别模型PlateNet,你可将训练好的Pytorch模型转换ONNX ,再转换成TNN模型,然后把原始的模型替换成你自己的TNN模型即可。

(4)CMake配置

这是CMakeLists.txt,其中主要配置OpenCV+OpenCL+base-utils+TNN这四个库,Windows系统下请自行配置和编译

bash 复制代码
cmake_minimum_required(VERSION 3.5)
project(Detector)

add_compile_options(-fPIC) # fix Bug: can not be used when making a shared object
set(CMAKE_CXX_FLAGS "-Wall -std=c++11 -pthread")
#set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG")
#set(CMAKE_CXX_FLAGS_DEBUG "-g")

if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    # -DCMAKE_BUILD_TYPE=Debug
    # -DCMAKE_BUILD_TYPE=Release
    message(STATUS "No build type selected, default to Release")
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Debug)" FORCE)
endif ()

# opencv set
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS} ./src/)
#MESSAGE(STATUS "OpenCV_INCLUDE_DIRS = ${OpenCV_INCLUDE_DIRS}")

# base_utils
set(BASE_ROOT 3rdparty/base-utils) # 设置base-utils所在的根目录
add_subdirectory(${BASE_ROOT}/base_utils/ base_build) # 添加子目录到build中
include_directories(${BASE_ROOT}/base_utils/include)
include_directories(${BASE_ROOT}/base_utils/src)
MESSAGE(STATUS "BASE_ROOT = ${BASE_ROOT}")


# TNN set
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake buil ds it for you.
# Gradle automatically packages shared libraries with your APK.
# build for platform
# set(TNN_BUILD_SHARED OFF CACHE BOOL "" FORCE)
if (CMAKE_SYSTEM_NAME MATCHES "Android")
    set(TNN_OPENCL_ENABLE ON CACHE BOOL "" FORCE)
    set(TNN_ARM_ENABLE ON CACHE BOOL "" FORCE)
    set(TNN_BUILD_SHARED OFF CACHE BOOL "" FORCE)
    set(TNN_OPENMP_ENABLE ON CACHE BOOL "" FORCE)  # Multi-Thread
    #set(TNN_HUAWEI_NPU_ENABLE OFF CACHE BOOL "" FORCE)
    add_definitions(-DTNN_OPENCL_ENABLE)           # for OpenCL GPU
    add_definitions(-DTNN_ARM_ENABLE)              # for Android CPU
    add_definitions(-DDEBUG_ANDROID_ON)            # for Android Log
    add_definitions(-DPLATFORM_ANDROID)
elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
    set(TNN_OPENCL_ENABLE ON CACHE BOOL "" FORCE)
    set(TNN_CPU_ENABLE ON CACHE BOOL "" FORCE)
    set(TNN_X86_ENABLE OFF CACHE BOOL "" FORCE)
    set(TNN_QUANTIZATION_ENABLE OFF CACHE BOOL "" FORCE)
    set(TNN_OPENMP_ENABLE ON CACHE BOOL "" FORCE)  # Multi-Thread
    add_definitions(-DTNN_OPENCL_ENABLE)           # for OpenCL GPU
    add_definitions(-DDEBUG_ON)                    # for WIN/Linux Log
    add_definitions(-DDEBUG_LOG_ON)                # for WIN/Linux Log
    add_definitions(-DDEBUG_IMSHOW_OFF)            # for OpenCV show
    add_definitions(-DPLATFORM_LINUX)
elseif (CMAKE_SYSTEM_NAME MATCHES "Windows")
    set(TNN_OPENCL_ENABLE ON CACHE BOOL "" FORCE)
    set(TNN_CPU_ENABLE ON CACHE BOOL "" FORCE)
    set(TNN_X86_ENABLE ON CACHE BOOL "" FORCE)
    set(TNN_QUANTIZATION_ENABLE OFF CACHE BOOL "" FORCE)
    set(TNN_OPENMP_ENABLE ON CACHE BOOL "" FORCE)  # Multi-Thread
    add_definitions(-DTNN_OPENCL_ENABLE)           # for OpenCL GPU
    add_definitions(-DDEBUG_ON)                    # for WIN/Linux Log
    add_definitions(-DDEBUG_LOG_ON)                # for WIN/Linux Log
    add_definitions(-DDEBUG_IMSHOW_OFF)            # for OpenCV show
    add_definitions(-DPLATFORM_WINDOWS)
endif ()
set(TNN_ROOT 3rdparty/TNN)
include_directories(${TNN_ROOT}/include)
include_directories(${TNN_ROOT}/third_party/opencl/include)
add_subdirectory(${TNN_ROOT}) # 添加外部项目文件夹
set(TNN -Wl,--whole-archive TNN -Wl,--no-whole-archive)# set TNN library
MESSAGE(STATUS "TNN_ROOT = ${TNN_ROOT}")

# Detector
include_directories(src)
set(SRC_LIST
        src/Interpreter.cpp
        src/pose_detector.cpp
        src/object_detection.cpp
        src/pose_filter.cpp
        src/yolov5.cpp
        )
add_library(dlcv SHARED ${SRC_LIST})
target_link_libraries(dlcv ${OpenCV_LIBS} base_utils)
MESSAGE(STATUS "DIR_SRCS = ${SRC_LIST}")
add_executable(Detector src/main.cpp)
target_link_libraries(Detector dlcv ${TNN} -lpthread)

(5)main源码

主程序中函数main实现提供了手部关键点检测的使用方法,支持图片,视频和摄像头测试

  • test_image_file(); // 测试图片文件
  • test_video_file(); // 测试视频文件
  • test_camera(); //测试摄像头
cpp 复制代码
//
// Created by 390737991@qq.com on 2020/6/3.
//

#include "pose_detector.h"
#include "object_detection.h"
#include "yolov5.h"
#include "Types.h"
#include <iostream>
#include <string>
#include <vector>
#include "file_utils.h"
#include "image_utils.h"

using namespace dl;
using namespace vision;
using namespace std;

const int num_thread = 1; // 开启CPU线程数目
DeviceType device = GPU;  // 选择运行设备CPU/GPU

// 目标检测SSD或者YOLOv5
const float scoreThresh = 0.5;
const float iouThresh = 0.3;
//const char *det_model_file = (char *) "../data/tnn/ssd/rfb1.0_person_320_320_sim.opt.tnnmodel";
//const char *det_proto_file = (char *) "../data/tnn/ssd/rfb1.0_person_320_320_sim.opt.tnnproto";
//ObjectDetectionParam model_param = PERSON_MODEL;//模型参数
//ObjectDetection *detector = new ObjectDetection(det_model_file, det_proto_file, model_param, num_thread, device);

const char *det_model_file = (char *) "../data/tnn/yolov5/yolov5s05_320.sim.tnnmodel";
const char *det_proto_file = (char *) "../data/tnn/yolov5/yolov5s05_320.sim.tnnproto";
YOLOv5Param dets_model_param = YOLOv5s05_320;//模型参数
YOLOv5 *detector = new YOLOv5(det_model_file,
                              det_proto_file,
                              dets_model_param,
                              num_thread,
                              device);
// 关键点检测
const float poseThresh = 0.3;
const char *pose_model_file = (char *) "../data/tnn/pose/litehrnet18_192_192.sim.tnnmodel";
const char *pose_proto_file = (char *) "../data/tnn/pose/litehrnet18_192_192.sim.tnnproto";
PoseParam pose_model_param = HAND_PARAM;//模型参数
PoseDetector *pose = new PoseDetector(pose_model_file, pose_proto_file, pose_model_param, num_thread, device);

void test_image_file() {
    //测试图片的目录
    string image_dir = "../data/test_image";
    std::vector<string> image_list = get_files_list(image_dir);
    for (string image_path:image_list) {
        cv::Mat bgr = cv::imread(image_path);
        if (bgr.empty()) continue;
        FrameInfo resultInfo;
        // 进行目标检测
        detector->detect(bgr, &resultInfo, scoreThresh, iouThresh);
        // 进行关键点检测
        pose->detect(bgr, &resultInfo, poseThresh);
        // 可视化代码
        pose->visualizeResult(bgr, resultInfo, pose_model_param.skeleton, false, 0);
    }

    delete detector;
    detector = nullptr;
    delete pose;
    pose = nullptr;
    printf("FINISHED.\n");
}


/***
 * 测试视频文件
 * @return
 */
int test_video_file() {
    //测试视频文件
    string video_file = "../data/video/video-test.mp4";
    cv::VideoCapture cap;
    bool ret = get_video_capture(video_file, cap);
    cv::Mat frame;
    while (ret) {
        cap >> frame;
        if (frame.empty()) break;
        FrameInfo resultInfo;
        // 进行目标检测
        detector->detect(frame, &resultInfo, scoreThresh, iouThresh);
        // 进行关键点检测
        pose->detect(frame, &resultInfo, poseThresh);
        // 可视化代码
        pose->visualizeResult(frame, resultInfo, pose_model_param.skeleton, false, 5);
    }
    cap.release();

    delete detector;
    detector = nullptr;
    delete pose;
    pose = nullptr;
    printf("FINISHED.\n");
    return 0;

}


/***
 * 测试摄像头
 * @return
 */
int test_camera() {
    int camera = 0; //摄像头ID号(请修改成自己摄像头ID号)
    cv::VideoCapture cap;
    bool ret = get_video_capture(camera, cap);
    cv::Mat frame;
    while (ret) {
        cap >> frame;
        if (frame.empty()) break;
        FrameInfo resultInfo;
        // 进行目标检测
        detector->detect(frame, &resultInfo, scoreThresh, iouThresh);
        // 进行关键点检测
        pose->detect(frame, &resultInfo, poseThresh);
        // 可视化代码
        pose->visualizeResult(frame, resultInfo, pose_model_param.skeleton, false, 5);
    }
    cap.release();
    delete detector;
    detector = nullptr;
    delete pose;
    pose = nullptr;
    printf("FINISHED.\n");
    return 0;

}

/***
 * 测试跟踪效果
 * @return
 */
int test_pose_track() {
    //测试视频文件
    string video_file = "../data/video/video-test.mp4";
    cv::VideoCapture cap;
    bool ret = get_video_capture(video_file, cap);
    cv::Mat frame;
    // 指定需要跟踪(滤波)的关键点,目前仅仅支持单目标的关键点跟踪,多目标不支持,会出现异常
    vector<int> filter_id = {0};
    // 初始化跟踪
    pose->initTrack(filter_id, 20, 0.35);
    while (ret) {
        cap >> frame;
        if (frame.empty()) break;
        FrameInfo resultInfo;
        // 进行目标检测
        detector->detect(frame, &resultInfo, scoreThresh, iouThresh);
        // 进行关键点检测和跟踪
        pose->track(frame, &resultInfo, poseThresh);
        // 可视化代码
        pose->visualizeResult(frame, resultInfo, pose_model_param.skeleton, false, 5);
    }
    cap.release();

    delete detector;
    detector = nullptr;
    delete pose;
    pose = nullptr;
    printf("FINISHED.\n");
    return 0;

}


int main() {
    test_image_file();   // 测试图片文件
    test_video_file();   // 测试视频文件
    test_camera();       //测试摄像头
    return 0;
}

(6)源码编译和运行

编译脚本,或者直接:bash build.sh

bash 复制代码
#!/usr/bin/env bash
if [ ! -d "build/" ];then
  mkdir "build"
else
  echo "exist build"
fi
cd build
cmake ..
make -j4
sleep 1
./Detector
  • 如果你要测试CPU运行的性能,请修改src/main.cpp

DeviceType device = CPU;

  • 如果你要测试GPU运行的性能,请修改src/main.cpp (需配置好OpenCL)

DeviceType device = GPU;

PS:纯CPU C++推理模式比较耗时,需要几秒的时间,而开启OpenCL加速后,GPU模式耗时仅需十几毫秒,性能极大的提高。

(7)Demo测试效果

C++版本与Python版本的结果几乎是一致,下面是手部关键点检测效果展示:


5.项目源码下载

C/C++实现手部关键点检测项目源码下载地址:C++实现手部关键点检测(手部姿势估计)源码下载

整套项目源码内容包含:

  1. C/C++源码支持YOLOv5手部检测
  2. C/C++源码提供高精度版本HRNet手部关键点检测
  3. C/C++源码提供轻量化模型LiteHRNet和Mobilenet-v2手部关键点检测
  4. C/C++源码支持CPU和GPU,开启GPU(OpenCL)可以实时检测和识别(纯CPU推理速度很慢,模型加速需要配置好OpenCL,GPU推理约15ms左右)
  5. C/C++源码Demo支持图片,视频,摄像头测试
  6. 项目配置好了base-utils和TNN,而OpenCV和OpenCL需要自行编译安装

Android手部关键点检测APP Demo体验:

https://download.csdn.net/download/guyuealian/88418582

如果你需要手部关键点检测的训练代码,请参考:手部关键点检测3:Pytorch实现手部关键点检测(手部姿势估计)含训练代码和数据集

相关推荐
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
机器视觉知识推荐、就业指导1 小时前
C++设计模式:享元模式 (附文字处理系统中的字符对象案例)
c++
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Ronin3052 小时前
11.vector的介绍及模拟实现
开发语言·c++
✿ ༺ ོIT技术༻2 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工2 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
唐诺8 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨9 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客10 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin10 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin