简介:这份PDF是一篇基于树莓派与Python的智能安防系统设计技术文献,面向嵌入式开发初学者、物联网与智能家居方向的学生及课程设计人员,可用于家庭入侵报警与远程监控方案的参考与选题借鉴。资源包内共1个PDF文件,大小约1.85MB,内容以论文形式系统呈现系统总体方案、硬件模块选型、软件编程与完整Python代码实现,涵盖红外传感器、蜂鸣器、迷你摄像头与树莓派GPIO引脚的连接方式与端口定义,以及wxpy微信接口和Picamera库的调用逻辑,从传感器采集信息、照片回传、人机对话到远程控制报警的全流程均有说明,并配有引脚图与连接表格。目前该文献已有284人学习,适合需要快速理解树莓派安防项目整体架构、对照代码复现实验或撰写相关论文的读者参考。
1. 一颗红外模块加一张微信照片,这套安防方案的真实定位
半夜两点手机弹出告警,是很多人对家庭安防最朴素的期待。成品云摄像头能实现,但要 7×24 上传、要绑 App、云存储按月收费,室内画面还容易被窗帘晃动、宠物走动误判。2020 年那篇《基于树莓派+Python 的智能安防系统设计》走的另一条路:红外模块只当触发源,树莓派负责判断,摄像头按需抓一张,蜂鸣器做本地威慑,告警经微信推到手机上等人回一句 y 或 n。整套硬件不到两百块,逻辑全在 Python 里,改一行就是一个新功能。它适合做树莓派毕设的学生、想给出租屋加一道本地告警的人,以及要把「传感器—判断—执行」这条链路跑通的嵌入式入门者。边界得先讲清楚:单点红外只知道有人没人,不认脸、不留存连续录像,好不好用几乎全看误报抑制做得多细。
2. 树莓派 GPIO 与红外模块接线:BCM 编号、电平与边沿触发
硬件这一步错得最多的地方不是焊接,而是编号体系。树莓派 40Pin 排针上印的是物理引脚序号,Python 里默认操作的是 SoC 的 GPIO 编号,两套序号对不上,就会出现「线接得没问题、代码也不报错,但值永远是 0」这种最难查的故障。
2.1 红外与蜂鸣器的接线表
本设计用的红外模块是发射管 + 接收管的对射式测距模块,靠反射信号差判断前方是否有物体,板载电位器调检测距离;蜂鸣器用有源型,内部自带振荡源,给直流电压就响。
| 模块 | 模块引脚 | 树莓派物理引脚 | GPIO 编号 | 说明 |
|---|---|---|---|---|
| 红外传感器 | VCC | Pin 4 | 5V | 供电正极 |
| 红外传感器 | GND | Pin 6 | GND | 共地 |
| 红外传感器 | Dout | Pin 18 | GPIO24 | 数字输出,触发时拉低 |
| 蜂鸣器 | VCC | Pin 2 | 5V | 供电正极 |
| 蜂鸣器 | GND | Pin 39 | GND | 共地 |
| 蜂鸣器 | I/O | Pin 22 | GPIO25 | 信号输入 |
提示:树莓派 GPIO 是 3.3V 电平,模块的 Dout 输出不能超过 3.3V。红外模块一般 3.3V 兼容,可以直接接;如果换用 5V 输出的传感器,中间必须加分压电阻或电平转换模块,长期倒灌会损伤 GPIO。
2.2 为什么代码里写 24 而不是 18
物理 Pin 18 对应的 SoC 编号是 GPIO24,物理 Pin 22 对应 GPIO25。GPIO.setmode(GPIO.BCM)之后,代码里的数字必须是 SoC 编号,这是 BCM 命名;如果改成GPIO.setmode(GPIO.BOARD),代码里的数字就要换成物理引脚号。两种模式混用是新手最常见的坑,建议整个项目统一用 BCM,然后在接线表里把两列都写下来,接线时照着物理引脚插,写代码时照着 GPIO 列填。
2.3 初始化与边沿等待
轮询读电平会白烧 CPU,红外这类事件型输入更适合边沿触发。下面这段是长期运行的写法,用wait_for_edge阻塞等待,避免 while 循环空转。
import RPi.GPIO as GPIO import time IR_PIN = 24 # 物理 Pin 18,BCM24 BUZZER_PIN = 25 # 物理 Pin 22,BCM25 GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) # 红外模块输出为开漏/推挽均可,这里开内部上拉,避免悬空误触发 GPIO.setup(IR_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # 蜂鸣器默认低电平,上电不响 GPIO.setup(BUZZER_PIN, GPIO.OUT, initial=GPIO.LOW) def wait_intrusion(timeout=None): """阻塞等待一次入侵触发,返回 True/False""" ch = GPIO.wait_for_edge(IR_PIN, GPIO.FALLING, timeout=timeout) return ch is not NoneGPIO.FALLING表示下降沿,因为模块检测到障碍物时 Dout 由高变低;如果你的模块是反相的,把这里改成GPIO.RISING即可。timeout参数单位是毫秒,传None表示一直等,传 1000 表示 1 秒内没有触发就返回,适合在等待过程中插入心跳检查。
2.4 抖动抑制:红外误报的真正来源
红外模块的 Dout 在触发瞬间会有一段几十毫秒的抖动,直接进业务逻辑会导致一次路过触发三四次报警。硬件上可以调模块上的电位器改变灵敏度,但更稳的是软件加一个屏蔽窗口。
import logging import time last_trigger = 0.0 COOLDOWN = 5.0 # 秒,冷却窗口内的重复触发直接丢弃 def on_intrusion(): global last_trigger now = time.monotonic() if now - last_trigger < COOLDOWN: logging.info("冷却期内,忽略重复触发") return last_trigger = now logging.warning("确认入侵,进入告警流程") handle_intrusion() # 抓拍 + 推送用time.monotonic()而不是time.time(),是因为前者不受系统时间调整影响,树莓派没有 RTC、靠 NTP 校时的时候,系统时间可能往回跳,用time.time()会出现冷却窗口永不生效的问题。COOLDOWN取多少要看安装位置:正对走廊的探头取 3~5 秒足够,对着窗户的取 10 秒以上,避免树影晃动反复触发。
3. Picamera 抓拍与时间戳命名:从摄像头启用到图像参数调优
告警的可信度靠照片撑着。只推一条「有人进入」的文本,用户第一反应是又误报了;推一张带时间戳的照片,判断成本立刻降下来。
3.1 启用摄像头接口与硬件自检
摄像头模块不接 GPIO 排针,而是插在靠近网口的 CSI 排线上,金手指朝向网口方向。接线前必须断电,带电插拔是烧排线接口最直接的原因。
旧版 Raspbian 在终端执行sudo raspi-config,进 Interface Options 打开 Camera 权限,重启后生效;也可以在/boot/config.txt里加一行start_x=1手工开启。自检命令是:
# 旧版 Raspbian(Buster 及之前) raspistill -o test.jpg vcgencmd get_camera # 期望输出 supported=1 detected=1看到supported=1 detected=1才说明系统认到了模块,只显示supported=1是排线没插好或者没开权限。
注意:树莓派 4B 之后的 Raspberry Pi OS 默认切到 libcamera 栈,
raspistill和picamera会提示找不到摄像头,命令要换成libcamera-still -o test.jpg,Python 侧对应换成picamera2。如果手上板子装的是新系统,却照着 2020 年的代码写import picamera,卡在这一步很常见。装picamera2用sudo apt install -y python3-picamera2。
3.2 PiCamera 的关键参数
picamera.PiCamera()返回的 camera 对象可以调曝光、白平衡、锐度等一整套参数,安防场景真正需要动的其实只有几个:
| 参数 | 常用取值 | 作用与取舍 |
|---|---|---|
resolution | (1280, 720) | 分辨率越大越清晰,但单张体积翻倍,微信推送要控制大小 |
rotation | 0/90/180/270 | 模块装反时旋转,比拆下来重装省事 |
iso | 200~800 | 室内暗光调高,噪点会明显增加 |
exposure_mode | 'night' / 'auto' | 夜间模式拉长曝光,画面亮但动态物体有拖影 |
awb_mode | 'auto' | 白平衡交给自动,除非画面偏色严重 |
sharpness | 0~100 | 抓拍后要看清细节可以调到 50 左右 |
3.3 抓拍函数与实例复用
import picamera import os import time import datetime SHOT_DIR = "/home/pi/alarm_shots" os.makedirs(SHOT_DIR, exist_ok=True) _cam = None def get_camera(): """复用同一个 camera 实例,避免反复开关机带来的 1~2 秒延迟""" global _cam if _cam is None: _cam = picamera.PiCamera() _cam.resolution = (1280, 720) _cam.rotation = 180 _cam.iso = 400 _cam.exposure_mode = "night" return _cam def capture(): cam = get_camera() ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") path = os.path.join(SHOT_DIR, f"{ts}.jpg") # 上电后 AGC/AWB 需要时间收敛,直接抓会偏暗偏色 time.sleep(2) cam.capture(path) return path每次触发都with picamera.PiCamera() as cam是教科书写法,但每次创建实例会重新初始化传感器,大约吃掉 1~2 秒,等你拍到时人已经走了。复用实例的代价是要自己管生命周期,进程退出前调_cam.close()。
3.4 时间戳与文件命名
文件名用%Y%m%d_%H%M%S而不是%s时间戳,好处是ls出来的顺序天然按时间排列,rm清理时也好筛。告警文本里的时间单独再格式化一次,用datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")拼进消息,避免用户还要自己换算。磁盘占用要提前算:1280×720 的 JPEG 约 150KB,一天误报 20 次也就 3MB,但连续跑半年还是要加清理逻辑,比如保留最近 7 天:
find /home/pi/alarm_shots -name "*.jpg" -mtime +7 -delete挂到 cron 里每天凌晨跑一次,-mtime +7表示修改时间在 7 天以前的文件。
4. wxpy 微信告警通道与人机对话:Bot 登录、消息注册与 y/n 闭环
推送通道决定了这套系统好不好用。2020 年那篇文章选 wxpy,理由很实际:不用开发 App,登录后直接给本人或指定好友发消息、发图片,还能通过装饰器接收回复,把「是否开启报警」的确认环节做成一次人机对话。
4.1 把告警通道抽成一层
写死bot.self.send_image(...)的代码,换通道时要改遍全项目。稳妥做法是先定接口形状,再往里填实现:
def send_alert(text, image_path=None): """告警出口:文本必发,图片可选""" raise NotImplementedError后面无论是 wxpy、企业微信机器人还是自建 HTTP 上报,都只需要重新实现这一个函数,业务逻辑不动。
4.2 Bot 初始化与登录
from wxpy import Bot # cache_path 保存登录状态,重启进程不用重新扫码 bot = Bot(cache_path=True, console_qr=False)cache_path=True会在工作目录生成wxpy.pkl,免去反复扫码;console_qr=False在树莓派桌面终端下更直观。第一次运行会弹二维码,手机扫一下即可。这里有个容易被忽略的点:这个状态文件是登录凭据,别提交到公开仓库。
4.3 发送给自己与指定好友
def send_alert(text, image_path=None, friend_id=None): if friend_id: # 按好友昵称/备注搜索,取第一个匹配项 target = bot.friends().search(friend_id)[0] else: target = bot.self # 发送给文件传输助手/自己 target.send(text) if image_path: target.send_image(image_path)bot.friends().search()支持昵称模糊匹配,返回的是列表,[0]取第一个。列表为空会抛IndexError,生产环境要包一层 try,并在找不到好友时退回到发给自己,别让告警因为找不到收件人整条链路断掉。
4.4 用 @bot.register 接住 y/n 回复
告警发出去只是单向通知,真正的闭环在回复上。wxpy 用装饰器注册消息处理器,收到好友消息后按文本内容决定是否拉响蜂鸣器:
import RPi.GPIO as GPIO import time BUZZER_PIN = 25 def alarm(duration=5): """PWM 驱动蜂鸣器,占空比控制响度""" pwm = GPIO.PWM(BUZZER_PIN, 2000) # 2000Hz pwm.start(50) # 50% 占空比 time.sleep(duration) pwm.stop() GPIO.output(BUZZER_PIN, GPIO.LOW) @bot.register(bot.self) def reply_me(msg): text = msg.text.strip().lower() if text in ("y", "yes", "是"): alarm() return "已开启报警" if text in ("n", "no", "否"): return "已取消报警" return "回复 y 开启报警,n 取消"msg.text是消息正文,strip().lower()先做归一化再比较,能同时兼容大写 Y 和误输入空格。返回值会作为回复消息发回给发送方,形成一次完整对话。如果只接受指定好友的指令,把bot.register(bot.self)换成bot.register(my_friend)即可,但要在前面先拿到好友对象。
注意:有源蜂鸣器内部自带振荡源,
PWM改变频率对它没有意义,调频率的是无源蜂鸣器。这里有源型的正确用法是固定频率、用占空比调响度,或者干脆GPIO.output(BUZZER_PIN, GPIO.HIGH)直接拉高。
4.5 通道失效时的替代落点
微信网页版登录在后续年份持续收紧,wxpy 项目本身也早已停止维护,这是绕不过去的现实。工程上的处理不是硬扛,而是把第 4.1 节那层接口用起来:换成企业微信机器人的 webhook,或者自己写一个 HTTP 上报到手机端聚合工具,接口形状不变,只改send_alert的实现。这也是把告警出口抽象出来的真正价值——换通道不动业务。
5. 把 demo 变成能值守一周的守护进程:systemd、日志与验证
实验室里跑通和连续值守一周是两件事。前者靠python3 main.py,后者靠三样东西:进程能被系统托管、出问题有日志可查、误报有机制压住。
5.1 用 systemd 托管主程序
直接nohup python3 main.py &的进程在断电重启、内存抖动、SSH 断开后都可能消失。写一个 systemd 单元更省心:
[Unit] Description=Raspberry Pi home security guard After=network-online.target [Service] ExecStart=/usr/bin/python3 /home/pi/guard/main.py WorkingDirectory=/home/pi/guard Restart=always RestartSec=5 User=pi StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.targetAfter=network-online.target保证网络就绪后再启动,微信登录这类依赖网络的动作才不会一开机就失败;Restart=always配合RestartSec=5让进程异常退出后 5 秒自动拉起。启用和查看状态:
sudo systemctl daemon-reload sudo systemctl enable --now guard.service sudo systemctl status guard.service journalctl -u guard.service -f # 实时跟踪日志5.2 日志落到文件并做轮转
journalctl在树莓派上默认占用有限,长期跑建议同时写文件并轮转:
import logging from logging.handlers import RotatingFileHandler handler = RotatingFileHandler( "/home/pi/guard/guard.log", maxBytes=5 * 1024 * 1024, # 单文件 5MB backupCount=3, # 保留 3 个历史文件 encoding="utf-8" ) handler.setFormatter(logging.Formatter( "%(asctime)s [%(levelname)s] %(message)s" )) logging.basicConfig(level=logging.INFO, handlers=[handler])maxBytes和backupCount一起决定磁盘占用上限,这里最坏情况是 20MB 左右。日志里至少要能查到三类事件:触发时刻、抓拍文件路径、推送是否成功,出问题的时候这三条能定位到九成故障。
5.3 误报抑制与验证方法
第 2.4 节的冷却窗口只能挡住短时间重复触发,挡不住环境干扰导致的孤立误报。更稳的是加一道「连续确认」:窗口内连续两次检测到才认定为入侵。
import time _hits = [] CONFIRM_WINDOW = 3.0 # 秒 CONFIRM_COUNT = 2 def confirmed_intrusion(): now = time.monotonic() _hits.append(now) # 只保留窗口内的记录 while _hits and now - _hits[0] > CONFIRM_WINDOW: _hits.pop(0) return len(_hits) >= CONFIRM_COUNT调参的取舍很直白:CONFIRM_COUNT=2能压掉绝大多数单次干扰,代价是真的快速穿过探测区域时可能漏报,所以窗口不能设太短,3 秒是个常用起点。
验证环节不用等真人闯入,把红外模块的 Dout 用杜邦线瞬时短接到 GND,就等价于一次下降沿触发,比量电压快得多。压测流程一般是:手动触发 10 次,看日志里触发次数、抓拍文件数、推送到达数三者是否一致;再故意断开网络,确认进程不崩、重连后仍能继续工作;最后sudo reboot一次,看 systemd 是否把服务拉起来、微信登录状态是否从缓存恢复。全部通过,这套树莓派智能安防系统才算从毕设代码变成了能挂在墙角不动的设备。
本文还有配套的精品资源,点击获取