
工业巡检机器人软件不是一个单一程序而是一整套从机器人边缘端、通信链路、服务端到 Web 后台的分布式系统。Salem RoboticsYC S26在 Launch HN 上发布的主题正是 Software for industrial inspection robots这类软件的目标是让机器人代替人工完成变电站、配电房、生产车间、数据中心等场景中的表计读数、设备状态检查、温度异常检测和日常巡检记录。与普通的遥控小车或视频直播工具不同工业巡检机器人软件的核心价值在于形成闭环自动导航到指定点位采集多模态数据识别设备状态生成结构化巡检记录并在异常时第一时间通知运维人员。本文围绕这类软件平台展开从问题定义、架构设计、最小可运行闭环、关键识别实现、故障排查到生产落地建议逐步梳理一套可以落到项目的技术方案。1. 工业巡检机器人软件到底在解决什么问题1.1 巡检机器人不是遥控车很多项目在启动时会把工业巡检机器人理解成“远程摄像头加四个轮子”。这个理解会在设计阶段埋下大量返工风险。因为业务方真正需要的并不是一路能看的视频流而是一份可以直接交给运维系统的巡检结果今天几号点位看到了什么仪表读数、温度是否超标、是否出现跑冒滴漏、图片存在哪里、告警有没有人跟进。要做到这一点机器人必须自主完成位姿估计、路径规划、点位到达判断、云台对准、图像采集、本地识别、结果上传、任务回执等一系列步骤。每一个步骤都依赖软件模块而不是简单的遥控操作。也就是说工业巡检机器人软件的重点在“巡检业务闭环”不在“机器人动起来”本身。这里有一个常见的误区先花很多时间调底盘的直行和转弯却忽略了巡检点位模型、图像采集质量、识别阈值、告警消缺流程。实际上机器人动起来只是前置条件真正决定系统能不能交付的是数据链路和业务规则是否完整。1.2 三类典型巡检场景不同行业的巡检对象差异很大软件设计不能只做一套通用识别模型而是要考虑场景的传感器组合和输出要求。巡检场景需要观察的对象核心传感器软件要输出的结果变电站、配电房油位、SF6 压力、避雷器、设备红外温度可见光相机、红外热像仪、声学传感器读数记录、温差告警、局部放电异常工厂生产线设备指示灯、仪表表盘、传送带状态、跑冒滴漏可见光相机、热像仪、温湿度传感器状态判定、图像证据、异常定位数据中心、机房服务器指示灯、机柜屏显、环境温度湿度可见光相机、温度传感器、湿度传感器屏显 OCR、温度趋势、机柜占用记录在这些场景里巡检点位可能分布在几十米甚至几公里范围内光照条件、设备朝向、表计类型都不固定。软件平台必须支持点位模板、识别模型和告警规则的可配置而不是每个项目都重新开发一套完整代码。1.3 软件功能分层结合实际项目工业巡检机器人软件通常可以分成五层感知层负责激光雷达、IMU、相机、红外热像仪、温湿度传感器、声学传感器的驱动与数据预处理。导航层负责 SLAM 建图、定位、全局路径规划、局部避障、自主回充。任务层负责解析巡检任务把“去几号点位、做什么动作、采什么数据”翻译成机器人可以执行的指令。业务层负责表计识别、温度判定、报警生成、图片归档、报告输出。集成层负责通过 MQTT、HTTP、OPC UA 等协议与后台系统、第三方工单系统对接。分层的核心原因是可以替换模块。比如底盘从差速轮换成四轮转向导航算法从单线激光换成激光视觉融合服务端从自建数据库换成云数据库只要模块接口不变上层业务就不必大改。这也是工业巡检软件能不能长期维护的关键。1.4 为什么不是通用机器人框架就够了ROS2 解决了机器人内部的驱动、通信、导航和节点管理问题但它并不是巡检业务系统。ROS2 不关心巡检任务模板怎么配置不关心告警如何流转也不关心巡检报告如何生成。因此实际项目中通常把 ROS2 作为边缘执行底座在它之上再建立自己的点位模型、任务模型、数据模型、告警模型和后台接口。如果只用 ROS2 把机器人跑起来距离一套可交付的巡检系统还差一半。另一半是业务闭环发现异常之后如何产生可追踪记录如何通知运维人员如何验证问题已经被处理。这是工业巡检机器人软件的真正价值所在。2. 软件平台的核心架构与关键模块2.1 边缘端机器人本体上应该放哪些软件边缘端是巡检机器人最复杂的地方因为它既要保证实时控制又要处理相机图像、运行识别模型还要在弱网条件下缓存数据。一个相对完整的边缘端程序结构至少包含这些进程设备驱动节点读取底盘里程计、激光雷达、IMU、相机和红外热像仪数据。导航节点运行 SLAM、定位、路径规划和避障。任务执行节点负责逐点巡检判断是否到达指定位置然后触发采集动作。识别节点对可见光图像和红外温度矩阵做本地分析。通信节点负责把数据上传到后台并接收后台下发的任务和配置。监控节点记录磁盘空间、CPU 使用率、网络状态方便远程定位问题。为了让点位配置可维护建议把巡检点位放在 YAML 配置文件里而不是写死在代码中。下面是一个简化示例robot: id: R-001 navigation: frame_id: map planner: nav2 camera: topic: /camera/image_raw resolution: [1920, 1080] auto_exposure: false exposure_value: 300 thermal: topic: /thermal/image_raw inspection: points: - id: P-12 name: 1号主变油位表 position: [2.3, 5.1, 0.0] yaw: 1.57 actions: [capture_visible, capture_thermal, read_meter]这个文件的价值在于现场新增巡检点位时不需要重新编译程序只需要在后台或者运维终端修改配置并下发到机器人。要注意的是配置必须带版本号。机器人正在执行任务时不能直接热更新点位否则可能出现任务执行到一半突然读取到新配置的情况。2.2 通信层机器人怎么把数据送到后台机器人本体的数据要传到后台通常有四种选择MQTT、HTTP/REST、WebSocket、OPC UA。它们各有适用场景不能只用一种协议包打天下。协议优点适用场景MQTT轻量、支持 QoS、适合弱网、有消息持久化设备状态上报、巡检结果推送、告警通知HTTP/REST通用、调试方便、适合请求响应任务下发、图片查询、后台管理 APIWebSocket双向实时、低延迟机器人位置轨迹、实时视频预览OPC UA工业标准、语义丰富、适合与 SCADA/DCS 对接需要集成到已有工业自动化体系的场景工业现场网络有一个明显特点机器人移动时会跨越多个无线 AP通信会出现短暂中断。因此通信层不能只做实时推送还要做边缘缓存。推荐的做法是机器人先本地写入 SQLite 或文件缓存上报成功后再标记为已同步网络恢复后按任务顺序补传保证数据不丢失。2.3 服务端任务编排与数据管理服务端主要负责三件事管理资产和点位、下发巡检任务、存储巡检结果。数据库设计会直接影响系统的可用性。先看巡检记录表的核心结构CREATE TABLE inspection_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, robot_id TEXT NOT NULL, task_id TEXT NOT NULL, device_code TEXT, point_id TEXT NOT NULL, inspect_time TEXT NOT NULL, image_paths TEXT, meter_value REAL, alarm_level TEXT, alarm_message TEXT, confidence REAL, created_at TEXT DEFAULT CURRENT_TIMESTAMP );这个表设计有几个关键点task_id和point_id共同标识一条巡检记录的来源。inspect_time必须使用机器人采集时生成的时间戳而不是后台接收时间。image_paths保存图片文件路径不要直接存图片二进制。alarm_level和alarm_message放在记录里方便后续报表直接查询避免每次联表判断。建议对task_id point_id inspect_time建唯一索引保证重复上报时不会产生重复记录。服务端还要有能力下发任务。任务下发接口至少包含任务 ID、机器人 ID、路线、点位列表、计划执行时间。后台用户创建任务后通过 MQTT 或者 HTTP 推送到机器人机器人执行完再把结果上报回来。2.4 应用层Web 后台与 API 设计Web 后台不需要做得很花哨但要能回答运维人员的三个问题机器人现在在哪、今天巡检了哪些点位、有没有异常没有处理。后台 API 建议按资源划分GET /api/robots获取机器人列表和在线状态。POST /api/tasks创建巡检任务。GET /api/tasks/{task_id}/records查看某个任务的巡检记录。POST /api/inspection/records机器人上报巡检记录。GET /api/alarms?statuspending获取待处理告警。一条巡检记录的上报接口可以用 FastAPI 快速实现from fastapi import FastAPI from pydantic import BaseModel from typing import Optional import sqlite3 app FastAPI() class InspectionRecord(BaseModel): robot_id: str task_id: str point_id: str inspect_time: str image_paths: list[str] meter_value: Optional[float] None alarm: Optional[str] None confidence: Optional[float] None app.post(/api/inspection/records) def create_record(record: InspectionRecord): conn sqlite3.connect(inspection.db) conn.execute( INSERT INTO inspection_record (robot_id, task_id, point_id, inspect_time, image_paths, meter_value, alarm, confidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?), ( record.robot_id, record.task_id, record.point_id, record.inspect_time, ,.join(record.image_paths), record.meter_value, record.alarm, record.confidence, ), ) conn.commit() conn.close() return {status: ok}实际项目中不建议每次请求都新建数据库连接这里只是演示接口结构。更重要的是上报接口要尽量设计成幂等操作同一台机器人、同一个任务、同一个点位、同一个采集时间即使重复上报也不应该生成重复记录。3. 搭建一套最小可运行闭环从采集到上报3.1 学习环境准备没有真机之前也可以先跑通软件闭环。推荐的学习环境如下组件推荐方案说明机器人本体TurtleBot3 仿真或直接用 Python 脚本模拟先验证任务执行和上报流程相机本地图片目录模拟用真实表计图片测试识别边缘端运行环境Ubuntu 22.04 Python 3.10与 ROS2 Humble 兼容性较好通信组件Mosquitto MQTT Broker启动简单适合本地调试服务端FastAPI SQLite学习阶段够用容器环境Docker docker-compose方便后面迁移测试环境如果后续要接真机建议使用 ROS2 Humble Nav2。学习阶段也可以先用脚本模拟底盘移动把更多精力放在任务执行、图像识别、数据上报这条业务主链路上。3.2 项目目录结构一个清晰的项目结构可以减少后续排查成本。下面是一个参考结构inspection-robot/ ├── robot_bringup │ ├── config/robot.yaml │ └── launch/robot.launch.py ├── robot_drivers │ ├── camera_node.py │ └── thermal_node.py ├── robot_task │ ├── task_executor.py │ └── point_action.py ├── robot_perception │ ├── meter_reader.py │ ├── thermal_checker.py │ └── models/ ├── robot_comm │ ├── mqtt_client.py │ └── uploader.py ├── server_api │ ├── main.py │ └── requirements.txt ├── server_web │ └── ... └── docker-compose.yml目录边界很重要robot_drivers只负责驱动和原始数据不写业务逻辑robot_task只负责任务执行流程robot_perception只做图像识别和异常判断robot_comm只负责通信。这样任何一个模块出现问题都能快速定位到目录和日志。3.3 用脚本模拟一次单点巡检为了先验证业务闭环可以写一个不依赖 ROS2 的最小任务执行脚本。它模拟机器人到达点位、采集图像、识别表计读数并生成记录的过程import time import random from pathlib import Path def execute_point(point_id, image_dir): # 模拟导航到位真机环境中这里由导航模块完成 arrive_time time.strftime(%Y-%m-%dT%H:%M:%S%z) # 模拟相机采集从图片目录中选择当前点位对应的图片 image_path image_dir / f{point_id}.jpg # 模拟识别结果实际项目中由识别模型输出 meter_value round(random.uniform(20.0, 60.0), 2) alarm OK if meter_value 50.0 else HIGH return { point_id: point_id, arrive_time: arrive_time, image_path: str(image_path), meter_value: meter_value, alarm: alarm, } if __name__ __main__: result execute_point(P-12, Path(./images)) print(result)这个脚本的核心目的是把“采集时间、图片路径、识别值、告警结果”这四类信息集中到一条结构化记录里。真机环境中区别只是time需要换成机器人系统时间image_path需要换成相机实际输出文件meter_value需要换成识别模型或 OCR 的结果。3.4 把记录通过 MQTT 上报到后台任务执行得到记录后下一步是上报。这里用 paho-mqtt 发布 JSON 到本地 brokerimport json import paho.mqtt.publish as publish payload { robot_id: R-001, task_id: T-20250715-001, point_id: P-12, inspect_time: 2025-07-15T09:30:0008:00, image_paths: [/files/2025/07/15/09/30/P-12.jpg], meter_value: 42.6, alarm: None, confidence: 0.91, } publish.single( inspection/records, json.dumps(payload, ensure_asciiFalse), hostnamelocalhost, port1883, qos1, )这里有几个细节需要注意qos1保证消息至少送达一次但可能造成重复所以后台必须做幂等处理。client_id在生产环境要唯一否则 broker 会踢掉旧连接。建议把image_paths设计成列表因为一个点位可能同时采集可见光和红外图。为了弱网场景上报前最好先把记录写入本地 SQLite成功后再删除防止进程崩溃导致数据丢失。3.5 验证闭环是否真的走通启动后台 API 和 MQTT broker 后可以按以下流程验证# 启动 Mosquitto docker run -d -p 1883:1883 --name mosquitto eclipse-mosquitto:2.0 # 启动 API 服务 uvicorn server_api.main:app --host 0.0.0.0 --port 8000 # 运行任务执行脚本 python robot_task/task_executor.py --point P-12 --image-dir images预期结果是脚本输出一条记录了点位、时间、图片路径和表计读数的 JSONMQTT 终端能订阅到inspection/records消息API 接口把记录写入 SQLite后台查询时能看到这条记录。要注意验证不能只看“程序没有报错”。还需要检查图片文件是否真实存在、数据库里是否出现重复记录、告警字段是否按预期写入。只有整条链路都符合预期才算闭环跑通。3.6 学习环境、真机测试与生产环境的差别很多问题并不是代码写错而是环境差异导致。下面这张表可以帮助提前判断哪些环节需要切换维度学习模拟环境真机测试环境生产环境底盘脚本模拟差速或四轮底盘多台机器人、多型号导航坐标直接跳转Nav2 SLAM多地图动态切换、自主充电相机图片文件USB/工业相机相机标定、自动曝光、多光谱通信localhost厂区 Wi-Fi/4G双链路、断点续传、消息鉴权存储SQLitePostgreSQL高可用数据库、对象存储、备份安全无特殊要求内网测试设备认证、TLS、权限控制运维日志文件docker compose监控、告警、版本回滚4. 把非结构化数据变成巡检记录的关键实现4.1 图像采集与压缩策略图像是巡检系统最重要的证据。同一台相机、同一个点位如果曝光时间、焦距、云台角度每次都不一致识别模型的效果会很不稳定。因此生产环境建议固定相机参数比如关闭自动曝光设置固定曝光值到达点位后先调整云台到预设角度再触发拍摄。图像体积也要控制。一张 1920x1080 的可见光图大约 1~2 MB一次巡检 30 个点位就是几十 MB。边缘端应该在本地生成预览图和缩略图只有故障点位或需要人工复核的点位才上传完整原图。缩略图生成可以用 OpenCVimport cv2 img cv2.imread(raw.jpg) thumb cv2.resize(img, (640, 480), interpolationcv2.INTER_AREA) cv2.imwrite(thumb.jpg, thumb, [cv2.IMWRITE_JPEG_QUALITY, 75])这段代码说明的是思路原始图用于识别和归档缩略图用于 Web 后台快速预览。识别用的区域不要过度压缩否则会直接影响读数准确度。4.2 表计读数识别从检测表盘到读取数值常见表计可以分成三类指针式表计、数字式表计、液晶屏表计。处理方式不同指针式表计检测表盘圆心和指针角度再根据量程映射到读数。数字式表计用 OCR 直接识别数码管或液晶屏数字。混合表计先用目标检测模型定位表盘再根据表计类型选择识别分支。一个典型的指针式表计检测思路是先用霍夫圆检测找到表盘区域再在区域内做后续处理import cv2 import numpy as np def detect_dial(image): gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (5, 5), 0) circles cv2.HoughCircles( blurred, cv2.HOUGH_GRADIENT, dp1.2, minDist50, param1100, param230, minRadius30, maxRadius200, ) if circles is None: return None circles np.round(circles[0, :]).astype(int) # 取最大圆作为表盘 x, y, r max(circles, keylambda c: c[2]) mask np.zeros_like(gray) cv2.circle(mask, (x, y), r, 255, -1) result cv2.bitwise_and(gray, gray, maskmask) return result实际项目中霍夫圆检测只能作为预处理手段不太适合直接作为生产唯一方案。因为表盘容易受光照、遮挡、倾斜影响。更稳妥的做法是先用 YOLO 这类目标检测模型定位表盘再根据表计类型选择读数模型。识别结果一定要输出置信度低于阈值时不要直接写入报告而是标记为“需人工复核”。4.3 红外温度异常检测红外热像仪返回的不是普通图像而是一个温度矩阵。每个像素点的值对应物体表面的温度。异常判断逻辑通常包含最高温、平均温和温差三个维度。简化示例import numpy as np def check_temperature(temp_matrix, roi, high_alarm80.0, diff_alarm15.0): area temp_matrix[roi[0]:roi[1], roi[2]:roi[3]] max_temp float(area.max()) avg_temp float(area.mean()) alarm None if max_temp high_alarm: alarm HIGH_TEMPERATURE elif max_temp - avg_temp diff_alarm: alarm TEMPERATURE_DIFF return { max_temp: max_temp, avg_temp: avg_temp, alarm: alarm, }这里要特别注意temp_matrix必须来自热像仪 SDK 的原始温度数据而不是从可见光图像转换出来。不同物体的发射率不同红外测温会受到强烈影响现场部署时需要对目标设备的发射率做补偿。单纯给可见光图写一个“看起来热”的算法无法满足工程验收。4.4 生成结构化巡检报告巡检结果最终要以报告形式呈现。报告内容通常包括任务 ID、机器人 ID、开始结束时间、巡检点位数量、异常清单以及每个点位的图片和读数。报告格式建议优先考虑 Excel 或 PDFExcel 适合运维人员做二次统计和筛选。PDF 适合打印归档和提交给安全部门。轻量场景可以直接导出 Markdown再转 HTML/PDF。用 openpyxl 生成 Excel 的示例from openpyxl import Workbook wb Workbook() ws wb.active ws.append([点位, 巡检时间, 表计读数, 最高温度, 告警, 图片路径]) for record in records: ws.append([ record[point_id], record[inspect_time], record.get(meter_value), record.get(max_temp), record.get(alarm), record.get(image_path), ]) wb.save(freport_{task_id}.xlsx)报告是给业务方看的但底层数据完整性更重要。生成报告前可以先做一次数据校验点位数量是否等于任务计划数量缺失图片是否超过阈值异常记录是否已经标记处理状态。如果原始数据有缺漏报告再好看也无法弥补。5. 常见故障排查现象、根因与修复路径5.1 机器人没有按规划路径巡检现象后台下发任务后机器人原地不动或者绕路严重。可能原因地图没有加载或者目标点位使用的坐标与地图坐标系不一致。定位漂移导致机器人认为目标点太远无法规划。Nav2 全局代价地图和局部代价地图的膨胀半径设置过大导致路径被判定为不可通行。下发任务时点位配置没有同步到机器人任务执行器读取不到目标点。检查方式ros2 topic echo /amcl_pose ros2 topic echo /goal_pose先看机器人当前位姿与目标点是否在同一个map坐标系下。如果 /amcl_pose 输出异常优先重新初始化定位。如果坐标没问题再检查代价地图参数。不要一上来就改控制 PID很多导航问题与底盘控制器无关。5.2 图片上传失败或丢失现象后台有巡检记录但记录里的图片路径打不开或者图片目录只生成了一部分。可能原因移动过程中无线网络切换上传请求超时。图片写入权限不足进程没有写入目标目录的权限。文件名冲突多任务同时写入同一个点位编号时互相覆盖。上传失败后没有重试机制失败记录被丢弃。检查方式查看边缘端日志中的上传失败记录确认/data/photos目录的磁盘和权限情况。日志中如果出现HTTP 503或connection reset基本可以确定是网络或服务端临时不可用。处理建议图片先写入本地临时目录按task_id/point_id分目录保存。上传成功后更新本地状态为已同步。定时扫描未同步文件按批次补传。图片目录要挂载独立磁盘磁盘使用率超过 80% 时触发告警。5.3 MQTT 连接不稳定导致数据缺失现象后台偶发缺少某几条巡检记录但机器人端日志显示已经发送。可能原因机器人重启后 MQTT client_id 与旧连接冲突broker 踢掉新连接。弱网时发布 QoS 为 0消息直接丢失。broker 设置了 clean_sessiontrue离线期间的持久会话消息被清空。机器人休眠后网络恢复但 MQTT 没有重连成功。检查方式查看 broker 日志确认是否有client disconnected和client connected频繁交替。也可以用订阅命令观察一段时间内的消息