当前位置: 首页 > news >正文

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

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相机”,但它有以下几个优势:

  1. 低延迟、高带宽:CSI-2是直接连接到树莓派SoC的专用接口,图像数据吞吐量大,延迟远低于USB摄像头。
  2. 可利用硬件加速:树莓派的GPU(VideoCore VI)和专用的图像处理管线(ISP)可以对CSI摄像头的数据进行硬件级的缩放、色彩转换等预处理,极大减轻CPU负担。
  3. 灵活的镜头选择:可以根据监控距离和视角更换镜头,我用的广角镜头能覆盖更大的区域。

当然,如果你追求极致的推理性能,可以考虑像Google Coral USB Accelerator(TPU加速棒)搭配普通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在树莓派上跑实时检测是不现实的。我们的目标是“检测人”,这是一个单类别的检测任务,可以大大简化模型。

我测试了两种方案:

  1. MobileNetV3-SSD Lite:这是一个经典的移动端检测架构。TorchVision的detection模块里就有预训练好的ssdlite320_mobilenet_v3_large模型,它在COCO数据集上训练过,能检测包括“人”在内的80类物体。我们可以只保留“person”这个类别的输出。
  2. YOLOv5n:Ultralytics发布的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', pretrained=True) 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)。

  1. 创建新项目:在OpenPLC Editor中,创建一个新的“连续功能图(CFC)”或“梯形图(LD)”项目。我们只需要一个简单的布尔变量来接收人员检测状态。
  2. 定义变量:在变量声明区,定义一个布尔型变量,命名为PersonDetected。这个变量将作为我们与外部世界通讯的接口。
  3. 编写简单逻辑:在程序编辑区,可以拖拽一个常开触点,关联到PersonDetected变量,后面连接一个线圈输出,命名为AlarmOutput。这样,当PersonDetected为True时,AlarmOutput就为True。你可以把这个输出映射到实际的物理输出点(如果OpenPLC连接了硬件IO模块),或者仅作为内部标志用于更复杂的逻辑。
  4. 设置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)进行配置:

  1. 进入“Slave Devices”设置
  2. 添加一个新的Modbus设备,选择类型为“Modbus TCP Slave”。
  3. 设置从站参数
    • Device Name: 例如 “AI_Camera_Link”。
    • Port: Modbus TCP标准端口是502,确保防火墙开放此端口。
    • Slave ID: 在Modbus TCP中,这个ID有时被忽略或用作单元标识符,通常设为1即可。
    • Address: 保持0.0.0.0以监听所有网络接口。
    • Scan Rate: 轮询速率,设为100ms足够快。
  4. 关键步骤:数据映射。在这里,你需要将之前在编辑器中映射的Modbus地址(如Coil 00001),与OpenPLC内部的PersonDetected变量绑定。这个界面通常提供一个表格,让你选择Modbus地址类型(Coil/Register)、起始地址,并关联一个内部变量。
  5. 保存并启动:保存设置,然后在“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(level=logging.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, port=PLC_PORT) if client.connect(): logging.info(f"成功连接到Modbus服务器 {PLC_IP}:{PLC_PORT}") return client else: logging.warning(f"连接失败,第{retry_count+1}次重试...") 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_size=5, positive_threshold=4): """ Args: window_size: 状态缓存队列长度,对应时间窗口 (window_size * 检测周期)。 positive_threshold: 判定为‘有人’所需的最小正样本数。 """ self.state_buffer = collections.deque(maxlen=window_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_size=5, positive_threshold=4) 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。

排查过程

  1. 检查网络pingOpenPLC主机,持续一段时间,看是否有丢包或延迟激增。工业现场网线质量、交换机端口都可能有问题。
  2. 检查OpenPLC Runtime状态:登录Web界面,查看PLC是否仍在“Running”状态,有时复杂的逻辑错误可能导致运行时崩溃。
  3. 检查防火墙:确认OpenPLC主机(尤其是Windows系统)的防火墙是否阻止了502端口,或者是否只允许了特定IP。可以将防火墙暂时关闭测试。
  4. 分析pymodbus日志:启用DEBUG级别日志,发现连接断开后,客户端没有自动重连机制。

解决方案

  • 在树莓派程序中实现一个心跳机制自动重连。除了上面的create_modbus_client重连函数,还可以定期(比如每读写10次)尝试一个简单的读操作(client.read_coils),如果失败则触发重连流程。
  • 在OpenPLC端,可以考虑使用其“Watchdog”功能,或者编写一个简单的通讯健康检查逻辑。

6.2 Modbus地址映射错误

问题现象:用Modbus Poll能读写成功,但树莓派程序写入后,OpenPLC内的变量状态无变化。

排查过程

  1. 确认地址:这是最常见的问题。Modbus地址有“1基”和“0基”之分。协议规范通常是1基(如00001),但很多软件库(包括pymodbus)使用0基地址。我程序中写的COIL_ADDRESS = 0,对应的是Coil 00001。需要确保OpenPLC映射表里设置的地址也是从1开始(00001),而不是0。
  2. 确认数据类型:我们传输的是布尔量,用的是Coil(0xxxx)。如果错误地映射到了保持寄存器(4xxxx),那么用write_coil是写不进去的。在OpenPLC的Modbus设备映射配置中,必须明确选择“Coil (0x)”类型。
  3. 使用Modbus Poll交叉验证:用Modbus Poll同时连接OpenPLC,并监控同一个Coil地址。当树莓派程序写入时,观察Modbus Poll里的值是否同步变化。如果Modbus Poll里变了而OpenPLC变量没变,问题就在OpenPLC内部的变量映射上;如果Modbus Poll里都没变,问题就在树莓派程序或网络链路上。

