본문 바로가기

School Study/Mobile System Programming

Mobile Sensing Pipeline 2

이 내용은 앞선 Mobile Sensing Pipeline 1에서 이어지는 내용이다.

https://mrlee.kr/entry/Mobile-Sensing-Pipeline-1

 

Mobile Sensing Pipeline 1

오늘날 스마트폰에 내장된 다양한 센서를 이용하여 각종 센싱 어플리케이션을 구동할 수 있다. Physical Activity Transportation Mode Plcae/Environment Context Conversation, Human Voice 모바일 센싱 디자인..

mrlee.kr

앞선 Step Monitor 예제(Version 1)에서 생각해 볼 문제들을 다시 한번 살펴보자.

  • 센서 데이터 중 y축 값만 보는 것으로 충분한가?
    • 실제 걷는 중이더라도 y축 방향의 움직임이 거의 없다면, 가속도의 변화가 크지 않을 것이고,
      그러면 걸음 수가 증가하지 않을 것이다.
  • 현재 값과 바로 이전 값의 차이를 보는 것으로 step을 구분하는 것이 적당한가?
    • 순간적인 움직임에도 걸음 수가 급격히 증가하는 경우가 생긴다.
  • Threshold를 어떻게 정하는가?
    • 실제 걸음 중의 가속도 변화 정도가 threshold를 정하는 데 반영이 안되어 있다.

이를 바탕으로 문제점들을 개선한 Step Monitor 예제(Version2)를 만들어보자.

  • 3축 어느 방향의 움직임이든 이에 의해 가해지는 가속도를 고려하자
  • 일정 시간의 데이터를 모아서 보자
  • 실제 걷는 중에도 가속도 값의 변화 추이를 고려하자

총 3가지 관점에서 문제점들을 개선할 것이다.

이를 위해 실제 가속도 데이터가 어떻게 변화하는지 관찰할 필요가 있다.

GraphView 라이브러리를 활용하면 쉽게 가속도 데이터를 시각화해서 관찰해볼 수 있다.
안드로이드 전용 graph plotting library 오픈소스 

http://www.android-graphview.org/

불러오는 중입니다...

이를 활용하기 위해서 app폴더 내에 build.gradle 파일의 dependencies에 아래 항목 추가가 필요하다.

1
implementation 'com.jjoe64:graphview:4.2.2'
cs

 

아래 예제를 통해 실제 가속도 데이터를 관찰해보자.

activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?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" tools:context=".MainActivity"
    android:orientation="vertical">
 
    <com.jjoe64.graphview.GraphView
        android:layout_width="match_parent"
        android:layout_height="200dip"
        android:id="@+id/graph"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textSize="20dp"
        android:id="@+id/accelX"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textSize="20dp"
        android:id="@+id/accelY"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textSize="20dp"
        android:id="@+id/accelZ"/>
    <com.jjoe64.graphview.GraphView
        android:layout_width="match_parent"
        android:layout_height="200dip"
        android:id="@+id/graphLinear"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textSize="20dp"
        android:id="@+id/rms"/>
 
</LinearLayout>
 
cs

 

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package kr.ac.koreatech.swkang.msp12_accelchart;
 
import android.graphics.Color;
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 android.util.Log;
import android.widget.TextView;
 
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.LegendRenderer;
import com.jjoe64.graphview.series.DataPoint;
import com.jjoe64.graphview.series.LineGraphSeries;
 
public class MainActivity extends AppCompatActivity implements SensorEventListener {
    private SensorManager mSensorManager;
    private Sensor mLinear;
 
    GraphView graph;
    GraphView graphRMS;
 
    private LineGraphSeries<DataPoint> seriesX;
    private LineGraphSeries<DataPoint> seriesY;
    private LineGraphSeries<DataPoint> seriesZ;
    private LineGraphSeries<DataPoint> seriesRMS;
 
    private TextView mX;
    private TextView mY;
    private TextView mZ;
    private TextView rmsTV;
 
    private long count;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        count = 0;
 
