简介本资源是一份面向工业智能化巡检场景的Python工程实践方案聚焦指针式模拟仪表读数自动识别这一典型视觉检测难题适用于计算机视觉初学者、自动化专业学生及石油、电力等行业现场工程师。资源以硕士论文PDF形式呈现完整涵盖基于DeepLabV3的表盘轮廓与数字区域语义分割、Tesseract OCR量程识别、梯度霍夫变换圆心/指针定位、角度法读数计算等核心算法实现并配套图像预处理灰度化、Otsu阈值、连通域标记与误差分析正常/倾斜/低照度场景下相对误差均≤3%。压缩包仅含1个15.24MB的PDF文件内容结构清晰包含系统设计原理、实验对比数据、可视化软件功能说明支持读数存储、趋势图绘制、仪表信息管理可直接用于课程设计参考、毕设复现或工业轻量化部署验证。目前已有184人学习下载。1. 为什么仪表盘图像一进模型就“失明”Python自动读数识别不是OCR套壳而是光路、刻度、指针三重校准的工程闭环某高校实验室在做工业设备远程巡检Demo时卡在最后一步摄像头拍到的压力表、温度表图像用现成OCR工具一跑90%的结果是乱码或空值。不是模型不准是根本没对上焦——传统OCR默认处理的是印刷体文字而模拟仪表盘上没有“字”只有弧形刻度线、旋转指针、反光玻璃罩、不规则表盘边框。这个标题说的“基于Python的模拟仪表读数自动识别系统”本质是一套面向物理世界成像缺陷的视觉闭环方案先用图像预处理把扭曲的表盘“掰直”再用几何约束定位指针轴心与刻度起止点最后用角度映射把像素偏移转成真实物理量比如指针偏转137°对应压力值2.45MPa。它适合两类人一是产线需要替代人工抄表的自动化工程师二是做毕业设计/课程项目但被“指针检测不准”反复折磨的学生。你不需要从零训练大模型但必须亲手调参每一张图的二值化阈值、霍夫圆检测半径、指针延长线交点容差——这活儿没法全自动但能稳定复现。2. 从原始图像到刻度归一化四步预处理链必须手调不存在“一键增强”仪表图像识别失败80%源于预处理没打穿物理成像链路。光照不均、玻璃反光、镜头畸变、表盘倾斜——这些不是噪声是必须建模的物理变量。我一般用OpenCV构建一个可调试的四步流水线每步输出中间图用于肉眼验证拒绝黑匣子式端到端。2.1 去反光局部对比度拉伸用CLAHE对抗玻璃眩光模拟仪表常带弧形玻璃罩强光下中心区域过曝成白块直接导致指针消失。全局直方图均衡会放大噪声必须用限制对比度的自适应直方图均衡CLAHEimport cv2 import numpy as np def remove_glass_reflection(img): # 转灰度并去噪 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) denoised cv2.fastNlMeansDenoising(gray, None, 10, 7, 21) # CLAHE增强裁剪限制2.0网格大小8x8适配常见表盘分辨率 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) enhanced clahe.apply(denoised) return enhanced # 使用示例 raw_img cv2.imread(pressure_gauge.jpg) preprocessed remove_glass_reflection(raw_img) cv2.imwrite(step1_clahe.jpg, preprocessed)参数说明clipLimit2.0是血泪经验——超过3.0会把指针边缘炸出伪影tileGridSize(8,8)对应640×480图像效果最佳若输入为1920×1080需改为(16,16)。关键逻辑CLAHE不是“提亮”而是让暗区细节如指针根部阴影和亮区纹理如刻度短线同时可分辨。2.2 表盘ROI粗定位霍夫圆检测必须加半径约束与面积过滤无约束的霍夫圆检测在复杂背景中会返回几十个圆必须用物理先验压缩搜索空间。典型压力表直径占图像宽的30%~60%且圆心必在图像中下部因安装位置固定def detect_dial_circle(img, min_radius_ratio0.25, max_radius_ratio0.55): h, w img.shape[:2] # 高斯模糊降噪避免边缘断裂 blurred cv2.GaussianBlur(img, (9,9), 0) # Canny边缘检测 edges cv2.Canny(blurred, 50, 150, apertureSize3) # 霍夫圆检测minRadius/maxRadius按图像尺寸动态计算 min_r int(min_radius_ratio * min(h, w)) max_r int(max_radius_ratio * min(h, w)) circles cv2.HoughCircles( edges, cv2.HOUGH_GRADIENT, dp1, minDist50, param1100, param230, minRadiusmin_r, maxRadiusmax_r ) if circles is not None: circles np.round(circles[0, :]).astype(int) # 过滤只保留圆心在图像下半部、且面积最大的圆 valid_circles [ c for c in circles if c[1] h//2 and c[0] w//4 and c[0] 3*w//4 ] if valid_circles: # 取面积最大者半径平方最大 best_circle max(valid_circles, keylambda x: x[2]**2) return best_circle # (cx, cy, r) raise ValueError(未检测到有效表盘圆请检查图像质量或调整半径范围) # 使用示例 circle detect_dial_circle(preprocessed) cx, cy, r circle # 在原图上画圆验证 vis_img cv2.cvtColor(preprocessed, cv2.COLOR_GRAY2BGR) cv2.circle(vis_img, (cx,cy), r, (0,255,0), 2) cv2.imwrite(step2_circle.jpg, vis_img)关键逻辑param230是累加器阈值太小会漏检指针遮挡导致圆弧不连续太大则误检表盘螺丝孔被当圆minDist50强制圆心间距避免同一表盘返回多个同心圆。失败时优先调param2而非param1——后者影响边缘检测强度易破坏指针细线。2.3 刻度归一化用极坐标变换把环形刻度“拉直”成直线序列传统方法在笛卡尔坐标系下拟合弧形刻度线极其困难。正确做法是将图像以表盘圆心为原点做极坐标变换此时所有刻度线变为垂直平行线指针变为水平线段def polar_transform(img, center, radius, angle_range(-135, 135)): cx, cy center r radius # 构建极坐标映射r_maxradiustheta_maxangle_range跨度 theta_min, theta_max np.radians(angle_range[0]), np.radians(angle_range[1]) theta_span theta_max - theta_min r_max r # 目标图像尺寸宽度角度跨度像素级高度半径 width int(theta_span * r_max) # 约240px height r_max # 构建映射矩阵 theta_map np.linspace(theta_min, theta_max, width) r_map np.linspace(0, r_max, height) Theta, R np.meshgrid(theta_map, r_map) # 笛卡尔坐标反变换 X cx R * np.cos(Theta) Y cy R * np.sin(Theta) # 插值生成极坐标图 polar_img cv2.remap( img, X.astype(np.float32), Y.astype(np.float32), interpolationcv2.INTER_LINEAR ) return polar_img # 使用示例 polar_img polar_transform(preprocessed, (cx,cy), r) cv2.imwrite(step3_polar.jpg, polar_img)参数说明angle_range(-135,135)对应标准压力表有效量程-135°到135°若为温度表需改为(-90,90)widthint(theta_span * r_max)确保角度分辨率≈1px/度避免刻度线在极坐标图中粘连。此步后刻度线在图像中呈清晰竖线可用HoughLinesP直接检测。2.4 刻度线精定位HoughLinesP检测聚类合并拒绝单条线思维极坐标图中刻度线是近似平行的竖线但因图像噪声和量化误差同一条刻度可能被检测为多段短线。必须用距离聚类合并def detect_scale_lines(polar_img): # 边缘检测 edges cv2.Canny(polar_img, 50, 150, apertureSize3) # 检测线段只检测接近垂直的线 lines cv2.HoughLinesP( edges, rho1, thetanp.pi/180, threshold30, minLineLength10, maxLineGap5 ) if lines is None: raise ValueError(未检测到刻度线请检查极坐标图质量) # 提取所有线段的x坐标竖线x基本不变 x_coords [] for line in lines: x1, y1, x2, y2 line[0] x_coords.append((x1 x2) // 2) # DBSCAN聚类eps3表示x坐标差3px视为同一条刻度 from sklearn.cluster import DBSCAN X np.array(x_coords).reshape(-1,1) clustering DBSCAN(eps3, min_samples2).fit(X) # 取每簇中心作为刻度位置 scale_positions [] for cluster_id in set(clustering.labels_): if cluster_id ! -1: # -1为噪声点 cluster_x X[clustering.labels_ cluster_id] scale_positions.append(int(np.mean(cluster_x))) return sorted(scale_positions) # 使用示例 scale_x_list detect_scale_lines(polar_img) print(f检测到{len(scale_x_list)}条刻度线位置{scale_x_list})关键逻辑minLineLength10过滤掉噪声短线eps3是刻度线像素宽度的经验值若表盘分辨率高如4K图需调至5。聚类后得到的是极坐标图中的x坐标需通过x / width * theta_span theta_min换算回实际角度。3. 指针检测的三种路径霍夫线变换失效时用模板匹配方向滤波救场指针检测是整个系统的咽喉。霍夫线变换HoughLines在指针细长、对比度低时极易失效——它假设指针是完整直线但实际图像中指针常被表盘文字遮挡、末端模糊、或存在反光高亮斑。必须准备至少两种后备方案。3.1 主力方案霍夫线变换长度/角度双过滤这是最轻量的方案适用于指针清晰、背景干净的场景def detect_needle_hough(img, center, radius): cx, cy center # 裁剪圆形ROI减少干扰 mask np.zeros(img.shape, dtypenp.uint8) cv2.circle(mask, (cx,cy), radius, 255, -1) roi cv2.bitwise_and(img, img, maskmask) # Canny边缘检测 edges cv2.Canny(roi, 50, 150, apertureSize3) # 霍夫线变换 lines cv2.HoughLines(edges, 1, np.pi/180, threshold80) if lines is None: return None # 过滤只保留过圆心的长线指针必过圆心 valid_lines [] for rho, theta in lines[:,0]: # 计算直线到圆心距离 dist_to_center abs(rho - cx*np.cos(theta) - cy*np.sin(theta)) # 要求距离5px且线长0.7*radius if dist_to_center 5 and rho 0.7 * radius: valid_lines.append((rho, theta)) if not valid_lines: return None # 取最长线rho最大者 best_line max(valid_lines, keylambda x: x[0]) rho, theta best_line # 计算指针端点从圆心沿theta方向延伸radius长度 x1 int(cx radius * np.cos(theta)) y1 int(cy radius * np.sin(theta)) x2 int(cx - radius * np.cos(theta)) y2 int(cy - radius * np.sin(theta)) return (x1,y1,x2,y2), theta # 使用示例 line, theta detect_needle_hough(preprocessed, (cx,cy), r) if line: vis_img cv2.cvtColor(preprocessed, cv2.COLOR_GRAY2BGR) cv2.line(vis_img, line[0], line[1], (0,0,255), 2) cv2.imwrite(step4_needle_hough.jpg, vis_img)参数说明threshold80是累加器阈值低于60会引入大量伪线dist_to_center5是核心过滤条件——指针物理上必过轴心此约束可剔除90%干扰线。若返回None立即切到备用方案。3.2 备用方案1模板匹配方向梯度滤波指针模糊时首选当指针边缘发虚Canny无法提取连续边缘时用指针灰度模板在ROI内滑动匹配再用Sobel梯度方向验证是否符合指针走向def detect_needle_template(img, center, radius): cx, cy center # 构造指针模板黑色背景白色细长矩形模拟指针 template_size (int(radius*0.8), 3) # 长度0.8r宽度3px template np.zeros(template_size, dtypenp.uint8) cv2.line(template, (0,1), (template_size[0]-1,1), 255, 1) # 在圆形ROI内多角度旋转模板匹配 best_match None best_score 0 angles range(-135, 136, 5) # 每5度试一次 for angle in angles: # 旋转模板 M cv2.getRotationMatrix2D((template_size[0]//2, template_size[1]//2), angle, 1) rotated_t cv2.warpAffine(template, M, template_size) # 在ROI内匹配 roi img[max(0,cy-radius):min(img.shape[0],cyradius), max(0,cx-radius):min(img.shape[1],cxradius)] res cv2.matchTemplate(roi, rotated_t, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) if max_val best_score: best_score max_val best_match (max_loc, angle, rotated_t.shape) if best_score 0.45: # 匹配阈值 return None # 用Sobel梯度方向二次验证指针区域梯度方向应集中于匹配角度±10° x, y best_match[0] h, w best_match[2] patch img[y:yh, x:xw] sobelx cv2.Sobel(patch, cv2.CV_64F, 1, 0, ksize3) sobely cv2.Sobel(patch, cv2.CV_64F, 0, 1, ksize3) angles_patch np.arctan2(sobely, sobelx) * 180 / np.pi # 统计梯度方向分布 hist, _ np.histogram(angles_patch, bins36, range(-180,180)) peak_angle (-180 np.argmax(hist) * 10) # bin宽度10° if abs(peak_angle - best_match[1]) 15: return None # 返回指针端点从圆心沿匹配角度延伸 theta_rad np.radians(best_match[1]) x1 int(cx radius * np.cos(theta_rad)) y1 int(cy radius * np.sin(theta_rad)) x2 int(cx - radius * np.cos(theta_rad)) y2 int(cy - radius * np.sin(theta_rad)) return (x1,y1,x2,y2), np.radians(best_match[1]) # 使用示例 line, theta detect_needle_template(preprocessed, (cx,cy), r)关键逻辑best_score 0.45是经验值低于0.4匹配不可信梯度方向验证是防伪关键——模板可能匹配到表盘文字但文字区域梯度方向杂乱而指针区域梯度必然沿长度方向集中。此方案计算量大但鲁棒性远超霍夫线。3.3 备用方案2轮廓分析最小外接矩形指针有颜色或高亮时若指针为红色/荧光色或存在镜面高亮可转HSV空间用颜色阈值分割def detect_needle_contour(img_bgr, center, radius): cx, cy center # HSV空间分割以红色指针为例 hsv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) # 红色在HSV中跨0°需分两段 lower_red1 np.array([0, 50, 50]) upper_red1 np.array([10, 255, 255]) lower_red2 np.array([170, 50, 50]) upper_red2 np.array([180, 255, 255]) mask1 cv2.inRange(hsv, lower_red1, upper_red1) mask2 cv2.inRange(hsv, lower_red2, upper_red2) mask cv2.bitwise_or(mask1, mask2) # 形态学闭运算连接断点 kernel np.ones((3,3), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 轮廓检测 contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 找最长轮廓指针应是最长的细长物 longest_contour max(contours, keycv2.contourArea) # 最小外接矩形 rect cv2.minAreaRect(longest_contour) box cv2.boxPoints(rect) box np.int0(box) # 矩形中心应接近表盘圆心且长宽比5指针细长 center_rect np.mean(box, axis0) if np.linalg.norm(center_rect - np.array([cx,cy])) radius*0.3: return None if rect[1][0] / rect[1][1] 5 and rect[1][1] / rect[1][0] 5: return None # 矩形角度即指针方向 angle np.radians(rect[2] if rect[1][0] rect[1][1] else rect[2]90) # 计算端点 x1 int(cx radius * np.cos(angle)) y1 int(cy radius * np.sin(angle)) x2 int(cx - radius * np.cos(angle)) y2 int(cy - radius * np.sin(angle)) return (x1,y1,x2,y2), angle适用场景此方案在指针有明确颜色特征如消防压力表红针或存在强高亮金属指针反光时效果最好但需提前标定HSV阈值。建议用cv2.imshow实时调试mask图像。4. 读数计算与标定物理量映射不是线性插值而是带零点偏移的仿射变换检测到指针角度和刻度位置后90%的人直接用angle / max_angle * full_scale计算读数——这会导致系统性偏差。真实仪表存在三大非理想因素零点偏移指针不指0、量程非线性如弹簧管压力表在低压区灵敏度低、安装倾斜表盘未垂直导致角度缩放。必须用至少两个已知刻度点做仿射标定。4.1 刻度点标定用已知物理值建立角度-读数映射表假设压力表量程0~10MPa刻度线对应0、2、4、6、8、10MPa。需手动标注其中至少两个点的角度推荐0和满量程点def calibrate_scale(scale_positions, angle_range, physical_values): scale_positions: 极坐标图中刻度x坐标列表 [x0, x1, x2, ...] angle_range: 表盘有效角度范围 (min_theta, max_theta) 单位度 physical_values: 对应物理值列表 [0, 2, 4, 6, 8, 10] theta_min, theta_max np.radians(angle_range[0]), np.radians(angle_range[1]) theta_span theta_max - theta_min # 将刻度x坐标归一化到[0,1]再映射到角度范围 x_norm np.array(scale_positions) / len(scale_positions) # 粗略归一 # 更精确做法用x坐标线性插值得到角度 thetas theta_min x_norm * theta_span # 构建角度-物理值映射用已知点做线性回归 # 假设我们标定了x0-0MPa, x5-10MPa known_indices [0, len(scale_positions)-1] # 第一个和最后一个刻度 known_thetas [thetas[i] for i in known_indices] known_values [physical_values[i] for i in known_indices] # 仿射变换value a * theta b A np.vstack([known_thetas, np.ones(len(known_thetas))]).T a, b np.linalg.lstsq(A, known_values, rcondNone)[0] return a, b # 使用示例假设检测到6条刻度线对应0,2,4,6,8,10MPa scale_x [20, 65, 110, 155, 200, 245] # 极坐标图中x坐标 a, b calibrate_scale(scale_x, (-135,135), [0,2,4,6,8,10]) print(f标定参数a{a:.4f}, b{b:.4f}) # 例如 a0.0745, b0.123关键逻辑a是角度到MPa的缩放系数b是零点偏移。若仪表零点不准指针在0MPa时指向-2°b会体现为非零值。此参数需对每台仪表单独标定不可复用。4.2 指针读数计算从像素坐标到物理量的四步转换指针角度检测后必须经四步转换才能得到真实读数def calculate_reading(needle_theta, a, b, center, radius, img_shape): needle_theta: 检测到的指针角度弧度0°为正右方逆时针增加 a,b: 标定参数 center: 表盘圆心 (cx,cy) radius: 表盘半径 img_shape: 图像尺寸 (h,w)用于验证指针是否在有效范围内 # 步骤1将指针角度归一化到表盘有效范围-135°~135° # 注意OpenCV坐标系y向下需翻转角度符号 theta_normalized -needle_theta # 翻转y轴 if theta_normalized np.pi: theta_normalized - 2*np.pi if theta_normalized -np.pi: theta_normalized 2*np.pi # 步骤2映射到-135°~135°区间单位度 theta_deg np.degrees(theta_normalized) if theta_deg -135: theta_deg -135 elif theta_deg 135: theta_deg 135 # 步骤3用标定参数计算物理值 reading a * np.radians(theta_deg) b # 步骤4安全限幅防止超量程 # 假设量程0~10MPa reading max(0, min(10, reading)) return round(reading, 2) # 使用示例 reading calculate_reading(theta, a, b, (cx,cy), r, preprocessed.shape) print(f当前读数{reading} MPa)参数说明theta_normalized -needle_theta是关键——OpenCV图像坐标系y轴向下而数学角度y轴向上不翻转会全盘错误theta_deg限幅确保不超出物理量程避免异常值污染后续系统。4.3 动态零点校准用开机自检消除机械漂移工业现场仪表存在热漂移指针零点会随温度缓慢偏移。需在每次启动时执行零点校准def auto_zero_calibration(img, center, radius, base_theta0): base_theta: 理论零点角度弧度通常为0正右方 # 在零点附近小范围搜索最优匹配角度 search_angles np.radians(np.arange(-5, 6)) # -5°到5° scores [] for ang in search_angles: # 生成该角度的指针模板短指针 template np.zeros((int(radius*0.3), 3), dtypenp.uint8) cv2.line(template, (0,1), (template.shape[0]-1,1), 255, 1) M cv2.getRotationMatrix2D((template.shape[0]//2, template.shape[1]//2), np.degrees(ang), 1) rotated_t cv2.warpAffine(template, M, template.shape) # 模板匹配 roi img[max(0,center[1]-radius):min(img.shape[0],center[1]radius), max(0,center[0]-radius):min(img.shape[1],center[0]radius)] res cv2.matchTemplate(roi, rotated_t, cv2.TM_CCOEFF_NORMED) _, score, _, _ cv2.minMaxLoc(res) scores.append(score) # 取最高分对应角度为实际零点 offset search_angles[np.argmax(scores)] print(f检测到零点偏移{np.degrees(offset):.2f}°) return base_theta offset # 使用示例开机时运行 zero_offset auto_zero_calibration(preprocessed, (cx,cy), r) # 后续所有reading计算中用 needle_theta - zero_offset 替代 needle_theta工程价值此步骤让系统具备自适应能力避免每月人工校准。实测某产线压力表在夏季温升15℃后零点漂移达0.8MPa此校准可完全补偿。5. 避坑指南这五个现象背后藏着仪表识别的底层物理逻辑刚上手时我踩过的坑几乎都源于忽略了一个事实仪表是精密机械不是平面图像。下面5个高频问题每个都对应一个物理约束被违反。5.1 现象霍夫圆检测总在表盘外圈找到圆而非指针轴心原因表盘外圈是金属环边缘锐利、对比度高而轴心区域常有反光或阴影边缘模糊。霍夫变换天然偏好强边缘。解决在cv2.HoughCircles前加掩膜强制只在图像中心区域搜索——用cv2.circle(mask, (cx,cy), int(r*0.3), 255, -1)画一个半径为0.3r的实心圆再cv2.bitwise_and应用到边缘图。轴心虽弱但在小区域内仍是唯一候选。5.2 现象极坐标变换后刻度线弯曲无法用HoughLinesP检测原因表盘未完全正对镜头存在透视畸变。极坐标变换假设表盘是完美正圆倾斜时弧线在极坐标图中呈抛物线。解决在极坐标变换前先用cv2.findHomography做单应性校正。采集4个角点表盘外框四角用cv2.perspectiveTransform校正为正方形再检测圆心。此步增加计算量但对倾斜安装场景必不可少。5.3 现象指针检测结果在相邻帧间跳变±5°读数抖动严重原因单帧检测噪声大未利用时序信息。指针转动是连续过程物理角速度有限。解决实现卡尔曼滤波跟踪。状态向量为[theta, omega]角度、角速度观测为单帧检测角度。Q矩阵设小过程噪声小R矩阵根据检测置信度动态调整匹配分数高则R小。实测可将抖动从±5°压至±0.3°。5.4 现象低光照下指针完全消失CLAHE增强后仍是一片黑原因CLAHE作用于灰度图但指针与表盘底色亮度接近如黑指针配黑表盘。灰度对比度本身不足。解决改用色彩空间分割。将图像转LAB空间提取A通道红绿对立指针常在此通道有显著响应。或直接用深度学习轻量模型如MobileNetV2微调做指针分割输入RGB三通道输出指针mask。虽增模型但解决根本矛盾。5.5 现象标定后读数系统性偏高0.2MPa且无法通过调b参数消除原因压力表量程非线性尤其在0~1MPa低压区弹簧管形变与压力不成正比。线性标定在高压区准低压区失真。解决改用分段线性标定。用0、2、4、6、8、10MPa六个点标定构建查表LUT。检测角度后用np.interp线性插值查表而非单一线性公式。实测某型号压力表在0.5MPa点误差从0.2MPa降至0.03MPa。6. 工程落地技巧用“三帧验证法”替代单帧判断把误报率压到0.1%以下在某高校实验室的锅炉房监控项目中我们曾因单帧误判导致系统频繁报警。后来采用“三帧验证法”不依赖单帧结果而用连续三帧的时空一致性决策。这不是简单投票而是构建一个轻量状态机。6.1 三帧状态机设计定义“稳定态”与“过渡态”指针运动有两种模式稳态长时间静止和过渡态快速摆动。状态机有三个节点STABLE连续3帧读数变化0.05MPa且每帧置信度0.8TRANSITION检测到变化0.05MPa进入过渡态持续最多5帧UNSURE任一帧置信度0.6或连续2帧变化方向相反抖动class NeedleStateMachine: def __init__(self): self.state STABLE self.readings [] # 存最近3帧读数 self.confidences [] # 存最近3帧置信度 self.transition_count 0 def update(self, reading, confidence): self.readings.append(reading) self.confidences.append(confidence) if len(self.readings) 3: self.readings.pop(0) self.confidences.pop(0) # 状态转移逻辑 if self.state STABLE: if confidence 0.6: self.state UNSURE elif len(self.readings) 3: delta abs(self.readings[-1] - self.readings[0]) if delta 0.05: self.state TRANSITION self.transition_count 1 elif all(c 0.8 for c in self.confidences): # 稳态确认 return self.readings[-1] # 输出最终读数 elif self.state TRANSITION: self.transition_count 1 if self.transition_count 5: # 强制回稳态取最新读数 self.state STABLE return reading # 过渡态不输出等待稳定 elif p a hrefhttps://download.csdn.net/download/robinfang2019/89767549 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p