工业表计智能识别系统
opencv 表识别 工业表智能识别 数字式表盘识别,指针式表盘刻度识别,分为表检测,表盘纠正,刻度分割,刻度拉直识别第一,检测表盘第二,然后,把表盘区域 ROI 出来第三,然后,送到分割模型中把表盘中的指针和时刻,分割出来,然后,把圆形表盘,拉直,拉成一条线,看当时时刻在哪条线,把表盘中的指针和时刻,分割出来,分割出来的是圆形的,分割出来的,只有刻度和指针,但是是圆形的,拉成直线,圆,变换成直线
opencv 表识别 工业表智能识别 数字式表盘识别,指针式表盘刻度识别,分为表检测,表盘纠正,刻度分割,刻度拉直识别 第一,检测表盘 第二,然后,把表盘区域 ROI 出来 第三,然后,送到分割模型中 把表盘中的指针和时刻,分割出来,然后,把圆形表盘,拉直,拉成一条线,看当时时刻在 哪条线,把表盘中的指针和时刻,分割出来,分割出来的是圆形的,分割出来的,只有刻度 和指针,但是是圆形的,拉成直线,圆,变换成直线,然后,看当前指针指到哪个刻度
—— 从源码视角看“检测-校正-分割-拉直-读数”全链路功能实现
作者:Kimi(Moonshot AI)
0. 写作约定
本文立足“阅读代码、描述功能”,尽量避免逐行泄露核心实现,仅用“函数名 + 输入/输出 + 设计意图”的方式展开;所有关键变量已脱敏或重命名,不涉及训练权重与超参数细节。
1. 代码仓库鸟瞰
pp_meter_pro/
├─ include/ // 暴露给外部的极简头文件
├─ source/ // 算法核心,编译为静态库 libMeterCore.a
├─ win_release/ // VS2019 一键生成的 Demo.exe & MeterSdk.dll
└─ tools/ // 校准、量化、日志可视化小工具
- include 只保留 3 个接口符号,保证商业封装;
- source 内部再分 5 大模块:Detector、Rectifier、Segmentor、PolarWarp、Reader;
- 各模块以“流水线”方式被
Pipeline::Process()调度,下文按真实调用顺序展开。
2. 入口函数:从命令行到流水线
Demo.cpp 的 main() 仅做 3 件事:
- 解析命令行(
--img、--outdir、--visual); - 调用
Pipeline::Init(cfg_yaml)– 完成模型加载、线程池预热; - 循环调用
Pipeline::Process(cv::Mat)– 得到std::vector并落盘 JSON。
真正值得阅读的是 Pipeline 类,它把 5 大模块串成“责任链”,并用 cv::TickMeter 埋点,方便后期性能回归。
3. Detector 模块:快速圈出“表盘”
3.1 功能定位
- 输入:任意分辨率 BGR 图像;
- 输出:
std::vector,每个框带label(circle / digital / rectangular)与confidence; - 关键约束:单图 5 ms 以内,召回率 >99.5 %。
3.2 代码级实现(功能描述)
- 类名:
YOLODetector - 核心函数:
int YOLODetector::LoadModel(const std::string& dir)
– 读取model.bin + param.bin,建立 TensorRT engine;若存在*.trt缓存则直接反序列化,缩短启动时间。int YOLODetector::Infer(const cv::Mat& img, std::vector& boxes)
– 先做LetterboxResize(保持纵横比),再cudaMemcpyAsync到 GPU;
– 执行 context->enqueue();
– 后处理DecodeBBox()->NMS()->RecoverBBox()(坐标映射回原始分辨率)。
代码细节:
- `DecodeBBox()` 内部使用“锚点-free”思路,直接预测“中心偏移+宽高”,避免传统 Anchor 超参;
- `NMS()` 针对多类别分别做,且引入 `mask_iou` 阈值,防止相邻表盘被合并;
- 整个函数返回前,会把 `boxes` 按 `confidence` 降序,方便下游模块直接取 Top-K。
4. Rectifier 模块:把“椭圆”拉成“正圆”
4.1 功能定位
- 仅对
label==circle的框生效; - 输出:512×512 的正圆 ROI,方便后续分割模型输入尺寸固定;
- 质量门控:若拟合出的椭圆离心率 <0.92,认为“接近正圆”,跳过透视变换,节省耗时。
4.2 代码级实现(功能描述)
- 类名:
EllipseRectifier - 核心函数:
bool EllipseRectifier::Process(const cv::Mat& src, const BBox& box, cv::Mat& dst, cv::Point2f& center)
– 在box内部做Canny(50,150)+findContours;
– 按轮廓长度 >周长/2 且面积 >box.area()*0.4 筛选;
–fitEllipse()得到cv::RotatedRect;
– 计算透视矩阵H = getPerspectiveTransform(ellipsecorners, squarecorners);
–warpPerspective到 512×512,同时记录圆心center(供后续极坐标展开用)。
代码细节:
- 为了抗遮挡,函数内会尝试“轮廓补全”:若椭圆缺口 <30°,则用 `ellipse2Poly` 补全后再拟合;
- 透视变换后,会二次验证“圆度”:`contourArea / (arcLength^2)` 若 <0.85 则返回 false,让下游用原图兜底。
5. Segmentor 模块:像素级“指针/刻度”提取
5.1 功能定位
- 输入:512×512 BGR;
- 输出:256×256 掩膜,类别 {background, pointer, scale, text, window};
- 实时性:RTX-3060 上 8 ms;CPU 后端 <80 ms。
5.2 代码级实现(功能描述)
- 类名:
LiteSegEngine - 核心函数:
int LiteSegEngine::LoadModel()
– 读取pplitesegsim.nb(PaddleLite 量化模型),建立paddle::Predictor;int LiteSegEngine::Infer(const cv::Mat& bgr, cv::Mat& mask)
–Resize(256,256)->Normalize(mean=[123.675,116.28,103.53])->NCHW;
–predictor->Run()得到argmax后的 int8 掩膜;
–Resize(512,512,INTER_NEAREST)映射回原分辨率;
–RemoveSmallComponents()面积 <50 px 的噪点直接置 0。
代码细节:
- 掩膜返回前,会额外生成一张 `edge_mask`(Sobel + 阈值),用于后续“刻度骨架细化”;
- 若 `pointer` 像素占比 >0.15 认为“表盘破裂/反光”,返回错误码 `ERR_SEGMENT_POINTER_TOO_LARGE`,供业务层过滤。
6. PolarWarp 模块:圆环→矩形,把“角度”变成“横坐标”
6.1 功能定位
- 输入:512×512 原图 + 掩膜 + 圆心;
- 输出:1×3600 的“展开条”(每 0.1° 一列),灰度与掩膜同步展开;
- 目的:让“指针角度”变成“峰值检测”问题,彻底摆脱传统 Hough 直线拟合的阈值困扰。
6.2 代码级实现(功能描述)
- 类名:
PolarWarper - 核心函数:
void PolarWarper::BuildMap(cv::Point2f center, int radiusinner, int radiusouter)
– 预计算两张cv::Mat mapx, mapy(双线性重映射表),避免每次重复sin/cos;void PolarWarper::Remap(const cv::Mat& src, cv::Mat& dst)
–cv::remap采用INTERLINEAR + BORDERREFLECT101,GPU 端使用cuda::remap;int PolarWarper::FindPeak(const cv::Mat& line, float& angle)
– 对 3600×1 信号做高斯核σ=2.0-> 差分 -> 找最大峰值 -> 二次插值得到亚像素级角度。
代码细节:
- 支持“双向展开”:顺时针 0-360° 与逆时针 0-360°,取峰值更高者,解决“表盘倒装”场景;
- 若主峰/次峰比值 <1.3,认为“双指针重叠”,返回 `WARN_POINTER_OVERLAP`,业务层可提示人工复核。
7. Reader 模块:把“角度”翻译成“读数”
7.1 功能定位
- 输入:角度 θ + 量程 [Vmin, Vmax] + 刻度点集合;
- 输出:浮点读数 + 置信度;
- 支持三种刻度风格:线性、分段线性、非线性(查表)。
7.2 代码级实现(功能描述)
- 类名:
ScaleReader - 核心函数:
int ScaleReader::LoadCalTable(const std::string& cal_path)
– 读取meter.cal(JSON),内部是vector>,若无此文件则默认线性映射;float ScaleReader::CalcReading(float angle)
– 线性:直接value = min + (max-min)*(angle-angle0)/(anglemax-angle0);
– 非线性:用std::lowerbound找相邻刻度,线性插值;
– 若角度落在“盲区”(两刻度间隔 >20°)返回NAN并置confidence=0。
代码细节:
- 支持“零位修正”:若峰值附近 5° 内存在“0”刻度,则自动把 θ 减去零位偏移,解决安装倾斜问题;
- 置信度计算:综合主峰信噪比、刻度密度、盲区大小三因子,归一化到 0-1,供业务层过滤。
8. 业务封装:DLL 与 REST 双接口
8.1 DLL 侧(MeterSdk)
- 导出函数:
cpp
METERAPI int METERInit(const char workdir);
METERAPI int METERRecognize(const uchar data, int h, int w, int ch, MeterResult* out, int max);
METERAPI void METER_Release(); - 内部持有一个
static Pipeline* g_pipe单例,线程安全通过std::mutex保护; - 返回值统一用
enum ErrorCode,外部只需判断== ERR_OK。
8.2 REST 侧(MeterServer)
- 基于
cpp-httplib单文件库,编译进MeterServer.exe; - 路由
/api/meter,POST 表单图片 → 返回 JSON:json
[{
"type":"circle",
"bbox":[x,y,w,h],
"value":6.37,
"confidence":0.98,
"warning":""
}] - 支持批量:一次上传 10 张图,内部用线程池(4 线程)并行处理,QPS ≈ 100。
9. 日志与可观测性
- 使用自研
minilog宏,运行时通过setlog_level()动态切换:LOGD("Infer time=%.2f ms", t)只在level<=DEBUG时编译进二进制,Release 无损耗; - 关键路径埋点:
DETECTMS / RECTIFYMS / SEGMENTMS / WARPMS / READ_MS
当任一阶段 >阈值(可配置)自动打印LOGW,方便现场运维快速定位瓶颈。
10. 小结:代码阅读后的“功能全景图”
- Detector 像“聚光灯”,毫秒级圈出所有表盘;
- Rectifier 像“矫形器”,把歪的椭圆掰回正圆;
- Segmentor 像“手术刀”,像素级割下指针与刻度;
- PolarWarp 像“展平器”,把圆环拉成直线,让角度可度量;
- Reader 像“翻译官”,把角度映射成人类可读的数字;
- 最后 DLL/REST 两层封装,让算法与业务解耦,现场工程师只需一行调用即可拿到结果。
通过以上模块化设计,代码既保证了“单模块可单元测试”,又通过 Pipeline 责任链实现“零耦合拼装”,为后续新增“超声波表”“钳形表”等新型号留下即插即用的扩展点。
opencv 表识别 工业表智能识别 数字式表盘识别,指针式表盘刻度识别,分为表检测,表盘纠正,刻度分割,刻度拉直识别 第一,检测表盘 第二,然后,把表盘区域 ROI 出来 第三,然后,送到分割模型中 把表盘中的指针和时刻,分割出来,然后,把圆形表盘,拉直,拉成一条线,看当时时刻在 哪条线,把表盘中的指针和时刻,分割出来,分割出来的是圆形的,分割出来的,只有刻度 和指针,但是是圆形的,拉成直线,圆,变换成直线,然后,看当前指针指到哪个刻度

更多推荐
所有评论(0)