☰
树莓派DIY可视门铃:移动侦测、远程查看与自动保存实践
2026/10/1 4:56:20 网站建设 项目流程

去年冬天我门口丢过一次快递——人还没下班,包裹就被拿走了。楼道监控只能拍到电梯口,门口近处全是盲区。从那天起,我决定用树莓派给自己搭一套可视门铃,把远程查看摄像头、移动侦测、自动保存视频这几件事一次做齐。平时手机随时能看门口画面,门口有人逗留就自动录像存到树莓派本地,不依赖任何云端收费服务。整套东西下来五百块出头。如果你也想补一个带录像能力的门口看护方案,这篇文章应该能让你少踩不少坑。

1. 门铃这事,为什么要自己用树莓派做一套

先说说我为什么没有直接买现成的智能门铃。市面上的可视门铃几百到上千不等,硬件本身不贵,真正花钱的是后面那张云录像订阅卡:按年收费,想回看门口发生了什么得持续交钱。更让我在意的是数据归属——所有录像都存在厂商服务器上,哪天对方调整政策或者关停服务,我积攒的影像记录说没就没。自建树莓派方案最大的好处就是数据完全在自己手里,TF卡一拔就是全部记录,没有任何中间商。

1.1 现成智能门铃的槽点

现成门铃的第二个问题是不够灵活。我不需要一个只会响铃、录满之后原地打转的盒子,我想要的是:人到了门口能精准触发录像,白天逆光时画质依然可用,回看时按照日期翻文件就行。这些需求在成品设备里往往要么没有开放配置,要么做得很粗糙。

还有一些细节让我很抓狂。比如部分门铃的移动侦测只能调"高/中/低"三档,没法针对门口通道画一条自定义区域;再比如有些产品必须通过专用App查看,浏览器和电脑访问非常别扭。树莓派方案本质上是把选择权全部拿回来——代码在自己手里,侦测逻辑、存储规则、访问方式都可以按需改。

1.2 核心需求拆解:三件必须做的事

动手之前我把需求拆成三条,这也是全文围绕的核心:

需求具体表现对应方案
移动侦测有人进入门口区域时能够感知并触发录像PIR人体红外 + OpenCV帧差法双重判定
远程查看人不在家时随时看到门口实时画面Flask提供Mjpeg视频流,局域网/公网均可访问
自动保存侦测到移动后自动把视频片段留存到本地picamera2录制H.264视频,按日期目录归档

这三件事串起来的完整链路是:家门口出现人 → PIR硬件先感知到动静 → 摄像头画面通过帧差法确认确实有运动 → 立即开始录像10~20秒 → 视频按日期存进TF卡 → 用户拿出手机打开网页就能回看和实时预览。

1.3 成本对比:自建是否真的划算

我来算一笔账。树莓派4B(2GB版)约三百多,树莓派官方CSI摄像头模块一百出头,加上TF卡、电源、PIR传感器、外壳散热,整套大约五百到七百。现成智能门铃硬件价位三百到一千,但云存储订阅普遍每年一百到三百。也就是说,自建方案用一年多在成本上就追平了成品方案,而且后续每多一年就省一份订阅费。

当然,自建是有代价的:你需要自己刷系统、装依赖、写代码、处理各种环境问题。这篇文章的剩余部分,就是把我在这个过程中踩过的坑、验证过的方案一五一十写出来。

2. 硬件准备那些事:摄像头与传感器选型的取舍

树莓派做门铃的硬件选型,网上方案五花八门。有人用USB摄像头,有人用官方CSI模块,还有人直接用摄像头加OpenCV硬刚人体检测。我的建议是先把基础传感器组合想清楚,再决定用哪颗摄像头。

2.1 CSI摄像头与USB摄像头:我为什么选了树莓派官方模块

树莓派官方CSI摄像头模块用的是OV5647传感器(500万像素),也就是老款Camera Module v1。这颗传感器虽然发布很多年了,但720p下画质依然够用,夜间配合红外补光也能应急。后来官方出了Camera Module v3,传感器升级到1200万像素,还带了自动对焦,预算多几十元的话值得考虑。

