基于phyphox的Android多传感器姿态测量与3D可视化开发全记录

基于phyphox的Android多传感器姿态测量与3D可视化开发全记录

原属项目:https://github.com/phyphox/phyphox-android

二次开发仓库:https://github.com/liubaiqing/Gyroscope

项目背景

phyphox是一款由RWTH亚琛大学开发的开源物理实验应用,利用智能手机传感器进行各种物理实验。本项目是对phyphox-android的二次开发,专为中国大学生物理学术竞赛(CUPT)演示而设计,专注于多传感器姿态测量和3D实时可视化。

开发目标

  1. 简化应用:移除不必要的功能和语言,专注于姿态测量
  2. 多传感器支持:集成加速度传感器、线性加速度传感器、重力传感器和旋转矢量传感器
  3. 3D实时可视化:使用OpenGL ES实现手机姿态的3D渲染
  4. 中文界面:仅保留中文语言支持
  5. 黑白主题:使用简洁的黑白色调设计

项目初始化与环境搭建

原始项目克隆

首先从GitHub克隆原始的phyphox-android项目:

1
git clone https://github.com/phyphox/phyphox-android.git

开发环境配置

  • Android Studio作为开发IDE
  • JDK 21
  • Android SDK 35
  • Gradle构建系统

第一阶段:功能简化与清理

移除不必要的功能模块

首先,我们移除了原始项目中不需要的功能:

1. 移除音频相关功能

删除了 AudioOutput.java 及相关音频处理类。

2. 移除蓝牙功能

删除了整个 Bluetooth 包,包括:

  • Bluetooth.java
  • BluetoothExperimentLoader.java
  • BluetoothInput.java
  • BluetoothOutput.java
  • BluetoothScanDialog.java
  • 以及其他蓝牙相关类

3. 移除相机功能

删除了 camera 包,包括:

  • CameraInput.kt
  • CameraPreviewFragment.kt
  • 相机分析器模块
  • 相机深度传感器模块

4. 移除GPS功能

删除了 GpsGeoid.javaGpsInput.java

5. 移除网络连接功能

删除了 NetworkConnection 包,包括MQTT相关功能。

语言文件清理

移除了除中文(zh-rCN)外的所有语言文件:

1
2
3
4
5
6
# 删除的语言目录示例
app/src/main/res/values-ar/
app/src/main/res/values-de/
app/src/main/res/values-es/
app/src/main/res/values-fr/
# ... 其他语言文件

修改 locales_config.xml,仅保留中文和英文:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" />
<locale android:name="zh-CN" />
</locale-config>

第二阶段:多传感器支持扩展

传感器类型定义

SensorInput.java 中扩展传感器类型:

1
2
3
4
5
6
7
8
public enum SensorName {
gyroscope,
accelerometer,
linear_acceleration,
gravity,
rotation_vector,
// ... 其他传感器
}

传感器解析方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static SensorName resolveSensorName(String name) {
switch (name) {
case "gyroscope":
return SensorName.gyroscope;
case "accelerometer":
return SensorName.accelerometer;
case "linear_acceleration":
return SensorName.linear_acceleration;
case "gravity":
return SensorName.gravity;
case "rotation_vector":
return SensorName.rotation_vector;
// ... 其他传感器
}
}

实验配置文件更新

gyroscope.phyphox 中添加新的传感器数据容器:

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
<data-containers>
<!-- 陀螺仪 -->
<container size="10000" static="false">
<output name="gyroTime" />
<output name="gyroX" />
<output name="gyroY" />
<output name="gyroZ" />
<output name="gyroAbs" />
</container>

<!-- 加速度传感器 -->
<container size="10000" static="false">
<output name="accTime" />
<output name="accX" />
<output name="accY" />
<output name="accZ" />
<output name="accAbs" />
</container>

<!-- 线性加速度传感器 -->
<container size="10000" static="false">
<output name="linearTime" />
<output name="linearX" />
<output name="linearY" />
<output name="linearZ" />
<output name="linearAbs" />
</container>

