简介:基于Python+OpenCV+Web+FFmpeg的智慧养老系统毕业设计/课程设计资源包,主要面向计算机相关专业学生,可用于课程设计或毕设参考。系统通过多组模拟摄像头画面,利用计算机视觉完成人脸录入与识别、表情识别、摔倒检测、闯入告警、老人与义工互动分析以及陌生人追踪,事件实时写入数据库并展示在Web报表中,方便管理人员及时响应。资源包共1078个文件,约316.77MB,涵盖Python源码、OpenCV与OpenPose等依赖配置、FaceNet和Mini-Xception模型文件(caffemodel、h5、prototxt)、前端Vue/CSS/JavaScript页面,以及PDF、DOCX、PPTX说明与答辩材料。已有237人学习下载,适合需要整体架构参考和算法落地细节的读者,可从中拆解系统设计、模型调用、数据库与前后端联动等具体实现。
1. 为什么智慧养老系统要选 Python + OpenCV + Web + FFmpeg 这个组合
现在谈智慧养老,很多方案一上来就推边缘盒子、带 NPU 的开发板,但落到“毕业设计、课程设计”这类场景,最实际的需求是:人能跑通,演示不翻车,答辩有东西讲。基于 python+opencv+web+ffmpeg 计算机视觉的智慧养老系统,本质是把摄像头采集、人体检测、异常报警、录像回放四条链路用通用软件串起来。Python 负责快速写算法和 Web 服务,OpenCV 处理每一帧图像和相机调用,FFmpeg 解决浏览器不能直接播放 RTSP 摄像头的流媒体问题,Web 页面承担值班看板和历史事件展示。这套组合不依赖特定硬件,在一台普通电脑上就能完成原型,老手用来验证算法效果也足够快。下面的内容按实际开发顺序展开,从检测模块到推流,再到报警闭环和答辩文档,每一步给出能直接抄的参数和代码。
2. 用 OpenCV 做智慧养老视频处理模块:从相机取帧到人体框选
2.1 OpenCV 安装与 FFmpeg 的版本搭配:opencv 调相机前先确认这 3 个配置
先解决环境。Python 版本建议用 3.10 或 3.11,OpenCV 选 4.8 以上。安装时如果一个项目既要 DNN 推理又要完整的 contrib 模块,直接装 opencv-contrib-python 即可,不要同时安装 opencv-python 和 opencv-contrib-python,两个包会发生文件冲突,import cv2 后可能出现属性缺失,这个是新手最常见的坑。
pip install opencv-contrib-python numpy flask requestsFFmpeg 不属于 Python 包。Windows 下从官方构建版本下载解压后,把 bin 目录加到系统 PATH;Ubuntu 下执行sudo apt install ffmpeg。装完在命令行输入ffmpeg -version能输出版本号才算成功。OpenCV 的 VideoCapture 本身虽然也能打开 RTSP,但对网络抖动和 H.265 编码支持不稳定,所以后面我们会让 FFmpeg 承担拉流和推流,OpenCV 只负责处理已经到手的帧。
# 检查 OpenCV 版本和 FFmpeg 是否存在 import cv2 import shutil print(cv2.__version__) print(shutil.which("ffmpeg"))这段代码的意义是先确认两个独立组件的可用性。cv2 会打印 OpenCV 版本,shutil.which 会返回 ffmpeg 的可执行文件路径,如果是 None,说明 ffmpeg 没加入 PATH。再强调一次:OpenCV 不是 ffmpeg 的替代品,它做的是图像计算,不是流媒体协议转换。
2.2 用 OpenCV DNN 加载人体检测模型:YOLOv8 ONNX 推理代码
人体检测是智慧养老系统的核心前置模块。常见做法是用 OpenCV 的 DNN 模块加载 YOLOv8 导出的 ONNX 模型,替代耗时较重的 PyTorch 推理。导出方法很简单:用 ultralytics 库加载模型后执行model.export(format="onnx", opset=12)。得到yolov8n.onnx后,OpenCV 直接加载。
import cv2 import numpy as np net = cv2.dnn.readNetFromONNX("yolov8n.onnx") src = cv2.imread("frame.jpg") blob = cv2.dnn.blobFromImage(src, 1/255.0, (640, 640), (0,0,0), swapRB=True) net.setInput(blob) outs = net.forward()blobFromImage 的参数需要解释清楚:第一个1/255.0是把像素归一化到 0 到 1;第二个(640, 640)是 YOLOv8 默认输入尺寸,数值越大精度略高但推理越慢;(0,0,0)是均值,YOLOv8 不需要减均值所以填三个 0;lastswapRB=True表示把 OpenCV 的 BGR 转成 RGB 输入给模型。输出outs的 shape 是(1, 84, 8400),前 4 行是中心点坐标和宽高,后 80 行是 COCO 类别置信度。这里我们要的类别一般是 0,也就是 person。
后处理做两件事:过滤低置信度框,再用 NMS 去掉重叠框。核心代码逻辑如下:
# 假设 outs shape 为 (1, 84, 8400) preds = outs[0].transpose(1, 0) # 变成 (8400, 84) boxes, scores = [], [] for pred in preds: cls_scores = pred[4:] conf = cls_scores[0] # person 类别 if conf < 0.35: continue cx, cy, w, h = pred[:4] x1 = cx - w / 2 y1 = cy - h / 2 boxes.append([x1, y1, w, h]) scores.append(float(conf)) idx = cv2.dnn.NMSBoxes(boxes, scores, 0.35, 0.5) person_boxes = [boxes[i] for i in idx]这里conf < 0.35是置信度阈值,下跌阈值会提高召回率,但也会把椅子、柜子误判成人;NMSBoxes的第二个参数 0.5 表示两个框的 IoU 超过 0.5 就保留得分更高的。对人看护场景,我一般把置信度放在 0.3 到 0.4 之间,因为老人会有弯腰、坐轮椅等非标准姿态,阈值过高会漏检。画框时要把输入尺寸和原图尺寸做换算,如果直接拿 640 尺寸框去原图画,位置会偏到角落。
2.3 检测结果结构化:从 bounding box 到报警事件的数据约定
OpenCV 推理得到的是坐标,但 Web 端和告警模块需要的是结构化数据。我一般会在检测线程里维护一个事件列表,每条记录包含event_id、timestamp、bbox、confidence、belly_down这类业务字段。这样做的好处是后期接 MQTT 或数据库时,只改序列化层,不用动检测代码。
def build_event(bbox, conf, ratio): return { "event_id": int(time.time() * 1000), "ts": time.strftime("%Y-%m-%d %H:%M:%S"), "bbox": [float(x) for x in bbox], "confidence": round(conf, 3), "wh_ratio": round(ratio, 3), "alarm": ratio < 0.55 }wh_ratio是宽高比,后面跌倒检测会用到。数据结构设计时,不要把 OpenCV 的Rect对象或 ndarray 直接放进列表,后面 Flask 做 jsonify 时会报 TypeError。统一转成 float 和 int,这是一个很多人都忽略的细节。
2.4 摄像头取帧是阻塞操作:把 VideoCapture 放进独立线程
OpenCV 的cap.read()在 RTSP 流里经常卡住,原因包括网络重传、摄像头码率过大、DNS 解析慢。更麻烦的是,如果检测逻辑写在主循环里,cap.read()一卡,整个 Web 服务和报警模块全被拖住。解决方式是把取帧放进独立线程,用一个队列把最新帧交给检测线程。
import threading, queue frame_queue = queue.Queue(maxsize=2) def capture_loop(rtsp_url): cap = cv2.VideoCapture(rtsp_url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) while True: ret, frame = cap.read() if ret: if frame_queue.full(): frame_queue.get() frame_queue.put(frame)CAP_PROP_BUFFERSIZE设置为 1 很关键。OpenCV 默认会缓冲多帧,导致实时性下降,处理的是几秒前的画面,跌倒检测就会失去意义。队列maxsize=2是防止帧消费不过来时内存无限增长。检测线程里再调用frame_queue.get(),如果队列为空就跳过这一轮。这个结构看起来简单,但对智慧养老项目来说,稳定性和实时性都能兼顾。
3. 用 FFmpeg 做 Web 视频流:HLS 推流命令与 OpenCV 帧管道
3.1 为什么浏览器不认 RTSP:HLS 协议选型与延迟预算
IP 摄像头最常见的输出协议是 RTSP,但浏览器不能直接播放。电玩城或智慧教室项目里有人用 WebRTC,延迟确实低,可以到 300 到 500 毫秒,但需要额外部署信令服务。对于养老看护场景,2 到 5 秒的延迟完全能接受,HLS 是最稳妥的方案:切分切片、天然支持 HTTP 缓存、CDN 分发也方便。部署时只需要一个静态目录,nginx 可以直接托管。所以这里选 HLS 而不是 RTMP 或 WebRTC,核心判断依据是复杂度可控、跨平台播放器多。
3.2 FFmpeg 转推 HLS 的常用命令与参数解释
FFmpeg 在这一环节的作用是从摄像头拉 RTSP 流,重编码成 H.264,再输出为 HLS 动态更新 m3u8 索引文件。常见命令如下:
ffmpeg -rtsp_transport tcp -i rtsp://admin:password@192.168.1.10:554/stream1 \ -c:v libx264 -preset veryfast -tune zerolatency \ -c:a aac -b:a 32k \ -f hls -hls_time 2 -hls_list_size 5 -hls_flags delete_segments \ /var/www/html/live/index.m3u8逐项拆开说明。-rtsp_transport tcp:把 RTSP 传输从默认的 UDP 改成 TCP,UDP 在弱网环境下会花屏,TCP 可靠但延迟略高,养老场景更看重画面连续性。-preset veryfast牺牲一点压缩率换来编码速度,避免旧电脑 CPU 跟不上。-tune zerolatency让编码器尽量少做帧间延时。-hls_time 2每个切片 2 秒,-hls_list_size 5只保留最近 5 个切片,-hls_flags delete_segments会自动删除旧切片,否则磁盘会被堆满。
hls_list_size 的设置可以直接影响延迟。列表越长越容易落后。5 个切片乘以 2 秒,相当于播放器最多落后 10 秒,这是一个相对舒服的中间值。如果觉得延迟还是高,把 hls_time 改成 1,但切片太短会增加文件句柄开销。
3.3 OpenCV 帧如何交给 FFmpeg:rawvideo 管道最小实现
如果摄像头输出的画面需要在进 Web 前先叠加检测框、时间戳或“跌倒报警”红色文字,就不能直接让 FFmpeg 去拉摄像头原始流,而是要让 OpenCV 先处理帧,再把帧喂给 FFmpeg。做法是用 subprocess 启动一个 ffmpeg 进程,从 stdin 读 rawvideo,然后转 HLS。
import subprocess import cv2 cmd = [ "ffmpeg", "-y", "-f", "rawvideo", "-pix_fmt", "bgr24", "-s", "640x480", "-r", "15", "-i", "-", "-c:v", "libx264", "-preset", "veryfast", "-tune", "zerolatency", "-f", "hls", "-hls_time", "2", "-hls_list_size", "5", "-hls_flags", "delete_segments", "live/index.m3u8" ] proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) while True: ret, frame = cap.read() if not ret: break frame = cv2.resize(frame, (640, 480)) proc.stdin.write(frame.tobytes())这里一个非常容易踩的坑是-s 640x480必须和 resize 后的尺寸严格一致。如果 OpenCV 的 frame 是 1920x1080,而 ffmpeg 还是按 640x480 读取,画面会变成绿色马赛克或直接报错。另外-r 15也建议保持和摄像头实际输出帧率一致,不要写 30 但实际只有 15,否则 ffmpeg 会重复或丢帧。
3.4 FFmpeg 参数对照表:实时性优先还是画质优先
以下参数表可以放在项目文档的性能测试章节里,答辩时列出实测数值比较有说服力:
| 参数组合 | CPU 占用 | 画面清晰度 | HLS 延迟 | 适用场景 |
|---|---|---|---|---|
| preset=ultrafast + hls_time=2 | 低 | 一般 | 2-4 秒 | 低配电脑演示 |
| preset=veryfast + hls_time=2 | 中 | 较好 | 2-5 秒 | 日常看护 |
| preset=slow + hls_time=4 | 高 | 清晰 | 5-8 秒 | 录像留档 |
| 原画转封装 -c copy | 极低 | 原始清晰度 | 2 秒 | 存储优先 |
-c copy不重编码,直接把 RTSP 里的 H.264 数据打包进 TS,CPU 占用低,但 Web 端播放兼容性依赖相机原始编码参数。如果相机的编码器输出带 B 帧,浏览器播放 HLS 时可能出现花屏。这个表在实际项目中不是让你死记,而是根据部署机器的 CPU 型号现场调。
4. Web 后端与视觉算法整合:跌倒检测告警闭合的工程实现
4.1 跌倒检测判断:宽高比、姿态关键点与连续帧确认
跌倒检测不一定非要上姿态估计模型。最轻量的方案是用人体检测框的宽高比变化:站立时高度大于宽度,宽高比约等于 0.4;摔倒后横向躺在地上,宽度大于高度,宽高比超过 0.8。这个规则对侧身跌倒有效,对正面趴下同样有效。为了避免弯腰捡东西误报,需要连续多帧确认。
# bbox: [x, y, w, h] ratio = bbox[2] / bbox[3] # width / height if ratio > 0.85: fall_frames += 1 else: fall_frames = 0 if fall_frames >= 5: alarm = True连续 5 帧按 15fps 计算,大约是 0.33 秒,既能滤掉瞬间抖动,又不会延迟太多。如果检测到正面趴下后一直没有动静,还要结合静止检测进一步上报“长时间倒地”。这个判断用中心点位置变化即可,前后帧中心点偏移小于 5 个像素就认为静止。
更精确的做法是叠加姿态关键点,把肩膀中心高度和髋部中心高度做比较。肩部平均坐标低于髋部平均坐标时,跌倒置信度提高。但姿态关键点模型推理速度比 YOLO 检测慢很多,在项目文档里要写清楚:阈值判断保证实时性,姿态关键点作为二次确认,两者是互补关系。
4.2 Flask 提供事件与快照 API,前端轮询告警记录
Web 后端在智慧养老系统里扮演“数据出口”的角色。检测线程得出报警事件后,放进一个带锁的内存列表,Flask 提供接口给前端查询。
from flask import Flask, jsonify, send_file import threading, io app = Flask(__name__) events = [] events_lock = threading.Lock() @app.get("/api/events") def get_events(): with events_lock: return jsonify(events[-50:]) @app.get("/api/snapshot") def get_snapshot(): ret, frame = cap.read() retval, buffer = cv2.imencode(".jpg", frame) return send_file(io.BytesIO(buffer.tobytes()), mimetype="image/jpeg")events[-50:]表示只返回最近 50 条,防止前端渲染过多 DOM 节点性能下降。快照接口每次调用时取最新帧,用于看护首页的实时画面缩略图。这里加锁是必要的,因为检测线程在往 events 里 append,Flask 线程在读列表,不加 lock 可能出现一边遍历一边写入的异常。
4.3 前端用 HLS.js 播放推流地址并展示报警事件
前端页面不复杂,但要注意 video 标签必须设置 muted 和 playsinline,移动端 webview 和部分浏览器禁止带声音自动播放。
<video id="video" muted autoplay playsinline></video> <script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script> <script> if (Hls.isSupported()) { var hls = new Hls(); hls.loadSource('/live/index.m3u8'); hls.attachMedia(document.getElementById('video')); } async function loadEvents() { const res = await fetch('/api/events'); const data = await res.json(); // data 渲染成列表,按时间倒序 data.slice().reverse().forEach(evt => { if (evt.alarm) { addAlarmItem(evt.ts, evt.bbox); } }); } setInterval(loadEvents, 2000);muted autoplay是自动播放的最小前提。如果不设置 muted,Chrome 会直接阻止 autoplay,页面出现黑屏但不报错。setInterval(loadEvents, 2000)是 2 秒轮询,报警实时性可以接受。如果要改成 WebSocket 推送,只需在后端定时 broadcast events,前端的 fetch 部分可以整个替换。
4.4 联调常见故障:FFmpeg 子进程退出与 OpenCV 摄像头占用
联调时最典型的报错是ffmpeg: command not found。出现这个错误时不要再去 pip install 任何包,Python 的 imageio-ffmpeg 和系统 ffmpeg 是两回事,先确认ffmpeg -version是否能正常执行。如果 FFmpeg 进程启动后马上退出,常见原因是输出目录不存在。HLS 的 m3u8 文件不会被自动创建目录,运行前要手动mkdir -p /var/www/html/live。
另一个坑是 Windows 下摄像头被手机 app 或 OBS 占用后,OpenCV 的 VideoCapture 打开成功但 read 永远返回 False。排查方式是在任务管理器中结束占用相机的进程,或者换一个非摄像头的演示视频文件作为输入源。这类问题要写进项目文档的“常见错误”部分,答辩时老师喜欢问。
5. 答辩文档和演示脚本里值得写明的三个验收技巧
5.1 用 FFmpeg 回放录屏做功能验收,避免现场相机不可用
答辩现场最容易翻车的不是算法不准,而是摄像头驱动不兼容。建议准备两段素材:一段正常行走的 mp4,一段模拟跌倒的 mp4。用 FFmpeg 循环推流成 RTSP 或直接作为 OpenCV 输入源,代码里只需要把cap = cv2.VideoCapture(0)改成cap = cv2.VideoCapture("demo.mp4")。这样即使现场没有 USB 摄像头,也能完整演示检测和报警流程。录屏素材还能保证复现性,同一段视频在调参前后跑出来的结果可以量化对比,这一点在项目文档里是加分项。
5.2 给检测日志加时间戳和置信度,量化跌倒检测阈值
很多人做视觉项目时只在前端弹报警,后端不留日志,答辩时被问“误报率多少”就答不上来。我一般会在检测循环里加一行结构化日志:
logger.info("detect person=%s conf=%.2f ratio=%.2f alarm=%s", len(boxes), max_conf, ratio, alarm)把时间、人员数量、最高置信度、宽高比和是否报警写进日志文件。答辩前运行 30 分钟,用日志统计误报警次数,在 PPT 文档里放一张简单的统计表,比任何流程图都有说服力。FFmpeg 那边同样可以用-loglevel warning收集断流时间,用于说明视频管道稳定性。
5.3 用三组测试片段固定阈值参数,写进项目文档
跌倒检测的阈值不能拍脑袋定。我通常准备三组测试片段:正常行走、弯腰拾物、快速跌倒。每组跑一遍,记录准确率和响应时间。比如置信度阈值从 0.3 调到 0.5,正常行走场景的误报率可能从 8% 降到 1%,但跌倒场景召回率也可能从 98% 降到 82%。把这两组数据放在项目文档的“参数调优”表格里,让后来者知道每个参数为什么是这个值。这个做法不单是写文档,更是给答辩老师一个明确的工程验证路径。
本文还有配套的精品资源,点击获取