拓冰建站拓冰建站
首页 / 资讯中心 / 正文

python的工业过程控制场景模拟第九十三篇:开发算法模拟巡检机器人,识别阀门开启位置,自动读取阀门状态。

巡检机器人阀门状态识别与自动读数仿真 —— 基于图像特征与状态机的OOP实战“那年大夜班零下二十度巡检工拿着防爆手电在管廊里挨个核对阀门。回来后冻得直哆嗦还说有个阀位看不清不敢确定是全开还是微开。后来我们在防爆机器人上加了视觉模块算法能精准识别手轮角度、阀杆位移甚至能通过指针式压力表读数。现在中控室里喝着茶就能知道现场几百个阀门的状态。”—— 哈尔滨工程大学《工业过程控制》课程核心思想延伸一、实际应用场景描述在石化、化工、核电等流程工业现场阀门是控制介质流动的核心执行机构。传统的定期人工巡检存在诸多弊端┌──────────────────────────────────────────────┐│ 智能巡检机器人阀门识别系统 ││ ││ [防爆巡检机器人底盘] ││ │ 自主导航 / SLAM ││ ▼ ││ ┌────────────────────────────┐ ││ │ 云台与视觉模块 │ ││ │ ┌──────────────────────┐ │ ││ │ │ 1. 相机标定 │ │ ││ │ │ (畸变校正/像素比) │ │ ││ │ └──────────────────────┘ │ ││ │ ┌──────────────────────┐ │ ││ │ │ 2. 目标检测 │ │ ││ │ │ (YOLO/HOG/模板) │ │ ││ │ └──────────────────────┘ │ ││ │ ┌──────────────────────┐ │ ││ │ │ 3. 特征提取 │ │ ││ │ │ (轮廓/角度/刻度) │ │ ││ │ └──────────────────────┘ │ ││ └────────────┬───────────────┘ ││ │ 图像/特征向量 ││ ┌───────┴───────┐ ││ ▼ ▼ ││ ┌─────────┐ ┌─────────┐ ││ │ 状态判别器 │ │ 读数识别器 │ ││ │ (Classifier)│ │ (OCR/Digit)│ ││ │ • 手轮角度 │ │ • 表盘读数 │ ││ │ • 阀杆位移 │ │ • 刻度识别 │ ││ │ • 开关状态 │ │ • 单位换算 │ ││ └────┬────┘ └────┬────┘ ││ │ 状态标签 │ 数值结果 ││ └───────┬───────┘ ││ ▼ ││ ┌────────────────────────────┐ ││ │ 上位机监控系统 │ ││ │ ┌──────────────────────┐ │ ││ │ │ 4. 数据融合 │ │ ││ │ │ (视觉DCS反馈) │ │ ││ │ └──────────────────────┘ │ ││ │ ┌──────────────────────┐ │ ││ │ │ 5. 异常告警 │ │ ││ │ │ (阀位不一致/卡涩) │ │ ││ │ └──────────────────────┘ │ ││ │ ┌──────────────────────┐ │ ││ │ │ 6. 趋势记录 │ │ ││ │ │ (开关时间/频次) │ │ ││ │ └──────────────────────┘ │ ││ └────────────┬───────────────┘ ││ │ Modbus/TCP, MQTT ││ ▼ ││ ┌────────────────────────────┐ ││ │ 中控室 SCADA/DCS │ ││ │ • 实时阀位显示 │ ││ │ • 历史趋势曲线 │ ││ │ • 设备健康度评估 │ ││ └───────────────────────────┘ ││ ││ 核心: 图像处理 几何特征 状态机 冗余校验 │└──────────────────────────────────────────────┘人工巡检 vs 机器人巡检维度 人工巡检 机器人视觉巡检安全性 ❌ 高风险环境 ✅ 本质安全一致性 ❌ 主观误差大 ✅ 客观量化时效性 ❌ 几小时一次 ✅ 分钟级/实时数据记录 ❌ 纸质/手抄 ✅ 数字化/可追溯恶劣环境 ❌ 受限 ✅ 全天候二、引入痛点2.1 现场的真实困境场景 现场发生了什么 根因“看花了眼” “以为是开的结果是关的” 视角盲区/光线差“冻僵的手” “零下20度抄表半小时” 环境恶劣“漏检隐患” “阀门微漏没发现” 巡检频次不足“数据造假” “没去现场就填了表” 管理漏洞“DCS不一致” “现场开着画面显示关” 反馈信号故障2.2 核心矛盾阀门状态是“位置量”而非简单的“通断量”。 很多阀门如球阀、蝶阀在全开/全关之间有过渡区而闸阀则靠阀杆升降。单纯依靠限位开关容易误判且无法识别“卡在中间”或“虚假反馈”的情况。解决方案是引入机器视觉通过识别手轮旋转角度或阀杆伸出长度结合指针仪表读数实现双重校验。2.3 我们要解决什么用一段精简的 Python 程序构建一个巡检机器人阀门状态识别仿真系统实现1. 阀门模型 —— 模拟不同种类阀门手轮角行程、阀杆直行程2. 视觉模拟 —— 模拟相机拍摄、加入噪声与畸变3. 特征提取 —— 基于几何特征角度、位移判断状态4. 状态机判别 —— 结合阈值判定全开/全关/中间/故障5. 读数识别 —— 模拟指针仪表读数模拟OCR6. 可视化 —— 展示识别过程与结果三、核心逻辑讲解3.1 阀门状态识别原理本工具基于哈工程《工业过程控制》第九章“执行器”和计算机视觉基础① 角行程阀门手轮/球阀/蝶阀通过检测手轮辐条或指示箭头的角度Angle \arctan2(p_y - c_y, p_x - c_x) \times \frac{180}{\pi}状态判定- 全关 (Closed): |Angle - Angle_{closed}| \epsilon- 全开 (Open): |Angle - Angle_{open}| \epsilon- 中间位 (Mid): 介于两者之间② 直行程阀门闸阀/截止阀通过检测阀杆端部的垂直位移Position y_{top} - y_{bottom}状态判定- 全关: Position Threshold_{min}- 全开: Position Threshold_{max}③ 指针仪表读数基于刻度盘圆心和指针端点计算角度映射到量程Value \frac{Angle - Angle_{zero}}{Angle_{full} - Angle_{zero}} \times (Range_{max} - Range_{min}) Range_{min}3.2 状态机设计┌─────────┐│ UNKNOWN │└────┬────┘│ 首次检测▼┌─────────┐◀────┐│ CLOSED │ │ 置信度阈值└────┬────┘ ││ 角度变化 │▼ │┌─────────┐ ││ MIDWAY │─────┘└────┬────┘│ 角度到位▼┌─────────┐│ OPEN │└────┬────┘│ 与DCS反馈不一致▼┌─────────┐│ FAULT │└─────────┘四、代码讲解面向对象设计4.1 类结构总览类名 职责 设计模式ValveType 阀门类型枚举 枚举ValveState 阀门状态枚举 枚举Point2D 二维点值对象 值对象ImageFrame 模拟图像帧 封装ValveConfig 阀门配置值对象 值对象ValveModel 阀门物理模型 策略模式CameraSensor 相机传感器模型 封装FeatureExtractor 特征提取器 模板方法StateClassifier 状态判别器 策略模式GaugeReader 仪表读数器 策略模式InspectionRobot 巡检机器人聚合根 聚合根VisualizationEngine 可视化引擎 封装4.2 数据模型与枚举from dataclasses import dataclass, fieldfrom typing import List, Dict, Optional, Tuple, Anyfrom enum import Enum, autofrom collections import dequeimport numpy as npimport matplotlib.pyplot as pltfrom datetime import datetimeimport mathimport cv2 # 模拟导入实际运行时若无cv2可注释相关部分class ValveType(Enum):阀门类型ANGLE_STROKE auto() # 角行程手轮/球阀/蝶阀LINEAR_STROKE auto() # 直行程闸阀/截止阀QUARTER_TURN auto() # 1/4回转球阀/蝶阀特例class ValveState(Enum):阀门状态UNKNOWN auto()FULLY_CLOSED auto()FULLY_OPEN auto()MID_POSITION auto()STUCK auto() # 卡涩FAULT auto() # 故障与DCS不一致dataclass(frozenTrue)class Point2D:二维点 —— 值对象x: floaty: floatdef distance_to(self, other: Point2D) - float:return math.hypot(self.x - other.x, self.y - other.y)def to_tuple(self) - Tuple[int, int]:return (int(self.x), int(self.y))dataclassclass ValveConfig:阀门配置 —— 值对象valve_id: strvalve_type: ValveTypecenter_point: Point2D # 旋转中心或底部参考点closed_angle: float 0.0 # 全关位置角度度open_angle: float 90.0 # 全开位置角度度full_stroke: float 100.0 # 直行程全长mmtolerance: float 5.0 # 判定容差度或mmgauge_center: Optional[Point2D] None # 附属仪表中心gauge_zero_angle: float -135.0 # 表盘0度位置gauge_full_angle: float 135.0 # 表盘满度位置gauge_range: Tuple[float, float] (0.0, 1.0) # 量程4.3 阀门物理模型策略模式class ValveModel:阀门物理模型 —— 策略模式模拟阀门的物理运动和当前状态def __init__(self, config: ValveConfig):self.config configself.current_angle: float config.closed_angle # 当前角度self.current_position: float 0.0 # 当前位移直行程self.target_angle: float config.closed_angleself.target_position: float 0.0self.state: ValveState ValveState.FULLY_CLOSEDself.history: List[Tuple[float, ValveState, float]] []self._last_update_time datetime.now().timestamp()def set_target(self, target_state: ValveState):设置目标状态if target_state ValveState.FULLY_OPEN:self.target_angle self.config.open_angleself.target_position self.config.full_strokeelif target_state ValveState.FULLY_CLOSED:self.target_angle self.config.closed_angleself.target_position 0.0# MID_POSITION 需要外部指定具体角度/位置def update(self, dt: float) - ValveState:更新阀门状态模拟物理运动Args:dt: 时间步长Returns:当前状态speed 30.0 # 度/秒 或 mm/秒if self.config.valve_type in [ValveType.ANGLE_STROKE, ValveType.QUARTER_TURN]:# 角度运动delta (self.target_angle - self.current_angle)if abs(delta) 0.1:move math.copysign(min(speed * dt, abs(delta)), delta)self.current_angle moveelse:self.current_angle self.target_angle# 状态判定if abs(self.current_angle - self.config.closed_angle) self.config.tolerance:self.state ValveState.FULLY_CLOSEDelif abs(self.current_angle - self.config.open_angle) self.config.tolerance:self.state ValveState.FULLY_OPENelse:self.state ValveState.MID_POSITIONelif self.config.valve_type ValveType.LINEAR_STROKE:# 直线位移delta (self.target_position - self.current_position)if abs(delta) 0.1:move math.copysign(min(speed * dt, abs(delta)), delta)self.current_position moveelse:self.current_position self.target_position# 状态判定if self.current_position self.config.tolerance:self.state ValveState.FULLY_CLOSEDelif abs(self.current_position - self.config.full_stroke) self.config.tolerance:self.state ValveState.FULLY_OPENelse:self.state ValveState.MID_POSITIONself.history.append((datetime.now().timestamp(), self.state, self.current_angle))return self.statedef get_current_feature(self) - Dict[str, Any]:获取当前特征用于视觉识别if self.config.valve_type in [ValveType.ANGLE_STROKE, ValveType.QUARTER_TURN]:return {type: angle,value: self.current_angle,center: self.config.center_point}else:return {type: position,value: self.current_position,base: self.config.center_point}def inject_fault(self, fault_type: str stuck):注入故障用于测试if fault_type stuck:self.target_angle self.current_angle # 停止运动self.target_position self.current_positionself.state ValveState.STUCK4.4 相机传感器模型class CameraSensor:相机传感器模型模拟图像采集、噪声、畸变def __init__(self, resolution: Tuple[int, int] (640, 480), noise_level: float 0.02):self.width, self.height resolutionself.noise_level noise_levelself.focal_length 800.0self.principal_point Point2D(resolution[0]/2, resolution[1]/2)def capture(self, valve_model: ValveModel) - ImageFrame:模拟拍摄阀门图像Args:valve_model: 阀门模型Returns:模拟图像帧# 创建空白图像img np.ones((self.height, self.width, 3), dtypenp.uint8) * 240feature valve_model.get_current_feature()if feature[type] angle:# 绘制手轮/指针center feature[center]angle_rad math.radians(feature[value])# 加入噪声noisy_angle angle_rad np.random.normal(0, self.noise_level * 0.1)noisy_center Point2D(center.x np.random.normal(0, self.noise_level * 5),center.y np.random.normal(0, self.noise_level * 5))# 计算指针端点length 80end_x noisy_center.x length * math.cos(noisy_angle)end_y noisy_center.y length * math.sin(noisy_angle)# 绘制cv2.circle(img, noisy_center.to_tuple(), 30, (200, 200, 200), -1) # 轮毂cv2.line(img, noisy_center.to_tuple(), (int(end_x), int(end_y)),(0, 0, 255), 3) # 指针cv2.circle(img, noisy_center.to_tuple(), 5, (0, 0, 0), -1) # 中心轴elif feature[type] position:# 绘制阀杆base feature[base]height feature[value] * 0.8 # 缩放因子# 加入噪声noisy_base Point2D(base.x np.random.normal(0, self.noise_level * 3),base.y np.random.normal(0, self.noise_level * 3))noisy_height height np.random.normal(0, self.noise_level * 2)top_y noisy_base.y - noisy_height# 绘制阀体cv2.rectangle(img,(int(noisy_base.x - 15), int(noisy_base.y)),(int(noisy_base.x 15), int(noisy_base.y 50)),(100, 100, 100), -1)# 绘制阀杆cv2.rectangle(img,(int(noisy_base.x - 5), int(top_y)),(int(noisy_base.x 5), int(noisy_base.y)),(0, 255, 0), -1)# 添加高斯噪声noise np.random.normal(0, self.noise_level * 255, img.shape).astype(np.int16)img np.clip(img.astype(np.int16) noise, 0, 255).astype(np.uint8)return ImageFrame(img, datetime.now().timestamp(), valve_model.config.valve_id)4.5 特征提取与状态判别class FeatureExtractor:特征提取器 —— 模板方法模式从图像中提取阀门特征def extract_features(self, frame: ImageFrame, valve_config: ValveConfig) - Dict[str, Any]:提取特征模拟版本实际应使用CV算法Args:frame: 图像帧valve_config: 阀门配置Returns:提取到的特征字典# 在真实系统中这里会是复杂的CV算法# 1. 预处理 (灰度化、滤波、边缘检测)# 2. 轮廓查找 (findContours)# 3. 几何拟合 (fitEllipse, minAreaRect)# 4. 关键点检测 (HoughLines, HoughCircles)# 由于这是仿真我们直接从完美的模拟图像中反推特征# 实际应用中这里是最复杂的部分img frame.imageh, w img.shape[:2]if valve_config.valve_type in [ValveType.ANGLE_STROKE, ValveType.QUARTER_TURN]:# 寻找红色指针简单颜色阈值hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV)lower_red np.array([0, 100, 100])upper_red np.array([10, 255, 255])mask cv2.inRange(hsv, lower_red, upper_red)# 查找轮廓contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)if contours:# 找到最长的轮廓指针cnt max(contours, keycv2.contourArea)# 拟合直线[vx, vy, x, y] cv2.fitLine(cnt, cv2.DIST_L2, 0, 0.01, 0.01)# 计算角度angle math.atan2(vy, vx) * 180 / math.pi# 调整到0-360度if angle 0:angle 360return {type: angle,raw_angle: angle,confidence: 0.9 # 模拟置信度}elif valve_config.valve_type ValveType.LINEAR_STROKE:# 寻找绿色阀杆简单颜色阈值hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV)lower_green np.array([40, 50, 50])upper_green np.array([80, 255, 255])mask cv2.inRange(hsv, lower_green, upper_green)# 查找轮廓contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)if contours:cnt max(contours, keycv2.contourArea)x, y, w, h cv2.boundingRect(cnt)position h # 高度代表位移return {type: position,raw_position: position,confidence: 0.85}return {type: unknown, confidence: 0.0}class StateClassifier:状态判别器 —— 策略模式根据提取的特征判断阀门状态def classify(self, features: Dict[str, Any], config: ValveConfig) - Tuple[ValveState, float]:分类阀门状态Args:features: 提取的特征config: 阀门配置Returns:(状态, 置信度)if features[type] angle:angle features[raw_angle]conf features[confidence]# 处理角度环绕例如355度接近0度def angle_diff(a1, a2):diff abs(a1 - a2)return min(diff, 360 - diff)if angle_diff(angle, config.closed_angle) config.tolerance:return ValveState.FULLY_CLOSED, confelif angle_diff(angle, config.open_angle) config.tolerance:return ValveState.FULLY_OPEN, confelse:return ValveState.MID_POSITION, conf * 0.8 # 中间位置置信度稍低elif features[type] position:pos features[raw_position]conf features[confidence]# 归一化位置假设图像中100像素代表全行程normalized_pos (pos / 100.0) * config.full_strokeif normalized_pos config.tolerance:return ValveState.FULLY_CLOSED, confelif abs(normalized_pos - config.full_stroke) config.tolerance:return ValveState.FULLY_OPEN, confelse:return ValveState.MID_POSITION, conf * 0.8return ValveState.UNKNOWN, 0.0class GaugeReader:指针仪表读数器 —— 策略模式读取附属的压力/流量/液位表def read_gauge(self, frame: ImageFrame, config: ValveConfig) - Optional[float]:读取仪表数值Args:frame: 图像帧config: 阀门配置包含仪表信息Returns:读数值失败返回Noneif not config.gauge_center:return None# 模拟读数基于阀门状态估算# 实际应使用与FeatureExtractor类似的指针检测算法if config.valve_type ValveType.ANGLE_STROKE:# 假设球阀开度与流量成正比angle_ratio (config.open_angle - config.closed_angle)if angle_ratio ! 0:current_ratio (frame.valve_model.current_angle - config.closed_angle) / angle_ratiovalue config.gauge_range[0] current_ratio * (config.gauge_range[1] - config.gauge_range[0])# 加入测量噪声value np.random.normal(0, 0.01 * (config.gauge_range[1] - config.gauge_range[0]))return max(config.gauge_range[0], min(value, config.gauge_range[1]))return None4.6 图像帧与巡检机器人聚合根dataclassclass ImageFrame:图像帧 —— 值对象image: np.ndarraytimestamp: floatvalve_id: strmetadata: Dict field(default_factorydict)class InspectionRobot:巡检机器人 —— 聚合根协调整个巡检流程def __init__(self, robot_id: str ROBOT-01):self.robot_id robot_idself.camera CameraSensor()self.extractor FeatureExtractor()self.classifier StateClassifier()self.gauge_reader GaugeReader()self.valves: Dict[str, ValveModel] {}self.dcs_states: Dict[str, ValveState] {} # 模拟DCS反馈self.inspection_history: List[Dict] []self.alarm_log: List[Dict] []# 统计信息self.stats {total_inspections: 0,successful_reads: 0,fault_detections: 0,discrepancies: 0}def add_valve(self, config: ValveConfig, dcs_state: ValveState ValveState.FULLY_CLOSED):添加阀门到巡检列表model ValveModel(config)model.state dcs_stateself.valves[config.valve_id] modelself.dcs_states[config.valve_id] dcs_stateprint(f 注册阀门: {config.valve_id} ({config.valve_type.name}))def inspect_valve(self, valve_id: str) - Dict:巡检单个阀门Args:valve_id: 阀门IDReturns:巡检结果字典if valve_id not in self.valves:raise ValueError(f阀门 {valve_id} 未注册)valve self.valves[valve_id]config valve.config# 1. 模拟物理更新如果阀门正在动作valve.update(0.1)# 2. 拍摄图像frame self.camera.capture(valve)# 3. 提取特征features self.extractor.extract_features(frame, config)# 4. 分类状态vision_state, confidence利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门