<!-- 重力传感器 -->
<container size="10000" static="false">
<output name="gravityTime" />
<output name="gravityX" />
<output name="gravityY" />
<output name="gravityZ" />
<output name="gravityAbs" />
</container>

<!-- 旋转矢量传感器 -->
<container size="10000" static="false">
<output name="rotationTime" />
<output name="rotationX" />
<output name="rotationY" />
<output name="rotationZ" />
</container>
</data-containers>

第三阶段:3D渲染功能实现

这是本项目的核心功能,使用OpenGL ES实现手机姿态的3D实时可视化。

FilamentView类创建

创建 FilamentView.java,继承自 GLSurfaceView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FilamentView extends GLSurfaceView implements GLSurfaceView.Renderer {

private int mProgram;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;
private FloatBuffer mVertexBuffer;
private IntBuffer mIndexBuffer;
private FloatBuffer mAxisVertexBuffer;
private float[] mRotationMatrix = new float[16];
private float[] mModelMatrix = new float[16];
private float[] mViewMatrix = new float[16];
private float[] mProjectionMatrix = new float[16];
private float[] mMVPMatrix = new float[16];
private float mScale = 1.0f;

// 手机尺寸:160.4mm x 75.1mm x 8.4mm
private static final float WIDTH = 75.1f / 1000.0f;
private static final float HEIGHT = 160.4f / 1000.0f;
private static final float THICKNESS = 8.4f / 1000.0f;
}

3D模型顶点定义

定义手机3D模型的顶点数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 顶点数据
private static final float[] VERTICES = {
// 前面
-WIDTH/2, -HEIGHT/2, THICKNESS/2,
WIDTH/2, -HEIGHT/2, THICKNESS/2,
WIDTH/2, HEIGHT/2, THICKNESS/2,
-WIDTH/2, HEIGHT/2, THICKNESS/2,
// 后面
-WIDTH/2, -HEIGHT/2, -THICKNESS/2,
WIDTH/2, -HEIGHT/2, -THICKNESS/2,
WIDTH/2, HEIGHT/2, -THICKNESS/2,
-WIDTH/2, HEIGHT/2, -THICKNESS/2
};

坐标轴绘制

添加X、Y、Z坐标轴的绘制:

1
2
3
4
5
6
7
8
9
10
11
12
// 坐标轴顶点数据
private static final float[] AXIS_VERTICES = {
// X轴(红色):从原点(0,0,0)到(0.15,0,0)
0.0f, 0.0f, 0.0f,
0.15f, 0.0f, 0.0f,
// Y轴(绿色):从原点(0,0,0)到(0,0.15,0)
0.0f, 0.0f, 0.0f,
0.0f, 0.15f, 0.0f,
// Z轴(蓝色):从原点(0,0,0)到(0,0,0.15)
0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.15f
};

OpenGL着色器

定义顶点着色器和片段着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"attribute vec4 vColor;" +
"varying vec4 fragmentColor;" +
"void main() {" +
" gl_Position = uMVPMatrix * vPosition;" +
" fragmentColor = vColor;" +
"}";

private static final String fragmentShaderCode =
"precision mediump float;" +
"varying vec4 fragmentColor;" +
"void main() {" +
" gl_FragColor = fragmentColor;" +
"}";

姿态更新方法

实现旋转矩阵的更新:

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
public void updateRotation(float[] rotationMatrix) {
System.arraycopy(rotationMatrix, 0, mRotationMatrix, 0, 16);
// 调整旋转方向以匹配手机实际运动
float[] invertedRotationMatrix = new float[16];
// 旋转矩阵的逆矩阵等于其转置矩阵(因为它是正交矩阵)
invertedRotationMatrix[0] = mRotationMatrix[0];
invertedRotationMatrix[1] = mRotationMatrix[4];
invertedRotationMatrix[2] = mRotationMatrix[8];
invertedRotationMatrix[3] = mRotationMatrix[12];
invertedRotationMatrix[4] = mRotationMatrix[1];
invertedRotationMatrix[5] = mRotationMatrix[5];
invertedRotationMatrix[6] = mRotationMatrix[9];
invertedRotationMatrix[7] = mRotationMatrix[13];
invertedRotationMatrix[8] = mRotationMatrix[2];
invertedRotationMatrix[9] = mRotationMatrix[6];
invertedRotationMatrix[10] = mRotationMatrix[10];
invertedRotationMatrix[11] = mRotationMatrix[14];
invertedRotationMatrix[12] = mRotationMatrix[3];
invertedRotationMatrix[13] = mRotationMatrix[7];
invertedRotationMatrix[14] = mRotationMatrix[11];
invertedRotationMatrix[15] = mRotationMatrix[15];

System.arraycopy(invertedRotationMatrix, 0, mRotationMatrix, 0, 16);
requestRender();
}

