ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

室内老人摔倒检测系统:基于YOLOv8-pose与FastAPI的实现

室内老人摔倒检测系统:基于YOLOv8-pose与FastAPI的实现 简介面向计算机、电子信息工程、数学等专业学生的课程设计与毕业设计参考这份室内老人摔倒检测系统资源完整覆盖前端交互界面、后端数据存储与处理逻辑以及核心的摔倒检测算法可应用于独居老人安全监测场景。压缩包共110个文件以Java源码为主辅以XML配置、YAML环境配置、SQL数据库脚本和Markdown说明文档整体仅148KB体积小巧而结构完整便于快速定位各功能模块。已有47人学习浏览适合需要快速搭建同类项目的开发者借鉴。资源内包含用户管理、设备管理、文件处理等后端服务代码以及前后端联调所需的配置与SQL初始化脚本能够帮助读者理解系统分层架构、算法集成方式并在此基础上进行摔倒识别算法的优化与功能扩展是一份兼顾工程实践与学术参考的完整样例。1. 室内老人摔倒检测系统一块摄像头画面背后的三层分工打开这个标题里的项目文件里面通常是一个标准的三层结构前端负责画面展示和告警提醒后端负责拉取摄像头视频流、调度算法并存储记录摔倒检测算法负责从每一帧画面里判断人是否跌倒。很多第一次做这个方向的人以为难点全在算法上实际真正花时间的反而是前端、后端和算法三个模块之间的联调。这个项目适合正在选毕设方向、或者课设需要快速出一个完整 Demo 的同学它能覆盖的姿态估计、视频流处理、前后端分离开发都是简历上能直接写的技术点。这个方向最反直觉的地方在于算法识别本身不难难的是把识别结果在几秒内可靠地送到监护人面前。2. 摔倒检测算法从 YOLOv8-pose 到摔倒判定逻辑的实现2.1 算法选型姿态关键点 vs 目标框宽高比课设该选哪条路室内摔倒检测的视觉方案业界和毕设里最常见的就两条路纯目标检测加几何判定或者姿态关键点加状态机判定。纯目标检测的做法是用 YOLOv8 这类模型检测出人的目标框然后算目标框的宽高比——人站立时框的高度明显大于宽度摔倒躺倒时框的宽度会超过高度所以宽高比可以作为摔倒的特征信号。这个方案实现非常快如果时间特别紧两天就能把主链路跑通。但实际场景里纯宽高比方案的误报率很难压下去。弯腰捡东西、蹲下系鞋带甚至坐到矮沙发上目标框的宽高比都会短暂超过阈值。姿态关键点方案则多一层信息YOLOv8-pose 或者 MediaPipe 可以给出鼻子、肩膀、髋部等 17 个关键点的坐标和置信度摔倒时鼻子和髋部中心点的垂直距离会急剧缩小髋部中心点的下落速度也会出现突变。这两个特征能把「摔倒」和「弯腰」区分开。从课设投入产出比看我最推荐 YOLOv8-pose。Ultralytics 的 pip 包装完就能用预训练权重自带CPU 也能跑关键点坐标直接挂在结果对象上不需要自己写复杂的后处理。下面这个表格是三个方案的粗对比方案推理速度误报率实现成本适合场景纯目标检测宽高比最快较高最低俯视固定机位、演示用YOLOv8-pose中中低中侧视/斜视、课设首选OpenPose 姿态估计慢低高科研、离线分析一句话总结选型逻辑不要一上来就上 OpenPose 这种重模型姿态估计模型对课设来说像个黑匣子权重体积大、推理慢动了参数还不好解释。YOLOv8n-pose 的权重只有十几 MB普通笔记本摄像头画面跑起来能稳定在 20fps 以上这在答辩演示时已经足够流畅。2.2 摔倒判定的核心逻辑宽高比突变、关键点下坠、连续帧确认算法工程里单帧判定永远是不靠谱的。摄像机画面本身有噪声姿态模型偶尔会出现关键点抖动老人弯腰或转身时目标框会瞬间变形。如果把每一帧的结果都当真一个 30 秒的视频能给你报十几次警。所以摔倒判定必须是一个状态机而不是一个简单的 if 判断。我一般会组合三个条件第一是目标框宽高比摔倒时宽度大于高度这个阈值通常取 1.2 左右第二是鼻子关键点与髋部中心点的垂直距离人站着时头在躯干上方这个距离接近一个身位摔倒躺平后鼻子和髋部基本在同一水平线上这个距离会缩到目标框高度的 0.4 倍以下第三是髋部中心点的下落速度摔倒是一个突发过程下坠速度远快于正常下蹲。三个条件同时满足时本帧才标记为疑似摔倒。之后还要做一个连续帧确认连续 5 帧以上都判定疑似摔倒才正式触发报警。触发了之后进入 10 秒冷却10 秒内不再重复报警。这套组合逻辑能挡掉大部分误报也是答辩时解释得清楚的亮点。2.3 代码实现YOLOv8-pose 推理加判定状态机下面这段代码是摔倒检测部分的核心。先写一个 FallState 类维护状态再写一个单帧判定函数两个文件拼接起来就是可运行的检测模块。import time import numpy as np from ultralytics import YOLO class FallState: def __init__(self, confirm_frames5, ratio_thresh1.2, cooldown10.0): self.confirm_frames confirm_frames self.ratio_thresh ratio_thresh self.cooldown cooldown self.hit_count 0 self.triggered False self.last_trigger_time 0.0 def update(self, is_fall, now): if self.triggered and now - self.last_trigger_time self.cooldown: return False if is_fall: self.hit_count 1 else: self.hit_count 0 self.triggered False if self.hit_count self.confirm_frames: if not self.triggered: self.triggered True self.last_trigger_time now return True return False def judge_fall(boxes, keypoints, conf_thresh0.5): if boxes is None or keypoints is None: return False for idx, box in enumerate(boxes): x1, y1, x2, y2 box w, h x2 - x1, y2 - y1 ratio w / (h 1e-6) kp keypoints[idx] kp_conf kp[:, 2] if kp_conf[0] conf_thresh or kp_conf[11] conf_thresh or kp_conf[12] conf_thresh: continue nose_y kp[0][1] hip_y (kp[11][1] kp[12][1]) / 2 head_to_hip abs(nose_y - hip_y) if ratio 1.2 and head_to_hip h * 0.4: return True return False model YOLO(yolov8n-pose.pt) fall_state FallState() def process_frame(frame): results model(frame, verboseFalse) if not results or results[0].keypoints is None: return False boxes results[0].boxes.xyxy.cpu().numpy() keypoints results[0].keypoints.data.cpu().numpy() is_fall judge_fall(boxes, keypoints) return fall_state.update(is_fall, time.time())逻辑说明judge_fall遍历画面中的每个人先检查鼻子和双髋三个关键点的置信度置信度不够的直接跳过防止模型在模糊帧上给出错误坐标。然后算宽高比和头髋距离两个条件都满足才返回 True。FallState.update负责计数确认和冷却process_frame是供后端循环调用的入口函数主循环每拿到一帧就调一次。参数说明confirm_frames5表示连续 5 帧疑似才触发按 25fps 的视频折算大约 0.2 秒既不会因为单帧抖动误报也不会让报警延迟到人难以接受。ratio_thresh1.2是宽高比阈值可根据摄像头安装角度微调俯视视角建议下调到 1.0。cooldown10.0是报警冷却时间避免同一场摔倒触发多次推送。2.4 参数调优置信度、跳帧间隔、视角适配YOLOv8-pose 的默认检测置信度是 0.25但那是对一般目标检测任务的经验值。摔倒检测场景里关键点坐标的稳定性比检测框的召回更重要所以我会把置信度阈值提到 0.5 左右。如果摄像头画面在傍晚光线差的环境降到 0.3 能避免漏检代价是有时会多出几个误报框。跳帧检测是另一个实用技巧。算法没必要在每一帧上都跑推理常见的做法是每 3 帧跑一次检测中间两帧沿用上一次的结果。因为摔倒本身是一个持续数秒的过程帧之间人的姿态不会突变跳帧对检测率影响很小但 CPU 占用能直接降一半以上。摄像头安装角度对阈值影响很大。侧装和平装时宽高比阈值 1.2 是可靠的如果是顶装俯视视角人摔倒后框的形状变化没那么剧烈我会把阈值降到 1.0同时放弃头髋距离判断改用肩部关键点连线和水平方向的夹角来判定。这部分的阈值调起来有点玄学建议用自己录的室内视频多跑几遍再定。3. 后端服务FastAPI 和 WebSocket 把算法结果推给前端的正确姿势3.1 后端技术栈为什么选 FastAPI 而不是 Flask 或 Java 后端这个项目的后端职责有三个接摄像头视频流、跑摔倒检测算法、把画面和告警推给前端。选型上 FastAPI 几乎是这个场景的标准答案。它原生支持异步 WebSocket写实时推送不需要额外引入 socket.io 之类的库自动生成 Swagger 文档答辩时打开/docs就能演示接口列表Pydantic 做参数校验也比 Flask 手动解析 JSON 省事得多。Flask 也能做但 WebSocket 要配 flask-socketio事件回调写多了之后代码会绕。至于用 Java 的 Spring Boot 做这个后端我觉得有点杀鸡用牛刀——光搭建环境和处理跨域配置就要多花两天而且答辩时老师追问「为什么用 Java 实现视频流推送」你很难给出比 FastAPI 更有说服力的理由。前后端分离的架构大家都很熟悉前端 Vue 起 8080后端 FastAPI 起 8000接口用 REST 加 WebSocket 就够了。3.2 视频流接入与算法进程调度RTSP 拉流、跳帧检测、队列缓冲室内摄像头绝大多数支持 RTSP 协议取流。后端拉流最直接的方式是用 OpenCV 的 VideoCapture但这里有一个非常容易翻车的点拉流和推理不能在同一个线程里顺序执行。如果网络出现抖动OpenCV 的 read 会阻塞住推理线程也跟着卡死前端画面就会停在最后一帧上看起来像死机。正确的做法是三个线程解耦主线程负责从摄像头读帧并把最新帧丢进一个容量很小的队列推理线程从队列取帧检测结果写回一个共享变量异步推流协程定时从共享变量取画面发给前端。队列容量设成 2 就够了满了直接丢旧帧因为视频流是实时画面旧帧没有保留价值。import asyncio import base64 import queue import cv2 from fastapi import FastAPI, WebSocket from fastapi.middleware.cors import CORSMiddleware app FastAPI() app.add_middleware( CORSMiddleware, allow_origins[*], allow_methods[*], allow_headers[*], ) frame_q queue.Queue(maxsize2) clients set() app.websocket(/ws/video) async def video_endpoint(ws: WebSocket): await ws.accept() clients.add(ws) try: while True: await ws.receive_text() except Exception: pass finally: clients.discard(ws) async def broadcaster(): while True: try: frame frame_q.get_nowait() except queue.Empty: await asyncio.sleep(0.03) continue ok, buf cv2.imencode(.jpg, frame, [cv2.IMWRITE_JPEG_QUALITY, 70]) if not ok: continue b64 base64.b64encode(buf).decode() for client in list(clients): try: await client.send_json({type: frame, data: b64}) except Exception: clients.discard(client) await asyncio.sleep(0.03)逻辑说明frame_q是帧缓冲队列broadcaster协程不断从队列取帧压缩成 JPEG 再 base64 编码通过 WebSocket 推给所有已连接的前端。之所以转成 JPEG 而不是直接发原始帧是因为原始 BGR 帧一张就有几十万字节浏览器也直接解析不了JPEG 质量压到 70一张 640x480 的画面大约 30KB前端秒开。参数说明IMWRITE_JPEG_QUALITY70是画质和带宽的平衡点实测 640x480 分辨率下质量低于 50 会出现明显色块高于 85 带宽翻倍但画质提升有限。asyncio.sleep(0.03)把推流帧率限制在约 30fps避免空转烧 CPU。frame_q maxsize2是刻意设置的小容量帧消费者跟不上时直接丢旧帧保证画面始终是「当前时刻」而不是越积越后的延迟画面。3.3 报警记录与历史查询SQLite 表设计和 REST 接口告警事件需要落库否则前端历史记录页就没有数据来源。这个项目用 SQLite 就足够了它是单文件数据库不需要单独装服务Python 标准库自带驱动答辩时把项目整个文件夹复制到另一台电脑也能直接跑。数据表只需要三个字段摄像头编号、报警帧保存路径、触发时间。报警帧的保存路径是很多人容易忽略的。前端告警弹窗弹出后用户想看现场到底发生了什么如果只有一条文字记录没有任何意义。报警触发时后端应该同时把当前帧保存成图片路径写进数据库。import sqlite3 import time from fastapi import FastAPI, HTTPException from pydantic import BaseModel DB_PATH fall_records.db def init_db(): conn sqlite3.connect(DB_PATH) conn.execute( CREATE TABLE IF NOT EXISTS alarm ( id INTEGER PRIMARY KEY AUTOINCREMENT, camera TEXT, frame_path TEXT, created_at REAL ) ) conn.commit() conn.close() class AlarmIn(BaseModel): camera: str cam1 frame_path: str app.post(/api/alarm) def create_alarm(item: AlarmIn): conn sqlite3.connect(DB_PATH) cur conn.execute( INSERT INTO alarm(camera, frame_path, created_at) VALUES(?, ?, ?), (item.camera, item.frame_path, time.time()), ) conn.commit() conn.close() return {ok: True, id: cur.lastrowid} app.get(/api/history) def get_history(limit: int 50): conn sqlite3.connect(DB_PATH) rows conn.execute( SELECT id, camera, frame_path, created_at FROM alarm ORDER BY created_at DESC LIMIT ?, (limit,), ).fetchall() conn.close() return [ {id: r[0], camera: r[1], frame_path: r[2], created_at: r[3]} for r in rows ]逻辑说明create_alarm由检测模块在触发报警时调用get_history给前端历史页面用。注意created_at用的是time.time()返回的 Unix 时间戳类型是 REAL前端拿到后直接乘 1000 就能喂给 JavaScript 的 Date 对象格式化不需要做字符串时区转换这种容易出 bug 的操作。参数说明limit50是历史记录接口的默认返回条数前端分页可以通过?limit200offset100的方式扩展。camera字段应对多路摄像头场景字段设计成 TEXT 而不是写死后面接第二路摄像头时表结构不用改。还需要随手加个init_db()的启动调用放在 FastAPI 的启动事件或主程序入口里都行。4. 前端可视化Vue3 实时画面与告警弹窗的对接细节4.1 前端工程结构Vue3 Vite 前后端分离的目录怎么摆前端最常见的搭法是 Vue3 加 Vite 加 Element Plus 组件库这套组合也是现在前后端分离实战里用得最多的。Element Plus 负责弹窗、表格、按钮这些界面组件自己只需要写业务逻辑。工程初始化用npm create vitelatest frontend -- --template vue即可然后按下面的目录组织代码。frontend/ ├─ src/ │ ├─ api/ # REST 接口封装比如 fetchHistory │ ├─ ws/ # WebSocket 连接与重连逻辑 │ ├─ views/ │ │ ├─ Monitor.vue # 实时监控页 │ │ └─ History.vue # 历史记录页 │ ├─ App.vue │ └─ main.js ├─ vite.config.js └─ package.json目录分 api 和 ws 两个模块是刻意的。api 目录里全是普通的 fetch 封装给历史记录页和告警列表用ws 目录单独封装 WebSocket 连接因为它的生命周期和普通 HTTP 请求不一样需要处理断线重连。Monitor.vue 只关心拿到画面对 canvas 绘制History.vue 只关心拉列表展示两个页面的状态不互相污染。4.2 实时画面不引重型播放器 SDK用 WebSocket 推 JPEG 帧渲染很多开发第一次接触视频画面第一反应是引一个前端播放器 SDK 或者接 HLS 流。这个方向在课设场景里是最容易踩坑的HLS 延迟在 5 到 10 秒级别老人摔倒报警等 10 秒才有画面体验已经不合格了WebRTC 倒是低延迟但信令服务器加 STUN/TURN 的配置工作量能顶半个项目。前后端已经用 WebSocket 连着最朴素的方案就是后端把画面帧压缩成 JPEGbase64 编码后通过 WebSocket 发过来前端拿到数据用 canvas 的drawImage画出来。延迟只有一帧的编码时间和网络传输时间在局域网内实测能控制在 200 毫秒以内。省掉了播放器 SDK 的集成和兼容性问题浏览器全系支持这才是这个场景里真正的前端安全技术操作——用最简单的技术栈做最可靠的事。// src/ws/monitor.js export function connectMonitor(canvas, onAlarm) { const ctx canvas.getContext(2d); let ws null; let retry 0; function build() { ws new WebSocket(ws://${location.host}/ws/video); ws.onmessage (ev) { const msg JSON.parse(ev.data); if (msg.type frame) { const img new Image(); img.onload () { canvas.width img.width; canvas.height img.height; ctx.drawImage(img, 0, 0); }; img.src data:image/jpeg;base64,${msg.data}; } else if (msg.type alarm) { onAlarm(msg); } }; ws.onclose () { retry 1; const delay Math.min(1000 * 2 ** retry, 15000); setTimeout(build, delay); }; } build(); }逻辑说明每收到一帧数据就new Image()并监听onload在图片加载完成后再绘制这样能确保上一帧画完才画下一帧canvas 上不会出现撕裂的半帧画面。canvas.width img.width这行是动态更新画布尺寸后端如果中途切换了分辨率前端不需要重启页面就能适配。参数说明retry变量维护重连次数每次断线后重连延迟按 2 的幂次递增1 秒、2 秒、4 秒这样封顶 15 秒。如果不做这个退避后端进程重启的那一瞬前端会疯狂发起 WebSocket 握手把服务端口打满。location.host让前端自动使用当前页面的域名和端口本地开发是localhost:8080部署到 Nginx 后自动变成服务器地址这套代码不需要改。4.3 告警状态与历史记录组件状态机与后端接口对接实时监控页需要一个简单的告警状态机正常、检测中、已告警、冷却中。后端推来的帧数据只负责画面更新而type: alarm的消息一旦到达前端要立刻做三件事——弹窗、响铃、调后端报警保存接口。弹窗用 Element Plus 的ElNotification组件提示音用 Web Audio API 实时生成一段蜂鸣声避免在项目里塞音频文件增加体积。// Monitor.vue 局部逻辑 import { connectMonitor } from ../ws/monitor.js; import { fetchHistory, saveAlarm } from ../api/index.js; const state { status: idle, alarmCount: 0 }; function onAlarm(msg) { state.status alarmed; state.alarmCount 1; saveAlarm({ camera: msg.camera, frame_path: msg.frame_path }); const ctx new AudioContext(); const osc ctx.createOscillator(); osc.connect(ctx.destination); osc.start(); setTimeout(() { osc.stop(); ctx.close(); }, 600); }逻辑说明saveAlarm调用第 3.3 节的POST /api/alarm把告警事件落库。Web Audio API 生成的蜂鸣声在多数浏览器上不需要用户手势之外的授权比引用 mp3 文件省事得多也不会遇到文件加载失败的问题。历史记录页与GET /api/history对接按时间倒序渲染表格每条记录附一张报警帧缩略图点开可以看大图。5. 前后端联调与部署5 个容易翻车的坑和排查路径5.1 CORS 跨域前端 8080 调后端 8000 连环报错的解法现象浏览器控制台大量刷blocked by CORS policy前端页面拿不到任何接口数据但后端日志里明明能看到请求进来了。原因前端跑在 8080后端跑在 8000协议、域名、端口三者中任意一个不同就是跨域。浏览器默认阻止跨域读取响应后端不加跨域头就直接拦截。解决FastAPI 加CORSMiddleware把允许来源配成前端地址。开发环境图省事可以allow_origins[*]但这个配置不允许与allow_credentialsTrue同时使用否则会报错。部署后如果走 Nginx 同域反代跨域自然消失中间件开着也不碍事。排查时先用 curl 带Origin头请求后端看响应里有没有Access-Control-Allow-Origin能快速定位是后端没配还是 Nginx 挡了。5.2 RTSP 取流卡顿花屏OpenCV 的坑与 FFmpeg 拉流方案现象画面每隔几秒出现绿屏或马赛克严重的直接卡住不动几十秒后才恢复。原因OpenCV 的 VideoCapture 默认用 UDP 方式拉 RTSP 流UDP 丢包不重传画面一丢包就花。另外很多摄像头默认码流是 1080p 主码流OpenCV 直接拉主码流解码压力大帧率反而上不去。解决第一URL 里强制指定 TCP 传输cap cv2.VideoCapture(rtsp://192.168.1.100:554/stream1, cv2.CAP_FFMPEG)然后cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)。第二优先拉子码流一般摄像头厂商会把低分辨率流放在/stream2路径分辨率降到 640x480解码开销直接降一个量级。如果 OpenCV 还是不稳定起一个 FFmpeg 子进程拉流转成本地数据管道是更稳的方案但毕设场景先试 TCP 加子码流九成问题能解决。5.3 弯腰捡东西误报判定逻辑加静置条件现象老人在客厅弯腰捡遥控器画面里目标框宽高比超过阈值系统直接报警一晚上能误报十几次。原因单帧几何条件里弯腰时框的形状变化和摔倒前期很像。只靠宽高比和头髋距离区分不了「弯腰捡东西」和「摔倒躺平」。解决加两个约束条件。第一个是髋部中心下落速度摔倒时髋部在 0.5 秒内下降几十像素弯腰时下移速度明显慢。第二个是头髋距离的绝对值弯腰时头和髋部还保持相当距离躺平时鼻子和髋部基本在同一水平线head_to_hip会小于框高的 0.3 倍。我在实测里把阈值从 0.4 调到 0.3弯腰误报基本清零。另外冷却时间一定要有不然一次弯腰动作触发报警后老人站起来的过程中又会触发第二次。5.4 算法进程崩溃后前端黑屏心跳重连与看门狗现象推理进程因为显存不足或未知异常退出后前端画面定格在最后一帧刷新页面也没用后端进程已经没了。原因代码里没有异常兜底模型推理线程一旦抛异常整个进程退出WebSocket 服务也随之断开。解决前端已经实现了指数退避重连第 4.2 节代码断线后会自动尝试恢复。后端这边用 supervisor 或 systemd 监控进程崩溃后自动拉起拉起后 WebSocket 端口重新监听前端重连自然成功。另外可以在后端加一个心跳协程每 10 秒检查一次推理线程是否还活着死了就自动重启并在日志里记录重启原因。这三层兜底加起来演示现场才不会出现画面黑屏的尴尬。5.5 从若依这类前后端分离模板改来登录鉴权把视频接口拦了现象直接套用若依等管理后台模板页面能打开但视频画面一直出不来WebSocket 握手反复失败报 401。原因这些模板自带登录鉴权和路由守卫后端把/api路径保护了但 WebSocket 握手请求没有走常规 HTTP 鉴权头被安全过滤器直接拦截。很多同学的前端开发是在模板基础上改的模板的权限拦截逻辑改得不够彻底就会这样。解决把视频流和告警相关的路径加到后端的匿名白名单里比如/ws/**和/api/alarm放行。如果项目不需要多用户登录干脆在改造时直接把登录过滤器的视频相关路径排除掉。部署到 Nginx 时需要额外注意/ws路径要配proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection upgrade否则 Nginx 会把升级协议降级成普通 HTTP 请求前端会一直卡在握手阶段。6. 从「能跑」到「好不好用」效果自测、报警推送与数据闭环系统跑通主链路只是第一步衡量这个项目质量的是两个数字漏报率和误报率。做效果验证时先拿公开数据集跑一轮召回率再拿自录视频跑误报率。摔倒检测领域常用的公开数据集有 UR Fall Detection 和 Le2i按时间戳逐帧标注判断报警触发时刻在跌倒发生后 5 秒内算命中没有跌倒却触发的次数除以测试时长就是误报率。自测时我习惯录三段视频分别是正面视角、侧面视角和斜 45 度视角每段至少 5 分钟动作覆盖走路、坐下、弯腰、下蹲和一次模拟摔倒这样测试结果才有说服力。误报率压不住时不要盲目调模型权重。更直观的做法是把每次报警的帧保存成图片攒一周后人工过一遍观察误报集中在什么场景。如果全是弯腰动作就把head_to_hip阈值往下调如果集中在光线昏暗的时段就调高关键点置信度阈值并让更多低置信度帧进入计数确认逻辑。这个收集误报样本、调阈值、再验证的循环就是最简单的数据闭环也是答辩时能讲出深度的加分点。报警推送也可以再接一层让系统在无人盯守时也能通过钉钉群机器人通知家属。这个功能代码量只有几行但对「室内老人摔倒检测系统」这个命题来说实用价值提升明显。import requests def push_dingtalk(webhook_url, alert_text): data {msgtype: text, text: {content: alert_text}} try: requests.post(webhook_url, jsondata, timeout3) except Exception as e: print(fpush failed: {e})逻辑说明钉钉群机器人本质就是一个 Webhook 地址requests.post推一条 JSON 过去群内全员收到通知。timeout3是必须加的——推送服务是外部依赖不能因为它的超时拖垮本地的报警主流程。我自己做这个项目时最先只调宽高比阈值就看到了不错的检测率当时挺得意结果拿到真实客厅环境一测被走廊的扫拖一体机和老人弯腰拿遥控器触发了一堆误报。后来老老实实把关键点距离约束和连续帧确认加进去误报才压下来。所以这个项目最后得分高低通常不在模型多先进而在误报率和报警延迟这两个数字好不好看。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进