独居老人在客厅摔倒装了几个摄像头却没人及时发现——做监控项目越久越觉得这类场景的问题关键不是有没有摄像头而是有没有视频内容理解能力。所以我花了大概一个月时间从零搭了一套基于深度学习YOLOv11的跌倒识别检测系统用YOLO格式数据集完成模型训练把跌倒识别封装成带UI界面和登录注册模块的完整Python项目最后打包交付源码、模型权重和配套说明。这篇文章我会把数据准备、模型训练、跌倒判定逻辑、界面集成到实测优化整个链路全部展开适合正在做毕业设计、课程设计或者想在真实监控环境里部署跌倒识别的开发者参考。1. 跌倒检测到底难点在哪为什么选YOLOv111.1 一个容易被忽视的真实痛点跌倒检测这个任务说简单也简单说难是真的难。说简单是因为它的目标很明确——只要人能跌倒并被识别出来就行说难是因为跌倒这个动作本身的形态变化极大。同样是跌倒有向前扑倒、向后仰倒、侧身摔倒还有扶着桌沿慢慢滑倒。姿态千差万别再加上监控摄像头通常固定在墙角高处视角固定、距离不定人物在画面里的大小、遮挡情况都很不稳定。这跟人脸识别不一样人脸是刚性目标跌倒属于强形变的非刚性目标。正因如此传统的人工设计特征方法比如轮廓检测、背景差分在简单场景还能用一旦遇到光线变化、移动物体干扰、多人重叠基本就废了。所以现在做跌倒识别主流方案都是走目标检测路线直接从图像里端到端检测出人和跌倒这两个类别。这样做的好处是不需要人工设计特征模型自己学习跌倒姿态的高层语义表达坏处是它对数据集质量要求高如果训练数据里没有覆盖足够多的跌倒姿态到了真实场景就容易漏检。1.2 YOLOv11相对早期版本的实用改进YOLO系列一路更新到现在YOLOv11在结构上的改进主要是把注意力机制和特征融合做得更精细在几乎不增加推理耗时的情况下提高了对小目标、遮挡目标的召回。我实际测试下来YOLOv11s在跌倒检测场景里比早先用的YOLOv5m体积更小推理速度反而更快精度上有肉眼可感知的提升。具体到选型我给每个型号都测过一遍n最轻CPU也能勉强跑s是精度和速度最均衡的项目最终选的就是YOLOv11sm和l精度更高但实时推理对显卡要求也更高。对于大多数室内单路监控场景s已经完全够用。另外YOLOv11对工程化支持好模型可以方便导出成ONNX或者TensorRT格式后续要部署到边缘设备也不用费劲重写推理代码这对一个完整交付型项目来说很重要。1.3 整套系统的功能范围与技术栈刚开始我只想训练一个跌倒检测模型但做着做着发现光有模型没法给非技术用户用。你要让人家真正在监控场景里跑起来得有用户账户体系得有可视化的检测界面得有报警记录。所以这个项目最终做成了一个相对完整的系统功能分为三块登录注册用户注册、登录、会话管理数据存SQLite本地数据库实时检测接入摄像头或读取视频文件实时识别画面中的人和跌倒行为报警记录保存报警时间、置信度和截图在界面上展示最近报警历史技术栈就是很标准的Python生态PyTorch做模型训练和推理Flask做Web服务OpenCV处理视频流前端用简单的HTMLCSSJavaScript。目录结构大概是这样的fall_detection/ ├── app.py # Flask主程序包含登录注册与检测路由 ├── models/ │ └── best.pt # 训练好的YOLOv11s权重 ├── dataset/ # 自定义数据集与标注文件 ├── fall_detector.py # 检测与跌倒判定逻辑封装 ├── requirements.txt # 项目依赖 ├── static/ # 前端样式、图片 └── templates/ ├── login.html # 登录页 ├── register.html # 注册页 └── index.html # 检测主界面整体架构不复杂但每一块都有不少值得记录的细节下面依次展开。2. 数据集准备视频抽帧、标注与类别设计2.1 数据来源与两个类别怎么定数据集是这类项目最花时间的一部分。我的数据来源分两块一块是公开的跌倒检测视频数据网上有几个开源数据集大多是实验室环境下模拟的跌倒场景另一块是自己录制和收集的室内场景片段。公开数据的优点是跌倒姿态覆盖广缺点是场景单一直接拿来做跨场景测试效果不稳定。所以我额外补充了一些不同光照、不同角度的视频合并成自己的训练集。类别设计上我只设了两个类person和fall。有朋友问为什么不把跌倒细分成倒地老人倒地成人若干个类其实没太大必要。目标检测的类别越多模型要学的区分边界就越复杂同类不同姿态样本不足时反而容易漏检。跌倒这件事形态太杂了前扑、仰倒、侧摔如果分成多个类每一类样本量都会被稀释最终效果一定不如合成一个类来训练。想要细分跌倒类型正确做法是做后处理阶段的姿态分类而不是在检测模型上强行加类别。有意思的是蹲下和跌倒非常像。从视觉上看蹲下时人的检测框宽高比也会变大但蹲下的动作速度、中心点位移幅度和跌倒是有明显差别的。这个差异我放在后面检测逻辑部分细说。数据集阶段要做的就是尽量保留这些困难正样本让模型见过足够多蹲下、坐下、弯腰的样本否则训练出来的模型会把蹲下一律判成跌倒。2.2 标注工具与标注规范标注我用的是LabelImg需要提前装好pyqt5依赖。跌倒视频不能一帧一帧全标那样工作量太大且相邻帧高度重复。我的做法是每个视频按每秒2到5帧抽帧抽出来的关键帧做标注这样既覆盖了动作过程又不会出现几百张几乎一样的标注文件。标注的边界要特别说清楚fall人物主体已经明显倒地或呈近似水平姿态比如躯干贴地、身体倒向地面person站立、行走、坐下、蹲下、弯腰等未倒地姿态全部归此类有个容易踩的坑跌落的中间过渡帧人体已经倾斜但还没完全倒地到底标什么类别。我踩过一次把过渡帧全标成了fall训练出来的模型在正常弯腰捡东西时频繁误报。后来统一改成只有主体姿态已经明显横向或已经接触地面才算fall过渡帧全部标成person。模型在推理时会因为连续几帧检测到fall状态而报警所以标注时宁严勿松。2.3 数据增强与样本平衡标注完以后一个普遍问题是跌倒样本远少于正常行走的样本。不均衡的数据会让模型过于保守或过于激进解决思路有两个对跌倒样本做过采样跌倒视频的帧全部保留正常样本按比例抽稀用数据增强扩充跌倒样本随机水平翻转、随机旋转±15度、亮度对比度变化、HSV色彩扰动旋转增强对跌倒检测特别有效。因为同一个跌倒动作在摄像头视角不同时形状差异很大随机旋转相当于模拟了不同安装角度下的画面。另外我还开了马赛克拼接增强把四张图拼成一张训练对小目标检测帮助明显。最终数据集大概在3000多张左右其中跌倒样本约1200张训练集:验证集:测试集按8:1:1划分。这个量级不大但对固定室内场景的跌倒识别已经够用。3. YOLOv11训练环境配置、参数调优与结果分析3.1 训练环境与常见安装坑训练基于PyTorch用的官方YOLOv11训练代码。环境配置按项目根目录的requirements.txt安装即可pip install -r requirements.txt这里有几个容易踩的坑我依次说。第一是PyTorch和CUDA版本不匹配。如果机器上已经装过老版本PyTorch执行训练时会报CUDA driver版本错误。稳妥的做法是先去官方网站查对应CUDA版本的安装命令比如CUDA 11.8对应torch 2.x的某个版本不要直接pip install torch拉默认版本。我踩过一次之后干脆在requirements.txt里锁定了版本号。第二是Windows下面LabelImg和PyTorch都可能因为路径带中文报错。数据集目录、标注文件路径、输出目录建议全部用英文这个老生常谈但每次都有人栽。第三是显存不足。YOLOv11s在batch16、640分辨率下大概需要8到10GB显存。如果只有6GB显存batch降到8或者把输入尺寸从640降到480也可以但精度会损失一点。我自己在训练时发现降低batch比降低分辨率对最终mAP的影响更小。3.2 关键训练参数与设置理由训练参数我核心着重的几个python train.py --data fall.yaml --weights yolov11s.pt \ --img 640 --batch 16 --epochs 120 \ --optimizer AdamW --lr0 0.01 \ --cosine --amp--img 640YOLO系列训练的默认尺寸检测框定位精度和小目标召回率比较均衡。如果画面中人物普遍较小可以用768或896但推理速度会下降--batch 16在显存允许范围内尽量大batch太小会导致收敛不稳定--epochs 120对于3000多张的数据集120轮足够收敛再多容易过拟合--optimizer AdamW相比SGDAdamW收敛更稳定尤其在使用Cosine学习率调度时--cosine学习率按余弦曲线衰减后期微调更细腻--amp混合精度训练显存占用低速度也更快用官方预训练权重yolov11s.pt做初始权重而不是从头训练。因为跌倒检测的数据量不大从COCO类通用权重迁移过来模型已经掌握了通用的边缘、纹理、人体形状特征只需要在跌倒场景上做微调。数据集配置文件的写法就是一个yaml文件指向训练集和验证集目录并声明类别数量path: dataset/ train: train/images val: val/images names: 0: person 1: fall3.3 训练曲线分析与早停判断训练过程中我会同时盯着两张图训练/验证loss曲线和PR曲线。loss曲线在正常训练里应该稳步下降到40到60轮左右下降速度变慢100轮后基本稳定。如果训练loss一直在降但验证loss开始上升就是过拟合了这时候应该早停或加大数据增强。我在训练时发现跌倒样本本来就不多到后期模型明显记住了训练集里的特定跌倒画面验证集上的表现反而变差。解决方法是降低了最后一个阶段的马赛克增强比例同时提前在验证最佳点上保存权重。验证指标重点关注mAP50和mAP50-95。经过大约110轮训练最终验证集上mAP50在87左右mAP50-95在60左右虽然比不上COCO那种大规模数据集的绝对数值但对于场景相对固定的室内跌倒检测这个精度已经够用了。保存权重的逻辑是每轮结束比较验证集mAP最好的保存为best.pt。后面推理部署直接用这个best.pt就好不要用last.pt那个是最后一轮的结果大概率已经过拟合。4. 识别逻辑检测框之外的是否跌倒判定4.1 只靠检测模型输出还不够模型训练完之后如果你只是拿到画面就送进模型检测到fall类就报警那误报率会高到没法用。为什么因为单帧图像信息量不够。同一个画面里一个人弯下腰捡东西检测框的形状和跌倒时非常接近一个人猛坐在椅子上中心点也会快速下移灯光闪烁或物体遮挡时模型很可能把某个瞬时轮廓误判成fall。如果每一帧都独立判断系统会被这些偶发误检折磨到天天假报警。所以我在检测模型之后加了一个规则判定层相当于给模型戴上逻辑安全带。它的任务是把连续多帧的检测结果综合起来判断一个人是不是真的在跌倒过程或者已经倒地。4.2 宽高比、中心点位移与持续帧数三重规则先对每个目标做跟踪。目标跟踪不用单独引入跟踪模型简单的IoU匹配就够当前帧检测框和上一帧每个框计算交并比IoU超过阈值的视为同一个目标记录下这个目标的检测框历史序列。对每个跟踪目标每帧计算三个关键量检测框宽高比w/h人站立时框高明显大于宽宽高比小于0.6跌倒或倒地位后框变为横向宽高比通常大于1.2检测框中心点的Y坐标变化跌倒是一个快速下坠过程几帧内中心点会向画面下方移动移动幅度通常是人体高度的一半以上持续帧数上述状态必须连续保持若干帧而不是只闪现一帧判定逻辑如下def is_fall_event(track): frame_history track.recent_boxes[-5:] # 最近5帧的检测框 if len(frame_history) 5: return False # 计算最近5帧的平均宽高比 avg_ratio sum(box.w / box.h for box in frame_history) / len(frame_history) # 计算中心点Y坐标位移 y_start frame_history[0].cy y_end frame_history[-1].cy y_shift y_end - y_start y_threshold frame_history[-1].h * 0.6 # 位移超过当前框高的60% # 宽高比由矮变高且中心点快速下坠且连续多帧保持 if avg_ratio 1.0 and y_shift y_threshold: return True return False宽高比和中心点位移两个条件同时满足比单帧检测到fall类更可靠。因为它从几何形态和运动趋势两个维度描述人体由直立变为倒地这个动态过程而不是只看某一个静态瞬间。同时模型检测到fall类的置信度也参与加权。如果连续三帧检测到fall类且置信度超过0.6即使几何规则没有完全触发也会记一次潜在警报等下一帧确认。两类信号互相补充漏报和误报都压到比较低。4.3 误报抑制与报警触发策略规则层再加两道保险。第一是连续帧投票机制。前面代码里已经体现了一部分我实际设置的是单个目标的疑似跌倒状态必须连续保持5帧以上才真正触发报警。1到2帧的偶发性误检通过这个机制直接过滤掉。蹲下动作偶尔会让宽高比变大一两帧但因为很快恢复正常投票机制不会判它报警。第二是报警冷却时间。同一个目标触发报警后60秒内不再重复报警防止一个人跌倒后系统刷屏。这个设计在真实运营场景里很重要因为一旦老人跌倒后无法自主起身检测系统不应该每分钟都弹一次报警而是保持持续报警状态即可。误报处理上最麻烦的是坐下这个动作。从侧面角度看快速坐下时中心点下坠很明显宽高比也在短时间内增大。坐下的宽高比一般在0.8到1.0之间不如跌倒那么横但如果坐的是矮凳且画面角度刁钻规则层确实可能误判。我的处理方式是把宽高比阈值从1.0提高到1.15并额外加上一条如果检测模型对该目标连续5帧有person类高置信度输出则即使几何规则触发也判定为坐下/蹲下而非跌倒。模型类别信息和几何规则互相对抗反而比单用其中一种更稳。5. 登录注册与检测界面把模型包装成完整系统5.1 登录注册模块的实现细节界面部分我选了Flask做Web服务原因很简单登录注册天然是Web的活而且踩坑少、部署快。模型推理在后台用户通过浏览器访问不用额外装客户端。登录注册模块核心注意三点密码不能明文存储、会话必须管理、未登录状态必须拦截。密码用Werkzeug的哈希函数处理这个Flask生态自带不需要额外安装。注册时把哈希后的密码存进SQLite登录时用同样的哈希函数校验。任何把用户密码明文写进数据库的做法都是给自己埋雷哪怕这个系统只是课上演示用也应该按规范来。代码大概是这样的from flask import Flask, request, session, redirect, render_template from werkzeug.security import generate_password_hash, check_password_hash import sqlite3 app Flask(__name__) app.secret_key replace-with-a-random-secret def init_db(): conn sqlite3.connect(users.db) conn.execute( CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL ) ) conn.close() app.route(/register, methods[POST]) def register(): username request.form.get(username) password request.form.get(password) password_hash generate_password_hash(password) conn sqlite3.connect(users.db) try: conn.execute( INSERT INTO users (username, password_hash) VALUES (?, ?), (username, password_hash), ) conn.commit() return redirect(/login) except sqlite3.IntegrityError: return 用户名已存在 finally: conn.close()session是通用的Flask登录状态方案登录成功后写入session[username]然后给检测页面加上登录校验。未登录的用户访问检测页面一律跳转到登录页。还有一个容易被忽略的细节注册接口要检查用户名是否已存在否则重复注册会报数据库唯一约束错误体验很差。上面代码里用IntegrityError做了兜底。5.2 检测主界面实时画面与报警展示检测主界面我做成左右两栏布局。左侧是实时视频流右侧是报警记录面板。视频流用Flask的流式响应实现后端不停把OpenCV解码的摄像头帧编码成JPEG推给前端def generate_frames(): while True: success, frame video_capture.read() if not success: break # 在这里调用检测模型绘制检测框 annotated_frame run_detection(frame) ret, jpeg cv2.imencode(.jpg, annotated_frame) yield (b--frame\r\n bContent-Type: image/jpeg\r\n\r\n jpeg.tobytes() b\r\n) app.route(/video_feed) def video_feed(): return Response(generate_frames(), mimetypemultipart/x-mixed-replace; boundaryframe)前端img标签直接指向/video_feed就能显示实时画面这个方案实现简单兼容性也好。报警记录存在内存列表里同时保留最近20条记录展示在界面右侧。每条记录包含报警时间、检测到的目标类别、置信度和一张保存到本地的现场截图。报警截图非常重要用户在界面上看到报警后最关心的是当时发生了什么没有截图只能靠时间点去回放视频体验完全不一样。5.3 摄像头输入与视频回放两种工作模式系统要支持两种输入模式实时摄像头和视频文件。模式切换在界面上用一个下拉框实现后端根据选择决定视频源。摄像头模式要注意几个坑。OpenCV读取USB摄像头时cv2.VideoCapture(0)有时候会打不开原因是摄像头被其他进程占用或者权限没开。建议代码里加上重试逻辑并且在前端显示连接状态。另外如果摄像头分辨率太高推理速度会跟不上我在代码里统一把输入帧缩放到640宽度再送进模型既保证检测精度又降低延迟。视频文件模式主要是给演示和测试用的。处理完一个视频后显示检测完成并把报警记录按时间顺序列出来。这里注意一个问题视频文件路径如果是中文OpenCV在Windows上读取会报错我在页面上上传视频后先把文件保存成英文临时文件名再做检测。两种模式共用一个推理接口和报警面板切换时只需要新建VideoCapture对象逻辑上不冲突。6. 实测表现与优化建议6.1 不同场景下的测试结果在室内监控场景下做了一轮系统测试我整理了有代表性的几类场景和对应表现测试场景误报情况漏报情况说明正常行走无无宽高比一直小于1规则层稳定快速坐下偶尔误报无矮凳侧面角度会触发高宽比下坠模型类别权重可压住蹲下捡东西低概率误报无下坠幅度不足连续帧投票能过滤明显跌倒无无系统在该场景的召回率最高侧面遮挡跌倒无存在漏报遮挡导致框不稳定中心点位移被截断需要调低位移阈值暗光环境无中等概率漏报需要加红外补光或使用低信念度检测融合这个表能直观看到系统在正常行走场景几乎不会误报警最难处理的还是坐下和蹲下跟跌倒的边界。遮挡场景漏报主要是目标跟踪断链导致的应对方案是调低IoU匹配阈值允许更大的框跳变。6.2 训练数据不够时的应对经验如果你复现这个项目后发现效果达不到上面数据第一步不是调模型结构而是检查数据集构成。我实测发现如果训练集里跌倒样本都是同一个摄像头角度拍的换一个摄像头高度之后mAP会掉10个点以上。这就是数据分布偏移。应对方式有三条收集多角度的跌倒视频哪怕数量不多角度覆盖广比数量大更有价值做针对性增强比如模拟低照度的亮度下降增强、模拟运动模糊的模糊增强用小规模的复检集做人工评估。不要只看验证集mAP要准备一些训练时没见过的真实监控片段跑一遍统计误报和漏报的具体画面反过来补样本补样本是最笨但最有效的方法。我第一次训练时漏报了侧摔因为我标注的数据里侧摔比例低。后来专门补了几十段侧摔视频重新标注训练问题基本解决。6.3 推理速度、模型体积与部署优化最后说下部署相关的事。在NVIDIA RTX级别的显卡上YOLOv11s处理640分辨率大约25到35毫秒一帧也就是30FPS左右满足实时监控需求。如果只带一路摄像头完全够用。但如果要用一台服务器处理多路视频流就要考虑性能优化换用YOLOv11n速度能提升一半左右精度会损失几个点导出ONNX并用TensorRT加速在保证精度的前提下推理延迟能压到10毫秒内输入分辨率从640降到544在人物不密集的场景下精度损失很小使用跳帧策略每两帧检测一次中间帧用上一次结果做简单的框插值模型体积方面YOLOv11s的权重大约在40到50MB之间对磁盘和内存都没压力。导出ONNX之后也能部署到树莓派、Jetson这类边缘设备上。不过边缘设备上我没有实测过跌倒检测的具体帧率只做过推理延迟估算这里就不给出数据误导人了。我的体会是跌倒识别这类任务真正决定项目能落地的不是算法多先进而是数据覆盖、规则设计和工程封装做得到不到位。训练一个模型只是起点把识别结果变成一个不给用户添乱的报警系统才是完整的交付。这套系统的源码、模型和UI界面都放在项目目录里照着上面的流程走一遍你也能做出自己的跌倒检测方案。