渲染循环实现

onDrawFrame 中实现3D渲染:

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
@Override
public void onDrawFrame(GL10 gl) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
GLES30.glUseProgram(mProgram);

// 计算 MVP 矩阵
// 重置模型矩阵为单位矩阵
setIdentityMatrix(mModelMatrix);
// 应用缩放
mModelMatrix[0] = mScale;
mModelMatrix[5] = mScale;
mModelMatrix[10] = mScale;
// 应用旋转
multiplyMatrices(mModelMatrix, mRotationMatrix, mModelMatrix);
// 应用视图矩阵
multiplyMatrices(mMVPMatrix, mViewMatrix, mModelMatrix);
// 应用投影矩阵
multiplyMatrices(mMVPMatrix, mProjectionMatrix, mMVPMatrix);

// 绘制手机模型
// ... 绘制代码

// 绘制坐标轴
// ... 绘制坐标轴代码
}

第四阶段:UI界面优化

启动界面

创建 SplashActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);

Button btnUnderstand = findViewById(R.id.btn_understand);
btnUnderstand.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SplashActivity.this,
de.rwth_aachen.phyphox.ExperimentList.ExperimentListActivity.class);
startActivity(intent);
finish();
}
});
}
}

启动界面布局 activity_splash.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
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="由liubai分支于开源项目phyphox-android进行二次开发,仅作CUPT参赛演示用途"
android:textSize="16sp"
android:textColor="#000000"
android:textAlignment="center"
android:padding="20dp"/>

<Button
android:id="@+id/btn_understand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/textView"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:text="明白"
android:textSize="16sp"
android:background="#000000"
android:textColor="#FFFFFF"
android:paddingHorizontal="40dp"
android:paddingVertical="10dp"
android:radius="8dp"/>

</RelativeLayout>

3D视图控制按钮

ExpView.java 中添加3D视图的缩放控制:

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
// Create a container for the 3D view and buttons
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);

// Create the buttons
LinearLayout buttonLayout = new LinearLayout(context);
buttonLayout.setOrientation(LinearLayout.HORIZONTAL);
buttonLayout.setGravity(Gravity.RIGHT);

// Zoom out button
MaterialButton zoomOutButton = new MaterialButton(context);
zoomOutButton.setText("-");
zoomOutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scale = Math.max(0.2f, scale - 0.2f);
filamentView.setScale(scale);
}
});

// Reset button
MaterialButton resetButton = new MaterialButton(context);
resetButton.setText("复位");
resetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scale = 1.0f;
filamentView.setScale(scale);
}
});

// Zoom in button
MaterialButton zoomInButton = new MaterialButton(context);
zoomInButton.setText("+");
zoomInButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scale += 0.2f;
filamentView.setScale(scale);
}
});

主题调整

修改颜色配置,使用黑白色调:

1
2
3
4
5
6
7
8
9
<resources>
<color name="phyphox_black">#000000</color>
<color name="phyphox_black_90">#1A1A1A</color>
<color name="phyphox_black_80">#333333</color>
<color name="phyphox_white">#FFFFFF</color>
<color name="phyphox_white_90">#E6E6E6</color>
<color name="phyphox_white_80">#CCCCCC</color>
<color name="phyphox_gray">#808080</color>
</resources>

第五阶段:传感器数据集成

旋转矢量传感器监听