        mX = (TextView)findViewById(R.id.accelX);
        mY = (TextView)findViewById(R.id.accelY);
        mZ = (TextView)findViewById(R.id.accelZ);
        rmsTV = (TextView)findViewById(R.id.rms);
 
        //********** x, y, z 축 가속도를 표시하기 위한 GraphView 설정 **********//
        // UI 레이아웃에 정의된 GraphView 객체 참조 변수 생성
        graph = (GraphView)findViewById(R.id.graph);
 
        // line graph를 그리는데 사용되는 데이터를 저장하는 객체 생성
        seriesX = new LineGraphSeries<DataPoint>();
        seriesY = new LineGraphSeries<DataPoint>();
        seriesZ = new LineGraphSeries<DataPoint>();
 
        // graph 객체에 이 graph를 그리기 위해서 사용될 series 데이터를 추가
        graph.addSeries(seriesX);
        graph.addSeries(seriesY);
        graph.addSeries(seriesZ);
 
        // graph 상에서 화면에 보여지는 부분에 대한 설정
        graph.getViewport().setXAxisBoundsManual(true);
        graph.getViewport().setYAxisBoundsManual(true);
        graph.getViewport().setMinY(-9.0);
        graph.getViewport().setMaxY(9.0);
 
        // horizontal zooming과 scrolling이 가능하도록 설정
        graph.getViewport().setScalable(true);
 
        // legend 설정
        seriesX.setTitle("accel X");
        seriesY.setTitle("accel Y");
        seriesZ.setTitle("accel Z");
        seriesX.setColor(Color.RED);
        seriesY.setColor(Color.BLUE);
        seriesZ.setColor(Color.GREEN);
 
        // graph에 legend를 표시하기 위한 과정
        graph.getLegendRenderer().setVisible(true);
        graph.getLegendRenderer().setAlign(LegendRenderer.LegendAlign.TOP);
        //****************************************************************************//
 
        //********** x, y, z 축 가속도 값의 RMS 값을 표시하기 위한 GraphView 설정 **********//
        // UI 레이아웃에 정의된 GraphView 객체 참조 변수 생성
        graphRMS = (GraphView)findViewById(R.id.graphLinear);
 
 
        // line graph를 그리는데 사용되는 데이터를 저장하는 객체 생성
        seriesRMS = new LineGraphSeries<DataPoint>();
        // graph 객체에 이 graph를 그리기 위해서 사용될 series 데이터를 추가
        graphRMS.addSeries(seriesRMS);
 
        // graph 상에서 화면에 보여지는 부분에 대한 설정
        graphRMS.getViewport().setXAxisBoundsManual(true);
        graphRMS.getViewport().setYAxisBoundsManual(true);
        graphRMS.getViewport().setMinY(0.0);
        graphRMS.getViewport().setMaxY(15.0);
 
        // horizontal zooming과 scrolling이 가능하도록 설정
        graphRMS.getViewport().setScalable(true);
 
        // legend 설정
        seriesRMS.setTitle("accel rms");
        seriesRMS.setColor(Color.GREEN);
 
        graphRMS.getLegendRenderer().setVisible(true);
        graphRMS.getLegendRenderer().setAlign(LegendRenderer.LegendAlign.TOP);
        //******************************************************************************//
 
