
简介Android端实时跌倒检测与识别Demo面向移动端AI开发者或安全监护类应用开发者可用于养老看护、独居老人安全监控、公共场所异常行为预警等场景解决将YOLOv5人体检测能力落地到手机端的工程参考问题。资源包总计2个文件主要包括可直接安装到手机的APK调试包及其对应的构建信息JSON文件APK用于真机运行与实时检测体验JSON则记录构建环境便于技术核对压缩包整体约50.44MB体积精简便于下载使用。已有1945人学习下载适合需要快速跑通跌倒检测流程的开发者。APK可直接安装到Android设备进行实时跌倒识别测试感受摔倒检测的实际效果配套作者博客系列还分别覆盖了跌倒检测数据集获取、YOLOv5训练实现、Android源码解读以及C端实现可作为从模型训练到多端部署的完整参考路径。1. 跌倒检测识别 Android Demo先弄清它能识别什么、不能识别什么如果你手头的需求是“给家人手机做个跌倒提醒”最稳的起步方式就是先拿一个 android 跌倒检测 Demo 跑通闭环——从传感器读取到摔倒识别再到通知报警。这个「跌倒检测识别 Android Demo」解决的正是这条链路采集加速度计和陀螺仪数据用状态机判断跌倒识别事件触发通知与震动。它不是训练好的花哨模型而是一个能让你在真机上感觉到“跌倒识别和摔倒识别到底怎么触发”的工程基线。适合想把行为识别快速落到手机端的 Android 开发者也适合做看护类应用的人拿它当测试基准。要说清楚的是它帮你判断“是不是摔了”不负责分析“为什么摔”。2. 传感器选型与识别原理为什么加速度计比摄像头更适合跌倒检测2.1 三条技术路线摄像头、可穿戴与手机内置传感器做跌倒识别第一件事是选信号来源。摄像头方案依赖人形检测和姿态估计算法室内光线不足、遮挡、隐私问题都很麻烦而且摄像头一直开着功耗和后台保活都是坎。可穿戴设备把传感器贴在身上信号质量好跌倒识别也准但你要给用户发额外硬件大多数人不会为尝鲜去额外买一只。手机内置传感器方案的优势是它复用用户每天都带在身上的设备加速度计和陀螺仪随系统采样不需要额外硬件也方便以后做远程报警。拆这个 Demo 时我印象最深的一点是它没有走“收集海量视频训练模型”的路子而是先用手机传感器把事件检测出来。这样做的好处是传感器耗电小、响应快数据是数值而不是画面天然比摄像头方案更容易过隐私这一关。所以如果你要做的只是一个“提醒有人可能摔了”的预警层手机传感器往往比摄像头更实用。等检测到跌倒事件再联动摄像头确认现场这样能兼顾功耗和隐私。2.2 加速度计与陀螺仪的分工合加速度、姿态角与冲击检测加速度计负责测量手机三个轴上的加速度静止时只感受到重力。这里的关键指标是合加速度 magnitude计算公式是sqrt(ax*ax ay*ay az*az)。站立或端坐时合加速度接近重力加速度约 9.8 m/s²如果手机突然自由落体比如从裤袋掉出合加速度会降到接近 0身体撞击地面时合加速度会在极短时间里冲到 20 m/s² 以上。这三个特征分别是“失重、撞击、恢复静止”的信号。陀螺仪输出的是各轴角速度用来补充旋转信息。比如人往侧面摔时手机会快速翻转角速度出现明显突起。有些机型还有气压计跌落时高度变化会导致气压跳变但气压计在部分 Android 设备上被裁剪掉了所以不适合做唯一依据。这份 Demo 的常见做法是加速度计为主陀螺仪为辅必要时用重力方向计算手机姿态角判断人是不是从直立变成平躺。信号跌倒特征典型参考阈值合加速度失重瞬间接近 0 2.0 m/s²合加速度撞击瞬间突变 20.0 m/s²姿态角手机从直立接近水平倾斜角 60°角速度身体快速翻转任一角速度 5 rad/s注意阈值不是死值跑步、跳跃、上下楼都会带来类似的加速度突跳所以必须有后面的多阶段状态机把关。2.3 Demo 的状态机判定逻辑失重、撞击、静止三阶段确认如果把“合加速度突变”当成唯一判据走路、拍口袋、蹦跳都容易误报。这个 Demo 把一次跌倒拆成三个阶段来确认顺序是先失重、再撞击、最后静止。第一阶段是失重加速度模值小于 2.0 m/s² 并持续约 200ms此时手机处于近似自由落体状态。如果这个过程中任何一帧加速度回到 9.8 附近就说明只是一次甩手或下蹲状态复位。第二阶段是撞击失重结束后的几百毫秒内加速度模值快速超过 20.0 m/s²。这里要注意跑步落地也能达到 15~18但不一定先有明显失重所以状态机要求“必须先经过失重才承认撞击”这个顺序本身就是一道过滤器。第三阶段是静止确认撞击之后如果合加速度在 1.5 秒左右恢复到 9.8 ± 2.0 之间说明人摔到地面并且暂时不动了这时才确认跌倒触发通知和震动。加上静止确认是为了过滤掉“摔下去马上爬起来继续走”的情况——那种场景大概率不是需要报警的跌倒。完整数据流可以看成传感器回调每次拿到一帧原始值先做简单低通滤波再计算合加速度与姿态角然后喂给状态机。低通滤波可以用output alpha * raw (1 - alpha) * outputalpha 取 0.3~0.5 即可太重会抹掉撞击尖峰太轻则保留噪声。状态机里还埋了一个姿态变化条件如果撞击后检测到手机接近水平也就是重力方向在设备坐标系 Z 轴的分量变小到接近 0置信度会更高。遇到扶着墙慢慢坐下来的情况失重阶段根本不会触发也就不会误报。如果你后续想替换成机器学习模型也可以保留这个状态机做第一层粗筛再让模型做细判这样推理频率不用拉满对功耗更友好。常见动作合加速度特征是否能通过三阶段正常走路偶尔出现 1.5~2.5 波动通常不能失重段不够长跑步落地冲击可到 15~18无明显失重不能顺序不对弯腰捡东西失重短暂且伴随晃动不能姿态基本直立从站到侧摔明显失重 撞击 静止能触发报警3. 把工程跑起来从 Android Studio 导入到真机权限配置3.1 导入前准备SDK 版本、构建工具与模拟器限制拿到 Demo 后别直接双击 APK先用 Android Studio 打开工程。常见做法是选择Open an Existing Project定位到解压目录下的根build.gradle等 Gradle Sync 跑完。这里第一个坑是版本如果 Demo 用的 Gradle 插件比较老新版 Android Studio 会提示升级建议先点“暂不升级”否则升级牵连到 AGP 和 Kotlin 版本排查起来很花时间。编译要求一般是minSdkVersion 21以上targetSdk看你手里的 Demo 而定。minSdk 过低会在大屏或低版本机上有兼容问题过高则要处理分区存储和通知权限。我一般会在app/build.gradle里把targetSdk对齐到当前要发布的应用市场要求然后按这个版本去补运行时权限。模拟器基本上跑不出真实效果因为它没有加速度计传感器getDefaultSensor通常会返回null。所以验证跌倒识别一定要用真机。如果手里的真机是 Android 13 及以上记得在运行前先授权通知权限否则后面报警不会弹。注意权限声明只写了POST_NOTIFICATIONS还不够它在 Android 13 上是运行时权限必须在页面里动态申请否则通知会被系统静默拦截。权限配置在AndroidManifest.xml里按需声明uses-permission android:nameandroid.permission.WAKE_LOCK / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / uses-permission android:nameandroid.permission.VIBRATE /说明一下WAKE_LOCK是防止屏幕熄灭后 CPU 休眠导致传感器停更FOREGROUND_SERVICE是让检测服务常驻POST_NOTIFICATIONS是 Android 13 引入的运行时通知权限VIBRATE用于报警震动。如果 Demo 里没写FOREGROUND_SERVICE_TYPE较新 SDK 可能要求补上比如android:foregroundServiceTypespecialUse。3.2 传感器数据采集代码注册 SensorManager 与回调缓冲传感器数据通过SensorManager注册监听。为了让状态机有稳定的采样节奏我一般用SENSOR_DELAY_GAME这个档位在多数设备上约 50Hz每帧间隔 20ms 左右既能满足跌倒特征捕捉又不会像SENSOR_DELAY_FASTEST一样把 CPU 拉满。private SensorManager sensorManager; private long lastSampleTime 0L; private static final long SAMPLE_INTERVAL_MS 20L; sensorManager (SensorManager) getSystemService(Context.SENSOR_SERVICE); Sensor accelerometer sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (accelerometer null) { // 没有加速度计就不支持跌倒检测降级处理 return; } sensorManager.registerListener(sensorEventListener, accelerometer, SensorManager.SENSOR_DELAY_GAME);注意这里没有注册陀螺仪因为陀螺仪全速采样很耗电先用加速度计验证主流程等要接入模型时再把陀螺仪加进去。SAMPLE_INTERVAL_MS是手动限频部分设备即使声明了SENSOR_DELAY_GAME回调频率也可能高于标称值在onSensorChanged里做时间戳过滤能保证状态机看到的数据节奏稳定。3.3 检测与报警代码状态机实现、通知与震动回调里先把数据交给检测器Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() ! Sensor.TYPE_ACCELEROMETER) return; long now System.currentTimeMillis(); if (now - lastSampleTime SAMPLE_INTERVAL_MS) return; lastSampleTime now; fallDetector.feed(event.values[0], event.values[1], event.values[2], now); }状态机核心用枚举切换。下面这段是简化版把前面说到的三阶段落成代码public void feed(float ax, float ay, float az, long timestamp) { double magnitude Math.sqrt(ax * ax ay * ay az * az); switch (state) { case NORMAL: if (magnitude FREEFALL_THRESHOLD) { freefallStartTime timestamp; state State.FREEFALL; } break; case FREEFALL: if (timestamp - freefallStartTime FREEFALL_DURATION_MS magnitude FREEFALL_THRESHOLD) { state State.WAITING_IMPACT; } else if (magnitude FREEFALL_THRESHOLD) { state State.NORMAL; } break; case WAITING_IMPACT: if (magnitude IMPACT_THRESHOLD) { impactTime timestamp; state State.RECOVERING; } else if (timestamp - freefallStartTime STAGE_TIMEOUT_MS) { state State.NORMAL; } break; case RECOVERING: if (timestamp - impactTime VERIFY_STILL_MS magnitude NEAR_GRAVITY_LOW magnitude NEAR_GRAVITY_HIGH) { state State.CONFIRMED; onFallDetected(); } else if (timestamp - impactTime STAGE_TIMEOUT_MS) { state State.NORMAL; } break; case CONFIRMED: // 报警后进入死区避免重复触发 if (timestamp - impactTime ALARM_COOLDOWN_MS) { state State.NORMAL; } break; } }几个参数建议放在单独的常量类里FREEFALL_THRESHOLD用 2.0FREEFALL_DURATION_MS用 200~300IMPACT_THRESHOLD用 20.0VERIFY_STILL_MS用 1500~2000NEAR_GRAVITY_HIGH/LOW用 12.0 和 7.0STAGE_TIMEOUT_MS用 3000。这里最需要注意的是WAITING_IMPACT阶段如果在失重后迟迟等不到撞击就说明也许只是把手机往上抛必须超时复位否则状态机卡死。确认跌倒后发通知加震动。这段是 Android 13 以下会用的写法高版本要额外检查通知权限NotificationManager manager getSystemService(NotificationManager.class); if (Build.VERSION.SDK_INT 26) { NotificationChannel channel new NotificationChannel( fall_alert, 跌倒报警, NotificationManager.IMPORTANCE_HIGH); manager.createNotificationChannel(channel); } NotificationCompat.Builder builder new NotificationCompat.Builder(this, fall_alert) .setSmallIcon(R.drawable.ic_fall_alert) .setContentTitle(检测到跌倒) .setContentText(请确认是否需要帮助) .setPriority(NotificationCompat.PRIORITY_MAX); notificationManager.notify(FALL_NOTIFY_ID, builder.build()); Vibrator vibrator (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); vibrator.vibrate(new long[]{0, 500, 300, 500}, -1);注意震动和通知只是最低限度的本地提醒。做正式产品时还要把确认事件写入本地日志或通过回调发送到服务器方便家属和平台联动。4. 避坑指南跌倒检测 Demo 常见的 5 个翻车现场4.1 传感器失效与系统吞权限现象、原因与解决现象一模拟器上打开就闪退。原因是模拟器没有加速度计getDefaultSensor返回null代码没判空就去注册监听。解决注册前先判断传感器是否存在不存在就提示“当前设备不支持跌倒检测”别硬启动。这类问题通常在第一次真机 Debug 时遇到分不清是权限问题还是传感器问题先用一行 logcat 把getSensorList的结果打出来是最快的定位办法。ListSensor accelSensors sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); if (accelSensors.isEmpty()) { // 不支持跌倒检测禁用相关按钮 return; }现象二Android 13 真机上通知不弹但代码没报错。原因是POST_NOTIFICATIONS属于运行时权限只在 Manifest 声明还不够需要在启动时动态申请否则notify()会被系统静默忽略。解决在 MainActivity 的onCreate里调用requestPermissions拿到授权后再进入检测页面同时检查通知开关。if (Build.VERSION.SDK_INT 33 checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) ! PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 2001); }现象三屏幕熄灭几个小时后检测完全失效。原因是应用没有跑前台服务也没有持 WakeLock系统休眠后传感器回调被冻结。解决把传感器监听放到前台服务里设置android:foregroundServiceType并在onCreate里获取PARTIAL_WAKE_LOCK还要引导用户打开忽略电池优化否则国产 ROM 的省电策略仍然会杀掉进程。Override public void onCreate() { PowerManager pm (PowerManager) getSystemService(Context.POWER_SERVICE); wakeLock pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, FallApp:WakeLock); wakeLock.acquire(); } Override public void onDestroy() { if (wakeLock ! null wakeLock.isHeld()) { wakeLock.release(); } }4.2 误报与漏报并存现象、原因与解决现象四弯腰捡钥匙、快速下蹲甚至拍桌上的手机都会误报。原因是阈值只认加速度模值忽略整个时序和姿态。下蹲时也会有失重段和落地冲击但手机没有从直立变成水平。解决状态机增加两重约束失重必须超过 200ms撞击后手机姿态接近水平才算确认。姿态角可以用重力向量在 Z 轴的分量算double gz az / magnitude; double tiltAngle Math.acos(Math.abs(gz)); if (tiltAngle Math.toRadians(60.0)) { // 手机接近水平跌倒置信度更高 }现象五报警延迟 4 秒以上或者一条报警没处理完又触发第二条。原因是静止确认窗口设定太长且没有报警死区与超时复位。解决VERIFY_STILL_MS设成 1500~2000ms确认后进入 30 秒冷却期冷却期内不再触发新报警在状态机每一阶段都加上超时复位防止卡死。另一个技巧是不要在SensorEventListener里直接写文件或做网络请求这些耗时操作会阻塞回调也会拉高延迟事件确认后把动作丢到单线程 Handler 里处理。case CONFIRMED: if (timestamp - impactTime ALARM_COOLDOWN_MS) { state State.NORMAL; } break;5. 提升识别准确率从阈值调参到替换成 TFLite 模型5.1 阈值参数调优灵敏度与误报的平衡把 Demo 跑通后第一步不是急着找模型而是先把手里的参数调到能过你的场地测试。我一般在调参前定义好两组动作一组是正常行为走动、慢跑、弯腰、坐下、跳一下另一组是模拟跌倒向前倒、向后倒、侧倒、从沙发上滑落。然后跑几轮统计误报数和漏报数。几个关键参数的调整逻辑参数调高时的效果调低时的效果FREEFALL_THRESHOLD更容易进入失重态对甩动手臂更敏感不易失重漏报下降误报也会减少FREEFALL_DURATION_MS需要有更长的失重时间过滤瞬时抛掷失重刚出现就进入撞击等待容易把快速下蹲当跌倒IMPACT_THRESHOLD要求更强烈的撞击漏报可能增加容易把跳跃或跺脚识别为撞击VERIFY_STILL_MS要求静止更久降低误报但延迟变高忽略“摔后不动”这一特征爬起来也会报警实际操作中我会先固定FREEFALL_DURATION_MS250和VERIFY_STILL_MS1800主要调两个阈值直到自己的场地测试通过。如果某类动作频繁误报就单独记录它的传感器曲线看看是失重段不明显还是撞击幅度不够对症去调参数而不是无脑把阈值往上推。5.2 接入 TFLite 模型训练后转格式与 Interpreter 调用阈值方案毕竟手调上限有限。等你有了一批标注数据可以把模型替换成 TensorFlow Lite。常见做法是先用 LSTM 或一维 CNN 处理 3 秒窗口的六轴序列输出跌倒/非跌倒两类概率。训练环境里把模型导出成 SavedModel再转成 tflite。下面这段是在 Python 一侧做转换的常用写法import tensorflow as tf converter tf.lite.TFLiteConverter.from_saved_model(fall_model_saved) converter.target_spec.supported_types [tf.float16] converter.optimizations [tf.lite.Optimize.DEFAULT] tflite_quant_model converter.convert() with open(fall.tflite, wb) as f: f.write(tflite_quant_model)说明target_spec.supported_types指定 FP16 半精度模型体积大约减半普通手机 CPU 和 GPU 都能跑精度损失一般可以接受。如果想把体积压得更小可以改成 INT8 量化但需要准备校准数据集而且部分老旧设备对 INT8 算子的支持不如 FP16 好。Android 端把fall.tflite放进assets目录加载和推理用Interpretertry (Interpreter tflite new Interpreter(loadModelFile(this, fall.tflite))) { float[][][] input new float[1][WINDOW_FRAMES][6]; float[][] output new float[1][2]; tflite.run(input, output); float score output[0][1]; if (score 0.8f stateMachine.allowsAlert()) { notifyFall(); } } catch (IOException e) { Log.e(FallModel, model load failed, e); }这里WINDOW_FRAMES对应 3 秒窗口乘以 50Hz也就是 150 帧。输入矩阵的最后一维是[ax, ay, az, gx, gy, gz]。喂给模型之前最好按训练时用的均值和标准差做归一化否则不同手机的传感器量程差异会直接拉低准确率。注意Interpreter是线程不安全的如果检测服务里同时有多个入口调用它最好加锁或复用单例。5.3 自制跌落数据录制 CSV 样本并快速验证没有现成数据集时自己录样本是最快的路径。我一般会让被测者把手机放在裤袋或随身包里然后按动作顺序录制。录数据代码写得很简单把每次回调追加到 CSV 文件File file new File(getExternalFilesDir(null), fall_samples.csv); try (FileWriter writer new FileWriter(file, true)) { String line String.format(%d,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f%n, System.currentTimeMillis(), ax, ay, az, gx, gy, gz); writer.write(line); } catch (IOException e) { Log.e(CsvRecorder, write failed, e); }写 CSV 时不要把动作标记行插在传感器数据行中间因为保存数据本身会带来几十毫秒延迟跨线程写文件还会打乱时序。更好做法是先开一个独立事件回调每个动作开始前插入一行带时间戳的标记数据采集完再做对齐。录制时每个动作重复 5~8 次跌倒动作要控制好落点最好在垫子或床上做别拿真马路去拼。录完以后用导出的 CSV 跑离线回放比反复真机测试快得多。你可以在电脑上写个小脚本把同样一份 6 轴数据按 20ms 一帧喂给状态机输出检测到的事件时间再对照标注判断漏报和延迟。这个流程会比每次插真机、找姿势快很多。6. 验证与交付给看护场景做一个最小可用版本6.1 交付前跑一遍「半小时烟雾测试」这里我要推荐一个每次都用的验证方法交付前固定跑半小时烟雾测试。测试用例要覆盖最容易翻车的几个动作而不是只在手里颠两下。测试动作预期结果正常走路 / 慢跑不触发报警弯腰捡东西 / 蹲下再站起不触发报警坐沙发时快速后仰可能触发需人工确认向前跌倒 / 向后跌倒触发报警延迟 2s摔倒后立刻坐起来不触发或延迟报警测试时用adb logcat把检测服务日志导出来方便回看adb logcat -s FallDetector -v time fall_run.log跑完之后看两样东西一是该触发的有没有触发二是误报率是不是高到会让人关掉通知。如果误报太高宁可把灵敏度降一点。6.2 给误报留一个「后悔药」按钮看护场景里误报对老人的打扰比漏报对家属的惊吓更伤人。所以我会在确认跌倒后弹一个 10 秒倒计时通知用户不需要时说“我没事”通知和震动立刻撤掉。这样即使传感器把一次坐空当成了跌倒也能在造成恐慌之前被取消。new CountDownTimer(10000, 1000) { Override public void onFinish() { // 保持报警并推送给家属端 } Override public void onTick(long millisUntilFinished) { updateNotification(millisUntilFinished); } }.start();倒计时结束才推送给现场用户留出反应时间也避免通知刷屏。我从第一次做模拟项目X的跌倒检测翻车之后养成了一个习惯任何阈值或模型改动先跑同一组录好的样本做离线验证再上真机。这能帮你把“感觉应该好了”和“真的好了”分开。希望这套验证流程也能帮到你。本文还有配套的精品资源点击获取