CSI摄像头和USB摄像头的核心区别在于传输链路。CSI走的是摄像头专用排线接口,直接连接到树莓派的ISP处理器,CPU占用低、延迟小、帧率稳定。USB摄像头则要先经过USB控制器,驱动层再走V4L2,虽然也能用,但在树莓派4B上跑720p30fps通常要占用更多CPU资源,尤其在同时做OpenCV计算和H.264编码时,CPU负载差别明显。

从门铃场景看,CSI模块还有一个好处:体积小,固定在外壳里几乎不占空间,适合做门口小盒子。USB摄像头如果选带麦克风的型号会多出录音能力,但在门铃隐私场景下,录音反而容易惹麻烦,我干脆不碰。

2.2 PIR人体感应:不能只靠画面识别

我强烈建议在画面侦测之外再加一个PIR人体红外传感器,型号用最常见的HC-SR501,几块钱一个,三个引脚:VCC接5V、GND接GND、OUT接GPIO17。它的原理是检测人体发出的红外线变化,有人进入检测范围时输出高电平,人离开后恢复低电平。

为什么需要它?因为OpenCV帧差法虽然能检测画面中的运动,但有个天然缺陷——光照变化会触发大量误报。楼道里声控灯突然亮起、傍晚阳光角度变化、窗帘飘动,这些都会让整帧画面亮度突变,帧差法会把这些全判成"有人移动"。PIR的作用是做一个前置粗检:只有真的有人体红外变化时才开始留意画面,再用帧差法去精确认证,两者同时满足才录像,误报率能压到很低。

HC-SR501背面有两个旋钮,左侧调延时时间、右侧调灵敏度。门铃场景建议把灵敏度调到中等,检测距离控制在2~3米,让门口通道区域刚好覆盖,别对着楼道大范围区域,也不要正对空调风口或暖气片,那些热源会让PIR频繁误触。

2.3 安装位置与角度:细节决定画质

摄像头安装高度建议离地1.4~1.5米左右,与人脸平视或略高一点。角度上略微向下倾斜10~15度,这样既能拍到人脸正面,也能覆盖到门口地面区域——快递放置的位置基本就在地面上。安装太高会导致画面只拍到头顶和天花板,太低又容易被随手破坏。

安装时还要确认面向的光线。如果门口是逆光环境(白天户外亮、楼道暗),摄像头自动曝光会让画面里的人脸死黑。这需要在软件层面做固定曝光处理,第6章我会具体展开。硬件层面有条件的可以加一个红外补光板,几个LED那种,夜间PIR触发后自动点亮,画面可用度会提升不少。

3. 移动侦测与录像触发:软硬件双重判定的实现

移动侦测是整个项目的核心逻辑。我一开始图省事,直接用PIR触发录像,结果发现误报率奇高——楼道有人经过、楼下汽车开过大灯扫过、甚至电梯开门的光线变化都能触发。后来改成PIR加帧差法双重判定,误报才算真正压下来。

3.1 帧差法原理:适合门铃的轻量级移动侦测

帧差法就是相邻两帧图像做差,差异大的像素就是画面里发生变化的位置。在门铃场景里,这个变化通常意味着"有东西在动"。它的优势在于计算量极低,不需要训练模型、不需要背景建模,树莓派处理720p图像绰绰有余。

具体流程是:取当前帧转成灰度,与上一帧灰度相减,得到差分图;再做一次高斯模糊去掉噪点;然后用阈值把差分图二值化——超过阈值的像素变成白色,其余为黑色;最后在白色区域里找轮廓,轮廓面积超过一定值就判定为"有移动"。这套逻辑几十行代码就能写完,而且效果在静态楼道环境下非常稳定。

