Android设备步态分析软件

1. 概述

手机上的运行效果图如下(实现思路1.编写界面和事件处理;2.收集加速度数据写入到文件中;3.使用libSVM训练Model;4.把model应用于真实的数据识别) 源码
截图

2. 环境准备

  1. 操作系统:Windows、Mac OS、 Linux均可
  2. 安装JDK,Android开发工具需要有JDK环境才能运行,所有要先安装JDK(下载地址) 下载JDK8
  3. 安装Android Studio(Android开发工具,可以编写Android程序并在设备上运行)(下载地址) 下载Android Studio 3.0
  4. 安装下载好的软件

3. 创建项目

打开Android Stuido,点击Start a new Android Studio project,然后按照下图一步一步操作就可以完成Android项目的创建。
截图
截图
截图
截图
截图
截图

4. 创建虚拟机

按照下图操作创建Android虚拟设备并运行
截图
截图
截图
截图
截图
截图
截图

5. 在虚拟机上运行项目

按照下图操作把项目在模拟器或真实设备上运行起来
截图
截图
截图

6. 把项目分享到Github上

截图
截图
截图
截图
截图
截图
截图
截图

7. 布局代码

<!--  布局代码 -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true"
    android:orientation="vertical"
    tools:context="cn.zhaoliang5156.svmprjo.MainActivity">

    <TextView
        android:id="@+id/tv_train_num"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="采集样本数量"
        android:textSize="20sp" />

    <RadioGroup
        android:id="@+id/rg_train_num"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/rb_train_num_one"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="30" />

        <RadioButton
            android:id="@+id/rb_train_num_two"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="50" />

        <RadioButton
            android:id="@+id/rb_train_num_three"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="100" />
    </RadioGroup>

    <TextView
        android:id="@+id/tv_lable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="标记动作"
        android:textSize="20sp" />

    <RadioGroup
        android:id="@+id/rg_lable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/rb_lable_one"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="静止(0)" />

        <RadioButton
            android:id="@+id/rb_lable_two"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="走路(1)" />

        <RadioButton
            android:id="@+id/rb_lable_three"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="跑步(2)" />
    </RadioGroup>

    <TextView
        android:id="@+id/tv_acc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_collection_num"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="已经采集:" />

    <TextView
        android:id="@+id/tv_accuracy"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="准确率" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">


        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="10dp">

            <ImageButton
                android:id="@+id/btn_collection"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:background="@mipmap/sample_off" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="采集" />
        </LinearLayout>


        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="10dp">

            <ImageButton
                android:id="@+id/btn_train"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:background="@mipmap/train_off" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="训练" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="vertical"
            android:padding="10dp">


            <ImageButton
                android:id="@+id/btn_understand"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:background="@mipmap/test_off" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="识别" />
        </LinearLayout>
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">

        <ImageView
            android:id="@+id/iv_still"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@mipmap/gait_still_off" />

        <ImageView
            android:id="@+id/iv_walk"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@mipmap/gait_walk_off" />

        <ImageView
            android:id="@+id/iv_run"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@mipmap/gait_run_off" />
    </LinearLayout>

    <Button
        android:id="@+id/btn_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="清空目录"
        android:textSize="20sp" />

</LinearLayout>

8. 逻辑代码

package cn.zhaoliang5156.svmprjo

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import cn.zhaoliang5156.svmprjo.svm.svm_predict
import cn.zhaoliang5156.svmprjo.svm.svm_scale
import cn.zhaoliang5156.svmprjo.svm.svm_train
import cn.zhaoliang5156.svmprjo.util.Util
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.toast
import java.io.*

/**
 * 步态分析
 */
class MainActivity : AppCompatActivity() {

    lateinit var mSensorManager: SensorManager      // 传感器管理类
    lateinit var mAccSensor: Sensor             // 传感器
    var mHz = (1000.0 * 1000.0 / 32).toInt()        // 拿数据的时间

    var lable = 0       // 要写入文件的lable标记
    var trainNum = 0        // 采集样本数量
    var currentCollectionTrainNum = 0   // 当前采集样本数据
    var isStartCollection = false    // 是否开始采集的标记
    var isStartUnderStand: Boolean = false