        //***** SensorManager를 이용하여 사용할 Sensor 지정 및 SensorEventListener 등록 *****//
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        mLinear = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
        mSensorManager.registerListener(this, mLinear, SensorManager.SENSOR_DELAY_NORMAL);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        //mSensorManager.registerListener(this, mLinear, SensorManager.SENSOR_DELAY_NORMAL);
    }
 
    @Override
    protected void onPause() {
        super.onPause();
        //mSensorManager.unregisterListener(this);
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSensorManager.unregisterListener(this);
    }
 
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
 
    }
 
    public void onSensorChanged(SensorEvent event) {
        if(event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
            // event.values 배열의 사본을 만들어서 values 배열에 저장
            float[] values = event.values.clone();
 
            count++;
 
            // graph에 가속도 값을 그리기 위해 series 객체에 데이터 추가
            // DataPoint 객체 생성: DataPoint(x, y)
            seriesX.appendData(new DataPoint(count, values[0]), true200);
            seriesY.appendData(new DataPoint(count, values[1]), true200);
            seriesZ.appendData(new DataPoint(count, values[2]), true200);
 
            // UI 레이아웃 상의 TextView 객체에 값 표시
            mX.setText("accel X: " + (values[0]));
            mY.setText("accel Y: " + (values[1]));
            mZ.setText("accel Z: " + (values[2]));
 
            // accelerometer x, y, z 축 값의 Root Mean Square 값 계산
            double rms = 
Math.sqrt(values[0* values[0+ values[1* values[1+ values[2* values[2]);
 
            // graph에 값을 그리기 위해 series 객체에 계산된 rms 데이터 추가
            seriesRMS.appendData(new DataPoint(count, rms), true100);
 
            // UI 레이아웃 상의 TextView 객체에 값 표시
            rmsTV.setText("rms: " + rms);
        }
    }
}
cs

 

  • 중력 가속도를 제외하고 폰에 가해지는 힘에 의한 가속도만 관찰해보자.
    • Sensor.TYPE_LINEAR_ACCELERATION 사용
  • 상단 그래프
    • 각 축 별로 linear acceleration 값을 선 그래프로 표시
  • 하단 그래프
    • 폰에 가해지는 가속도의 크기를 보기 위해 3축 가속도 값의 RMS 값을 선 그래프로 표시
    • RMS = sqrt(x*x + y*y + z*z)
      • Root Mean Square
가속도 데이터가 출력되는 모습

 

따라서 RMS를 바탕으로 어떻게 개선시킬지 살펴보면

  • Linear accelration 값의 RMS 값을 계산하여 사용
  • 1초동안의 가속도 RMS의 평균을 계산하여 사용
  • RMS 값의 변화 추이를 관찰하여 Threshold 결정
    • 걷는 동안 RMS 값과 움직이지 않는 동안 RMS 값의 차이 관찰

 

다시 데이터 처리 과정을 정리해보자.

  • Sensor data collection
    • 3축 가속도 데이터 수집
  • Feature extraction
    • 가속도 데이터 업데이트 마다 RMS 계산
    • 1초간 데이터 buffering
    • 1초 동안의 RMS 값이 모였을 때, 평균 RMS 계산
  • Classification
    • Step 유무 판단
      • 평균 RMS가 일정 threshold보다 크면 step이 있었다고 판단
      • 작으면 없었다고 판단
    • Step이 있었다고 판단되면 step count 증가
      • 초당 걸음 수가 일정하다고 가정하여 그 수만큼 증가

 

Version 1과 비교해 아래 두 함수(StepMonitor.java)만 수정되었다.

  • onSensorChanged()
  • computeSteps()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 센서 데이터가 업데이트 되면 호출
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
    //*** 데이터 업데이트 주기 확인 용 코드 ***//
    // SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI,
    // SENSOR_DELAY_GAME, SENSOR_DELAY_FASTEST로
    // 변경해가면서 로그(logcat)를 확인해 볼 것
    currT = event.timestamp;
    double dt = (currT - prevT)/1000000;
    // logcat에 로그를 출력하려면 아래 code line의 주석을 해제
    //Log.d(LOGTAG, "time difference=" + dt);
    prevT = currT;
    //***************************************
    //***** sensor data collection *****//
    // event.values 배열의 사본을 만들어서 values 배열에 저장
    float[] values = event.values.clone();
    // simple step calculation
    computeSteps(values);
    }
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// a simple inference for step count
private void computeSteps(float[] values) {
    double avgRms = 0;
    //***** feature extraction *****//
    // calculate feature data:
    // 여기서는 3축 가속도 데이터의 RMS 값의 1초 간의 평균값을 이용
    // 1. 현재 업데이트 된 accelerometer x, y, z 축 값의 Root Mean Square 값 계산
    double rms = Math.sqrt(values[0* values[0+ values[1* values[1+ values[2* values[2]);
    // 2. 위에서 계산한 RMS 값을 rms 값을 저장해 놓는 배열에 넣음
    // 배열 크기는 1초에 발생하는 가속도 데이터 개수 (여기서는 13)
    if(rmsCount < NUMBER_OF_SAMPLES) {
        rmsArray[rmsCount] = rms;
        rmsCount++;
    } else if(rmsCount == NUMBER_OF_SAMPLES) {
        // 3. 1초간 rms 값이 모였으면 평균 rms 값을 계산
        double sum = 0;
        // 3-1. rms 값들의 합을 구함
        for(int i = 0; i < NUMBER_OF_SAMPLES; i++) {
            sum += rmsArray[i];
        }    
        // 3-2. 평균 rms 계산
        avgRms = sum / NUMBER_OF_SAMPLES;
        Log.d(LOGTAG, "1sec avg rms: " + avgRms);
        // 4. rmsCount, rmsArray 초기화: 다시 1초간 rms sample을 모으기 위해
        rmsCount = 0;
        for(int i = 0; i < NUMBER_OF_SAMPLES; i++) {
            rmsArray[i] = 0;
        }
        // 5. 이번 업데이트로 계산된 rms를 배열 첫번째 원소로 저장하고 카운트 1증가
        rmsArray[0= rms;
        rmsCount++;
    }
    //***** classification *****//
    // check if there is a step or not:
    // 1. 3축 가속도 데이터의 1초 평균 RMS 값이 기준 문턱값을 넘으면 step이 있었다고 판단함
    if(avgRms > AVG_RMS_THRESHOLD) {
        // 1-1. step 수는 1초 걸음 시 step 수가 일정하다고 가정하고, 그 값을 더해 줌
        steps += NUMBER_OF_STEPS_PER_SEC;
        Log.d(LOGTAG, "steps: " + steps);
        // if step counts increase, send steps data to MainActivity
        Intent intent = new Intent("kr.ac.koreatech.msp.stepmonitor");
        // 걸음수는 정수로 표시되는 것이 적합하므로 int로 형변환
        intent.putExtra("steps", (int)steps);
        // broadcast 전송
        sendBroadcast(intent);
    }
}
cs

 

Version2에서 정확도가 많이 발전했을 것이다.
하지만 여기서도 생각해 볼 문제들이 존재한다.

  • 왜 1초의 시간인가?
    • 걷는 중이라는 것을 1초간 가속도 RMS 평균 값으로 판단하고 있다.
    • 더 짧게 하거나 길게 하는 것이 더 좋을까?
  • 왜 RMS 평균인가?
    • 더 나은 기준값이 있을까?
  • 가속도 RMS의 Threshold 기반으로 걸음 여부 판단
    • 폰을 들어 올리거나 폰을 쥐고 움직이는 경우도 걸음으로 오인식 할 수 있다.
    • Threshold 값을 사전에 결정해줘야 한다.
  • 시간당 걸음 수 가정
    • 현재는 항상 분당 90보를 걷는다고 가정
    • 천천히 걷거나 빨리 걷는 경우 오차 발생

http://developer.android.com/guide/topics/sensors/sensors_overview.html

 

Sensors Overview  |  Android Developers

Most Android-powered devices have built-in sensors that measure motion, orientation, and various environmental conditions. These sensors are capable of providing raw data with high precision and accuracy, and are useful if you want to monitor three-dimensi

developer.android.com

위 사이트에서 Sensor 관련 Overview 내용들을 확인해볼 수 있다.

자체적으로 지원하는 TYPE_STEP_COUTER 상수가 존재한다.

이 값을 통해 자체적으로 걸음 수를 받아올 수 있다.

실제 우리가 만들어 본 예제와 비교해보며 어느 정도 정확한지 측정해볼 수 있을 것 같다.