为什么不用更高级的背景减除(MOG2)或者目标检测(YOLO)?背景减除对光照变化也很敏感,需要维护背景模型;YOLO虽然能直接识别人,但在树莓派4B上跑实时推理会把CPU占满,同时还要录像编码,整机温度会非常难看。门铃场景的诉求是"感知有东西动了"而不是"识别出这是什么",帧差法刚好够用。

3.2 主循环代码:PIR粗检加画面精检

下面是完整的侦测主循环骨架。系统环境是树莓派4B + Raspberry Pi OS Bookworm(64位),摄像头使用官方CSI模块。

import os import time import subprocess import cv2 import numpy as np import RPi.GPIO as GPIO from datetime import datetime from picamera2 import Picamera2 PIR_PIN = 17 VIDEO_DIR = "/home/pi/doorbell_videos" FRAME_DIFF_THRESHOLD = 30 MIN_AREA = 800 COOLDOWN_SECONDS = 5 CLIP_SECONDS = 15 GPIO.setmode(GPIO.BCM) GPIO.setup(PIR_PIN, GPIO.IN) picam2 = Picamera2() config = picam2.create_preview_configuration( main={"size": (1280, 720), "format": "BGR888"} ) picam2.configure(config) picam2.start() prev_gray = None last_trigger_time = 0 def detect_motion(frame, prev_gray): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) if prev_gray is None: return False, gray diff = cv2.absdiff(prev_gray, gray) _, thresh = cv2.threshold(diff, FRAME_DIFF_THRESHOLD, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours( thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) moved = any(cv2.contourArea(c) > MIN_AREA for c in contours) return moved, gray def record_clip(picam2, duration=CLIP_SECONDS): date_dir = datetime.now().strftime("%Y/%m/%d") filename = datetime.now().strftime("%Y%m%d_%H%M%S") full_dir = os.path.join(VIDEO_DIR, date_dir) os.makedirs(full_dir, exist_ok=True) h264_path = os.path.join(full_dir, filename + ".h264") encoder = H264Encoder(bitrate=2000000) picam2.start_recording(encoder, FileOutput(h264_path)) time.sleep(duration) picam2.stop_recording() mp4_path = h264_path.replace(".h264", ".mp4") subprocess.run(["MP4Box", "-add", h264_path, mp4_path]) if os.path.exists(mp4_path): os.remove(h264_path) print(f"saved: {mp4_path}") while True: frame = picam2.capture_array() has_frame_motion, prev_gray = detect_motion(frame, prev_gray) pir_triggered = GPIO.input(PIR_PIN) == GPIO.HIGH if pir_triggered and has_frame_motion: now = time.time() if now - last_trigger_time >= COOLDOWN_SECONDS: record_clip(picam2) last_trigger_time = now time.sleep(0.05)

注意代码里我用了"BGR888"作为摄像头输出格式,这样OpenCV直接拿到的就是BGR顺序,颜色不会偏。录制函数里用到了H264Encoder和FileOutput,需要从picamera2.encoders和picamera2.outputs导入,我用到了MP4Box封装,这是后话,下一章细说。

3.3 去抖与冷却:避免一次脚步声录出三分钟视频

这个项目里最容易被忽略的细节是触发后的冷却时间。PIR信号本身就是高电平持续好几秒,如果人站在门口不动但身体有轻微晃动,帧差法会持续判定有运动,于是录像一直录下去,三分钟、五分钟不停。门口来个人站门口等开门,大概率会录到开门进去为止。

解决思路是控制单次录像时长,并在停止录像后加冷却时间。我设置的方案是:每次PIR加帧差双重确认触发时,固定录15秒,录完立刻stop_recording,同时记录最近触发时间,5秒之内不再重复录制。这里没有做"运动持续则延长录像"的动态逻辑,因为门铃场景下15秒足够覆盖从出现到离开门口的主要过程,而且固定时长的文件更容易管理,不会出现一个巨无霸文件把空间吃掉。

如果你想要更智能的延长逻辑,可以在录像过程中每秒做一次运动检测,检测到就继续录,静止超过10秒才停止,这样处理一次完整的门口访客事件体验更好,代价是代码复杂度高一些。

4. 视频自动保存与存储管理:别让TF卡被录满

自动保存这件事,表面看就是"录完写个文件",实际上有一堆细节:文件怎么命名、按什么目录组织、用什么编码格式、占多大空间、存满了怎么办。每一步处理不好,长期运行都会出问题。

4.1 录像文件怎么组织:按日期分目录,按时间戳命名

我会把视频根目录设成/home/pi/doorbell_videos,内部按年/月/日三层目录组织,例如/home/pi/doorbell_videos/2024/06/15/20240615_142315.mp4。这样做的最大好处是回看时只需要在文件管理器里逐层点进去,按日期自然就能找到对应片段,不需要依赖任何数据库和索引工具。

文件名使用时间戳年月日_时分秒,精确到秒。同一天出现两次触发时,文件名不会冲突,因为秒级别的时间戳已经足够区分。我在文件名里没有加入PIR标识或者"motion"字样,因为目录本身就代表意义,文件名保持纯时间最干净。

4.2 H.264编码与封装:录像体积和画质的平衡点

录制编码我选了H.264,码率设为2Mbps。720p分辨率下,这个码率的人眼观感足够清晰,细节保留也不错,而文件体积控制在约250KB每秒。15秒一个片段大约3~4MB,一天触发30次也就100MB出头,32GB的TF卡可以攒一个月不清理。

picamera2录制H.264时直接输出裸流.h264文件,这个格式很多播放器能放,但不够通用。我习惯用MP4Box把裸流转封装成mp4,一条命令的事:

sudo apt install gpac

然后在录制完成后执行:

MP4Box -add 20240615_142315.h264 20240615_142315.mp4

转封装基本是秒级完成,就算录了15秒视频也就一两秒的事。转完就可以把h264源文件删掉,只留mp4。这里要提醒一个坑:MP4Box命令在部分系统上需要全小写执行,如果在PATH里找不到,先确认gpac装了没有。

4.3 空间清理机制:让TF卡自动保持健康水位

TF卡空间管理,我的策略很简单:删除超过30天的目录,同时监控剩余空间低于20%时自动删除最早的录像目录。用一个cron任务每天凌晨执行一次清理脚本就够,没必要做得太复杂。

#!/bin/bash # /home/pi/clean_videos.sh VIDEO_ROOT="/home/pi/doorbell_videos" DAYS=30 find "$VIDEO_ROOT" -type f -name "*.mp4" -mtime +$DAYS -delete find "$VIDEO_ROOT" -type d -empty -delete THRESHOLD=20 USAGE=$(df /home/pi | awk 'NR==2 {print $5}' | tr -d '%') if [ "$USAGE" -gt $((100 - THRESHOLD)) ]; then find "$VIDEO_ROOT" -type f -name "*.mp4" | sort | head -1000 | xargs rm -f fi

脚本里的思路是先按时间删老文件,再处理空间不足的极端情况。这里没有用rm整个目录,而是选择最老的文件逐个删,避免误删正在写入的当前记录。最老的文件按文件名排序即可,因为文件名就是时间戳,sort就是按时间排。

5. 远程查看通道:从局域网到外网访问的搭建

远程查看是整个项目里体验感提升最明显的一块。装好之后,我在公司摸鱼时掏出手机就能看到家门口画面,快递员有没有把包裹放门口、门口有没有可疑逗留,一目了然。搭建分成两步:先做局域网内的Web视频流,再加外网访问通道。

5.1 局域网Mjpeg预览:一套代码同时满足手机和电脑

我选择用Flask直接输出Mjpeg流,而不是用Motion、mjpg-streamer这类现成组件。原因是我希望视频流直接从picamera2的缓冲区取帧,不经过文件系统中转,延迟更低;另外Flask框架本身轻量,顺手就能加上访问密码和路由控制。

from flask import Flask, Response import cv2 from picamera2 import Picamera2 app = Flask(__name__) picam2 = Picamera2() config = picam2.create_preview_configuration( main={"size": (1280, 720), "format": "BGR888"} ) picam2.configure(config) picam2.start() def generate_frames(): while True: frame = picam2.capture_array() ret, jpeg = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 70]) if ret: yield (b"--frame\r\n" b"Content-Type: image/jpeg\r\n\r\n" + jpeg.tobytes() + b"\r\n") @app.route("/") def index(): return "<h1>Doorbell Camera</h1><img src='/stream'>" @app.route("/stream") def stream(): return Response(generate_frames(), mimetype="multipart/x-mixed-replace; boundary=frame") if __name__ == "__main__": app.run(host="0.0.0.0", port=8080, threaded=True)