    // 采集的加速度监听器类
    val sensorListener: SensorEventListener = object : SensorEventListener {

        var accArr = DoubleArray(128)
        var currentIndex = 0

        override fun onSensorChanged(event: SensorEvent) {
            var x = event.values[0]
            var y = event.values[1]
            var z = event.values[2]
            val sqrt = Math.sqrt((x * x + y * y + z * z).toDouble())
            tv_acc.text = "${sqrt}"
            // 当不够128个数据的时候继续收集数据,够128个数据的时候写入文件
            if (currentIndex >= 128) {
                val features = Util.dataToFeatures(accArr, mHz)

                if (isStartCollection) {  // 如果是开始收集数据走到这里,就存起来
                    Util.writeToFile("${filesDir}/train", lable, features)
                    currentCollectionTrainNum++
                    tv_collection_num.text = currentCollectionTrainNum.toString()

                    if (currentCollectionTrainNum >= trainNum) {        // 已经采集够了
                        collection(isStartCollection)
                        currentCollectionTrainNum = 0
                        tv_collection_num.text = "已经采集:${currentCollectionTrainNum}"
                    }
                } else {     // 否则则是识别
                    val code = Util.predictUnScaleData(features)
                    result(code.toInt())
                }
                currentIndex = 0

            } else {
                accArr[currentIndex++] = sqrt
            }
        }

        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 选择采集样本数量
        rg_train_num.setOnCheckedChangeListener { radioGroup, i ->
            when (i) {
                R.id.rb_train_num_one -> trainNum = 30
                R.id.rb_train_num_two -> trainNum = 50
                R.id.rb_train_num_three -> trainNum = 100
            }
            tv_train_num.text = "采集样本数量${trainNum}"
        }
        rg_train_num.check(R.id.rb_train_num_one)   // 设置默认选中30

        // 选中标记lable
        rg_lable.setOnCheckedChangeListener { radioGroup, i ->
            when (i) {
                R.id.rb_lable_one -> lable = 0
                R.id.rb_lable_two -> lable = 1
                R.id.rb_lable_three -> lable = 2
            }
            tv_lable.text = "lable:${lable}"
        }
        rg_lable.check(R.id.rb_lable_one)

        mSensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager // 获取传感器管理类
        mAccSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)     // 获取加速度传感器

        btn_collection.setOnClickListener {
            collection(isStartCollection)
        }

        // 开始训练按钮点击调用
        btn_train.setOnClickListener {
            btn_train.setBackgroundResource(R.mipmap.train)
            doAsync {
                createScaleFile(arrayOf("-l", "0", "-u", "1", "-s", "${filesDir}/range", "${filesDir}/train"))
                createModelFile(arrayOf("-s", "0", "-c", "128.0", "-t", "2", "-g", "8.0", "-e", "0.1", "${filesDir}/scale", "${filesDir}/model"))
                createPredictFile(arrayOf("${filesDir}/scale", "${filesDir}/model", "${filesDir}/predict"))
                runOnUiThread {
                    var reader: BufferedReader = BufferedReader(InputStreamReader(FileInputStream("${filesDir}/accuracy")))
                    val line = reader.readLine()
                    tv_accuracy.text = line.replace("Accuracy", "准确率")
                    btn_train.setBackgroundResource(R.mipmap.train_off)
                }
            }
        }

        // 开始识别按钮点击调用
        btn_understand.setOnClickListener {
            understand(isStartUnderStand)
        }

        // 删除文件
        btn_delete.setOnClickListener {
            doAsync {
                val file = File("${filesDir}")
                for (item in file.list()) {
                    File("${filesDir}/${item}").delete()
                }
                runOnUiThread {
                    toast("删除成功!")
                }
            }
        }
    }

    /**
     * 创建归一化文件
     */
    fun createScaleFile(args: Array<String>) {
        val out = System.out
        var outScaleFile = PrintStream("${filesDir}/scale")
        System.setOut(outScaleFile)
        svm_scale.main(args)
        System.setOut(out)
    }

    /**
     * 创建model文件
     */
    fun createModelFile(args: Array<String>) {
        svm_train.main(args)
    }

    /**
     * 创建Predict文件
     */
    fun createPredictFile(args: Array<String>) {
        val out = System.out
        var outAccuracy = PrintStream("${filesDir}/accuracy")
        System.setOut(outAccuracy)
        svm_predict.main(args)
        System.setOut(out)
    }

    /**
     * 采集数据
     */
    private fun collection(b: Boolean) {
        if (!b) { // 开始采集
            mSensorManager.registerListener(sensorListener, mAccSensor, mHz)
            btn_collection.setBackgroundResource(R.mipmap.sample)
        } else { // 停止采集
            mSensorManager.unregisterListener(sensorListener)
            btn_collection.setBackgroundResource(R.mipmap.sample_off)
        }
        isStartCollection = !isStartCollection
    }

    /**
     * 识别结果
     */
    private fun understand(b: Boolean) {
        if (!b) { // 开始识别
            Util.loadFile("${filesDir}/range", "${filesDir}/model")
            mSensorManager.registerListener(sensorListener, mAccSensor, mHz)
            btn_understand.setBackgroundResource(R.mipmap.test)
        } else { // 停止识别
            mSensorManager.unregisterListener(sensorListener)
            btn_understand.setBackgroundResource(R.mipmap.test_off)
            result(-1)
        }
        isStartUnderStand = !isStartUnderStand
    }

    fun result(code: Int) {
        iv_still.setBackgroundResource(R.mipmap.gait_still_off)
        iv_walk.setBackgroundResource(R.mipmap.gait_walk_off)
        iv_run.setBackgroundResource(R.mipmap.gait_run_off)
        when (code) {
            0 -> iv_still.setBackgroundResource(R.mipmap.gait_still)
            1 -> iv_walk.setBackgroundResource(R.mipmap.gait_walk)
            2 -> iv_run.setBackgroundResource(R.mipmap.gait_run)
        }
    }
}