基于phyphox的Android多传感器姿态测量与3D可视化开发全记录
原属项目:https://github.com/phyphox/phyphox-android
二次开发仓库:https://github.com/liubaiqing/Gyroscope
项目背景 phyphox是一款由RWTH亚琛大学开发的开源物理实验应用,利用智能手机传感器进行各种物理实验。本项目是对phyphox-android的二次开发,专为中国大学生物理学术竞赛(CUPT)演示而设计,专注于多传感器姿态测量和3D实时可视化。
开发目标
简化应用 :移除不必要的功能和语言,专注于姿态测量
多传感器支持 :集成加速度传感器、线性加速度传感器、重力传感器和旋转矢量传感器
3D实时可视化 :使用OpenGL ES实现手机姿态的3D渲染
中文界面 :仅保留中文语言支持
黑白主题 :使用简洁的黑白色调设计
项目初始化与环境搭建 原始项目克隆 首先从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.java 和 GpsInput.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 ; 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 = { 0.0f , 0.0f , 0.0f , 0.15f , 0.0f , 0.0f , 0.0f , 0.0f , 0.0f , 0.0f , 0.15f , 0.0f , 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); 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 LinearLayout container = new LinearLayout (context);container.setOrientation(LinearLayout.VERTICAL); LinearLayout buttonLayout = new LinearLayout (context);buttonLayout.setOrientation(LinearLayout.HORIZONTAL); buttonLayout.setGravity(Gravity.RIGHT); 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); } }); MaterialButton resetButton = new MaterialButton (context);resetButton.setText("复位" ); resetButton.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { scale = 1.0f ; filamentView.setScale(scale); } }); 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); 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
项目成果 功能特点
✅ 多传感器数据采集(陀螺仪、加速度、线性加速度、重力、旋转矢量)
✅ 实时3D姿态可视化
✅ 三轴坐标轴显示(X:红, Y:绿, Z:蓝)
✅ 3D模型缩放控制(放大、缩小、复位)
✅ 多格式数据导出(Excel、CSV)
✅ 简洁的中文界面
✅ 黑白主题设计
仓库提交历史 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
使用说明
启动应用,点击”明白”进入主界面
选择”多传感器姿态测量”实验
点击右上角播放按钮开始数据采集
切换到”3D渲染姿态”选项卡查看实时姿态
使用缩放按钮调整3D模型大小
点击停止按钮结束采集
使用导出功能保存数据
注意事项
本项目基于phyphox开源项目二次开发,仅作CUPT参赛演示用途
遵循GNU General Public License开源协议
“phyphox”和”RWTH Aachen”是注册商标
建议在实际使用前进行充分测试
鸣谢 :感谢RWTH亚琛大学提供的优秀开源项目phyphox,本项目在此基础上进行了针对性的二次开发。