Mjpeg流有一个天然优势:几乎所有设备上的浏览器都能直接显示,不用装插件。手机、笔记本、平板,打开浏览器输入地址就能看。画质压缩我设成70,720p分辨率下单帧大约30~50KB,局域网Wi-Fi下整条流的码率在3~5Mbps,现代路由器毫无压力。Flask自带的Werkzeug服务器虽然性能一般,但门铃场景只需要同时服务一两个观看端,绰绰有余。

5.2 外网访问的两种路径:路由器映射与内网穿透

局域网访问没问题后,第二步是让外网也能看。这里分两种情况。第一种是宽带有公网IP,可以在路由器设置里做端口映射,把树莓派的8080端口映射到公网,再用DDNS(动态域名解析)把动态公网IP绑定到一个固定域名上,之后访问域名加端口就行。这种方案延迟最低、不依赖第三方服务器。

第二种是宽带没有公网IP(很多小区宽带是运营商NAT),端口映射打了也白打。这时候需要用内网穿透工具。我用的是frp这类开源工具的原理:在一台有公网IP的云服务器上部署服务端,树莓派上部署客户端,把树莓派的8080端口通过长连接反向映射到云服务器的某个端口。手机访问云服务器IP加端口,数据经由云服务器转发到树莓派,实现外网访问。

