资讯动态

Android跌倒检测Demo:基于加速度计与状态机的老人摔倒识别方案

发布时间:2026/10/17 12:25:05 来源:尧图企业网站定制
简介面向Android开发者的跌倒检测识别Demo压缩包解决移动端实时跌倒和摔倒检测与告警问题适用于老人看护、独居监护、安防巡检等场景适合具备Android或深度学习基础的开发者快速接入相关能力。压缩包共两个文件主体是release版APK安装包可直接安装到设备体验实时识别效果另附一个JSON元数据文件记录构建信息整体约五十MB。目前已有1945人学习下载即可获得一个无需编译的开箱即用Demo能快速验证跌倒检测功能。该Demo基于YOLOv5目标检测方案实现可在手机端识别跌倒姿态配套系列博客还提供跌倒检测数据集与训练代码下载指引帮助读者完整走通数据训练、模型优化到端侧部署的链路通过该Demo可以快速评估模型效果并为自建跌倒识别App提供参考架构。1. 老人独自在家摔倒没人知道这个 Android Demo 就是干这个的家里老人独自在客厅活动从摔倒到被发现往往超过半小时独居年轻人洗澡滑倒手机就在一米外却拿不到。这个「跌倒检测识别 Android Demo」解决的问题很具体利用手机内置的加速度计以约 50Hz 频率采集三轴加速度通过滑动窗口实时计算合加速度变化捕捉摔倒瞬间的冲击、失重与后续静止特征触发弹窗报警、震动提示并写入本地日志。它作为一套 android 跌倒检测入门工程可以直接导入 Android Studio 编译运行同时把跌倒识别算法独立封装成检测器模块适合做健康监测、老人看护类应用的开发者拿去做二次开发。对新手来说它是一条完整的传感器采集、特征计算、阈值判定链路对熟手来说它是可以替换算法模型和报警策略的现成骨架。2. 工程结构与数据链路一个检测项目从界面到传感器的完整骨架2.1 项目目录检测器、数据采集器和界面各自待在哪这个 Demo 的工程结构不算复杂但分层很清晰。拿到解压后的项目首先会看到 app 模块下的标准 Android 工程布局核心代码都集中在 java 目录里。我一般会把这样的项目按职责划成三层界面层负责启动检测和展示报警结果采集层负责向传感器要数据算法层负责从原始数据里判断是否发生跌倒。app/src/main/ ├── java/com/example/fall_detection/ │ ├── MainActivity.java // 入口界面启动/停止检测、显示状态 │ ├── sensor/ │ │ ├── SensorDataCollector.java // 传感器采集器注册监听、回调原始数据 │ │ └── FallDetector.java // 跌倒检测器窗口缓冲、特征计算、判定逻辑 │ └── ui/ │ ├── AlertActivity.java // 报警界面全屏提醒、震动、日志写入 │ └── AlertDialogHelper.java // 弹窗辅助类封装报警展示逻辑 ├── res/ │ ├── layout/ │ │ ├── activity_main.xml // 主界面布局按钮、状态文字 │ │ └── activity_alert.xml // 报警界面布局大字提醒、停止按钮 │ └── values/ │ ├── strings.xml │ └── colors.xml └── AndroidManifest.xml // 权限声明、Activity 注册这个包结构有一个值得借鉴的点FallDetector 不持有任何 Android 上下文它只接收 float 数组、输出布尔结果。这样设计的好处是算法可以脱离手机单独测试——想验证判定逻辑直接写一个 Java main 方法喂数据就行不用启动模拟器。采集器和检测器之间的调用关系也很简单SensorDataCollector 在 onSensorChanged 回调里拿到三轴加速度转成 float 数组交给 FallDetector 的 addSample 方法检测器内部维护窗口返回结果后由 MainActivity 决定是否拉起报警界面。2.2 传感器采集链路SensorManager 里那趟 50Hz 的数据管线整个 Demo 的数据源头是 SensorManager 注册的加速度计监听。这段代码决定了后续所有算法的输入质量频率选低了漏检选高了耗电所以通常取 SENSOR_DELAY_GAME 这个档位大约每 20 毫秒回调一次也就是 50Hz。两秒的窗口正好需要 100 个采样点这个数字在后面对应缓冲区的长度。public class SensorDataCollector implements SensorEventListener { private static final int SAMPLING_RATE SensorManager.SENSOR_DELAY_GAME; private SensorManager sensorManager; private Sensor accelerometer; private OnSensorDataCallback callback; public interface OnSensorDataCallback { void onAccelerationData(float x, float y, float z, long timestamp); } Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() Sensor.TYPE_ACCELEROMETER) { float x event.values[0]; // 手机坐标系 X 轴加速度单位 m/s² float y event.values[1]; // 手机坐标系 Y 轴加速度 float z event.values[2]; // 手机坐标系 Z 轴加速度 long timestamp event.timestamp; callback.onAccelerationData(x, y, z, timestamp); } } public void start() { sensorManager.registerListener(this, accelerometer, SAMPLING_RATE); } public void stop() { sensorManager.unregisterListener(this); } }这段代码里有几个参数值得留意。SENSOR_DELAY_GAME 在大多数机型上对应 20ms 左右的回调间隔但部分 ROM 在息屏或省电模式下会把这个间隔拉长到 50ms 以上后面避坑章节会专门说这个问题。timestamp 用的是事件自带的时间戳而不是 System.currentTimeMillis()因为纳秒级时间戳在做插值和窗口对齐时更可靠。回调接口单独定义而不是直接把 FallDetector 传进来是为了后续接 TensorFlow Lite 替换算法时不动采集层代码。2.3 构建配置与权限跑起来前要确认的三个点Demo 项目使用的构建配置很克制没有引入重型依赖。build.gradle 里通常只需要一个 appcompat 支持库和一个 constraintlayout传感器相关 API 属于 Android 框架自带不需要额外依赖。这在导入工程时是个优点——同步速度快不容易因为网络问题卡在依赖下载上。需要重点检查的是 SDK 版本配置我一般会确认 compileSdk 和 targetSdk 保持在本地已安装的 SDK 版本附近否则会提示下载缺失平台。android { namespace com.example.fall_detection compileSdk 33 defaultConfig { applicationId com.example.fall_detection minSdk 21 targetSdk 33 versionCode 1 versionName 1.0 } } dependencies { implementation androidx.appcompat:appcompat:1.6.1 implementation androidx.constraintlayout:constraintlayout:2.1.4 }AndroidManifest 里的权限声明是整个 Demo 跑通的关键。加速度计属于普通权限不需要运行时申请但两个隐性的权限要注意一是报警时震动需要声明 VIBRATE二是 SDK 33 及以上弹通知需要在运行时申请 POST_NOTIFICATIONS 权限否则报警声音能响但通知栏看不到消息。常见做法是直接在 MainActivity 的 onCreate 里统一检查这两个权限避免报警触发了用户却毫无感知。uses-permission android:nameandroid.permission.VIBRATE / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / application activity android:name.MainActivity / activity android:name.ui.AlertActivity android:exportedfalse android:showOnLockScreentrue / /applicationshowOnLockScreen 这个属性值得专门说一句。跌倒场景下用户很可能已经倒地手机可能已经锁屏报警界面设置成可以在锁屏上显示能最大限度让周围人看到提醒。这个属性在部分国产 ROM 上会被忽略但在原生系统上实测有效。如果需要语音播报还要额外处理音频焦点Demo 阶段可以先不做。3. 核心代码拆解跌倒识别里的冲击、失重与静止三阶段判定3.1 特征计算先把三轴数据折算成合加速度跌倒检测的第一件事不是直接判断而是把三维的加速度向量折算成一个标量这样才能用简单的阈值去刻画「剧烈冲击」这个特征。合加速度 SVM 的计算公式是取三轴的平方和再开方静止时它约等于 9.8 m/s²也就是 1g摔倒时人体撞到地面这个值会瞬间冲到 2.5g 到 4g 以上。这个折算过程把三个维度的判断变成了一个维度的比较是后面所有阈值逻辑的基础。public class FallDetector { private static final float GRAVITY 9.81f; private static final float IMPACT_THRESHOLD 3.0f * GRAVITY; // 冲击阈值约 3g private static final float WEIGHTLESS_THRESHOLD 0.5f * GRAVITY; // 失重阈值约 0.5g private static final float REST_THRESHOLD 1.8f * GRAVITY; // 静止判定阈值 private static final float POSE_CHANGE_THRESHOLD 60f; // 姿态变化角度阈值 private float computeSvm(float x, float y, float z) { return (float) Math.sqrt(x * x y * y z * z); } }四个阈值是这套算法的关键参数。IMPACT_THRESHOLD 取 3g 是个常见的经验值大部分日常活动如快步走、弯腰、坐下都很难超过 2g真正的跌倒冲击通常远超 3g所以这个值能在误报和漏检之间取一个平衡。WEIGHTLESS_THRESHOLD 检测的是跌倒初期的失重阶段人失去支撑到落地之间那几百毫秒合加速度会跌破 0.5g这是区分跌倒和「被重物砸到」之类纯冲击事件的重要中间态。REST_THRESHOLD 用于确认落地后人体短暂静止——正常摔倒后不太可能马上站起来跑动。3.2 窗口缓冲与状态机2 秒时间窗里走完三阶段判定单次采样值没有意义跌倒是一个过程所以需要用一个环形缓冲区保存最近 2 秒的采样数据。把数据结构和状态判定分开写是这套代码比较清爽的地方环形缓冲只负责存储和滑动状态机负责消费缓冲里的数据。检测器内部维护一个枚举状态依次经过 IDLE、IMPACT、WEIGHTLESS、POSTURE_CHECK、ALERT 这几个阶段任一阶段条件不满足就回到 IDLE 重新等待。public class FallDetector { private enum State { IDLE, IMPACT_DETECTED, WEIGHTLESS_DETECTED, REST_CONFIRMED, ALERT } private State state State.IDLE; private float[] buffer new float[100]; // 2秒窗口50Hz采样 x 2秒 private int bufferIndex 0; private long lastImpactTime 0; public void addSample(float x, float y, float z) { buffer[bufferIndex] computeSvm(x, y, z); bufferIndex (bufferIndex 1) % buffer.length; switch (state) { case IDLE: if (isLocalMaxAboveThreshold()) { state State.IMPACT_DETECTED; lastImpactTime System.currentTimeMillis(); } break; case IMPACT_DETECTED: if (hasWeightlessPhase()) { state State.WEIGHTLESS_DETECTED; } else { state State.IDLE; } break; case WEIGHTLESS_DETECTED: if (isStableAfterFall()) { state State.ALERT; } else { state State.IDLE; } break; } } public boolean isFallDetected() { return state State.ALERT; } }状态机的核心思想是不依赖单一次采样而是验证一条完整的事件序列。isLocalMaxAboveThreshold 检查窗口内的局部最大值是否超过 3g也就是寻找冲击峰hasWeightlessPhase 在冲击峰附近往回找是否有低于 0.5g 的采样点isStableAfterFall 检查冲击后 1 秒内窗口的均值是否回落到 1.8g 以下模拟「摔完躺平不动」。只有这三个条件按顺序都成立才拉响警报这也是这套算法误报率比「只看冲击阈值」低一个数量级的原因。3.3 姿态角辅助判断解决手机摔了但人没事的尴尬只靠加速度幅值有一个经典误报场景手机从沙发滑落到地板合加速度冲击也轻松超过 3g。为了过滤这类「手机摔了但人没事」的情况Demo 引入了姿态角变化判断。手机的姿态可以近似用重力方向在手机坐标系里的分量计算跌倒前人体竖直时重力主要在 Z 轴倒地后手机横躺重力方向会转到 X 轴或 Y 轴。这个角度变化用反正切函数计算即可。private float computeTiltAngle(float x, float y, float z) { // 重力方向与手机Z轴的夹角单位度 double verticalComponent z; double horizontalComponent Math.sqrt(x * x y * y); double angle Math.toDegrees(Math.atan2(horizontalComponent, verticalComponent)); return (float) angle; }逻辑说明当手机竖直放置时horizontalComponent 接近 0角度接近 0 度手机平躺时z 接近 0角度接近 90 度或 180 度。在实际判断中我把跌倒前后的姿态角差作为一个附加条件——超过 60 度才认为人体姿势发生了大幅变化。这个参数不能设太小否则低头捡东西这种动作也会误判。需要理解的是姿态角判断依赖的是重力方向而不是手机屏幕方向所以即使手机插在口袋里也能工作这是很多新手容易搞反的点。3.4 报警触发与日志检测到之后怎么让周围的人注意到检测结果最终要落到用户可感知的动作上。Demo 的做法是跳转到一个全屏的 AlertActivity同时震动并写入日志。这里有一个设计取舍为什么不直接在 MainActivity 里弹 Dialog因为 Dialog 在锁屏状态下很可能弹不出来而且如果 MainActivity 已经被系统回收跳转 Activity 更可靠。日志写入则选用了 getExternalFilesDir 路径这在 Android 10 以后不需要存储权限是分区存储时代比较省事的做法。public class AlertActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_alert); Vibrator vibrator (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); long[] pattern {0, 500, 200, 500}; // 立即开始、震动500ms、停200ms、再震500ms vibrator.vibrate(pattern, 0); writeLogToFile(Fall detected at System.currentTimeMillis()); // 这里预留接口可以继续扩展为发送短信、上传云端或拨打紧急电话 } private void writeLogToFile(String message) { File logFile new File(getExternalFilesDir(null), fall_log.txt); try (FileWriter writer new FileWriter(logFile, true)) { writer.append(message).append(\n); } catch (IOException e) { e.printStackTrace(); } } }震动模式采用长震动加间隔比单次震动更容易引起注意。日志文件追加写入而非覆盖写保证多次检测记录不丢失。FileWriter 以 true 打开即追加模式这个细节对后续做数据分析很重要——你可以拿着 log 文件回放当时的传感器趋势判断阈值设得合不合理。这段代码还留了一个扩展注释点想接短信报警只需要在同一个位置调用 SmsManager想接云端推送把消息发到自己的服务器即可。4. 运行与避坑排查从导入工程到真机调试的关键步骤4.1 从 Android Studio 导入到真机运行的完整流程这个 Demo 导入 Android Studio 的过程很标准但有几处容易翻车的点值得提前说。第一步是在欢迎页选择 Open直接选中解压后的项目根目录等 Gradle 同步完成。Gradle 同步阶段最常见的失败原因是 SDK 版本不匹配报错信息里会明确提示缺少哪个版本的 Build Tools 或 Platform点 SDK Manager 安装对应版本即可不需要修改项目代码。第二步是真机准备。跌倒检测必须依赖真实的加速度计模拟器无法真实模拟摔倒的动态过程所以不要想着在 AVD 上验证效果。开启开发者选项里的 USB 调试后连接电脑Android Studio 会自动识别设备。注意部分机型首次连接需要在手机上确认 RSA 指纹授权否则会一直显示 offline。第三步是在 MainActivity 里点开始检测然后把手机握在手里做一个「快速下蹲 顺势倒地」的动作。如果动作幅度不够大合加速度峰值达不到 3g 阈值就不会触发报警。测试时需要拉开架势尽力模拟真实的摔倒冲击。下面这段是 MainActivity 里启动检测的核心调用public class MainActivity extends AppCompatActivity { private SensorDataCollector dataCollector; private FallDetector fallDetector; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); checkRequiredPermissions(); fallDetector new FallDetector(); dataCollector new SensorDataCollector(); dataCollector.setCallback((x, y, z, timestamp) - { fallDetector.addSample(x, y, z); if (fallDetector.isFallDetected()) { runOnUiThread(() - { Intent intent new Intent(this, AlertActivity.class); startActivity(intent); }); } }); } private void checkRequiredPermissions() { if (Build.VERSION.SDK_INT 33) { requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 1); } } }这段代码有一个值得注意的细节回调里直接使用 lambda 把传感器数据喂给检测器检测到跌倒后通过 runOnUiThread 切换到主线程再启动 Activity。传感器回调本身运行在 Looper 所在的线程如果直接在这里 startActivity 会抛异常因为跨线程 UI 操作必须经过主线程。这也是新手最容易踩的坑之一。4.2 高频踩坑记录真机调试时反复遇到的四个问题现象一模拟跌倒动作时永远不报警但看日志发现合加速度明明很高。原因是采样频率不够部分省电模式下 SENSOR_DELAY_GAME 会被拉长到 40ms 以上冲击峰值恰好落在两次采样之间就漏掉了。解决办法是不要只依赖 onSensorChanged 的调用频率在检测器里做一次时间戳校验如果相邻两帧间隔超过 30ms就把上一步动作记录保留到下一帧不让状态机直接丢弃冲击窗口。现象二手机从沙发上滑落却触发了报警。原因是只有冲击阈值而没有考虑人体失重和姿态变化。对照前面的状态机代码检查是否完整实现了三阶段判定如果只实现了 IMPACT_THRESHOLD 单条件再高的姿态角过滤逻辑也救不回来。这个问题的本质是「加速度冲击」和「人体跌倒」不是等价事件需要把状态机补全。现象三日志文件写不进去FileWriter 一直报 FileNotFoundException。原因是 targetSdk 升到 29 以上后路径直接写成 Environment.getExternalStorageDirectory() 会被分区存储拦截。解决方法是改用 getExternalFilesDir(null)这个路径属于应用专属目录不需要任何存储权限卸载应用时数据自动清除对 Demo 来说反而更干净。现象四锁屏后检测服务被系统杀掉解锁发现报警 Activity 还在但不是自己触发的。原因是普通 Activity 无法在锁屏状态下稳定运行很多 ROM 会在息屏几分钟后回收后台 Activity。解决方法是尝试给 MainActivity 所在的包关闭电池优化或者在应用内申请忽略电池优化白名单如果要在后台持续检测需要把采集器放进前台服务并伴随常驻通知。Demo 阶段用 Activity 方案足够真正产品化必须迁移到 Service。5. 二次开发把阈值 Demo 改造成真正可用的跌倒报警器这个 Demo 的价值不在于开箱即用而在于它是一个能跑的真骨架。拿到手之后最值得做的第一件事不是改 UI 或换图标而是把传感器原始数据落盘。具体做法是在 SensorDataCollector 里加一个可开关的数据录制功能把每次回调的 x、y、z 和时间戳追加写入 CSV。有了真实数据你后续调阈值、训练模型、验证算法才有依据不然每次都是让同事一遍遍摔手机效率低还不安全。接下来是三个具体改造方向。第一个是动态阈值现在 IMPACT_THRESHOLD 是固定 3g但老人和年轻人的跌倒冲击差异很大。常见做法是用一个短窗口比如 5 秒估算用户的日常活动峰值把这个值乘以 1.5 作为自适应冲击阈值这样误报会明显减少。第二个是替换检测模型把 FallDetector 的接口保持不动内部实现从状态机换成 TensorFlow Lite 模型输入是 100 个合加速度点加姿态角序列输出是跌倒概率。我一般会先在 PC 上把 CSV 数据导入 Python 训练好模型再导出 tflite 文件放进 assets 目录。第三个是报警方式扩展在 AlertActivity 的预留位置接上 SmsManager 发送紧急短信或者用 OkHttp 把位置信息 POST 到云端接口这取决于你的应用场景。验证方法也要讲究。不要盲目让测试者反复摔倒在硬地板上一下午下来人受不了数据质量也差。我的习惯是分两步先用「手机固定在测试者腰部从站立位置快速蹲坐到垫子上」来模拟这个动作能产生接近真实跌倒的冲击和姿态变化再用手持手机快速下挥到低处来验证误报率。每轮测试后立即查看 CSV 里的合加速度波形确认冲击峰、失重谷、静止段三段都完整再决定要不要调整阈值。从那以后我每次做传感器类项目拿到手的第一个版本代码一律先补上数据录制再谈调参和模型替换这个习惯帮我少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