6.3 树莓派推理性能波动与误报

问题现象:白天系统运行稳定,夜晚误报警增多;或者当树莓派同时运行其他任务时,检测延迟变大,导致防抖逻辑失效。

排查过程

  1. 监控资源:使用htop命令监控树莓派的CPU和内存使用率。发现当推理帧率下降时,CPU占用率常达到100%。
  2. 分析图像质量:夜晚光线不足,摄像头采集的图像噪声大,对比度低,模型置信度下降。为了不漏检,程序降低了置信度阈值,但同时也引入了更多背景误报(如将昏暗处的物体轮廓误认为人)。

解决方案

  • 优化树莓派进程优先级:使用niceionice命令,赋予检测程序更高的CPU调度优先级和IO优先级,减少被其他后台任务干扰。
    nice -n -10 python3 person_detection_modbus.py
  • 引入动态参数调整:根据环境光传感器读数或图像平均亮度,动态调整模型的置信度阈值和防抖窗口参数。夜晚时,可以适当增大防抖窗口(window_size),要求更长时间持续的检测才判定为有效。
  • 硬件辅助:如果条件允许,可以为树莓派增加一个小型散热风扇,防止因过热降频导致性能下降。也可以考虑使用带红外补光的摄像头,提升夜间图像质量。

6.4 OpenPLC逻辑处理与响应延迟

问题现象:树莓派状态已更新,PLC输出动作有明显延迟(超过1秒)。

排查过程

  1. 检查OpenPLC扫描周期:在OpenPLC项目设置中,有一个“循环时间”或“扫描周期”。如果这个值设置得太大(比如默认的100ms),PLC处理完一段逻辑后,会等待这个周期结束才开始下一次扫描。对于需要快速响应的应用,可以将其适当调小(如20ms)。
  2. 优化PLC程序逻辑:检查梯形图程序是否过于复杂,包含了大量的定时器、计数器或复杂的数学运算,这些都会增加单个扫描周期的时间。确保人员检测触发的逻辑路径尽可能简洁。
  3. 使用立即输出:在某些OpenPLC实现中,标准的线圈输出可能在扫描周期结束时才统一更新到物理输出。查阅OpenPLC文档,看是否有“立即输出”指令,可以在逻辑执行中即刻更新输出状态,减少延迟。

通过以上四个层面的联调和问题解决,一个稳定可靠的“树莓派AI视觉检测+OpenPLC控制”的边缘智能系统就真正搭建完成了。这个方案的成功,关键在于理解每个组件的边界和特性,并在它们之间建立一条简单、健壮的数据通道。它证明了用低成本的开源硬件和软件,完全可以构建出满足特定工业需求的智能控制系统。

http://www.cnnetsun.cn/news/4106922.html

相关文章:

  • Day53-Docker:Dockerfile最佳实践 + 多阶段构建 + docker-compose编排
  • 游泳腹痛的原因
  • 汽车电子电气架构演进:从分布式ECU到域集中与中央计算
  • 奥迪与保时捷合作开发高性能纯电平台:从PPE到下一代架构的深度解析
  • 致读者:感谢你陪伴我们走完这1000篇文章的旅程
  • 基于BLE与ARM Cortex-M3的无线MIDI控制器设计与实现
  • 第 3 章 SDMA 指令集:Packet 格式速览
  • 机器人开发中如何平衡稳定性与敏捷性:从硬件选型到控制算法的工程实践
  • ESP32墨水屏PC性能监控器:低功耗硬件方案与全栈实践
  • 拆解英飞凌最小ToF模组:技术原理、实现与手机面部解锁应用
  • STM32H750 DMA驱动SPI LCD与AHT21传感器C语言嵌入式开发实践
  • 【关注可白嫖源码】--课程设计--毕业设计--基于Django框架的房屋租赁系统的设计与实现[编号:project28636](案件分析)
  • Arduino MKR WAN 1310物联网开发板:LoRa远距离通信与低功耗设计实战
  • 数据库与中间件
  • 借助 AI-DLC 完成研发团队转型,传统企业该挑选哪些云上工具和方案?
  • OpsFlash v0.2.0 版本发布:新增多项功能,跨平台桌面运维工具再升级!
  • 突破性DSP语音模组AP-0316引领声学革命
  • 基于合成数据与ESP32-S3的跨语言关键词唤醒模型实战指南
  • AIoT边缘计算硬件选型与推理部署实战指南
  • leetcode 1722. Minimize Hamming Distance After Swap Operations
  • Spring Boot AOP记录用户操作日志
  • 嵌入式开发入门:从LED与传感器控制到物联网系统构建
  • 基于EasyUI与KnockoutJS的通用分页查询与数据导出ViewModel设计
  • 广州微闻网络AI落地技术实践:Agent定制、Token供应与云计算全栈技术解析
  • 在线教育平台开课前三网验收:视频域、直播与 API
  • 多个人同时提问但位置有限
  • UnrealPakViewer 完整上手教程:三步摸清任意 UE4 Pak 文件内部结构
  • Think-a-Tron Mini:从复古玩具到DIY电子项目,探索伪随机数生成与电路设计
  • 102.环形缓冲区之读指针与写指针:原理、实现与完整代码
  • BBDown完整使用手册:让哔哩哔哩视频下载变成一行命令的事