最近在做室内定位相关研究,希望通过手机传感器数据判断人的姿态和手机位置。之前很多研究只用到了加速度数据,但是判别的准确度偏低。本次我们的研究准备将加速度传感器、角速度传感器、方位传感器、压力传感器等数据结合起来,用于提高判别的准确度。
具体如何采集这些数据,网上并没有相关代码。在这里把自己的实现方法分享出来,供大家参考。
首先是建立加速度的类AccData(本文只涉及传感器数据采集相关代码,AccData类中从数据库获取数据的代码已经略去):
/**
* Created by Maolin Liu on 2016/3/12.
*/
public class AccData {
public long timestamp;
public double ax;
public double ay;
public double az;
public static final String TABLE_NAME = "AccData";
public static final String _ID = "_id";
public static final String TIMESTAMP = "timestamp";
public static final String AX = "ax";
public static final String AY = "ay";
public static final String AZ = "az";
//生成表的sql语句
public static String tableCreateSQL() {
StringBuffer sql = new StringBuffer();
sql.append("create table ");
sql.append(TABLE_NAME);
sql.append(" (");
sql.append(_ID);
sql.append(" integer primary key autoincrement,");
sql.append(TIMESTAMP);
sql.append(" long,");
sql.append(AX);
sql.append(" double,");
sql.append(AY);
sql.append(" double,");
sql.append(AZ);
sql.append(" double");
sql.append(" )");
return sql.toString();
}
public long insertDataBase(SQLiteDatabase sqLiteDatabase) {
ContentValues values = new ContentValues();
values.put(TIMESTAMP, timestamp);
values.put(AX, ax);
values.put(AY, ay);
values.put(AZ, az);
return(sqLiteDatabase.insert(TABLE_NAME, null, values));
}
public AccData clone() {
AccData accData = new AccData();
accData.timestamp = timestamp;
accData.ax = ax;
accData.ay = ay;
accData.az = az;
return accData;
}
}
然后是加速度的SQLiteOpenHelper类,即AccDBOpenHelper:
/**
* Created by Maolin Liu on 2016/3/12.
*/
public class AccDBOpenHelper extends SQLiteOpenHelper {
private static final String DBNAME = "Acc Data.db3";
private static final int VERSION = 1;
public AccDBOpenHelper(Context context){
super(context,DBNAME,null,VERSION);
}
public AccDBOpenHelper(Context context, String name,SQLiteDatabase.CursorFactory factory, int version){
super(context, name, factory, version);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db){
db.execSQL(AccData.tableCreateSQL());
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
System.out.println( "----------AccDBOpenHelper onUpdata Called----------" + oldVersion + "-->" + newVersion);
}
}
按照和上面类似的方法为角速度数据、方位数据、压力数据建立数据类和对应的SQLiteOpenHelper类。
下面是具体的实现了。如果将采集数据的的代码放在Activity中,用户体验不是特别好。所以我们在后台Service中进行数据采集。需要注意几个地方,第一,往数据库中存储大量数据的时候最好添加事务,这样会提高存储效率;第二,方向数据要通过计算获得,而不是通过传感器直接获取,如果需要用到azimuth、pitch和roll数据需要特别注意这一点(详情点击:Android Orientation Sensor(方向传感器)详解与应用)。第三,API等级在19以上,可以自由设置传感器的采样频率,注意单位是微秒(10 -6秒)。
下面是Service具体代码:
/**
* Created by Maolin Liu on 2016/3/12.
*/
public class DataAcquireService extends Service {
private SensorManager sensorManager;
private MySensorBinder mySensorBinder;
private Timer updateTimer;
private AccDBOpenHelper accDBOpenHelper;
private SQLiteDatabase accDB;
private List<AccData> accDataList;
private AccData accData;
private float accValues[];
private GyrDBOpenHelper gyrDBOpenHelper;
private SQLiteDatabase gyrDB;
private List<GyrData> gyrDataList;
private GyrData gyrData;
private float gyrValues[];
private MagAndOriDBOpenHelper magAndOriDBOpenHelper;
private SQLiteDatabase magAndOriDB;
private List<MagAndOriData> magAndOriDataList;
private MagAndOriData magAndOriData;
private PreDBOpenHelper preDBOpenHelper;
private SQLiteDatabase preDB;
private List<PreData> preDataList;
private PreData preData;
private float preValue;
private float magValues[];
private boolean isRecord ; //开始存储或者关闭存储的Boolean值
public static final String ACTION_STATE_COLLECT_SERVICE = "edu.geosis.service.DATA_ACQUIRE_SERVICE";
@Override
public IBinder onBind(Intent intent) {
System.out.println("DataAcquireService is binded ! ");
return mySensorBinder;
}
@Override
public void onCreate() {
super.onCreate();
initializeVariables();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("DataAcquireService is started ! ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
if (accDB != null && accDB.isOpen()) {
accDB.close();
}
if (gyrDB != null && gyrDB.isOpen()) {
gyrDB.close();
}
if (magAndOriDB != null && magAndOriDB.isOpen()) {
magAndOriDB.close();
}
if (preDB != null && preDB.isOpen()) {
preDB.close();
}
sensorManager.unregisterListener(mySensorListener);
System.out.println("DataAcquireService is destroyed ! ");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
System.out.println("DataAcquireService is onUnbinded ! ");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
System.out.println("DataAcquireService is onRebinded ! ");
super.onRebind(intent);
}
private void initializeVariables() {
mySensorBinder = new MySensorBinder();
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
accDBOpenHelper = new AccDBOpenHelper(this, "Acc Data.db3", null, 1); //存储在默认路径下
accDB = accDBOpenHelper.getReadableDatabase();
accDataList = new ArrayList<AccData>();
accData = new AccData();
gyrDBOpenHelper = new GyrDBOpenHelper(this, "Gyr Data.db3", null, 1);
gyrDB = gyrDBOpenHelper.getReadableDatabase();
gyrDataList = new ArrayList<GyrData>();
gyrData = new GyrData();
magAndOriDBOpenHelper = new MagAndOriDBOpenHelper(this, "MagAndOri Data.db3", null, 1);
magAndOriDB = magAndOriDBOpenHelper.getReadableDatabase();
magAndOriDataList = new ArrayList<MagAndOriData>();
magAndOriData = new MagAndOriData();
preDBOpenHelper = new PreDBOpenHelper(this, "Pre Data.db3", null, 1);
preDB = preDBOpenHelper.getReadableDatabase();
preDataList = new ArrayList<PreData>();
preData = new PreData();
isRecord = false;
}
/**
* Service对应的Activity通过下面的Binder类对采集数据的过程进行控制
**/
public class MySensorBinder extends Binder {
public void startRecord() {
isRecord = true;
}
public void stopRecord() {
isRecord = false;
}
public void clearRecord() {
accDataList.clear();
gyrDataList.clear();
magAndOriDataList.clear();
preDataList.clear();
}
public boolean insertDB() {
//make clear whether it is "||" or "&&"
if (accDataList.isEmpty() && gyrDataList.isEmpty() && preDataList.isEmpty()&& magAndOriDataList.isEmpty()) {
return false;
} else {
//存储时开启事务能大大加快存储速度,否则一条一条地存储特别费时
accDB.beginTransaction();
try {
for (AccData accData: accDataList) {
accData.insertDataBase(accDB);
}
accDB.setTransactionSuccessful();
}
catch(Exception e) {
e.printStackTrace();
System.out.println("accData cannot insert into the database !");
}
finally {
accDB.endTransaction();
}
gyrDB.beginTransaction();
try {
for (GyrData gyrData: gyrDataList) {
gyrData.insertDataBase(gyrDB);
}
gyrDB.setTransactionSuccessful();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
gyrDB.endTransaction();
}
magAndOriDB.beginTransaction();
try {
for (MagAndOriData magAndOriData: magAndOriDataList) {
magAndOriData.insertDataBase(magAndOriDB);
}
magAndOriDB.setTransactionSuccessful();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
magAndOriDB.endTransaction();
}
preDB.beginTransaction();
try {
for (PreData preData: preDataList) {
preData.insertDataBase(preDB);
}
preDB.setTransactionSuccessful();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
preDB.endTransaction();
}
return true;
}
}
public void registerListener() {
boolean result = sensorManager.registerListener(mySensorListener,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_GAME); //最后一个参数用于控制传感器数据获取的频率,频率可以自由调整
Log.d("maolin",""+result); //LOG查看监听是否成功注册
sensorManager.registerListener(mySensorListener,
sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(mySensorListener,
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(mySensorListener,
sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE),
SensorManager.SENSOR_DELAY_GAME);
}
public void unregisterListener() {
sensorManager.unregisterListener(mySensorListener);
System.out.println("StateCollectService listener is unregistered ! ");
}
}
//下面是传感器变化监听的关键类
private SensorEventListener mySensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
switch(event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
accValues = event.values.clone();
synchronized (accData) {
accData.timestamp = System.currentTimeMillis();
accData.ax = accValues[0];
accData.ay = accValues[1];
accData.az = accValues[2];
if (isRecord) {
accDataList.add(accData.clone());
}
}
break;
case Sensor.TYPE_GYROSCOPE:
gyrValues = event.values.clone();
synchronized (gyrData) {
gyrData.timestamp = System.currentTimeMillis();
gyrData.gx = gyrValues[0];
gyrData.gy = gyrValues[1];
gyrData.gz = gyrValues[2];
if (isRecord) {
gyrDataList.add(gyrData.clone());
}
}
break;
case Sensor.TYPE_PRESSURE:
preValue = event.values[0];
synchronized (preData) {
preData.timestamp = System.currentTimeMillis();
preData.pressure = preValue;
if (isRecord) {
preDataList.add(preData.clone());
}
}
break;
case Sensor.TYPE_MAGNETIC_FIELD: //加速度、陀螺仪和气压计都可以直接获得,但是磁力计和方位角数据要通过计算才能得到
magValues = event.values;
break;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
};
}
然后就是操作界面DataAcquireActivity :
/**
* Created by GeoSIS on 2016/3/12.
*/
public class DataAcquireActivity extends Activity {
private Chronometer chronometer = null; //添加一个计时器,知道采集的时长;
private Button btnStart,btnEnd;
private ServiceConnection dataAcquireConnection;
private DataAcquireService.MySensorBinder mySensorBinder;
private ArrayList<Long> savedTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_acquire);
initializeVariables();
initializeButton();
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
unbindService(dataAcquireConnection);
super.onDestroy();
}
private void initializeVariables() {
// TODO Auto-generated method stub
chronometer = (Chronometer)findViewById(R.id.chronometer);
btnStart = (Button)findViewById(R.id.start_pick_up);
btnEnd = (Button)findViewById(R.id.end_pick_up);
}
private void initializeButton() {
// TODO Auto-generated method stub
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
mySensorBinder.registerListener();
mySensorBinder.startRecord();
btnStart.setClickable(false); btnStart.setBackgroundColor(getResources().getColor(R.color.yellow));
}
});
btnEnd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
chronometer.stop();
mySensorBinder.stopRecord();
mySensorBinder.insertDB();
mySensorBinder.clearRecord();
mySensorBinder.unregisterListener();
finish();
}
});
}
}
Activity对应的XML文件非常简单,上面只有一个计时器(Chronometer)、一个开始采集的按钮(Button)和一个结束采集的按钮(Button),这里就不列出来了。