Experiment.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
// 获取旋转矢量传感器
Sensor rotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
if (rotationVectorSensor != null) {
sensorManager.registerListener(rotationVectorListener, rotationVectorSensor,
SensorManager.SENSOR_DELAY_GAME);
}

// 传感器数据监听
final SensorEventListener rotationVectorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
float[] rotationMatrix = new float[16];
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values);

// 更新3D视图
for (filamentElement element : filamentElements) {
if (element.filamentView != null) {
element.filamentView.updateRotation(rotationMatrix);
}
}
}
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};

第六阶段:数据导出功能

导出配置

gyroscope.phyphox 中配置导出数据:

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
<export>
<set label="陀螺仪数据">
<data output="gyroTime" />
<data output="gyroX" />
<data output="gyroY" />
<data output="gyroZ" />
<data output="gyroAbs" />
</set>
<set label="加速度传感器数据">
<data output="accTime" />
<data output="accX" />
<data output="accY" />
<data output="accZ" />
<data output="accAbs" />
</set>
<set label="线性加速度传感器数据">
<data output="linearTime" />
<data output="linearX" />
<data output="linearY" />
<data output="linearZ" />
<data output="linearAbs" />
</set>
<set label="重力传感器数据">
<data output="gravityTime" />
<data output="gravityX" />
<data output="gravityY" />
<data output="gravityZ" />
<data output="gravityAbs" />
</set>
<set label="旋转矢量传感器数据">
<data output="rotationTime" />
<data output="rotationX" />
<data output="rotationY" />
<data output="rotationZ" />
</set>
</export>

关键技术点总结

1. 传感器坐标系

1
2
3
4
手机坐标系(设备坐标系):
- X轴:水平向右(红色)
- Y轴:垂直向上(绿色)
- Z轴:垂直屏幕向外(蓝色)

2. 旋转矩阵处理

旋转矩阵是正交矩阵,其逆矩阵等于其转置矩阵:

1
2
3
4
5
6
// 旋转矩阵的逆矩阵等于其转置矩阵
float[] invertedRotationMatrix = new float[16];
invertedRotationMatrix[0] = mRotationMatrix[0];
invertedRotationMatrix[1] = mRotationMatrix[4];
invertedRotationMatrix[2] = mRotationMatrix[8];
// ... 其他元素

3. OpenGL渲染优化

  • 使用按需渲染模式:GLSurfaceView.RENDERMODE_WHEN_DIRTY
  • 只在传感器数据变化时才调用 requestRender()
  • 合理的传感器采样率:SensorManager.SENSOR_DELAY_GAME

项目成果

功能特点

  1. ✅ 多传感器数据采集(陀螺仪、加速度、线性加速度、重力、旋转矢量)
  2. ✅ 实时3D姿态可视化
  3. ✅ 三轴坐标轴显示(X:红, Y:绿, Z:蓝)
  4. ✅ 3D模型缩放控制(放大、缩小、复位)
  5. ✅ 多格式数据导出(Excel、CSV)
  6. ✅ 简洁的中文界面
  7. ✅ 黑白主题设计

仓库提交历史

1
2
3
4
5
6
655f7cf9 删除多余功能,专注设备姿态记录
c0590ca3 Remove .trae folder
7041fab6 Remove deleted files
e56b2d69 Update README to Chinese
9fd497d7 Rename project to Gyroscope and update README
72980546 Update project with 3D rendering, splash screen, and other improvements

使用说明

  1. 启动应用,点击”明白”进入主界面
  2. 选择”多传感器姿态测量”实验
  3. 点击右上角播放按钮开始数据采集
  4. 切换到”3D渲染姿态”选项卡查看实时姿态
  5. 使用缩放按钮调整3D模型大小
  6. 点击停止按钮结束采集
  7. 使用导出功能保存数据

注意事项

  1. 本项目基于phyphox开源项目二次开发,仅作CUPT参赛演示用途
  2. 遵循GNU General Public License开源协议
  3. “phyphox”和”RWTH Aachen”是注册商标
  4. 建议在实际使用前进行充分测试

鸣谢:感谢RWTH亚琛大学提供的优秀开源项目phyphox,本项目在此基础上进行了针对性的二次开发。