资讯详情

基于YOLO与DeepLab的仪表识别:从检测到读数的全流程解析

📅 2026/9/30 12:08:14 | 华诺云谱 👁 阅读
基于YOLO与DeepLab的仪表识别:从检测到读数的全流程解析
简介这份文档面向从事计算机视觉与深度学习落地的开发者聚焦仪表自动读数这一典型工业场景给出从检测、分割到读数计算的完整方案与踩坑记录。内容围绕仪表识别流程展开先用YOLO5s裁剪定位仪表再用Deeplab对刻度条做语义分割并椭圆拟合提取刻度曲线接着用YOLO5x掩膜提取指针并结合OTSU、Canny与HoughLine定位指针线段最后通过圆心角计算法与边计算法求取读数比例并给出误差取舍规则。文档还总结了指针分割失效、刻度拟合空洞、透视畸变等问题的成因与替代思路如仿射标准化、27×27膨胀、特例角点检测等对方案选型与排错有直接参考价值。资源包为1个docx文件约2.86MB结构紧凑适合需要快速理解仪表识别整体链路并规避常见陷阱的读者。目前已有935人学习。1. 从一张歪斜的仪表照片到精确读数这套 YOLODeepLab 方案到底怎么跑通工厂巡检拍回来的仪表照片十张里有八张是歪的光线忽明忽暗表盘上还带着油污和反光。人工抄表不仅效率低夜班还容易看走眼。这套基于 python 深度学习 的仪表识别方案核心思路是用 YOLO5s 先把仪表从整张图里框出来再用 DeepLab 分割刻度条用 YOLO5x 提取指针最后靠 OpenCV 的几何计算把读数比例算出来。它适合有一定深度学习基础、手头有标注数据、想把巡检流程自动化的工程师。整套流程不是端到端模型而是多个模型串联加后处理好处是每一步都可解释、可单独调优坏处是误差会累积对图像质量比单一模型敏感得多。下面按实际复现顺序拆开讲重点放在参数怎么设、坑在哪、结果怎么验证。2. 仪表检测与裁剪YOLO5s 训练 200 epochs 的实操细节2.1 为什么选 YOLO5s 做第一级检测第一级任务是从整张巡检图里把仪表区域框出来属于典型的目标检测问题。选 YOLO5s 而不是更大的模型原因很直接仪表在图中通常只占一个区域背景干扰有限小模型足够收敛推理速度也快方便后续串联多个模型。训练 200 epochs 是原文给出的配置实际跑下来如果数据集里仪表形态单一、背景不复杂100 epochs 左右 loss 就趋于平稳200 epochs 更多是留余量防止欠拟合。常见做法是先用预训练权重初始化再在自己的仪表数据集上微调学习率设 0.001 起步batch size 根据显存调整一般 8 或 16。2.2 训练与裁剪的代码落地# 仪表检测训练与推理裁剪示例 import cv2 import torch from pathlib import Path # 训练阶段YOLO5s 微调200 epochs # 假设使用 ultralytics 风格的训练接口 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.train( datameter_dataset.yaml, # 数据集配置含 train/val 路径和类别数 epochs200, # 原文配置实际可观察 val loss 提前停止 batch_size16, # 按显存调整显存不足降到 8 imgsz640, # 输入尺寸仪表较小可提到 1280 lr00.001, # 初始学习率 projectruns/meter # 输出目录 ) # 推理阶段检测并裁剪仪表区域 def crop_meter(img_path, model, conf_thres0.5): img cv2.imread(img_path) results model(img) boxes results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls] crops [] for box in boxes: if box[4] conf_thres: continue x1, y1, x2, y2 map(int, box[:4]) # 适当外扩避免表盘边缘被切掉 pad 10 x1 max(0, x1 - pad) y1 max(0, y1 - pad) x2 min(img.shape[1], x2 pad) y2 min(img.shape[0], y2 pad) crop img[y1:y2, x1:x2] crops.append(crop) return crops代码逻辑说明训练部分用预训练 YOLO5s 在仪表数据集上微调200 epochs 是原文配置实际可根据验证集 loss 提前停。推理部分拿到检测框后做外扩裁剪外扩 10 像素是为了防止表盘边缘被切掉导致后续分割丢刻度。参数方面conf_thres 设 0.5 是平衡漏检和误检的常用起点如果仪表被漏检就降到 0.3如果背景误检多就提到 0.6。imgsz 如果仪表在原图中占比很小建议提到 1280否则缩放后仪表细节丢失严重。提示裁剪时外扩像素不要太大否则会把背景杂物带进来影响后续分割模型的输入质量。3. 刻度语义分割与椭圆拟合DeepLab 的 27×27 膨胀和曲线提取3.1 DeepLab 分割刻度的适用边界原文用 DeepLab 对刻度条做语义分割100 epoch 时效果最佳。这里有个关键判断DeepLab 适合对个体进行区域分割也就是把刻度条作为一个整体区域分出来而不是逐个刻度分割。原文也提到对于常规仪表指针Unet 即可精确分割但 DeepLab 在个体区域分割上更强。实际选型时如果刻度条细长且连续DeepLab 的膨胀卷积能保留更多上下文如果刻度是离散的小段Unet 的跳跃连接反而更稳。训练时注意输入尺寸和原图比例刻度条太细的话下采样过程中容易丢特征常见做法是把输入分辨率提高或者在 DeepLab 里加空间注意力机制但原文没引入注意力而是靠后处理弥补。3.2 从 mask 到拟合曲线的完整步骤import cv2 import numpy as np def fit_scale_curve(mask): # mask: DeepLab 输出的刻度二值图0/255 # 步骤 a27*27 膨胀填补空洞 kernel np.ones((27, 27), np.uint8) dilated cv2.dilate(mask, kernel, iterations1) # 步骤 b椭圆拟合 contours, _ cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 取最大轮廓避免噪点干扰 cnt max(contours, keycv2.contourArea) ellipse cv2.fitEllipse(cnt) (XC, YC), (MA, ma), angle ellipse # 圆心和长短轴 R max(MA, ma) / 2.0 # 步骤 d制作椭圆边 mask edge_mask np.zeros_like(mask) cv2.ellipse(edge_mask, ellipse, 255, 2) # 线宽 2代表椭圆边 # 步骤 e边 mask 与膨胀图交运算得到刻度拟合曲线 curve cv2.bitwise_and(edge_mask, dilated) # 步骤 f3*3 卷积核遍历找端点 # 目标角点特征001 010 000原文定义 corner_kernel np.array([[0, 0, 1], [0, 1, 0], [0, 0, 0]], dtypenp.uint8) corners [] h, w curve.shape for y in range(1, h - 1): for x in range(1, w - 1): patch (curve[y-1:y2, x-1:x2] 0).astype(np.uint8) if np.array_equal(patch, corner_kernel): corners.append((x, y)) # 步骤 g计算曲线长度 ys, xs np.where(curve 0) points list(zip(xs.tolist(), ys.tolist())) length len(points) return { XC: XC, YC: YC, R: R, corners: corners, points: points, length: length, curve: curve }逻辑说明27×27 膨胀是原文反复强调的关键参数目的是填补分割空洞让椭圆拟合不被断裂的刻度干扰。膨胀核越大空洞填补越彻底但曲线偏移也越大所以后面必须做偏移补偿。椭圆拟合用 OpenCV 的 fitEllipse拿到圆心和长短轴。边 mask 与膨胀图交运算得到一段弧这段弧就是刻度的替代曲线。角点检测没有用 Harris 或 Sobel因为原文试过结果太杂筛选复杂所以自定义了 3×3 卷积核匹配特定角点特征。参数方面膨胀核 27×27 是原文实测值如果刻度更粗或图像分辨率更高可以适当加大但偏移补偿要同步调整。注意椭圆拟合前一定要取最大轮廓否则噪点会产生多个小轮廓拟合出的椭圆完全偏离表盘。4. 指针提取与读数计算YOLO5x 加 HoughLine 的避坑记录4.1 指针提取为什么放弃分割改用检测原文明确写了DeepLab 对指针分割结果几乎为 0原因是这次仪表的指针非常规指针与表盘中间空洞在分割特征上类似DeepLab 卷积尺度深特征直接被卷掉。所以改用 YOLO5x 做目标检测最佳结果在 90-100 epoch。YOLO 的双注意力机制对目标识别更准拿到指针区域后做掩膜只保留指针部分。然后 OTSU 二值化加 Canny 边缘检测再用 HoughLine 找最长线段。这里有个血泪经验最开始用二值图做直线拟合结果光线和污渍直接影响拟合趋势后来改成轮廓检测加 Hough 变换测试组图像指针定位才正常。4.2 读数比例计算的两种方法和误差控制import numpy as np def compute_reading(point_axis, XC, YC, pt1, pt2, corner_1, corner_2, length, dilate_kernel_size27): # 步骤 1定位指针指向的刻度点 def dist(p, q): return np.sqrt((p[0]-q[0])**2 (p[1]-q[1])**2) corner min(point_axis, keylambda pt: min(dist(pt, pt1), dist(pt, pt2))) # 步骤 2a圆心角计算法 def line_len(p, q): return dist(p, q) line_C_1 line_len((XC, YC), corner_1) line_C_2 line_len((XC, YC), corner_2) line_C_c line_len((XC, YC), corner) line_c_1 line_len(corner, corner_1) line_c_2 line_len(corner, corner_2) def cos_angle(a, b, c): # 余弦定理a/b/c 为三边长度返回角 c 对应的角度 val (a*a b*b - c*c) / (2*a*b) val np.clip(val, -1.0, 1.0) return np.arccos(val) theta_1_C_c cos_angle(line_C_1, line_C_c, line_c_1) theta_2_C_c cos_angle(line_C_2, line_C_c, line_c_2) theta_1_C_2 cos_angle(line_C_1, line_C_2, line_c_1 line_c_2) theta_s min(theta_1_C_c, theta_2_C_c) theta_m max(theta_1_C_c, theta_2_C_c) # 偏移量计算膨胀导致的误差用等腰三角形近似 def bias(line_C): dis line_C half_k dilate_kernel_size / 2.0 val (2*dis*dis - half_k*half_k) / (2*dis*dis) val np.clip(val, -1.0, 1.0) return np.arccos(val) bias_s bias(line_C_1 if theta_1_C_c theta_2_C_c else line_C_2) bias_m bias(line_C_2 if theta_1_C_c theta_2_C_c else line_C_1) ratio_a (2*np.pi - theta_1_C_c - bias_m) / (2*np.pi - theta_1_C_2 - bias_s - bias_m) # 步骤 2b边计算法 def slope(p, q): if q[0] p[0]: return float(inf) return (q[1]-p[1]) / (q[0]-p[0]) k_1_C slope((XC, YC), pt1) k_2_C slope((XC, YC), pt2) def side_of_line(p, p1, p2): # 返回点在直线哪一侧用于筛选点集 return (p[0]-p1[0])*(p2[1]-p1[1]) - (p[1]-p1[1])*(p2[0]-p1[0]) points_1_C [pt for pt in point_axis if side_of_line(pt, (XC, YC), pt1) * (1 if k_1_C 0 else -1) 0] points_2_C [pt for pt in point_axis if side_of_line(pt, (XC, YC), pt2) * (1 if k_2_C 0 else -1) 0] points_c_C [pt for pt in point_axis if side_of_line(pt, (XC, YC), corner) 0] common set(points_1_C) set(points_2_C) set(points_c_C) ratio_b 1 - len(common) / length # 步骤 3误差判断 if abs(ratio_a - ratio_b) 0.02: return (ratio_a ratio_b) / 2.0 else: return max(ratio_a, ratio_b)逻辑说明定位指针指向的刻度点是遍历所有刻度点找离指针线段两个端点最近的那个点。圆心角计算法用余弦定理算三个夹角再减去膨胀导致的偏移量偏移量用等腰三角形近似底边是膨胀核的一半。边计算法用斜率判断点在直线上下方取交集后算比例。最后两种方法误差小于 0.02 取平均否则取最大值。参数方面dilate_kernel_size 必须和前面膨胀用的核大小一致否则偏移补偿完全错位。0.02 的阈值对应约一个小刻度原文测试组里误差超过两个小刻度0.04的有 6 张图说明这个阈值是合理的分界线。4.3 避坑与常见问题排查现象一DeepLab 分割指针结果几乎为 0。原因是指针与表盘空洞特征相似DeepLab 卷积尺度深特征被卷掉。解决方法是改用 YOLO5x 做指针检测不要硬调 DeepLab 参数。现象二椭圆拟合出的曲线不连续出现多个角点。原因是膨胀后仍有空洞或边缘毛凸。解决方法是用自适应卷积核遍历各端点两两端点形成矩形区域如果区域内 255 点数量大于 2说明属于同一条曲线否则是相邻不同曲线据此筛选角点组合。现象三读数误差超过两个小刻度。原因是图像倾斜过大刻度被遮挡或者分割结果大面积不连续。解决方法是先做仿射变换用标准仪表 template 对裁剪图做标准化再做后续分割和拟合。原文 2021-2-20 的优化就是加了这一步精度明显提升。现象四原图中目标体越小误差越大。原因是裁剪出的仪表图分辨率低分割网络识别率下降。解决方法是提高 YOLO 输入尺寸或者对裁剪图做超分辨率预处理但更根本的是调整分割网络结构提高小目标识别率。现象五HoughLine 检测到的指针线段不稳定。原因是光线和污渍影响二值图。解决方法是放弃直线拟合改用轮廓检测加 Hough 变换并且对二值图先做 OTSU 自适应阈值减少光照影响。提示测试组 124 张图里5 张无法通过模型9 张分割错误25 张倾斜过大这些数据说明图像预处理和标准化不是可选项是必选项。5. 仿射变换标准化与角点检测特例把误差压到一个小刻度内的技巧原文在 2021-2-20 的优化里加了一个关键步骤检测到仪表后事先选定一张最标准的仪表图作为 template用这个 template 对 YOLO 检测出的每个裁剪图做仿射变换把图像尽可能标准化。这一步对偏差离散的抑制效果很明显精度提升比较明显。仿射变换的核心是找三对对应点通常选表盘圆心、刻度起点、刻度终点然后用 OpenCV 的 getAffineTransform 和 warpAffine 做变换。template 的选择有讲究要选光照均匀、刻度清晰、没有遮挡的那张否则标准化会把其他图带偏。角点检测这块原文试过 Harris 和 Sobel结果太杂筛选复杂所以自定义了 3×3 卷积核匹配特定角点特征 001010000。这个特例检测的好处是目标明确只找这一种角点输出结果符合预期。实际用的时候如果角点特征因为图像旋转变了需要先把图像转正或者定义多个旋转版本的角点特征。不连续曲线的角点组合筛选用自适应卷积核两两端点形成矩形区域区域内 255 点数量大于 2 就是同一条曲线否则是不同曲线这个方法在测试组里把多个角点的问题解决了。验证方法上建议用测试组图像跑一遍统计误差分布。原文测试组 124 张误差超过两个小刻度的 6 张分割不连续导致拟合出错的 3 张定点算法受干扰的 1 张。如果自己的测试组误差集中在某个区间优先检查那个区间的图像是不是倾斜过大或者分割结果有空洞。我一般会先把所有测试图的仿射变换结果可视化一遍确认标准化没问题再跑后续流程。从那以后我每次做仪表识别项目都强制先做 template 仿射标准化再跑分割和拟合这个习惯帮我省了很多返工时间。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