
1. 项目缘起一个被忽视的室内安全痛点去年我参与了一个线下小型展览的筹备工作。活动本身规模不大但现场人流却出乎意料地聚集尤其是在几个热门展品前人群几乎挤成了疙瘩。当时我们虽然安排了工作人员进行疏导但效果有限主要靠肉眼判断和口头提醒既被动又容易引发摩擦。活动结束后复盘一个核心问题被反复提及我们如何能客观、实时地知道一个封闭空间里到底有多少人以及他们是否过于密集这个问题看似简单却直接关系到公共安全与活动体验。无论是社区活动中心、小型图书馆、咖啡馆的阅读区还是我们刚才提到的展览、小型会议室管理者往往只能凭感觉或事后查看监控录像来评估人流密度缺乏一个低成本、自动化、可量化的预警手段。传统的解决方案要么是昂贵的商业客流统计系统要么是依赖复杂的AI摄像头和云端分析对于预算有限或注重隐私的小型场所来说门槛太高。于是我萌生了自己动手做一个简易版室内人流密度监控系统的想法。核心目标很明确低成本、易部署、本地运行、实时反馈。我把它命名为piCounter v1.0。这个名字直接点明了它的核心基于树莓派Raspberry Pi的计数器Counter用于室内社交距离的辅助控制。它不是要取代人工管理而是为管理者提供一个可靠的“数据眼睛”在人群密度超过预设阈值时及时发出警报从而主动干预防患于未然。2. 核心设计思路为什么是“树莓派传感器”的方案在构思piCounter时我评估了几种主流的技术路径。基于摄像头OpenCV的人体检测方案精度高但涉及隐私问题部署心理门槛高且对树莓派的算力要求不低在复杂光照和多人物遮挡场景下容易误判。另一种思路是Wi-Fi探针通过嗅探手机的MAC地址来统计人数但这同样有隐私争议且无法区分设备是否属于当前室内人员比如路过门口的路人准确性存疑。经过权衡我选择了基于红外对射或区域型红外传感器的方案。它的原理非常直观在入口处安装一对红外发射和接收管当有人穿过光束时就会产生一个计数信号。对于单入口的小空间一个计数器就足够了对于有多个入口或需要区分进出方向的情况则可以部署两套传感器形成“方向判断逻辑”。这个方案的优点非常突出绝对隐私友好它不采集任何图像或生物特征信息只记录一个“穿过光束”的事件从根本上避免了隐私纠纷。成本极低一对红外对射传感器模块只需十几元树莓派Zero W本身也是一款性价比极高的微型电脑。反应迅速且可靠红外传感是物理中断触发几乎无延迟不受光照影响在室内环境下非常稳定。逻辑清晰计数逻辑简单直接代码易于编写和维护核心就是一个累加或累减的算法。当然它也有局限性主要是无法追踪人在室内的具体位置只能提供“总人数”这个宏观数据。但对于解决“是否超员”这个核心问题它已经足够有效。整个系统的架构可以概括为传感器采集信号 - 树莓派处理信号并计数 - 树莓派判断阈值并控制输出 - 通过显示屏或网络界面展示状态。3. 硬件选型与电路连接精打细算下的稳定组合硬件是项目的地基稳定可靠是第一要务。下面是我在v1.0版本中使用的组件清单和选型理由核心控制器Raspberry Pi Zero W选择Zero W而非性能更强的3B或4B主要基于以下几点考虑功耗与体积Zero W体积小巧功耗极低约1.2W适合7x24小时不间断运行也便于隐藏安装。成本价格更具优势。功能满足本项目逻辑简单对CPU算力要求不高。Zero W自带的Wi-Fi和蓝牙功能为未来扩展无线数据传输或手机通知提供了可能虽然v1.0未使用但保留了升级空间。GPIO引脚尽管只有40个GPIO引脚但对于连接几个传感器和输出设备绰绰有余。注意树莓派GPIO引脚的工作电压是3.3V严禁直接接入5V信号否则会损坏主板。所有外部设备必须确认其信号电平是否兼容。传感单元红外对射传感器模块E18-D80NK这是一款常见的漫反射型光电开关但我将其作为对射管使用。它内部集成了发射、接收和信号调理电路输出干净的数字信号检测到物体时输出低电平常态高电平使用起来比分离的红外发射接收管方便得多。有效距离最大80cm可根据入口宽度调整安装位置。输出类型NPN常开型。这对我们编程很重要意味着默认状态下输出高电平3.3V或5V需跳线选择当光束被阻断时输出低电平0V。调节旋钮模块上有一个灵敏度调节旋钮在安装后需要仔细调试以避免因环境光或微小飞虫引起的误触发。状态反馈单元RGB LED模块与OLED显示屏RGB LED用于直观的灯光报警。我定义了三色状态绿色人数正常、黄色接近阈值预警、红色超过阈值警报。我选择了一个集成的RGB LED模块它自带限流电阻直接连接GPIO即可。0.96英寸OLED显示屏I2C接口用于显示实时人数、设定阈值等信息。选择I2C接口的型号是因为它仅需两根数据线SDA, SCL节省GPIO资源且驱动库成熟。其他杜邦线母对母、母对公、一个5V/2A的Micro USB电源适配器给树莓派供电以及一个用于给传感器模块供电的5V电源可与树莓派共用但需注意总电流。电路连接示意图文字描述红外传感器VCC接5V电源正极GND接电源负极与树莓派GND共地。OUT引脚接树莓派的GPIO引脚例如GPIO17。关键步骤务必用万用表或通过观察模块指示灯确认其在触发时输出的是低电平0V。RGB LED模块通常有4个引脚VCC接3.3V或5V看模块规格、GND、R红色信号线接GPIO、G绿色、B蓝色。分别连接到三个GPIO引脚例如GPIO22, GPIO23, GPIO24。OLED显示屏VCC接3.3VGND接地SDA接树莓派的GPIO2SDASCL接GPIO3SCL。将所有设备的GND地线连接到树莓派的GND引脚形成共同的参考地这是电路正常工作的基础。硬件连接完成后建议先不编写复杂逻辑而是写一个简单的测试脚本分别验证每个传感器和输出设备是否正常工作这一步能排除大部分硬件连接错误。4. 软件环境搭建与核心逻辑剖析系统运行在树莓派官方的Raspberry Pi OS Lite无桌面版上以节省资源。核心程序使用Python编写因为它对GPIO操作有非常友好的库支持。4.1 环境与依赖库安装首先通过SSH登录到你的树莓派Zero W。# 更新系统 sudo apt update sudo apt upgrade -y # 安装Python3及包管理工具pip如果尚未安装 sudo apt install python3 python3-pip -y # 安装操作GPIO的核心库RPi.GPIO sudo apt install python3-rpi.gpio -y # 安装用于驱动OLED显示屏的库这里以Adafruit的SSD1306库为例 sudo pip3 install adafruit-circuitpython-ssd1306 # 该库依赖Pillow用于图像处理以及Adafruit-Blinka用于硬件抽象层 sudo pip3 install pillow adafruit-circuitpython-blinka4.2 核心计数逻辑与防抖处理这是整个项目的“大脑”。红外传感器非常敏感人在穿过光束时可能会因为身体不同部位如手臂摆动、背包导致光束被多次阻断和恢复从而产生多个快速交替的脉冲信号如果不加处理就会导致一次通过被计数多次。import RPi.GPIO as GPIO import time # 设置GPIO模式为BCM编号 GPIO.setmode(GPIO.BCM) # 定义红外传感器引脚 IR_PIN 17 # 定义RGB LED引脚 LED_R 22 LED_G 23 LED_B 24 # 初始化引脚 GPIO.setup(IR_PIN, GPIO.IN, pull_up_downGPIO.PUD_UP) # 启用内部上拉确保默认高电平 GPIO.setup([LED_R, LED_G, LED_B], GPIO.OUT) GPIO.output([LED_R, LED_G, LED_B], GPIO.LOW) # 初始灯灭 # 全局变量 person_count 0 MAX_CAPACITY 10 # 最大容纳人数阈值 WARNING_THRESHOLD 8 # 预警阈值 last_trigger_time 0 DEBOUNCE_TIME 0.5 # 防抖时间单位秒。根据人通过速度调整通常0.3-1秒。 def sensor_callback(channel): 红外传感器中断回调函数 global person_count, last_trigger_time current_time time.time() # 防抖判断如果两次触发间隔小于防抖时间则视为一次触发的抖动忽略 if current_time - last_trigger_time DEBOUNCE_TIME: return # 判断当前传感器状态因为我们是低电平触发 if GPIO.input(IR_PIN) GPIO.LOW: # 光束被阻断有人进入这里假设单传感器只统计进入 person_count 1 print(f[有人进入] 当前人数: {person_count}) last_trigger_time current_time # 更新显示和灯光状态 update_display_and_leds() # 添加事件检测检测下降沿从高到低的变化即光束被阻断的瞬间 GPIO.add_event_detect(IR_PIN, GPIO.FALLING, callbacksensor_callback, bouncetime300) # bouncetime是硬件防抖参数单位毫秒 def update_display_and_leds(): 根据当前人数更新OLED显示和RGB LED状态 # 1. 控制RGB LED if person_count MAX_CAPACITY: # 红色警报 GPIO.output(LED_R, GPIO.HIGH) GPIO.output(LED_G, GPIO.LOW) GPIO.output(LED_B, GPIO.LOW) elif person_count WARNING_THRESHOLD: # 黄色预警通过红色绿色混合 GPIO.output(LED_R, GPIO.HIGH) GPIO.output(LED_G, GPIO.HIGH) GPIO.output(LED_B, GPIO.LOW) else: # 绿色正常 GPIO.output(LED_R, GPIO.LOW) GPIO.output(LED_G, GPIO.HIGH) GPIO.output(LED_B, GPIO.LOW) # 2. 更新OLED显示 (此处省略具体SSD1306驱动代码仅展示逻辑) # display_clear() # display_text(fCount: {person_count}, 0, 0) # display_text(fMax: {MAX_CAPACITY}, 0, 20) # if person_count MAX_CAPACITY: # display_text(FULL! ALERT!, 0, 40) # display_show() try: print(piCounter v1.0 已启动。当前人数, person_count) while True: # 主循环可以处理其他任务如通过网络接口获取命令 # 计数由中断回调函数异步处理不阻塞主循环 time.sleep(1) except KeyboardInterrupt: print(\n程序被用户中断。) finally: # 程序退出前清理GPIO资源非常重要 GPIO.cleanup()代码关键点解析中断与回调使用GPIO.add_event_detect并指定callback函数是树莓派编程中处理GPIO事件的最佳实践。它避免了低效的“轮询”不断检查引脚状态而是当事件发生时由系统自动调用函数节省CPU资源。双重防抖代码中包含了两个防抖措施。一是bouncetime300参数这是硬件层面的防抖告诉库忽略300毫秒内的电平抖动。二是我们在回调函数里自己实现的软件防抖DEBOUNCE_TIME用于处理更长时间窗口内的、可能由人体缓慢通过造成的多次触发。这个时间需要根据实际安装高度和人的通过速度进行微调。全局变量person_count作为全局变量在中断回调函数中被修改。在多线程或异步编程中需要小心处理但在这个简单场景下是可行的。更严谨的做法是使用线程锁。4.3 进出方向判断的逻辑扩展如果空间有出口需要区分进出以准确计算室内人数就需要部署两套传感器A和B安装距离约等于人的肩宽。逻辑判断如下进入光束A先被阻断然后光束B被阻断A-B。离开光束B先被阻断然后光束A被阻断B-A。实现上需要在回调函数中记录每个传感器被触发的时间戳和顺序通过一个小的状态机来判断方向。这比单传感器复杂但原理清晰是许多商场客流统计系统的简化版。5. 部署、调试与实战中遇到的“坑”硬件项目三分做七分调。将piCounter部署到真实环境时会遇到一系列在桌面上想不到的问题。5.1 传感器安装与调试这是最易出错的一环。红外对射传感器需要精确对准确保发射管的光束能被接收管稳定接收。在调试时我用了一张深色卡纸作为调试工具。问题一环境光干扰。强烈的日光灯或太阳光直射可能导致接收器误触发。解决方案选择调制型红外传感器如E18-D80NK本身就是它发出的是特定频率的红外光接收器只解调该频率抗干扰能力强。安装时尽量避免正对光源。问题二触发不稳定。有时人走过没计数有时没东西却自己计数。解决方案使用万用表测量传感器OUT引脚电压。无遮挡时应为高电平接近VCC遮挡时应为稳定的低电平接近0V。如果电压跳动说明对准不佳或灵敏度需要调节。仔细调节传感器上的灵敏度旋钮。顺时针调高灵敏度探测距离变远但也易受干扰逆时针调低。目标是让人体通过时稳定触发而小飞虫或灰尘飘过不触发。在代码中增加print语句实时打印传感器状态和计数这是最直接的调试手段。5.2 电源与稳定运行树莓派Zero W虽然功耗低但长期运行且连接外设对电源质量有要求。问题使用劣质手机充电器或过长的USB线可能导致树莓派在传感器触发时因电流瞬间变化而重启或程序崩溃。解决方案使用官方推荐或品牌可靠的5V/2.5A电源适配器。传感器模块的供电尽量与树莓派分开或者从树莓派的GPIO的5V引脚取电时确认总电流未超过板载稳压芯片的限额通常1A左右。编写一个看门狗脚本。最简单的办法是利用系统的cron计划任务定期检查主程序是否在运行如果不在则重启它。更高级的可以用硬件看门狗芯片。5.3 网络与远程管理可选但实用虽然v1.0设计为本地独立运行但为其添加简单的网络功能会极大方便管理。我后来增加了一个功能通过树莓派内置的Wi-Fi创建一个简单的Web页面显示当前人数和状态。实现使用Python的Flask框架搭建一个轻量级Web服务器。代码片段示例from flask import Flask, render_template_string app Flask(__name__) app.route(/) def index(): html f htmlbody h1piCounter 监控页面/h1 p当前室内人数: strong{person_count}/strong/p p最大容量: {MAX_CAPACITY}/p p状态: {span stylecolor:red;已超员/span if person_count MAX_CAPACITY else 正常}/p /body/html return render_template_string(html) if __name__ __main__: # 注意在生产环境中不要使用debugTrue且应绑定到特定IP app.run(host0.0.0.0, port5000, debugFalse, threadedTrue)部署后我就可以在同一个局域网的手机或电脑上通过浏览器访问树莓派的IP地址和5000端口实时查看状态无需走到设备面前。6. 从v1.0到未来可能的优化与扩展方向piCounter v1.0已经能可靠地完成基础计数和报警任务。但在实际使用中总会发现可以做得更好的地方。6.1 硬件层面的优化电池供电与低功耗对于无固定电源的场所可以考虑使用大容量充电宝供电并优化代码让树莓派在无人时段进入休眠状态仅由传感器中断唤醒这将极大延长续航。传感器升级使用更专业的“红外光束计数器”模块它们通常成对出现自带对准指示灯安装更便捷。或者为了应对更宽的入口可以考虑使用红外光幕它由多组红外对射管组成一排覆盖区域更广不易被“跨步跳过”。声光报警集成除了RGB LED可以连接一个高分贝的蜂鸣器或语音模块在超员时发出声音警报提醒效果更直接。6.2 软件与功能扩展数据记录与可视化将每日、每周的人数数据如峰值人数、平均停留时间估算记录到SQLite数据库或CSV文件中并利用Matplotlib生成简单的趋势图。这对于分析空间使用效率非常有价值。多区域监控一个树莓派可以连接多组传感器监控多个房间或区域。代码需要为每个传感器分配独立的ID和计数变量并在显示界面上清晰区分。阈值动态调整通过Web界面或物理按钮允许管理员在不修改代码的情况下动态调整MAX_CAPACITY和WARNING_THRESHOLD。云端同步与通知结合物联网平台如阿里云IoT、Home Assistant将数据同步到云端并设置规则在超员时向管理员的手机发送推送通知。6.3 关于误判与漏判的再思考没有任何系统是完美的。piCounter基于红外光束一个根本的局限性是如果多人紧挨着同时通过光束只被阻断一次就会漏计。这在人流密集时确实会发生。对于精度要求极高的场合这不是最佳方案。但在大多数需要“社交距离控制”的场景下人群过于密集以至于紧贴通过这本身就已经是违反了社交距离原则此时系统即使少计了人数但“超员警报”的状态依然是正确的因为实际人数肯定更多。从这个角度看它虽然牺牲了一点计数精度但依然有效地服务于“防止过度拥挤”的核心目标。这个项目给我的最大启发是技术方案的选择永远是在成本、复杂度、隐私、精度和可靠性之间寻找最佳平衡点。piCounter v1.0可能看起来简单甚至“简陋”但它精准地命定了小型室内场所管理者的真实痛点——需要一个不贵、不麻烦、不侵犯隐私的“电子哨兵”。当你亲手把它调试好看到LED灯随着人员进出准确变换颜色时那种解决实际问题的满足感是任何现成产品都无法替代的。