提到云服务器,新手选择轻量应用服务器就够用,一年几十块钱,配置选1核1G的入门款即可。frp配置不复杂,服务端写监听端口,客户端写要映射的本地端口和远程端口,官方文档有现成示例,照着填参数就行。

5.3 远程访问安全:加锁和改口的简单实践

把摄像头画面暴露到公网,不加任何保护就像把家钥匙挂在门口。我至少做三件事:

  • 给Flask加HTTP Basic Auth,用户名和密码放在环境变量里,不硬编码在代码中;
  • 更改默认端口,不用80/8080这类一眼就能扫出来的常用端,改成一个高位端口;
  • 不在公网直接暴露视频流端口,而是通过内网穿透只开放到云服务器,云服务器上再配置访问密钥或者IP白名单。

如果只是家庭成员使用,更省心的方案是用组网工具把手机和树莓派放进同一个虚拟局域网,手机装上客户端、树莓派装对应Agent,家里和外面都像在同一个局域网内访问内网地址。延迟和安全性都优于直接暴露端口,而且不用买云服务器。这个方案适合有动手能力的读者自行研究。

6. 真实环境磨合:从测试到长期布署的细节与坑

跑测试和在真实门口长期挂着完全两回事。测试时阳光明媚、楼道安静,真实环境里白天有逆光、晚上有灯光自动切换、夏天温度高、Wi-Fi偶尔抽风。这一章是我认为最值得写的内容,全是实际使用中遇到的坑。

6.1 逆光与夜间曝光:楼道灯光如何影响画面质量

小区楼道普遍装了感应灯,白天不亮、晚上有动静就亮。问题就出在这里:夜间灯亮起时,摄像头会自动调整曝光,画面亮度会先暗后亮地闪跳几秒,帧差法会把这种亮度变化当成"有运动",同时录像画面质量也不稳定。

解决思路是锁定曝光参数。先用默认自动曝光跑一段时间,通过picam2.capture_metadata()读取典型亮度下的曝光时间和增益值,然后手动锁定:

