这次要落地的东西,一句话说清楚:把家里二楼客厅和阳台这两路画面,接入一套完全跑在本地的小型视频分析系统,画面不出内网,事件、录像、快照全部留在自己的硬盘里。这类两层户型在选购文案里最爱写“私密性极佳”,但真正决定私密性的不是楼间距,而是画面数据最终流向哪台服务器。所以这篇不聊户型,聊怎么用开源方案把这套房子的视频能力自己搭起来。
核心方案基于 Frigate 这个开源 NVR(网络视频录像机)项目,配合本地目标检测模型完成人形、宠物、车辆等对象的识别。它最值得关注的几个点:一是全链路本地化,不依赖任何云端推理服务;二是支持 RTSP 拉流,普通家用摄像头就能接入;三是自带 WebUI 和 REST API,能查事件、拉快照、导片段;四是支持检测区域(zone)划分,可以只盯着阳台栏杆那一条线,避免满屏误报;五是有录像保留策略,事件片段和全天录像可以分开存。
本文会带读者走完整条链路:从环境准备、Docker Compose 部署、摄像头接入、客厅与阳台两个点位的区域配置,到白天夜晚的效果验证、API 调用、批量导出和显存占用观察。全程给可直接复制的配置,参数按自己设备替换。适合已经有一台常开的小主机或带独显的旧电脑、想低成本做本地视频分析、又不想把家庭画面传到第三方平台的读者。
先明确一点:下文所有硬件数字、端口、模型参数都以你实际安装的版本和本机测试为准,不同版本配置字段会有增减,遇到不一致时以容器启动日志和项目文档为准,不要硬套。
1. 核心能力速览
先用一张表把关键规格摊开,读者可以三十秒判断要不要继续往下看。这张表里的参数分成两类:一类是方案本身的固有能力,比如是否支持 API、是否支持分区检测;另一类是取决于你机器的能力,比如推理速度和显存占用,这部分只能给出观察方法,不能给死数字。
| 能力项 | 说明 |
|---|---|
| 项目类型 | 开源本地 NVR + 实时目标检测,支持多路 RTSP 摄像头接入 |
| 开源来源 | Frigate 为开源项目,采用宽松开源协议,可自建、可修改 |
| 主要功能 | 实时目标检测、事件录像、快照留存、区域过滤、WebUI 回放、REST API |
| 推荐硬件 | 常开的小主机、旧笔记本、带核显的 NAS,或带独显的台式机 |
| 显存占用 | 走 CPU 推理时几乎不占显存;走 GPU 推理时随分辨率和检测帧率变化,需按本机实测 |
| 支持平台 | Linux 为主,Docker 部署最省心;Windows 建议走 WSL2 或虚拟化 |
| 启动方式 | Docker Compose 一键起容器,配置文件挂载进容器 |
| 是否支持 API | 支持,提供事件查询、统计信息、快照、片段等 HTTP 接口 |
| 是否支持批量任务 | 支持,可批量拉取事件列表、批量下载片段、批量做二次分析 |
| 适合场景 | 家庭室内外分区看护、宠物活动记录、门口与阳台边界告警、本地留存 |
| 不适合场景 | 需要云台自动追踪、需要人脸身份识别、需要对外提供公共服务的场景 |
表里“不适合场景”这一行建议认真读。很多人一上来就想做人脸识别,这块涉及生物特征信息,法律风险和伦理风险都很高,普通家庭场景完全没必要做。用“有没有人”这种粗粒度信息,已经能覆盖绝大多数看护需求,而且数据敏感度低得多。
另外要强调,检测帧率不是越高越好。家用场景里人走过阳台栏杆通常持续几秒,每秒抓 5 帧做检测完全够用,把帧率降下来能明显减轻 CPU 和显存压力,这是后面性能优化最有效的一招。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 适用场景与使用边界
这套方案最适合的,是那种“想要一点自动化,但不想把家里画面交出去”的需求。比如二楼客厅白天没人的时候,想确认一下老人有没有按时活动;比如阳台外沿,想记录有没有人翻越或者宠物跑出去;再比如快递放在门口,想留一段可回溯的片段。这些需求的共同点是:只关心“有没有发生”,不关心“是谁”,信息粒度粗,隐私敏感度低,非常适合本地推理。
具体到两层住宅,点位规划其实有讲究。二楼客厅属于室内大空间,光照稳定、背景复杂,容易把沙发靠垫、窗帘晃动误判成人形,所以这块要重点做区域裁剪和最小面积过滤。阳台属于半室外,白天逆光、夜晚补光,画面质量波动大,检测难度更高,通常需要单独调参,甚至给阳台单独配一个更低的检测帧率和更宽容的阈值。这也是为什么本文把客厅和阳台分成两个 camera 分别配置,而不是一套参数糊上去。
不适合的场景也要说清楚。第一,不要用这类方案做需要精确身份判断的事情,人脸比对不在本文讨论范围。第二,不要指望它替代专业安防系统,普通家用摄像头的码流稳定性和夜视能力有限,断流、花屏是常态,方案只能做到“尽量不丢事件”,做不到“绝不丢事件”。第三,不要把服务端口直接映射到公网,WebUI 一旦暴露在外网,等于把家里的实时画面挂到了互联网上,这是最典型的安全事故。
关于边界,有三条硬线必须守住。第一是拍摄范围,阳台摄像头最容易越界,如果镜头能扫到邻居家窗户、公共走廊或者小区道路,必须物理调整角度或者用遮挡片裁掉,不能靠软件打码糊弄,因为原始流里信息还在。第二是告知义务,家里有同住人员、家政人员时,应当明确告知存在拍摄设备,这是最基本的尊重。第三是留存期限,录像不是存得越久越好,建议按“事件片段长期保留、全天录像短期滚动”的策略配置,过期自动覆盖,减少数据暴露面。
3. 本地部署环境准备
环境这块先给一张检查清单,逐条对一遍再动手,能省掉后面一大半的排查时间。第一,操作系统建议用 Linux 发行版,Ubuntu 系或 Debian 系都行,Docker 生态最顺;如果只有 Windows 机器,走 WSL2 也可以,但要注意 WSL2 的网络模式对局域网摄像头发现不太友好,建议直接用 IP 地址拉流,别依赖自动发现。
第二,Docker 和 Docker Compose 必须先装好,这套方案本质上是“一个容器 + 一份配置文件 + 两个挂载目录”,不用折腾 Python 虚拟环境,也不用编译任何东西。装完之后执行版本检查,确认 Compose 是 v2 语法(命令是 docker compose 而不是 docker-compose),不然后面示例里的配置格式会对不上。
bash复制# 检查 Docker 与 Compose 版本,确认使用 v2 语法
docker --version
docker compose version
# 确认当前用户在 docker 组内,避免每条命令都要 sudo
groups | grep docker
第三,摄像头必须支持 RTSP 或者 RTMP 输出。市面上大部分家用 IPC 都支持,但需要在摄像头自己的管理后台里手动开启,默认常常是关闭的。开启后记下四个信息:IP 地址、端口(常见 554)、用户名、密码,以及主码流和子码流的路径。这里有个实用建议,检测用子码流,录像用主码流,子码流分辨率低,解码压力小,检测精度损失很小,整体资源占用能降一大截。
第四,磁盘空间要提前规划。全天录像非常吃容量,一路 1080p 摄像头一天下来几个 GB 起步,两路存一周就是几十 GB。建议单独挂一块盘给媒体目录,并且和系统盘分开,避免录像写满导致系统异常。第五,检查端口占用,方案默认会用到 WebUI 端口、RTSP 转发端口和 WebRTC 端口,起容器前先确认这些端口没被别的服务占着。
bash复制# 检查常用端口是否被占用,有输出说明冲突,需要改映射
ss -lntp | grep -E ':(5000|8554|8555)\b'
如果输出里有进程占着,最省事的做法是改宿主机侧的映射端口,比如把 5000:5000 改成 15000:5000,容器内部不用动,直接就能跑起来。
4. Docker Compose 部署与启动方式
部署这一步,直接上 Compose 文件最稳,比一行行敲 docker run 好维护得多。下面这份是通用模板,路径、端口、时区按自己环境替换即可。注意 shm_size 这个参数不能省,视频解码会用到共享内存,给小了会出现莫名的解码失败或者花屏。
yaml复制services:
frigate:
container_name: frigate
image: ghcr.io/blakeblackshear/frigate:stable
restart: unless-stopped
# 视频解码依赖共享内存,1080p 双路建议 256mb 起步
shm_size: "256mb"
volumes:
- ./config:/config
- ./media:/media/frigate
- /etc/localtime:/etc/localtime:ro
ports:
- "5000:5000" # WebUI 与 API,不同版本端口可能变化
- "8554:8554" # RTSP 转发,供第三方播放器回看
- "8555:8555/tcp" # WebRTC 相关
environment:
- FRIGATE_RTSP_PASSWORD=换成你自己的密码
目录结构建议一开始就理清楚,不然后面找文件会很痛苦。配置目录放 config.yml,媒体目录下面会自动生成录像、快照、片段等子目录,输入输出分开管理,备份的时候只备份配置目录就够了。
bash复制# 建议的目录结构
mkdir -p ~/home-nvr/config
mkdir -p ~/home-nvr/media
cd ~/home-nvr
# 先写一份最小配置,再起容器
cat > config/config.yml <<'EOF'
mqtt:
enabled: false
detect:
enabled: true
detectors:
cpu:
type: cpu
EOF
docker compose up -d
docker compose logs -f --tail=100
启动之后盯着日志看,重点看三件事:容器有没有正常起来、配置文件有没有解析报错、摄像头有没有成功拉流。日志里出现配置项不认识的警告不用太紧张,多半是版本差异导致字段改名,但如果出现 Unable to read frames 这类拉流失败,就要回头查 RTSP 地址和账号密码。启动完成后浏览器访问 http://本机IP:5000,能看到界面说明基础链路通了。
这里提一个版本差异的坑:较新的版本在 WebUI 访问上引入了鉴权入口,端口可能不是 5000,而是另一个专门用于界面访问的端口。如果你访问 5000 打不开或者被重定向,先看容器日志里打印的访问地址,再确认端口映射是否覆盖到了。这类问题不需要改配置,改映射就行。
5. 摄像头接入与客厅阳台分区配置
真正决定这套方案好不好用的,不是能不能跑起来,而是配置写得细不细。下面这份配置把二楼客厅和阳台拆成两个 camera,分别设检测分辨率、检测帧率和跟踪对象,并且给阳台单独划了一个“外沿”区域。坐标是像素坐标,格式是 x1,y1,x2,y2,...,从左上角开始顺时针。实际坐标要根据自己的画面分辨率调整,不要直接抄。
yaml复制mqtt:
enabled: false
detect:
enabled: true
# 先用 CPU 推理跑通链路,稳定后再换硬件加速检测器
detectors:
cpu:
type: cpu
# 硬件加速解码按实际平台选择,Intel 核显常用 vaapi 预设
ffmpeg:
hwaccel_args: preset-vaapi
cameras:
living_room_2f:
ffmpeg:
inputs:
- path: rtsp://用户名:密码@192.168.1.20:554/sub
roles: [detect]
- path: rtsp://用户名:密码@192.168.1.20:554/main
roles: [record]
detect:
width: 1280
height: 720
fps: 5
objects:
track: [person]
filters:
person:
min_area: 3000
min_score: 0.6
balcony:
ffmpeg:
inputs:
- path: rtsp://用户名:密码@192.168.1.21:554/sub
roles: [detect]
- path: rtsp://用户名:密码@192.168.1.21:554/main
roles: [record]
detect:
width: 960
height: 540
fps: 4
objects:
track: [person, dog, cat]
filters:
person:
min_area: 1500
zones:
edge:
coordinates: "0,0,960,0,960,200,0,200"
objects: [person]
配置里有几个点值得展开。roles 分开写是核心技巧,检测吃子码流、录像吃主码流,两条流互不干扰,画质和性能都能兼顾。detect.fps 设成 4 到 5 是家用场景的甜点值,再高收益很小。min_area 是最小检测面积,客厅大,人离得远,面积阈值给大一点能过滤掉抱枕、拖把这些小物件;阳台空间窄,人一出现就占很大比例,阈值可以给小一些。
zones 是这套方案最实用的功能之一。阳台那块,真正需要关注的往往是外沿那一小条,而不是整个阳台地面。把区域划在栏杆附近,只有当检测框和这个区域重叠时才触发,误报率会明显下降。这里要提醒,区域坐标一定要对着实际画面量,配置写错一个数字,可能整个区域跑到画面外面去,表面上“配置没问题”,实际一条告警都不会出。
录像保留策略也建议在这时候一并定好,不要等硬盘写满了再回头改。事件片段和全天录像分开配置保留天数,全天录像保留三到七天,事件片段保留三十天左右,是比较常见的均衡做法。
yaml复制record:
enabled: true
retain:
days: 3
mode: motion
alerts:
retain:
days: 30
6. 功能测试与效果验证
配置写完不等于能用,必须逐项验证。建议按下面的顺序来,从易到难,出问题好定位。
6.1 拉流与实时预览测试
测试目的:确认两路摄像头都能稳定解码,画面不卡不花。操作步骤:打开 WebUI 进入对应摄像头页面,观察实时画面连续播放三十秒以上,同时看容器日志有没有反复出现重连。预期结果是画面流畅、时间戳连续、日志无异常重试。判断成功的标准是连续播放无卡顿、无绿屏。常见失败原因是子码流地址写错、摄像头并发连接数达到上限,或者共享内存给小了。如果只有一路能出画面,优先怀疑并发数限制,把另一路临时停掉再试。
6.2 人形检测准确性测试
测试目的:确认检测框能稳定框住人,不框沙发、不框窗帘。操作步骤:一个人从客厅一端走到另一端,走三到五遍,然后回看事件列表。预期结果是每趟都能生成事件,事件里能看到快照和片段。判断成功的标准是检出率明显高于漏检率,且没有大量静止物体误报。常见失败原因是阈值过低导致误报,或者分辨率给太低导致远距离人体特征丢失。调参顺序建议先调 min_score,再调 min_area,一次只改一个。
6.3 阳台区域过滤测试
测试目的:确认只有进入外沿区域才触发,阳台内部走动不触发。操作步骤:先在阳台内侧走动,再去栏杆边上站一会,对比两次的事件记录。预期结果是内侧走动不产生告警,靠近栏杆产生告警。判断成功的关键是这两次行为有明确区分。常见失败原因是区域坐标画偏,或者区域对象列表里没写 person,导致区域形同虚设。排查时把区域在界面上可视化打开,肉眼看它到底盖在哪。
6.4 夜间与逆光场景测试
测试目的:确认低照度下方案是否还可用。操作步骤:分别在傍晚、全黑补光、白天逆光三种条件下各测一遍。预期结果是夜间检出率下降但仍能工作,逆光时可能出现漏检。这一项没有“完美通过”,重点是摸清可用边界,比如夜间把检测帧率适当提高,或者把摄像头补光灯打开。如果夜间完全不可用,就得接受“夜间只录像不检测”的现实,不要在这上面无限投入时间。
6.5 稳定性与长时运行测试
测试目的:确认连续跑二十四小时不崩、不漏录。操作步骤:放着跑一天,第二天检查事件时间轴是否连续、容器有没有重启记录、磁盘增长是否符合预期。判断成功的标准是时间轴无明显断档、内存占用平稳。常见失败原因是内存缓慢增长,通常和解码参数、共享内存配置有关,遇到就先把检测分辨率降一档观察。
7. 接口 API 调用与批量任务
这套方案的价值有一半在界面上,另一半在接口上。有了 HTTP 接口,就能把它接进自己的自动化流程:比如有人出现在阳台就推一条消息到自己的脚本,比如每天凌晨批量导出前一天的事件片段做归档。下面是几个常用调用的示例,接口路径和参数名以你实际版本的接口文档为准,不同版本会有差异。
bash复制# 查看服务版本与运行统计,确认检测是否在正常出帧
curl -s http://127.0.0.1:5000/api/version
curl -s http://127.0.0.1:5000/api/stats | python3 -m json.tool
# 按摄像头拉取最近事件列表,参数名以实际接口文档为准
curl -s "http://127.0.0.1:5000/api/events?camera=balcony&limit=20" | python3 -m json.tool
# 拉取某个摄像头的最新快照,可用于自己做的看板
curl -s -o ./balcony_latest.jpg http://127.0.0.1:5000/api/balcony/latest.jpg
批量任务这块,最常见的需求是“把某段时间的事件片段全部下载下来”。写个小脚本遍历事件列表逐个下载就行,关键是加超时、加失败重试、加本地去重,不然脚本跑一半断了会很麻烦。下面这个模板可以直接改路径用。
python复制import os
import time
import requests
BASE = "http://127.0.0.1:5000"
OUT_DIR = "./exports"
os.makedirs(OUT_DIR, exist_ok=True)
def fetch_events(camera: str, limit: int = 50):
resp = requests.get(
f"{BASE}/api/events",
params={"camera": camera, "limit": limit},
timeout=30,
)
resp.raise_for_status()
return resp.json()
def download_clip(event_id: str, retries: int = 3):
target = os.path.join(OUT_DIR, f"{event_id}.mp4")
if os.path.exists(target):
return target # 已下载过,直接跳过
url = f"{BASE}/api/events/{event_id}/clip.mp4"
for i in range(retries):
try:
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
with open(target, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
if chunk:
f.write(chunk)
return target
except Exception as exc:
print(f"重试 {i + 1}/{retries}: {event_id} -> {exc}")
time.sleep(2 * (i + 1))
return None
if __name__ == "__main__":
events = fetch_events("balcony", limit=30)
for item in events:
eid = item.get("id")
if eid:
print(download_clip(eid))
这段脚本有三个工程细节值得注意。第一,先判断本地文件是否存在,避免重复下载,批量任务一定要做幂等。第二,重试用递增等待,不要死循环拼命重连,否则服务端压力会上去。第三,流式写入而不是一次性读进内存,片段多了内存会顶不住。如果要把这套东西做成定时任务,建议加一个任务日志目录,每次跑完记录成功数和失败数,出问题能快速定位是哪一条卡住了。
8. 资源占用与性能观察
性能这块不给具体数字,因为影响因素太多,给数字反而误导。但观察方法是确定的,照着做就能判断自己的机器扛不扛得住。
先看整体统计接口,里面会包含检测帧率、推理耗时、各摄像头的状态。检测帧率如果长期低于你配置的目标帧率,说明算力不够,优先降低检测分辨率或者减少检测帧率,而不是急着换硬件。再看容器级别的资源占用,用 docker stats 盯着看几分钟,重点看 CPU 是否长期贴着 100%、内存是否持续上涨。
bash复制# 观察容器资源占用,重点看 CPU 与内存是否持续增长
docker stats frigate --no-stream
# 带独显时观察显存占用,需先安装 nvidia-smi 并透传 GPU
nvidia-smi --query-gpu=name,memory.used,memory.total,utilization.gpu --format=csv
CPU 推理和 GPU 推理的差别,主要体现在同样检测帧率下能带几路摄像头、推理延迟稳不稳定。CPU 推理的优势是零额外依赖,劣势是多路并发时很容易吃满所有核心,连带影响录像写入。GPU 推理能把这些活从 CPU 上卸下来,但会增加显卡常开功耗,而且需要额外配置运行环境透传。对于只有一两路摄像头的家庭场景,CPU 推理通常够用,不必为了这点性能去添置硬件。
真正影响资源占用的参数,按影响从大到小排:检测分辨率 > 检测帧率 > 摄像头路数 > 检测对象数量。想省资源,先把检测分辨率从 1280 降到 960,再把帧率从 5 降到 4,这两个动作做完,占用会有非常明显的下降,而检出率几乎不变。另外,关闭不必要对象的跟踪也是立竿见影的,只跟 person 比同时跟 person、dog、cat 要轻不少。
还有两个容易被忽略的隐性消耗。一是录像写入,如果全天录像开着又用主码流,磁盘 IO 会持续占用,机械盘上尤其明显,建议录像目录单独挂盘。二是进程残留,反复 docker compose up 而不做 down,老容器可能还占着端口,表现出来就是“端口被占用”或者“改了配置不生效”,遇到异常先 docker compose ps 看一眼有没有多余容器。
9. 常见问题排查与工程化最佳实践
排错这部分,把最常见的几类问题整理成表,遇到时按“现象—原因—排查—解决”的顺序走,比盲目改配置高效得多。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| WebUI 打不开 | 端口未映射或被占用 | 看容器日志打印的访问地址,用 ss -lntp 查端口 |
改宿主机映射端口,重启容器 |
| 配置改了不生效 | 容器未重建、配置语法错误 | 看启动日志有无解析报错 | 修配置后 docker compose down 再 up -d |
| 画面卡顿或绿屏 | 共享内存不足、解码参数不匹配 | 观察日志是否反复重连 | 调大 shm_size,换硬件加速预设 |
| 检测框不到人 | 阈值过高、分辨率过低 | 临时把 min_score 调低对比 |
逐步调阈值,适当提高检测分辨率 |
| 满屏误报 | 区域未划分、最小面积过小 | 打开区域可视化看覆盖位置 | 划检测区域,调大 min_area |
| 夜间几乎不触发 | 低照度、逆光、补光不足 | 对比白天与夜间事件数 | 开补光灯,或接受夜间只录像 |
| 事件片段下载失败 | 接口路径或参数版本不符 | 先用 curl 单条试,看返回码 | 按实际版本文档修正路径 |
| 磁盘快速增长 | 全天录像保留过久 | 查媒体目录各子目录占用 | 缩短保留天数,只让事件长期存 |
| 容器反复重启 | 配置错误、权限不足 | docker compose logs 看退出原因 |
修配置,检查挂载目录权限 |
表格之外,再补几个经验性的建议。
第一,永远先用最小配置跑通,再往上加功能。一开始只配一路摄像头、只跟 person、不开录像,确认链路通了,再逐项加区域、加录像、加第二路。一次性把配置写满再启动,出问题根本不知道是哪一项引起的。
第二,配置目录要纳入版本管理。config.yml 是这套系统里最值钱的东西,调参过程会有很多次试错,每次改动前 commit 一次,改坏了随时回滚,比回忆“上次那个值是多少”可靠得多。
第三,输入素材和输出结果强制分目录。摄像头配置、区域坐标图放一处,录像、快照、导出片段放另一处,两者不要混在同一个盘上。这样清理磁盘时不会误删配置,迁移机器时也能整体打包。
第四,接口服务限定访问范围。WebUI 和 API 只在内网监听,需要远程看的时候走自己的内网访问方式,不要把端口直接映射出去。同一内网里也建议开鉴权,家庭成员和访客的权限应当区分开。
第五,涉及画面的一切操作都要过合规这一关。镜头角度不能覆盖邻居窗户和公共区域,家里有家政、维修人员上门时要明确告知,录像保留期限到了就自动覆盖,不要长期囤积。这些不是形式主义,而是这类系统能否长期安心使用的前提。
第六,商用或对外发布前必须做效果复核。同一个配置在不同光照、不同季节下表现差异很大,冬天衣服厚、夏天逆光强,检出率都会变。上线前至少连续观察一周的事件记录,确认没有系统性漏检,再决定是否依赖它做任何判断。
如果你手上正好有那么一台常年开着的旧机器,建议先按第 3 章和第 4 章把最小链路跑起来,只接一路阳台画面,把检测区域划到栏杆那一小条,跑上两天看看误报有多少。这一步做完,你就知道自己这套房子适不适合继续往下折腾,也大概能判断出后面要不要加硬盘、要不要换硬件加速。剩下的接口对接、批量归档、看板展示,都是在这个最小可用版本上慢慢长出来的东西。
