☰
基于Python+OpenCV+FFmpeg的智慧养老系统实战:跌倒检测与Web监控
2026/10/6 2:57:53 网站建设 项目流程

简介:这是一套面向高校毕业设计与课程设计场景的智慧养老系统完整项目包,基于 Python、OpenCV、Web 与 FFmpeg 构建,适合计算机视觉、人工智能方向的学生及需要综合实战案例的开发者。系统分为 Web 管理端与摄像头群组两部分,通过多路模拟摄像头实时分析老人情绪、摔倒、闯入禁区、义工互动及陌生人出现等事件,并即时写入数据库、更新报表,帮助管理人员快速响应。技术栈覆盖 OpenCV 人脸检测与录入、FaceNet 单样本人脸识别、Mini-Xception 表情识别、Happy-Elder-Care-Net 微笑与陌生人检测、OpenPose 摔倒判定、GF(4) 交互检测、质心跟踪入侵追踪以及 Nginx-RTMP 直播推流。压缩包共 1078 个文件,约 316.77MB,包含 68 个 py 源码、30 个 vue 前端页面、78 个 css 样式、60 个 vcxproj 工程文件及 caffemodel、prototxt 等模型权重,另附答辩 pptx 与项目文档。已有 238 人学习,可帮助读者快速理解多模块协同的视觉养老系统架构与实现思路。

1. 智慧养老系统为什么值得用 Python+OpenCV+Web+FFmpeg 做一遍

养老院最真实的痛点不是缺摄像头,而是摄像头后面没人盯。护工三班倒,夜里一个人要看十几路画面,老人跌倒、久坐不起、走出电子围栏这些事,靠人眼盯屏基本等于玄学。这套智慧养老系统的核心思路,就是用 Python 把 OpenCV 的视觉能力和 FFmpeg 的流处理能力串起来,再套一层 Web 页面给护工和管理者看,形成一个能落地、能答辩、能继续迭代的完整项目。

它适合三类人:一是做计算机视觉大作业或毕业设计的学生,需要一个有真实场景、有前后端、有视频链路的题目;二是想入门 Python+OpenCV 的开发者,需要一个比"识别猫狗"更有说服力的练手项目;三是养老机构里懂点技术的负责人,想先跑一个低成本原型验证可行性。整套东西不依赖昂贵硬件,一台带摄像头的普通 PC 或一块树莓派就能起步,Web 端用浏览器就能访问,FFmpeg 负责把 RTSP 流拉下来转成浏览器能播的格式。下面按"先跑通最小链路,再补视觉算法,最后做 Web 和避坑"的顺序讲清楚。

2. 把视频链路先跑通:FFmpeg 拉流、转码与 Web 可播格式

2.1 为什么视频链路要单独拎出来做

很多人一上来就写 OpenCV 的跌倒检测,结果发现摄像头画面根本进不了浏览器,或者延迟高到护工看到的是十秒前的画面。智慧养老系统里,视频链路是地基,地基不稳,上面算法再准也没用。常见做法是:摄像头用 RTSP 输出,FFmpeg 负责拉流并转成 HLS 或 HTTP-FLV,Web 端用 video 标签或 flv.js 播放。OpenCV 则从同一路 RTSP 或本地文件里取帧做分析,两条链路互不干扰。

这里有个选型理由要说清楚:为什么不直接用 OpenCV 的 VideoCapture 推给 Web?因为 OpenCV 的 imshow 是本地窗口,没法直接给浏览器;而把每帧转成 JPEG 再通过 WebSocket 推,延迟和 CPU 占用都很难看。FFmpeg 在转码和封装上是专业工具,OpenCV 专注做视觉计算,各干各的活,系统才稳。

2.2 用 FFmpeg 把 RTSP 转成 HLS 的最小命令

先确认 FFmpeg 装好,Windows 下去官网下载压缩包解压后把 bin 目录加进 PATH,Linux 下 apt 或源码编译都行。下面这条命令是把一路 RTSP 转成 HLS,供 Web 端播放:

ffmpeg -rtsp_transport tcp -i "rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101" \ -c:v libx264 -preset ultrafast -tune zerolatency \ -c:a aac -f hls -hls_time 2 -hls_list_size 3 \ -hls_flags delete_segments /var/www/hls/room1.m3u8

