指针压力表读数3:C/C++实现指针压力表读数(可实时检测)
目录
指针压力表读数3:C/C++实现指针压力表读数(可实时检测)
[1. 前言](#1. 前言)
[2. 部署框架MNN](#2. 部署框架MNN)
[3. 导出MNN模型](#3. 导出MNN模型)
[4. MNN C++推理实现](#4. MNN C++推理实现)
(2)配置开发环境(OpenCV+OpenCL+base-utils+MNN)
[5. Android版本](#5. Android版本)
[6. 项目源码下载](#6. 项目源码下载)
1. 前言
本篇文章是项目《指针压力表读数 》系列文章之《PyTorch实现指针压力表读数》。项目基于 PyTorch 深度学习框架,构建了一套完整的指针压力表自动读数识别方案。整体流程采用"压力表检测 + 关键点检测 + 读数计算 "的三阶段设计:首先利用 YOLO11 模型从复杂工业背景中快速定位压力表表盘区域,裁剪得到表盘 ROI;随后在 ROI 内对表盘量程范围起点(零刻度线)、量程范围终点(最大刻度线)、指针起点和指针终点四个关键位置进行关键点检测;最后基于关键点之间的几何关系计算指针偏转角度,并结合量程信息换算得到最终压力读数。

其中,压力表表盘检测采用 YOLO11 模型。YOLO11 在检测速度、小目标识别能力和模型导出兼容性方面具有较好优势,能够适应工业现场中表盘尺度变化大、拍摄角度倾斜、光照不均等复杂场景。 表盘指针关键点检测则基于开源 HRNet 进行改进,构建了完整的指针压力表读数训练与测试流程。HRNet 通过保持高分辨率特征表示,能够更稳定地回归指针尖端、表盘中心等细长或弱纹理关键点;在此基础上,项目进一步引入轻量化分支,支持高精度 HRNet、LiteHRNet 以及 MobileNet 等多种骨干网络的训练和测试,从而在检测精度、推理速度和模型体积之间进行灵活权衡。
为了方便后续模型工程化和 Android 平台部署,项目同时提供了 Python、C++ 和 Android 多个版本的实现。Python 版本主要用于算法验证、模型训练、离线测试和效果可视化;C++ 版本基于 MNN等推理后端实现,便于在边缘设备、工控机或嵌入式平台上集成;Android 版本则面向移动端巡检场景,支持相机采集、实时推理和读数结果显示。
轻量化Mobilenet-v2模型在普通Android手机上可以达到实时的检测效果,CPU(4线程)约40ms左右,GPU约30ms左右 ,基本满足业务的性能需求。下表格给出HRNet,以及轻量化模型LiteHRNet和Mobilenet的计算量和参数量,以及其检测精度
|--------------|----------------|---------------|------------|--------|
| 模型 | input-size | params(M) | GFLOPs | AP |
| HRNet-w32 | 192×192 | 28.48M | 5734.05M | 0.9801 |
| LiteHRNet18 | 192×192 | 1.10M | 182.15M | 0.9708 |
| Mobilenet-v2 | 192×192 | 2.63M | 529.25M | 0.9589 |
【尊重原创,转载请注明出处】https://blog.csdn.net/guyuealian/article/details/168873267
更多项目《指针压力表读数》系列文章请参考:
- 指针压力表读数1:指针压力表数据集下载https://blog.csdn.net/guyuealian/article/details/168873184
- 指针压力表读数2:Pytorch实现指针压力表读数(含训练代码) https://blog.csdn.net/guyuealian/article/details/168873246
- 指针压力表读数3:C/C++实现指针压力表读数(可实时检测) https://blog.csdn.net/guyuealian/article/details/168873267
- 指针压力表读数4:Android实现指针压力表读数(可实时检测) https://blog.csdn.net/guyuealian/article/details/168873316
2. 部署框架MNN
GitHub:https://github.com/alibaba/MNN
MNN 是阿里巴巴集团开源的轻量级深度学习推理框架,旨在为移动端、嵌入式设备提供高效的模型部署能力。它已在**宝、**猫、钉钉等 30 多个阿里系应用中大规模使用,每天运行数百万次推理。MNN 支持 TensorFlow、Caffe、ONNX 等主流模型格式转换,兼容 CNN、RNN、GAN、Transformer 等常见网络结构,并针对 CPU、GPU、NPU 等硬件后端进行了深度优化。 在 CPU 端,MNN 不依赖第三方计算库,通过手写汇编、Winograd 卷积、多线程调度和内存复用等方式充分发挥 ARM CPU 算力;在 GPU 端,它支持 OpenCL、Vulkan、Metal、CUDA 等多种加速后端,其中 Android 平台可使用 OpenCL/Vulkan,iOS/macOS 平台可使用 Metal,PC/服务器端还可使用 CUDA。 对于需要更高性能的场景,开启 GPU 加速后推理速度通常可提升数倍,部分机型甚至可获得更明显的加速效果。
此外,MNN 还支持 INT4/INT8/FP16 等低精度量化、算子融合、图优化和异构计算,能够在保持模型精度的同时显著降低推理延迟和内存占用。 结合其轻量的库体积、完善的 Python/C++ API 以及跨平台部署能力,MNN 非常适合作为本项目 Android 端推理后端。
3. 导出MNN模型
在开始 C++ 部署之前,首先需要将训练好的 PyTorch 模型导出为 MNN 格式。项目Python源码提供了导出脚本libs/converter/torch2mnn.py。
以LiteHRNet18为例,导出命令如下:
需要安装依赖库: pip install onnx onnxsim onnxruntime mnn
python
"""
This code is used to convert the pytorch model into an onnx/MNN format model.
"""
import os
import torch.onnx
from pose.inference import PoseEstimation
from basetrainer.utils.converter import pytorch2onnx, onnx2mnn
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 convert2mnn(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])
onnx_file = pytorch2onnx.convert2onnx(model,
input_shape=input_shape,
input_names=['input'],
output_names=['output'],
onnx_file=onnx_file,
opset_version=11,
)
mnn_file = onnx2mnn.convert2mnn(onnx_file, fp16=True)
if __name__ == "__main__":
model_file = "work_space/pressure_meter/LiteHRNet18_4_192_192_custom_coco_20261023_105744_0418/model/best_model_209_1.0000.pth"
config_file = "work_space/pressure_meter/LiteHRNet18_4_192_192_custom_coco_20261023_105744_0418/litehrnet18_192_192.yaml"
其他模型导出方式完全一致,只需替换对应的model_file模型文件和配置文件config_file即可导出的*.mnn 文件。
4. MNN C++推理实现
本项目采用 CLion 作为集成开发环境(IDE)。在依赖库方面,OpenCV 为必须安装的核心依赖,用于图像处理基础功能;base-utils 与 MNN 推理框架已集成于项目内部,开发者无需额外配置;OpenCL 作为可选组件,主要用于在支持该标准的设备上对模型推理进行硬件加速,用户可根据实际硬件情况选择性启用。
(1)项目结构

(2)配置开发环境(OpenCV+OpenCL+base-utils+MNN)
项目模型推理采用MNN部署框架(支持多线程CPU和GPU加速推理);图像处理采用OpenCV库,模型加速采用OpenCL/Vulkan,在普通电脑设备即可达到实时处理。
- 项目仅在Ubuntu20.04进行测试,Windows系统下请自行配置和编译
- 如果你想在这个 C++ Demo部署你自己训练的模型,你可以将训练好的Pytorch模型转换成MNN 模型,然后把原始的模型替换成你自己的MNN模型即可。
- 安装OpenCV:图像处理
图像处理(如读取图片,图像裁剪等)都需要使用OpenCV库进行处理
安装教程:Ubuntu20.04安装opencv和opencv_contrib
OpenCV库使用opencv-4.3.0版本,opencv_contrib库暂时未使用,可不安装
- 安装OpenCL:模型加速
安装教程:Ubuntu20.04安装OpenCL
OpenCL和Vulkan用于模型GPU推理加速,若不使用GPU,纯C++推理模型,速度会比较慢。OpenCL支持性更好,推进使用OpenCL。
- 安装Vulkan :模型加速
安装教程:Ubuntu20.04安装Vulkan
OpenCL和Vulkan用于模型GPU推理加速,若不使用GPU,纯C++推理模型,速度会比较慢。MNN对OpenCL支持性更好,推荐优先使用OpenCL。
- base-utils:C++库
GitHub:https://github.com/PanJinquan/base-utils (无需安装,项目已经配置了)
base_utils是个人开发常用的C++库,集成了C/C++ OpenCV等常用的算法
- MNN:模型推理
GitHub:GitHub - alibaba/MNN: MNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI. · GitHub (无需安装,项目已经配置了)
MNN (Mobile Neural Network) 是由阿里巴巴集团开源的一个轻量级、高性能的深度学习推理引擎。它专为移动端、嵌入式设备以及边缘计算场景设计,支持OpenCL/Vulkan,同时也支持服务器端的高并发推理。自2019年开源以来,MNN 已经成为业界主流的端侧推理框架之一,广泛应用于**宝、**猫、**酷、**钉等阿里系App中,覆盖了搜索推荐、图像识别、OCR、AR特效、语音处理等数百个业务场景。
(3)CMake配置
具体查看项目配套的CMakeLists.txt文件,主要配置了OpenCV+base-utils+MNN这四个库,Windows系统下请自行配置和编译。
(4)main源码
主程序src/main.cpp主程序提供了Demo,其中test_image_file()用于测试图片,test_video_file()用于测试视频文件,test_usb_camera()用于测试USB摄像头,test_performance用于测试性能。
cpp
//
// Created by Pan on 2024/6/24.
//
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <vector>
#include <string>
#include "keypoints.h"
#include "Types.h"
#include "file_utils.h"
#include "yolo.h"
using namespace std;
DeviceType device = CPU; // 使用CPU运行
// DeviceType device = OPENCL; // 使用OPENCL运行
// DeviceType device = VULKAN; // 使用VULKAN运行
const int max_nums = -1; // 最大检测数量,-1表示不限制
const int numThread = 4; // 线程数
// TODO 目标检测模型
string dets_file = "../data/model/yolo11n-meter-320.mnn";
YOLOParam dets_param = YOLO11_PARAM;
const float scoreThresh = 0.25f; // 检测阈值
const float iouThresh = 0.7f; // IOU阈值
YOLO *dets = new YOLO(dets_file, dets_param, numThread, device, false, max_nums);
// TODO 关键点检测模型
// string kpts_file = "../data/model/hrnetw32_192_192.mnn";
string kpts_file = "../data/model/litehrnet_192_192.mnn";
// string kpts_file = "../data/model/mobilenetv2_192_192.mnn";
KptsParam kpts_param = KPTS_PARAM;
const float poseThresh = 0.3;
Keypoints *pose = new Keypoints(kpts_file, kpts_param, numThread, device);
/***
* 测试图片文件
* @return
*/
int test_image_file() {
// 测试图片
string image_dir = "../data/test_image";
vector<string> image_list = get_image_list(image_dir);
for (string image_path: image_list) {
printf("%s\n", image_path.c_str());
cv::Mat frame = cv::imread(image_path);
if (frame.empty()) continue;
FrameInfo resultInfo;
// 进行目标检测
dets->detect(frame, resultInfo, scoreThresh, iouThresh);
// 进行关键点检测
pose->detect(frame, resultInfo, poseThresh, false);
// 可视化代码
pose->drawResult(frame, resultInfo, 0, true);
}
printf("FINISHED.\n");
return 0;
}
/***
* 测试视频文件
* @return
*/
int test_video_file() {
string video_file = "../data/video/video01.mp4"; //视频文件
cv::VideoCapture cap;
bool ret = get_video_capture(video_file, cap);
cv::Mat frame;
// TODO 关键点去抖动滤波器
pose->initFilter(5, 0.8);
while (ret) {
cap >> frame;
if (frame.empty()) break;
FrameInfo resultInfo;
// 进行目标检测
dets->detect(frame, resultInfo, scoreThresh, iouThresh);
// 进行关键点检测
pose->detect(frame, resultInfo, poseThresh, true);
// 可视化代码
pose->drawResult(frame, resultInfo, 10, true);
}
cap.release();
printf("FINISHED.\n");
return 0;
}
/***
* 测试USB摄像头
* @return
*/
int test_usb_camera() {
int camera = 0; //摄像头USB ID号(请修改成自己摄像头USB ID号)
cv::VideoCapture cap;
bool ret = get_video_capture(camera, cap);
cv::Mat frame;
// TODO 关键点去抖动滤波器
pose->initFilter(5, 0.8);
while (ret) {
cap >> frame;
if (frame.empty()) break;
FrameInfo resultInfo;
// 进行目标检测
dets->detect(frame, resultInfo, scoreThresh, iouThresh);
// 进行关键点检测
pose->detect(frame, resultInfo, poseThresh, true);
// 可视化代码
pose->drawResult(frame, resultInfo, 10, true);
}
cap.release();
printf("FINISHED.\n");
return 0;
}
/***
* 测试性能
* @return
*/
int test_performance() {
// 测试图片
string image_dir = "../data/test_image";
vector<string> image_list = get_image_list(image_dir);
for (string image_path: image_list) {
cv::Mat frame = cv::imread(image_path);
FrameInfo resultInfo;
ObjectInfo obj;
obj.x1 = 0;
obj.x2 = 0;
obj.x2 = frame.cols;
obj.y2 = frame.rows;
obj.name = "pressure_meter";
resultInfo.info.push_back(obj);
pose->performance(frame, resultInfo, poseThresh, 10);
pose->drawResult(frame, resultInfo, 0, true);
}
printf("FINISHED.\n");
return 0;
}
int main() {
// test_image_file();
test_video_file();
// test_usb_camera();
// test_performance();
return 0;
}
(5)源码编译和运行
编译脚本,或者直接:bash build.sh
bash
#!/usr/bin/env bash
mkdir -p build && cd build
cmake .. -DMNN_VULKAN=ON
make -j4
sleep 1
#./video_for_pose
./Detector
- 如果你要测试CPU运行的性能,请修改src/main.cpp
DeviceType device = CPU;
- 如果你要测试GPU运行的性能,请修改src/main.cpp (需配置好OpenCL)
DeviceType device = OPENCL;
(6)Demo效果
C++ MNN版本与Python版本的指针压力表检测效果基本一致,运行效果:

下面效果图只显示了指针与零刻度线夹角/零刻度线与量程夹角的比值,指针读数时,需要将比值×量程(最大刻度),获得真实的压力表指针读数。




5. Android版本
如果你需要在Android端部署指针压力表读数模型,请参考:https://blog.csdn.net/guyuealian/article/details/168873316
指针压力表读数Android APK安装包体验效果: https://download.csdn.net/download/guyuealian/93758160

6. 项目源码下载

项目资源内容包含:
- 项目MNN C/C++源码支持CPU和GPU运行,GPU模型加速需要配置好OpenCL,可以达到实时检测效果。
- 提供YOLO11n指针压力表目标检测C++推理代码
- 提供指针压力表:零刻度线、最大刻度线、指针起点和指针终点共四个关键点检测C++推理代码
- 提供MNN模型权重文件:高精度版本HRNet-w32,以及轻量化模型LiteHRNet和Mobilenet-v2模型,配置好环境,可直接运行
- 项目C/C++源码Demo提供视频关键点去抖动算法(filter=true)
- 项目C/C++源码Demo支持图片,视频,摄像头测试