from libcamera import controls picam2.set_controls({ "AeEnable": False, "ExposureTime": 10000, "AnalogueGain": 1.5, })

我这里写的是示例值,实际数值要看你门口的光线亮度。锁定曝光后,画面亮度不再剧烈跳动,帧差法误报也明显降低。代价是白天户外的强光下画面可能略微过曝或欠曝,但门铃关注的是人脸可辨识度,轻微亮度偏差完全不影响使用。

夜间如果楼道全黑,摄像头基本什么都拍不到。这个问题的终极解法是加红外补光板,PIR探测到人后点亮一排红外LED,摄像头传感器在红外光下能拍到清晰的灰度人脸。树莓派官方摄像头模块本身没有红外灯,需要外接补光设备,网上有成品小模块,接线和继电器逻辑都很简单。

6.2 无线网络与供电:门铃卡顿的隐形杀手

树莓派4B支持双频Wi-Fi,门铃场景强烈建议用5GHz频段,不要用2.4GHz。实测在同一个路由器下,2.4GHz频段干扰严重时视频流帧率能掉到十几帧,画面卡顿明显,切换到5GHz后稳定在25~30帧。

供电是另一个容易忽略的地方。树莓派4B满负载跑OpenCV加视频编码时电流需求逼近3A,如果电源适配器输出不足或者线材过长,电压跌落会导致Wi-Fi网卡反复断开、系统自动降频。我实际遇到过:录像录到一半网络断开,查了半天发现是插了一个劣质电源。换回官方电源之后,问题立刻消失。如果门铃设备到插座的距离比较远,建议用5V3A电源加短线供电,或者给树莓派配一个质量好一点的电源模块。

6.3 夏季高温与PIR误报:长期运行的稳定性观察

树莓派4B的处理器在持续视频编码时会发热,加上夏天室外温度高,CPU温度经常顶到80°C以上,然后系统自动降频,编码帧率下滑、录像卡顿。我这套设备放在门口半封闭的弱电箱里,只靠被动散热根本压不住,后来加了一个5V风扇朝着散热片吹,温度控制在60°C出头,长期稳定运行。

PIR传感器在高温环境下也更容易误报。夏天楼道温度高,人体红外信号与环境温差变小,HC-SR501的灵敏度飘忽。我的处理是把PIR灵敏度旋钮往中低方向调,同时把传感器朝向从正对楼道过道改成斜向下对准门口区域,误报次数明显下降。如果你家楼道有空调外机或暖气管道,安装时务必错开这些热源,否则夏天会录一堆"空气移动"视频。

6.4 TF卡寿命与数据备份:录像存哪才踏实

长时间高频写入对TF卡是个考验。我的设备每天触发10~30次,每次写3~4MB,日写入量大概100MB。这个量级对现代TLC颗粒的TF卡来说并不算高,正常使用寿命内问题不大。但我会定期把目录里的视频同步到NAS和云盘,防止TF卡突然坏死导致一个月记录全部丢失。

如果你和我一样用的是A1/U3级别TF卡,建议打开树莓派系统的Trim支持(sudo systemctl enable fstrim.timer),可以减缓长期写入导致的性能衰减。如果你的设备支持,把根文件系统放到SSD、TF卡只存录像也是好方案,不过成本会更高一些。这个取舍看你自己的预算和数据重要程度。

整个项目从测试到真正稳定运行,我花了大约三周时间。回头再让我做一遍,我会省去很多冤枉路:PIR一定从第一天就接上,别先裸跑OpenCV调试两周再返工;视频格式一开始就用H.264加MP4Box,别先折腾几天MJPG AVI才换;固定曝光参数在部署第一天就锁好,别被自动曝光的画面带偏了排查方向。

门铃这件事的坑多数不是技术难题,而是环境因素——光线、温度、供电、网络,每一项都要在真实位置实测才能发现。如果你打算复刻这套方案,千万别只看代码调通了就收工,把它挂在门口连续跑一周看录像内容,你才能明白我前面说的那些"细节"到底有多影响体验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询