逻辑说明:-rtsp_transport tcp强制用 TCP 拉流,避免 UDP 丢包导致花屏;-c:v libx264把摄像头常见的 H.264/H.265 统一转成浏览器兼容的 H.264;-preset ultrafast -tune zerolatency是低延迟的关键,牺牲一点压缩率换实时性;-hls_time 2 -hls_list_size 3表示每个切片 2 秒、列表保留 3 个,延迟大概在 6 到 8 秒,对养老监护够用。如果要求更低延迟,可以换 HTTP-FLV,用-f flv输出到支持 FLV 的流媒体服务,Web 端用 flv.js 播放,延迟能压到 2 秒内。

参数怎么改:摄像头分辨率高、CPU 弱的时候,加-s 1280x720降分辨率;带宽紧张就调-b:v 1500k限制码率;多路摄像头就写多个 FFmpeg 进程或用-f tee分流。失败时先看 FFmpeg 的报错,最常见的是 RTSP 地址错、账号密码没 URL 编码、防火墙挡了 554 端口。

2.3 OpenCV 从同一路流取帧的写法

视觉分析这条线,用 OpenCV 的 VideoCapture 直接读 RTSP:

import cv2 # RTSP 地址,注意密码里的特殊字符要 URL 编码 rtsp_url = "rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101" cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG) # 设置缓冲区为 1,减少延迟累积 cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) if not cap.isOpened(): raise RuntimeError("无法打开视频流,检查地址和网络") while True: ret, frame = cap.read() if not ret: # 流断了,尝试重连 cap.release() cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG) continue # 这里放后续的视觉处理 cv2.imshow("monitor", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

逻辑说明:cv2.CAP_FFMPEG指定用 FFmpeg 后端解码,比默认后端对 RTSP 支持更好;CAP_PROP_BUFFERSIZE设成 1 能明显降低延迟,否则 OpenCV 会缓存一堆旧帧。参数上,如果只是做分析不需要显示,把 imshow 去掉能省不少 CPU。常见坑是ModuleNotFoundError: No module named 'opencv',那是没装 opencv-python,pip install opencv-python即可;如果报cv2.error跟 GStreamer 相关,Linux 下装libgstreamer1.0-dev或改用 FFmpeg 后端。

3. 视觉算法落地:跌倒、久坐与区域入侵怎么用 OpenCV 实现

3.1 先想清楚养老场景要检测什么

养老监护不需要识别几百类物体,核心就三件事:跌倒、长时间不动、走出安全区域。这三件事用 OpenCV 加轻量模型就能做,不必上重型深度学习。跌倒检测的常见思路是人体框的宽高比突变加中心点快速下移;久坐检测是统计人体框在某个区域内的停留时长;区域入侵是判断人体框和预设多边形有没有交集。选型上,人体检测可以用 HOG+SVM(OpenCV 自带,零额外依赖)或 YOLO 的轻量版,前者适合答辩演示,后者适合真实部署。

3.2 用 HOG 做人体检测并判断跌倒的最小代码

import cv2 import numpy as np hog = cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) # 记录上一帧的人体框,用于判断跌倒 prev_boxes = [] def detect_fall(frame, prev_boxes): # 检测人体,winStride 和 padding 影响速度和精度 boxes, weights = hog.detectMultiScale(frame, winStride=(8, 8), padding=(16, 16), scale=1.05) falls = [] for (x, y, w, h) in boxes: aspect = w / float(h) # 正常站立宽高比小于 0.6,跌倒时接近或大于 1 if aspect > 0.9: falls.append((x, y, w, h)) return boxes, falls cap = cv2.VideoCapture("rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101") while True: ret, frame = cap.read() if not ret: break frame = cv2.resize(frame, (640, 480)) boxes, falls = detect_fall(frame, prev_boxes) for (x, y, w, h) in boxes: cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) for (x, y, w, h) in falls: cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 3) cv2.putText(frame, "FALL", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) cv2.imshow("fall detection", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

逻辑说明:HOG 检测器返回人体框,宽高比是判断跌倒最直观的特征。参数上,winStride越小越准但越慢,scale是金字塔缩放步长,1.05 是精度和速度的折中。这段代码只是演示,真实场景要加连续帧确认,避免一个人弯腰就被误报。常见误报来源是坐姿和蹲下,解决办法是结合中心点下移速度做二次判断。

3.3 久坐和区域入侵用多边形和计时器实现

久坐检测不需要复杂模型,维护一个字典记录每个人体框中心点进入某个区域的时间戳,超过阈值就报警。区域入侵用cv2.pointPolygonTest判断中心点是否在多边形内:

