资讯动态

树莓派+OpenPLC:基于YOLOv5n与Modbus的边缘AI工业控制方案

发布时间:2026/8/19 23:15:44 来源:尧图企业网站定制
1. 项目缘起当边缘AI遇上工业控制最近在做一个挺有意思的自动化项目客户需要在仓库的特定区域实现人员闯入检测一旦检测到未经授权的人员就要联动现场的PLC可编程逻辑控制器去控制声光报警器、关闭安全门甚至暂停AGV小车。传统的方案要么是依赖昂贵的工业视觉相机加专用工控机要么就是用网络摄像机把视频流送到云端服务器分析前者成本太高后者又担心网络延迟和隐私问题。琢磨了一下手头正好有闲置的树莓派和配套的AI摄像头模块一个想法就冒出来了能不能用树莓派直接在边缘端跑人员检测模型然后把检测结果通过工业上最通用的Modbus协议实时发送给现场的OpenPLC一个开源的软PLC呢这样整个系统就变成了一个低成本、高响应速度、且不依赖外部网络的独立边缘智能控制单元。这个组合听起来有点跨界——一边是玩嵌入式AI和Python的树莓派另一边是搞梯形图和工业通讯的PLC。但实际跑通后发现这种“AI感知工业执行”的架构在不少轻量级自动化场景里特别实用比如小型工厂的安全区域监控、智能仓储的人员管理或者实验室的自动化安全联锁。它把复杂的视觉分析留在边缘设备完成只把最关键的“有没有人”这个布尔量信号发给PLC让PLC专心做它最擅长的逻辑控制和设备驱动。接下来我就把从硬件选型、环境搭建、模型部署到Modbus通讯集成的完整过程以及中间踩过的几个坑详细拆解一遍。如果你也想试试用树莓派和OpenPLC搞点智能控制这篇内容应该能帮你省下不少折腾的时间。2. 硬件与软件栈选型为什么是它们工欲善其事必先利其器。这个项目的核心是让树莓派“看见”并“告诉”PLC所以硬件和软件的选择每一步都有讲究不是随便抓个摄像头和库就能用的。2.1 核心硬件树莓派与AI摄像头的考量主控我选择了Raspberry Pi 4B 4GB版本。3B理论上也能跑但考虑到要同时运行视觉推理和通讯服务4B更强的CPU和更大的内存会更从容。更重要的是它的USB 3.0接口和PCIe通道对于某些高性能摄像头模块至关重要。摄像头的选择是重中之重。普通USB网络摄像头比如罗技C920最容易上手用OpenCV的cv2.VideoCapture就能读取但它的所有图像处理压力都压在树莓派的CPU上。做实时人员检测帧率可能只能勉强跑到10FPS左右CPU占用率会很高。所以我最终选择了树莓派官方的高质量摄像头Raspberry Pi High Quality Camera搭配一个广角镜头并通过CSI-2接口连接。这不是一个严格意义上的“AI相机”但它有以下几个优势低延迟、高带宽CSI-2是直接连接到树莓派SoC的专用接口图像数据吞吐量大延迟远低于USB摄像头。可利用硬件加速树莓派的GPUVideoCore VI和专用的图像处理管线ISP可以对CSI摄像头的数据进行硬件级的缩放、色彩转换等预处理极大减轻CPU负担。灵活的镜头选择可以根据监控距离和视角更换镜头我用的广角镜头能覆盖更大的区域。当然如果你追求极致的推理性能可以考虑像Google Coral USB AcceleratorTPU加速棒搭配普通USB摄像头或者使用内置NPU的专用AI摄像头模组。但综合成本、易得性和生态树莓派官方CSI摄像头是一个平衡性很好的选择。2.2 软件生态OpenPLC与Modbus的必然性在PLC端我选择了OpenPLC。它是一个开源的、符合IEC 61131-3标准的软PLC运行时。为什么不用传统的西门子、三菱PLC原因很简单成本和开放性。零硬件成本OpenPLC可以运行在树莓派甚至是一台旧的电脑上本身就是一个强大的控制器。完全开源你可以深入查看其Modbus通讯栈的实现遇到问题有地方可查。标准兼容它支持标准的梯形图LD、功能块图FBD等编程语言工业控制逻辑的编写方式和传统PLC无异。通讯协议毫无悬念地选择了Modbus TCP。在工业环境里Modbus就像普通话几乎所有的设备PLC、HMI、传感器、驱动器都支持。它简单、可靠、易于调试。相比于Modbus RTU串口Modbus TCP基于以太网布线方便速度更快更适合树莓派和运行OpenPLC的工控机或另一台树莓派之间的通讯。整个数据流是这样的树莓派上运行的Python人员检测程序 - 通过pymodbus库封装检测结果 - 作为保持寄存器Holding Register或线圈Coil写入 - OpenPLC作为Modbus TCP从站Slave接收并映射到其内部变量 - OpenPLC的逻辑程序根据变量值执行相应的控制动作如启动报警。这个架构清晰地将“感知”和“控制”解耦树莓派只负责“看”和“报”复杂的连锁逻辑、设备时序控制、安全处理全部交给专业的PLC环境这才是符合工业实践的做法。3. 树莓派端环境搭建与人员检测模型部署让树莓派学会“看人”是整个项目的第一步。这里的关键不是追求最高的检测精度而是在有限的算力下实现稳定、实时的推理。3.1 基础系统与驱动配置首先安装树莓派操作系统Raspberry Pi OS Lite 64位版本并启用SSH和配置好网络。接着是摄像头驱动对于CSI摄像头需要在sudo raspi-config中启用Camera Interface。完成后可以用libcamera-hello命令测试摄像头是否正常工作。对于AI推理我们需要一个高效的框架。我放弃了臃肿的TensorFlow选择了PyTorch配合TorchVision。虽然Arm64的PyTorch安装稍麻烦但它的API更友好模型转换和部署也更灵活。更重要的是我们可以利用TorchVision官方提供的、经过预训练的轻量级模型。# 安装PyTorch和TorchVision (具体版本号请查阅PyTorch官网针对树莓派的最新安装指令) pip3 install torch torchvision --extra-index-url https://download.pytorch.org/whl/cpu # 安装OpenCV用于图像采集和预处理 pip3 install opencv-python-headless # 安装Modbus客户端库 pip3 install pymodbus注意务必安装opencv-python-headless这个版本没有GUI相关的依赖体积更小更适合服务器或无头模式运行的树莓派。3.2 轻量级人员检测模型的选择与优化直接使用庞大的Faster R-CNN或YOLOv5s在树莓派上跑实时检测是不现实的。我们的目标是“检测人”这是一个单类别的检测任务可以大大简化模型。我测试了两种方案MobileNetV3-SSD Lite这是一个经典的移动端检测架构。TorchVision的detection模块里就有预训练好的ssdlite320_mobilenet_v3_large模型它在COCO数据集上训练过能检测包括“人”在内的80类物体。我们可以只保留“person”这个类别的输出。YOLOv5nUltralytics发布的YOLOv5 nano版本是迄今为止最轻量的YOLO模型之一。通过PyTorch Hub可以轻松加载并且专门为边缘设备优化过。经过实测在树莓派4B上输入图像尺寸调整为320x320时MobileNetV3-SSD Lite推理速度约120ms/帧CPU占用率较高但集成简单。YOLOv5n推理速度约220ms/帧但准确率特别是对小目标和部分遮挡的人的检测明显优于SSD Lite。我最终选择了YOLOv5n。虽然单帧慢一点但考虑到我们的应用场景安全监控对漏报的容忍度极低准确率优先。4-5 FPS的检测速度对于“人员闯入”这种非瞬间事件来说已经足够触发PLC联动了。部署时有几个关键的优化点模型预热在循环开始前先用一张空白图片跑一次推理触发PyTorch的JIT编译和底层优化避免第一次检测时的长时间延迟。非极大值抑制NMS阈值调整由于只检测“人”可以适当提高NMS的iou_threshold比如从0.45调到0.6让模型在一个人体可能产生多个重叠框时更快地合并成一个减少后处理时间。置信度阈值动态调整根据环境光照白天/夜晚可以微调检测的置信度阈值。白天光线好阈值可以设高些如0.7以减少误报夜晚或光线差时可以适当降低如0.5以避免漏报但同时需要在PLC逻辑端增加去抖动滤波。import torch import cv2 # 加载模型 (首次运行会自动从网上下载) model torch.hub.load(ultralytics/yolov5, yolov5n, pretrainedTrue) model.conf 0.6 # 置信度阈值 model.iou 0.6 # NMS IoU阈值 model.classes [0] # 只检测person类 (COCO数据集中人的ID是0) # 预热模型 _ model(torch.zeros(1, 3, 320, 320)) # 图像采集循环示例 cap cv2.VideoCapture(0) # 对于CSI摄像头可能需要使用GStreamer管道或libcamera while True: ret, frame cap.read() if not ret: break # 推理 results model(frame) # results.pandas().xyxy[0] 包含了检测框、置信度、类别信息 person_detected len(results.pandas().xyxy[0]) 0 # 将 person_detected 状态通过Modbus发送出去4. OpenPLC环境配置与Modbus从站设置现在我们需要让OpenPLC准备好接收来自树莓派的信号。我选择将OpenPLC Runtime安装在一台旧的x86工控机上与树莓派处于同一局域网。当然你也可以把它安装在树莓派本机上通过容器或直接安装实现“All in One”但那样会争夺计算资源不推荐用于要求高的场景。4.1 OpenPLC的安装与基础项目创建从OpenPLC官网下载适用于你操作系统Linux/Windows的编辑器OpenPLC Editor和运行时OpenPLC Runtime。安装完成后首先在运行时的机器上启动OpenPLC Runtime服务它会提供一个Web配置界面默认端口8080。创建新项目在OpenPLC Editor中创建一个新的“连续功能图CFC”或“梯形图LD”项目。我们只需要一个简单的布尔变量来接收人员检测状态。定义变量在变量声明区定义一个布尔型变量命名为PersonDetected。这个变量将作为我们与外部世界通讯的接口。编写简单逻辑在程序编辑区可以拖拽一个常开触点关联到PersonDetected变量后面连接一个线圈输出命名为AlarmOutput。这样当PersonDetected为True时AlarmOutput就为True。你可以把这个输出映射到实际的物理输出点如果OpenPLC连接了硬件IO模块或者仅作为内部标志用于更复杂的逻辑。设置Modbus映射这是最关键的一步。在OpenPLC Editor的“资源”或“设置”中找到Modbus映射表。我们需要将PersonDetected这个内部变量映射到Modbus的某个地址空间。通常离散量输入Coils 可读可写的地址范围是0xxxx保持寄存器Holding Registers 可读可写是4xxxx。对于简单的布尔信号映射到一个Coil地址更符合习惯例如地址00001。4.2 配置OpenPLC为Modbus TCP从站在OpenPLC Runtime的Web管理界面通常是http://runtime_ip:8080进行配置进入“Slave Devices”设置。添加一个新的Modbus设备选择类型为“Modbus TCP Slave”。设置从站参数Device Name: 例如 “AI_Camera_Link”。Port: Modbus TCP标准端口是502确保防火墙开放此端口。Slave ID: 在Modbus TCP中这个ID有时被忽略或用作单元标识符通常设为1即可。Address: 保持0.0.0.0以监听所有网络接口。Scan Rate: 轮询速率设为100ms足够快。关键步骤数据映射。在这里你需要将之前在编辑器中映射的Modbus地址如Coil 00001与OpenPLC内部的PersonDetected变量绑定。这个界面通常提供一个表格让你选择Modbus地址类型Coil/Register、起始地址并关联一个内部变量。保存并启动保存设置然后在“Dashboard”页面启动PLC运行时。此时OpenPLC就开始在502端口监听Modbus TCP请求并准备读写我们映射的变量了。为了测试Modbus从站是否工作我强烈推荐在调试阶段使用Modbus Poll主站模拟器和Modbus Slave从站模拟器这对黄金组合。你可以先用Modbus Slave模拟一个从站用Modbus Poll去读写熟悉协议。然后再用Modbus Poll直接连接我们刚配置好的OpenPLC尝试写入Coil 00001的值观察OpenPLC Web界面里PersonDetected变量的状态是否随之改变。这一步能极大避免后续集成时的通讯类问题。5. PyModbus通讯集成与数据同步逻辑树莓派端的程序检测到人后需要可靠地将这个状态“推送”到OpenPLC。我们使用pymodbus库来实现Modbus TCP客户端的功能。5.1 建立稳定的Modbus TCP连接首先要处理网络的不稳定性。工业现场网络也可能有波动我们的程序不能因为一次连接失败就崩溃。from pymodbus.client import ModbusTcpClient import time import logging logging.basicConfig(levellogging.INFO) PLC_IP 192.168.1.100 # OpenPLC运行时所在机器的IP PLC_PORT 502 COIL_ADDRESS 0 # Modbus地址对应Coil 00001。注意pymodbus通常使用0基地址。 def create_modbus_client(): 创建并连接Modbus客户端包含重试机制 retry_count 0 max_retries 5 while retry_count max_retries: try: client ModbusTcpClient(PLC_IP, portPLC_PORT) if client.connect(): logging.info(f成功连接到Modbus服务器 {PLC_IP}:{PLC_PORT}) return client else: logging.warning(f连接失败第{retry_count1}次重试...) time.sleep(2) retry_count 1 except Exception as e: logging.error(f连接时发生异常: {e}) time.sleep(2) retry_count 1 logging.error(f无法连接到Modbus服务器已达到最大重试次数{max_retries}) return None client create_modbus_client()5.2 状态写入与防抖滤波设计直接每次检测到人就立刻写入True没检测到就立刻写入False会产生大量频繁的通讯请求并且任何单帧的误检都会导致PLC误动作。因此必须在树莓派端加入**软件防抖Debounce**逻辑。防抖的逻辑是只有当“检测到人”的状态持续一定时间比如1秒我们才认为这是一个有效事件并通知PLC。同样只有当“未检测到人”的状态持续一定时间才认为人确实离开了。import collections class PersonDetectorDebouncer: def __init__(self, window_size5, positive_threshold4): Args: window_size: 状态缓存队列长度对应时间窗口 (window_size * 检测周期)。 positive_threshold: 判定为‘有人’所需的最小正样本数。 self.state_buffer collections.deque(maxlenwindow_size) self.window_size window_size self.positive_threshold positive_threshold self.last_reported_state False def update(self, current_detection): 更新当前检测状态并返回经过防抖处理后的稳定状态 self.state_buffer.append(current_detection) if len(self.state_buffer) self.window_size: # 窗口未填满不改变状态 return self.last_reported_state positive_count sum(self.state_buffer) # 判断逻辑窗口内正样本数超过阈值则判定为有人否则无人。 new_state positive_count self.positive_threshold # 只有状态发生变化时才返回新状态并记录 if new_state ! self.last_reported_state: self.last_reported_state new_state return new_state else: return None # 状态未变化返回None # 在主循环中使用 detector PersonDetectorDebouncer(window_size5, positive_threshold4) last_write_time time.time() write_interval 0.2 # 最小写入间隔避免过度通讯 while True: # ... 图像采集和模型推理得到 person_detected (True/False) stable_state detector.update(person_detected) if stable_state is not None and (time.time() - last_write_time) write_interval: # 状态稳定且发生了变化且距离上次写入已过最小间隔 try: # 写入Modbus Coil。注意write_coil的address参数是0基。 # 写入 True 或 False response client.write_coil(COIL_ADDRESS, stable_state) if response.isError(): logging.error(f写入Modbus失败: {response}) else: logging.info(f状态已更新为: {stable_state}) last_write_time time.time() except Exception as e: logging.error(f通讯异常: {e}) # 可以考虑在这里加入重连逻辑 client create_modbus_client()这个设计确保了系统的抗干扰能力。即使模型在某几帧里误检了飞过的鸟或晃动的窗帘只要不是连续发生就不会触发PLC动作。6. 系统联调与实战中的关键问题排查把所有部分连接起来才是挑战的开始。下面是我在联调过程中遇到的几个典型问题及其解决方法。6.1 通讯超时与连接中断问题现象树莓派程序运行一段时间后日志开始报“Connection timed out”或“Modbus IOException”之后检测状态无法再更新到PLC。排查过程检查网络pingOpenPLC主机持续一段时间看是否有丢包或延迟激增。工业现场网线质量、交换机端口都可能有问题。检查OpenPLC Runtime状态登录Web界面查看PLC是否仍在“Running”状态有时复杂的逻辑错误可能导致运行时崩溃。检查防火墙确认OpenPLC主机尤其是Windows系统的防火墙是否阻止了502端口或者是否只允许了特定IP。可以将防火墙暂时关闭测试。分析pymodbus日志启用DEBUG级别日志发现连接断开后客户端没有自动重连机制。解决方案在树莓派程序中实现一个心跳机制和自动重连。除了上面的create_modbus_client重连函数还可以定期比如每读写10次尝试一个简单的读操作client.read_coils如果失败则触发重连流程。在OpenPLC端可以考虑使用其“Watchdog”功能或者编写一个简单的通讯健康检查逻辑。6.2 Modbus地址映射错误问题现象用Modbus Poll能读写成功但树莓派程序写入后OpenPLC内的变量状态无变化。排查过程确认地址这是最常见的问题。Modbus地址有“1基”和“0基”之分。协议规范通常是1基如00001但很多软件库包括pymodbus使用0基地址。我程序中写的COIL_ADDRESS 0对应的是Coil 00001。需要确保OpenPLC映射表里设置的地址也是从1开始00001而不是0。确认数据类型我们传输的是布尔量用的是Coil0xxxx。如果错误地映射到了保持寄存器4xxxx那么用write_coil是写不进去的。在OpenPLC的Modbus设备映射配置中必须明确选择“Coil (0x)”类型。使用Modbus Poll交叉验证用Modbus Poll同时连接OpenPLC并监控同一个Coil地址。当树莓派程序写入时观察Modbus Poll里的值是否同步变化。如果Modbus Poll里变了而OpenPLC变量没变问题就在OpenPLC内部的变量映射上如果Modbus Poll里都没变问题就在树莓派程序或网络链路上。6.3 树莓派推理性能波动与误报问题现象白天系统运行稳定夜晚误报警增多或者当树莓派同时运行其他任务时检测延迟变大导致防抖逻辑失效。排查过程监控资源使用htop命令监控树莓派的CPU和内存使用率。发现当推理帧率下降时CPU占用率常达到100%。分析图像质量夜晚光线不足摄像头采集的图像噪声大对比度低模型置信度下降。为了不漏检程序降低了置信度阈值但同时也引入了更多背景误报如将昏暗处的物体轮廓误认为人。解决方案优化树莓派进程优先级使用nice和ionice命令赋予检测程序更高的CPU调度优先级和IO优先级减少被其他后台任务干扰。nice -n -10 python3 person_detection_modbus.py引入动态参数调整根据环境光传感器读数或图像平均亮度动态调整模型的置信度阈值和防抖窗口参数。夜晚时可以适当增大防抖窗口window_size要求更长时间持续的检测才判定为有效。硬件辅助如果条件允许可以为树莓派增加一个小型散热风扇防止因过热降频导致性能下降。也可以考虑使用带红外补光的摄像头提升夜间图像质量。6.4 OpenPLC逻辑处理与响应延迟问题现象树莓派状态已更新PLC输出动作有明显延迟超过1秒。排查过程检查OpenPLC扫描周期在OpenPLC项目设置中有一个“循环时间”或“扫描周期”。如果这个值设置得太大比如默认的100msPLC处理完一段逻辑后会等待这个周期结束才开始下一次扫描。对于需要快速响应的应用可以将其适当调小如20ms。优化PLC程序逻辑检查梯形图程序是否过于复杂包含了大量的定时器、计数器或复杂的数学运算这些都会增加单个扫描周期的时间。确保人员检测触发的逻辑路径尽可能简洁。使用立即输出在某些OpenPLC实现中标准的线圈输出可能在扫描周期结束时才统一更新到物理输出。查阅OpenPLC文档看是否有“立即输出”指令可以在逻辑执行中即刻更新输出状态减少延迟。通过以上四个层面的联调和问题解决一个稳定可靠的“树莓派AI视觉检测OpenPLC控制”的边缘智能系统就真正搭建完成了。这个方案的成功关键在于理解每个组件的边界和特性并在它们之间建立一条简单、健壮的数据通道。它证明了用低成本的开源硬件和软件完全可以构建出满足特定工业需求的智能控制系统。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价