import cv2 import time # 定义安全区域多边形,坐标按实际画面标定 safe_zone = np.array([[100, 100], [500, 100], [500, 400], [100, 400]], np.int32) sit_start = {} # 记录每个人进入久坐区域的时间 def check_zone(frame, boxes): alerts = [] for i, (x, y, w, h) in enumerate(boxes): cx, cy = x + w // 2, y + h // 2 # 判断中心点是否在安全区域内 inside = cv2.pointPolygonTest(safe_zone, (cx, cy), False) >= 0 if not inside: alerts.append(("intrusion", (x, y, w, h))) # 久坐判断:在区域内停留超过 30 分钟 if inside: if i not in sit_start: sit_start[i] = time.time() elif time.time() - sit_start[i] > 1800: alerts.append(("sedentary", (x, y, w, h))) else: sit_start.pop(i, None) return alerts

逻辑说明:pointPolygonTest返回正值表示点在多边形内,负值在外。久坐阈值 1800 秒按实际需求调,演示时可以改成 30 秒方便看效果。参数上,安全区域坐标要用鼠标事件在画面上标定,别硬编码。这套逻辑的边界是:多人重叠时人体框会合并,导致 ID 跳变,真实部署建议上轻量跟踪算法如 CSRT 或 ByteTrack。

4. Web 端怎么把视频和告警串起来:Flask 接口与前端播放

4.1 后端用 Flask 暴露视频和告警接口

Web 层不需要多复杂,Flask 足够。核心是两个接口:一个返回 HLS 播放地址或直接代理视频流,一个返回最新告警列表。告警数据可以存 SQLite,也可以放内存队列,答辩演示用内存就够。

from flask import Flask, jsonify, render_template import sqlite3 app = Flask(__name__) def init_db(): conn = sqlite3.connect("alerts.db") conn.execute("CREATE TABLE IF NOT EXISTS alerts (id INTEGER PRIMARY KEY, type TEXT, time TEXT, room TEXT)") conn.commit() conn.close() @app.route("/") def index(): return render_template("index.html") @app.route("/api/alerts") def get_alerts(): conn = sqlite3.connect("alerts.db") rows = conn.execute("SELECT type, time, room FROM alerts ORDER BY id DESC LIMIT 20").fetchall() conn.close() return jsonify([{"type": r[0], "time": r[1], "room": r[2]} for r in rows]) @app.route("/api/stream/<room>") def get_stream(room): # 返回该房间的 HLS 地址,前端用 video 标签播放 return jsonify({"url": f"/static/hls/{room}.m3u8"}) if __name__ == "__main__": init_db() app.run(host="0.0.0.0", port=5000)

逻辑说明:/api/alerts给前端轮询告警,/api/stream返回播放地址。参数上,生产环境别用 Flask 自带服务器,换 gunicorn 或 uwsgi;host="0.0.0.0"是为了局域网内其他设备能访问。注意 Web 服务器安全,别把调试模式开到公网,debug=True只在本地用。

4.2 前端用 video 标签加轮询展示

前端页面用原生 HTML+JS 就行,video 标签播 HLS 需要 hls.js(Safari 原生支持):

<video id="player" controls autoplay muted></video> <ul id="alert-list"></ul> <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> <script> const video = document.getElementById('player'); const src = '/static/hls/room1.m3u8'; if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(src); hls.attachMedia(video); } else { video.src = src; } // 每 5 秒拉一次告警 setInterval(async () => { const res = await fetch('/api/alerts'); const data = await res.json(); document.getElementById('alert-list').innerHTML = data.map(a => `<li>${a.time} ${a.room} ${a.type}</li>`).join(''); }, 5000); </script>

逻辑说明:hls.js 负责把 m3u8 切片喂给 video 标签,轮询接口刷新告警列表。参数上,轮询间隔 5 秒对养老场景够用,太频繁会增加服务器压力。如果要做 Web 端实时视频,HLS 延迟偏高,可以换 WebSocket 推 JPEG 帧或 HTTP-FLV,但复杂度上升,答辩演示 HLS 足够。

4.3 告警写库和视觉进程的衔接

视觉进程检测到跌倒后,要写进数据库并让 Web 端能看到。最简单的方式是视觉进程直接写 SQLite,Web 端读同一个库:

import sqlite3 from datetime import datetime def save_alert(alert_type, room): conn = sqlite3.connect("alerts.db") conn.execute("INSERT INTO alerts (type, time, room) VALUES (?, ?, ?)", (alert_type, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), room)) conn.commit() conn.close()

逻辑说明:SQLite 支持多进程读写,但并发高时会锁库。演示场景没问题,真实部署建议换 PostgreSQL 或加消息队列。参数上,时间格式统一用字符串存,方便前端直接显示。注意别在视觉主循环里频繁开闭数据库连接,可以攒一批再写。

5. 避坑与排查:从环境到部署的 5 个血泪教训

5.1 现象:pip 装完 opencv 还是报 ModuleNotFoundError

原因:装的是 opencv-python 但 Python 环境不对,或者虚拟环境没激活,或者装成了 opencv-contrib-python 和 opencv-python 冲突。解决:先pip list | grep opencv看装了什么,统一用pip install opencv-python,虚拟环境里重装。Linux 下如果报cv2.error跟 libGL 相关,装libgl1-mesa-glx。

5.2 现象:FFmpeg 拉流几小时后卡死

原因:RTSP 流断开后 FFmpeg 没退出,或者 HLS 切片堆积占满磁盘。解决:加-timeout参数让 FFmpeg 超时退出,用 supervisor 或 systemd 守护进程自动重启;HLS 加delete_segments标志自动删旧切片,定期清理目录。

5.3 现象:Web 端视频延迟十几秒

原因:HLS 切片太长,或者 OpenCV 和 FFmpeg 抢同一路流导致解码慢。解决:-hls_time降到 1 到 2 秒,-hls_list_size设 3;OpenCV 分析用低分辨率子码流,FFmpeg 转码用主码流,别让两者抢资源。

5.4 现象:跌倒检测误报频繁

原因:HOG 检测器对坐姿、弯腰敏感,宽高比阈值太松。解决:加连续帧确认,比如连续 5 帧都判定跌倒才报警;结合中心点下移速度,只有快速下移才触发;阈值从 0.9 调到 1.0 以上减少误报。

5.5 现象:局域网其他设备访问不了 Web 页面

原因:Flask 默认只监听 127.0.0.1,或者防火墙挡了 5000 端口。解决:app.run(host="0.0.0.0"),Windows 防火墙放行端口,Linux 用ufw allow 5000。注意别把服务暴露到公网,养老数据涉及隐私,局域网内用就行。

6. 让系统更像"产品":多路管理、告警去重与答辩演示技巧

多路摄像头管理是这套系统从 demo 走向可用的关键。我一般会写一个配置文件,把每个房间的 RTSP 地址、HLS 输出路径、安全区域坐标都放进去,用 Python 读配置批量启动 FFmpeg 进程和 OpenCV 分析线程。这样加一个房间只改配置,不用动代码。配置用 YAML 或 JSON 都行,下面是个 YAML 示例:

rooms: - name: room1 rtsp: "rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101" hls: "/var/www/hls/room1.m3u8" safe_zone: [[100, 100], [500, 100], [500, 400], [100, 400]] - name: room2 rtsp: "rtsp://admin:password@192.168.1.65:554/Streaming/Channels/101" hls: "/var/www/hls/room2.m3u8" safe_zone: [[80, 80], [480, 80], [480, 380], [80, 380]]

告警去重是另一个容易被忽略的点。同一个人跌倒,视觉进程可能连续报十次,护工看到一堆重复告警会麻木。我的做法是给每个告警加一个冷却时间,同一房间同一类型告警 60 秒内只写一次库。实现上用一个字典记录上次告警时间,写库前判断一下就行。这个细节在答辩时讲出来,评委能看出你考虑过真实使用场景。

答辩演示有个技巧:别只放一段录好的视频,现场用手机开个 RTSP 推流或者用本地视频文件模拟,让评委看到从视频输入到 Web 告警的完整链路。演示前把阈值调低,比如久坐 30 秒就报警,跌倒检测放宽一点,保证现场能触发。同时准备一个"翻车预案",万一网络断了,切到本地视频文件继续演示,别让答辩卡在拉流上。

最后说个我自己的习惯:这套系统我做完后,会把每个模块的启动命令写成一个 shell 脚本,答辩前跑一遍确认全链路通。视觉算法部分,我会留一个--debug开关,打开就显示检测框,关掉就只写告警,方便调试和演示切换。智慧养老这个方向,技术不是最难的,难的是把视频、算法、Web 和真实场景的约束捏在一起,跑通一次完整的链路,比单独调一个检测模型有价值得多。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询