AR Core与CameraX的融合:测量应用从原理到实现

引言:AR测量如何改变我们对现实的认知

在现实世界中,我们经常需要测量物体的大小:装修时需要量家具尺寸,购物时需要知道包装大小,工作中需要精确测量距离。传统的测量工具(卷尺、激光测距仪)都有局限性。现在,通过手机摄像头和AR技术,我们可以实现"所见即所得"的智能测量。本文将带你深入理解AR测量的原理,并实现一个完整的AR测量应用。

第一章:技术基础 - 理解AR测量的核心原理

1.1 AR测量技术栈架构

┌─────────────────────────────────────┐ │ 用户界面与交互层 │ │ 测量标注、手势识别、结果展示 │ ├─────────────────────────────────────┤ │ AR引擎层 (AR Core) │ │ 运动跟踪│环境理解│光照估计│点云生成 │ ├─────────────────────────────────────┤ │ 相机控制层 (CameraX) │ │ 图像采集│实时预览│图像分析│自动对焦 │ ├─────────────────────────────────────┤ │ 传感器融合层 │ │ 陀螺仪│加速度计│磁力计│深度传感器 │ ├─────────────────────────────────────┤ │ 计算机视觉算法层 │ │ 特征点检测│平面检测│距离计算│3D重建 │ └─────────────────────────────────────┘ 

1.2 AR测量与普通测量的对比

测量方式传统卷尺激光测距仪AR测量
精度±1-2mm±1-2mm±2-5cm
测量范围0-10m0-100m0-10m
操作难度中等简单非常简单
功能扩展单一单一长度、面积、体积、角度
环境要求需要反射面需要纹理丰富的平面
成本中等只需手机

1.3 AR测量的核心挑战与解决方案

挑战1:如何将2D屏幕坐标转换为3D世界坐标?

  • 解决方案:光线投射(Ray Casting) + 平面检测

挑战2:如何保证测量的准确性?

  • 解决方案:多帧优化 + 传感器校准 + 环境光补偿

挑战3:如何处理动态环境(如光照变化)?

  • 解决方案:自适应特征点跟踪 + 实时重定位

第二章:环境搭建与基础配置

2.1 项目依赖配置

// app/build.gradle android { compileSdk 34 defaultConfig { applicationId "com.example.armeasure" minSdk 24 // AR Core最低要求 targetSdk 34 versionCode 1 versionName "1.0" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { // AR Core核心库 implementation "com.google.ar:core:1.40.0" // Sceneform UX(AR场景管理) implementation "com.google.ar.sceneform.ux:sceneform-ux:1.40.0" // CameraX核心库 def camerax_version = "1.3.0" implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" implementation "androidx.camera:camera-lifecycle:${camerax_version}" implementation "androidx.camera:camera-view:${camerax_version}" // 视图相关 implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.10.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' // 数学计算(向量、矩阵运算) implementation 'org.apache.commons:commons-math3:3.6.1' // 单元测试 testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } 

2.2 权限与特性声明

<!-- AndroidManifest.xml --><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.example.armeasure"><!-- AR Core必需权限 --><uses-permissionandroid:name="android.permission.CAMERA"/><!-- 可选权限:提高AR体验 --><uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/><!-- AR Core必需特性声明 --><uses-featureandroid:name="android.hardware.camera.ar"android:required="true"/><uses-featureandroid:name="android.hardware.camera"android:required="true"/><uses-featureandroid:name="android.hardware.camera.autofocus"android:required="false"/><!-- 可选,但推荐 --><!-- 应用配置 --><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:theme="@style/Theme.ARMeasure"><!-- AR Core检查Activity --><activityandroid:name=".CheckArActivity"android:exported="true"android:theme="@style/Theme.ARMeasure.Fullscreen"><intent-filter><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/></intent-filter></activity><!-- 主测量Activity --><activityandroid:name=".MeasureActivity"android:configChanges="orientation|screenSize|keyboardHidden"android:exported="false"android:screenOrientation="portrait"android:theme="@style/Theme.ARMeasure.Fullscreen"/><!-- AR Core配置 --><meta-dataandroid:name="com.google.ar.core"android:value="required"/><!-- AR Core最小版本 --><meta-dataandroid:name="com.google.ar.core.min_apk_version"android:value="1.40.0"/><!-- 支持深度传感器(如果设备有) --><meta-dataandroid:name="com.google.ar.core.depth"android:value="optional"/></application></manifest>

第三章:CameraX与AR Core的协同工作

3.1 CameraX相机初始化

classARCameraManager(privateval context: Context,privateval surfaceProvider: Preview.SurfaceProvider ){privatelateinitvar cameraProvider: ProcessCameraProvider privatelateinitvar preview: Preview privatevar camera: Camera?=null// 相机配置dataclassCameraConfig(val targetResolution: Size =Size(1920,1080),val focusMode: Int = CameraSelector.LENS_FACING_BACK,val enableAutoFocus: Boolean =true,val frameRate: IntRange =30..30)/** * 初始化CameraX相机 */funinitializeCamera(config: CameraConfig =CameraConfig()): ListenableFuture<Camera>{val cameraProviderFuture = ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener({try{ cameraProvider = cameraProviderFuture.get()// 配置预览用例 preview = Preview.Builder().setTargetResolution(config.targetResolution).setTargetAspectRatio(AspectRatio.RATIO_16_9).build().also{ it.setSurfaceProvider(surfaceProvider)}// 选择摄像头(后置)val cameraSelector = CameraSelector.Builder().requireLensFacing(config.focusMode).build()// 绑定到生命周期 camera = cameraProvider.bindToLifecycle( context as LifecycleOwner, cameraSelector, preview )// 配置自动对焦if(config.enableAutoFocus){setupAutoFocus()}}catch(e: Exception){ Log.e("ARCameraManager","相机初始化失败", e)}}, ContextCompat.getMainExecutor(context))return cameraProviderFuture }/** * 设置连续自动对焦(AR测量需要稳定对焦) */privatefunsetupAutoFocus(){ camera?.cameraControl?.setLinearFocus(0f)// 0表示自动对焦// 监听对焦状态 camera?.cameraInfo?.focusState?.observe(context as LifecycleOwner){ focusState ->when(focusState?.state){ FocusState.STATE_FOCUSED ->{ Log.d("ARCameraManager","对焦成功")} FocusState.STATE_NOT_FOCUSED ->{ Log.d("ARCameraManager","未对焦")}else->{// 对焦中或其他状态}}}}/** * 获取相机内参(用于AR Core坐标转换) */fungetCameraIntrinsics(): CameraIntrinsics?{return camera?.cameraInfo?.cameraCharacteristics?.let{ characteristics ->val focalLength = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)?.firstOrNull()val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)val pixelArraySize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)if(focalLength !=null&& sensorSize !=null&& pixelArraySize !=null){CameraIntrinsics( focalLength = focalLength, sensorWidth = sensorSize.width, sensorHeight = sensorSize.height, imageWidth = pixelArraySize.width, imageHeight = pixelArraySize.height )}else{null}}}dataclassCameraIntrinsics(val focalLength: Float,// 焦距(毫米)val sensorWidth: Float,// 传感器宽度(毫米)val sensorHeight: Float,// 传感器高度(毫米)val imageWidth: Int,// 图像宽度(像素)val imageHeight: Int // 图像高度(像素)){// 计算焦距像素值funfocalLengthPixels(): Pair<Float, Float>{val fx =(focalLength * imageWidth)/ sensorWidth val fy =(focalLength * imageHeight)/ sensorHeight returnPair(fx, fy)}// 计算主点坐标(通常为图像中心)funprincipalPoint(): Pair<Float, Float>{val cx = imageWidth /2fval cy = imageHeight /2freturnPair(cx, cy)}}}

3.2 AR Core会话管理

classARSessionManager(privateval context: Context,privateval arSceneView: ArSceneView ){privatevar arSession: Session?=nullprivatevar arConfig: Config?=nullprivatevar isSessionCreated =false// AR会话状态enumclass SessionState { NOT_INITIALIZED, INITIALIZING, TRACKING, PAUSED, STOPPED, ERROR }privatevar currentState = SessionState.NOT_INITIALIZED /** * 创建AR会话(关键步骤) */funcreateARSession(): SessionState {if(isSessionCreated){return currentState }try{ currentState = SessionState.INITIALIZING // 1. 检查AR Core可用性val availability = ArCoreApk.getInstance().checkAvailability(context)if(!availability.isSupported){throwARNotSupportedException("设备不支持AR Core")}// 2. 请求安装AR Core(如果需要)if(availability.isTransient){// 显示安装对话框 ArCoreApk.getInstance().requestInstall(context,true)}// 3. 创建AR会话 arSession =Session(context).apply{// 配置会话 arConfig =Config(this).apply{// 启用平面检测 planeFindingMode = Config.PlaneFindingMode.HORIZONTAL // 启用光照估计 lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR // 启用深度(如果设备支持) depthMode = Config.DepthMode.AUTOMATIC // 启用点云(用于特征点可视化) cloudAnchorMode = Config.CloudAnchorMode.ENABLED }// 应用配置configure(arConfig)}// 4. 设置AR SceneView的会话 arSceneView.setupSession(arSession!!) isSessionCreated =true currentState = SessionState.TRACKING // 5. 开始平面检测setupPlaneDetection() Log.i("ARSessionManager","AR会话创建成功")}catch(e: Exception){ currentState = SessionState.ERROR Log.e("ARSessionManager","AR会话创建失败", e)}return currentState }/** * 设置平面检测回调 */privatefunsetupPlaneDetection(){ arSession?.setOnTapPlaneListener{ hitResult: HitResult, plane: Plane, motionEvent: MotionEvent ->// 当用户在平面上点击时触发 onPlaneTapped?.invoke(hitResult, plane, motionEvent)}// 监听平面更新 arSceneView.scene.addOnUpdateListener{ frameTime ->val frame = arSession?.update() frame?.let{// 获取所有检测到的平面val planes = it.getUpdatedTrackables(Plane::class.java)for(plane in planes){if(plane.trackingState == TrackingState.TRACKING){ onPlaneUpdated?.invoke(plane)}}}}}/** * 执行光线投射(屏幕坐标转3D坐标) */funperformRayCast(x: Float, y: Float): List<HitResult>?{val frame = arSession?.update()?:returnnullreturntry{// 执行光线投射 frame.hitTest(x, y)}catch(e: Exception){ Log.e("ARSessionManager","光线投射失败", e)null}}/** * 计算两个3D点之间的距离 */funcalculateDistance(point1: Pose, point2: Pose): Float {// 使用欧几里得距离公式val dx = point1.tx()- point2.tx()val dy = point1.ty()- point2.ty()val dz = point1.tz()- point2.tz()returnsqrt(dx * dx + dy * dy + dz * dz)}/** * 暂停AR会话 */funpause(){ arSession?.pause() currentState = SessionState.PAUSED }/** * 恢复AR会话 */funresume(){ arSession?.resume() currentState = SessionState.TRACKING }/** * 销毁AR会话 */fundestroy(){ arSession?.close() arSession =null isSessionCreated =false currentState = SessionState.STOPPED }// 回调接口var onPlaneTapped:((HitResult, Plane, MotionEvent)-> Unit)?=nullvar onPlaneUpdated:((Plane)-> Unit)?=nullclassARNotSupportedException(message: String):Exception(message)}

第四章:AR测量核心算法实现

4.1 屏幕到世界坐标转换算法

classCoordinateTransformer(privateval cameraIntrinsics: ARCameraManager.CameraIntrinsics ){/** * 屏幕坐标转相机标准化坐标(NDC) */funscreenToNDC(screenX: Float, screenY: Float, screenWidth: Int, screenHeight: Int): Pair<Float, Float>{// 归一化设备坐标(-1到1之间)val ndcX =(2.0f* screenX / screenWidth)-1.0fval ndcY =1.0f-(2.0f* screenY / screenHeight)// Y轴反向returnPair(ndcX, ndcY)}/** * NDC坐标转相机空间坐标 */funndcToCamera(ndcX: Float, ndcY: Float): Vector3 {// 获取相机内参val(fx, fy)= cameraIntrinsics.focalLengthPixels()val(cx, cy)= cameraIntrinsics.principalPoint()// 反向投影到相机空间val cameraX =(ndcX * cx)/ fx val cameraY =(ndcY * cy)/ fy val cameraZ =1.0f// 假设深度为1returnVector3(cameraX, cameraY, cameraZ)}/** * 相机空间坐标转世界坐标 */funcameraToWorld(cameraPoint: Vector3, cameraPose: Pose): Vector3 {// 获取相机的旋转矩阵和平移向量val rotationMatrix =FloatArray(16)val translationMatrix =FloatArray(16) cameraPose.toMatrix(rotationMatrix,0) cameraPose.toMatrix(translationMatrix,0)// 提取旋转和平移分量val rotation = Matrix3x3.fromArray(rotationMatrix)val translation =Vector3( translationMatrix[12], translationMatrix[13], translationMatrix[14])// 应用变换:世界坐标 = 旋转 * 相机坐标 + 平移val rotatedPoint = rotation.multiply(cameraPoint)val worldPoint = rotatedPoint.add(translation)return worldPoint }/** * 完整转换:屏幕坐标 -> 世界坐标 */funscreenToWorld( screenX: Float, screenY: Float, screenWidth: Int, screenHeight: Int, cameraPose: Pose, hitDepth: Float?=null): Vector3?{try{// 步骤1:屏幕坐标 -> NDCval(ndcX, ndcY)=screenToNDC(screenX, screenY, screenWidth, screenHeight)// 步骤2:NDC -> 相机坐标var cameraPoint =ndcToCamera(ndcX, ndcY)// 如果有深度信息,调整Z值 hitDepth?.let{ cameraPoint = cameraPoint.normalize().multiply(it)}// 步骤3:相机坐标 -> 世界坐标returncameraToWorld(cameraPoint, cameraPose)}catch(e: Exception){ Log.e("CoordinateTransformer","坐标转换失败", e)returnnull}}/** * 计算测量误差(基于设备移动) */funcalculateMeasurementError( point1: Vector3, point2: Vector3, cameraMovement: Float, distanceToObject: Float ): Float {// 误差模型:误差 = 基础误差 + 相机移动误差 + 距离误差val baseError =0.02f// 2cm基础误差// 相机移动带来的误差(假设移动1米带来5cm误差)val movementError = cameraMovement *0.05f// 距离带来的误差(越远误差越大)val distanceError = distanceToObject *0.03freturn baseError + movementError + distanceError }}// 数学工具类dataclassVector3(val x: Float,val y: Float,val z: Float){funadd(other: Vector3): Vector3 {returnVector3(x + other.x, y + other.y, z + other.z)}funsubtract(other: Vector3): Vector3 {returnVector3(x - other.x, y - other.y, z - other.z)}funmultiply(scalar: Float): Vector3 {returnVector3(x * scalar, y * scalar, z * scalar)}funnormalize(): Vector3 {val length =sqrt(x * x + y * y + z * z)returnif(length >0)Vector3(x / length, y / length, z / length)elsethis}fundistanceTo(other: Vector3): Float {val dx = x - other.x val dy = y - other.y val dz = z - other.z returnsqrt(dx * dx + dy * dy + dz * dz)}fundot(other: Vector3): Float {return x * other.x + y * other.y + z * other.z }funcross(other: Vector3): Vector3 {returnVector3( y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x )}}class Matrix3x3 privateconstructor(privatevaldata: FloatArray){companionobject{funfromArray(array: FloatArray): Matrix3x3 {// 提取3x3旋转矩阵(忽略平移部分)returnMatrix3x3(floatArrayOf( array[0], array[1], array[2], array[4], array[5], array[6], array[8], array[9], array[10]))}funidentity(): Matrix3x3 {returnMatrix3x3(floatArrayOf(1f,0f,0f,0f,1f,0f,0f,0f,1f))}}funmultiply(vector: Vector3): Vector3 {returnVector3(data[0]* vector.x +data[1]* vector.y +data[2]* vector.z,data[3]* vector.x +data[4]* vector.y +data[5]* vector.z,data[6]* vector.x +data[7]* vector.y +data[8]* vector.z )}}

4.2 多点测量与几何计算

class GeometryCalculator {/** * 计算点到直线的距离 */funpointToLineDistance( point: Vector3, linePoint1: Vector3, linePoint2: Vector3 ): Float {val lineVector = linePoint2.subtract(linePoint1)val pointVector = point.subtract(linePoint1)val lineLength = lineVector.distanceTo(Vector3(0f,0f,0f))if(lineLength ==0f)return pointVector.distanceTo(Vector3(0f,0f,0f))val projectionLength = pointVector.dot(lineVector)/ lineLength val projection = lineVector.normalize().multiply(projectionLength)return pointVector.subtract(projection).distanceTo(Vector3(0f,0f,0f))}/** * 计算三角形的面积(海伦公式) */funtriangleArea( pointA: Vector3, pointB: Vector3, pointC: Vector3 ): Float {val sideAB = pointA.distanceTo(pointB)val sideBC = pointB.distanceTo(pointC)val sideCA = pointC.distanceTo(pointA)val s =(sideAB + sideBC + sideCA)/2freturnsqrt(s *(s - sideAB)*(s - sideBC)*(s - sideCA))}/** * 计算多边形的面积(适用于平面多边形) */funpolygonArea(points: List<Vector3>): Float {if(points.size <3)return0fvar area =0f// 使用鞋带公式(Shoelace Formula)for(i in points.indices){val current = points[i]val next = points[(i +1)% points.size] area +=(current.x * next.z - next.x * current.z)}returnabs(area)/2f}/** * 计算矩形的面积 */funrectangleArea( corner1: Vector3, corner2: Vector3, corner3: Vector3 ): Float {val width = corner1.distanceTo(corner2)val height = corner2.distanceTo(corner3)return width * height }/** * 计算体积(长方体) */funcuboidVolume( corner1: Vector3, corner2: Vector3, corner3: Vector3, heightPoint: Vector3 ): Float {// 计算底面积val baseArea =rectangleArea(corner1, corner2, corner3)// 计算高度(点到平面的距离)val height =pointToPlaneDistance(heightPoint, corner1, corner2, corner3)return baseArea * height }/** * 计算点到平面的距离 */funpointToPlaneDistance( point: Vector3, planePoint1: Vector3, planePoint2: Vector3, planePoint3: Vector3 ): Float {// 计算平面法向量val vector1 = planePoint2.subtract(planePoint1)val vector2 = planePoint3.subtract(planePoint1)val normal = vector1.cross(vector2).normalize()// 计算点到平面的距离val vectorToPoint = point.subtract(planePoint1)returnabs(vectorToPoint.dot(normal))}/** * 计算角度(三点法) */funcalculateAngle( vertex: Vector3, point1: Vector3, point2: Vector3 ): Float {val vector1 = point1.subtract(vertex).normalize()val vector2 = point2.subtract(vertex).normalize()val dotProduct = vector1.dot(vector2)val angle =acos(max(-1f,min(1f, dotProduct)))return Math.toDegrees(angle.toDouble()).toFloat()}/** * 检查点是否共线 */funarePointsCollinear( point1: Vector3, point2: Vector3, point3: Vector3, tolerance: Float =0.01f): Boolean {val area =triangleArea(point1, point2, point3)return area < tolerance }/** * 计算最佳拟合平面(最小二乘法) */funbestFitPlane(points: List<Vector3>): PlaneEquation {if(points.size <3){throwIllegalArgumentException("至少需要3个点来计算平面")}// 计算重心val centroid =Vector3( points.map{ it.x }.average().toFloat(), points.map{ it.y }.average().toFloat(), points.map{ it.z }.average().toFloat())// 构建协方差矩阵var xx =0fvar xy =0fvar xz =0fvar yy =0fvar yz =0fvar zz =0ffor(point in points){val dx = point.x - centroid.x val dy = point.y - centroid.y val dz = point.z - centroid.z xx += dx * dx xy += dx * dy xz += dx * dz yy += dy * dy yz += dy * dz zz += dz * dz }// 计算特征值和特征向量val detX = yy * zz - yz * yz val detY = xx * zz - xz * xz val detZ = xx * yy - xy * xy val maxDet =maxOf(detX, detY, detZ)val normal =when{ maxDet == detX ->Vector3( detX, xz * yz - xy * zz, xy * yz - xz * yy ) maxDet == detY ->Vector3( xz * yz - xy * zz, detY, xy * xz - yz * xx )else->Vector3( xy * yz - xz * yy, xy * xz - yz * xx, detZ )}.normalize()// 平面方程: ax + by + cz + d = 0val d =-(normal.x * centroid.x + normal.y * centroid.y + normal.z * centroid.z)returnPlaneEquation(normal.x, normal.y, normal.z, d)}dataclassPlaneEquation(val a: Float,val b: Float,val c: Float,val d: Float){fundistanceToPoint(point: Vector3): Float {returnabs(a * point.x + b * point.y + c * point.z + d)/sqrt(a * a + b * b + c * c)}}}

第五章:用户界面与交互设计

5.1 测量界面实现

class MeasureActivity :AppCompatActivity(){privatelateinitvar arSceneView: ArSceneView privatelateinitvar cameraPreviewView: PreviewView privatelateinitvar controlPanel: LinearLayout privatelateinitvar measurementView: MeasurementOverlayView privatelateinitvar arCameraManager: ARCameraManager privatelateinitvar arSessionManager: ARSessionManager privatelateinitvar coordinateTransformer: CoordinateTransformer // 测量状态管理privateenumclass MeasureMode { LENGTH,// 长度测量 AREA,// 面积测量 VOLUME,// 体积测量 ANGLE,// 角度测量 MULTI_POINT // 多点测量}privatevar currentMode = MeasureMode.LENGTH privateval measurementPoints = mutableListOf<MeasurementPoint>()privatevar isMeasuring =falseoverridefunonCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_measure)// 初始化视图initViews()// 请求权限requestPermissions()// 初始化AR组件initARComponents()// 设置交互监听setupInteractionListeners()}privatefuninitViews(){ arSceneView =findViewById(R.id.ar_scene_view) cameraPreviewView =findViewById(R.id.camera_preview_view) controlPanel =findViewById(R.id.control_panel) measurementView =findViewById(R.id.measurement_overlay)// 设置AR SceneView配置 arSceneView.apply{ planeRenderer.isVisible =true planeRenderer.isShadowReceiver =true// 设置平面渲染颜色(半透明蓝色) planeRenderer.material.setFloat3("color", Color.colorToFloatArray(Color.argb(100,0,120,255)))}}privatefuninitARComponents(){// 初始化CameraX相机 arCameraManager =ARCameraManager( context =this, surfaceProvider = cameraPreviewView.surfaceProvider )// 初始化AR Core会话 arSessionManager =ARSessionManager(this, arSceneView)// 获取相机内参并初始化坐标转换器 arCameraManager.getCameraIntrinsics()?.let{ intrinsics -> coordinateTransformer =CoordinateTransformer(intrinsics)}// 设置AR会话回调 arSessionManager.onPlaneTapped ={ hitResult, plane, motionEvent ->handlePlaneTap(hitResult, plane, motionEvent)} arSessionManager.onPlaneUpdated ={ plane ->updatePlaneVisualization(plane)}}privatefunsetupInteractionListeners(){// AR SceneView触摸监听 arSceneView.setOnTouchListener{ _, event ->when(event.action){ MotionEvent.ACTION_DOWN ->{if(isMeasuring){handleMeasurementTap(event.x, event.y)true}else{false}}else->false}}// 控制按钮监听setupControlButtons()}privatefunsetupControlButtons(){// 长度测量按钮 findViewById<Button>(R.id.btn_length).setOnClickListener{ currentMode = MeasureMode.LENGTH resetMeasurement()showInstruction("点击起点和终点测量长度")}// 面积测量按钮 findViewById<Button>(R.id.btn_area).setOnClickListener{ currentMode = MeasureMode.AREA resetMeasurement()showInstruction("点击三个点测量矩形面积")}// 体积测量按钮 findViewById<Button>(R.id.btn_volume).setOnClickListener{ currentMode = MeasureMode.VOLUME resetMeasurement()showInstruction("点击四个点测量长方体体积")}// 角度测量按钮 findViewById<Button>(R.id.btn_angle).setOnClickListener{ currentMode = MeasureMode.ANGLE resetMeasurement()showInstruction("点击三个点测量角度")}// 多点测量按钮 findViewById<Button>(R.id.btn_multi_point).setOnClickListener{ currentMode = MeasureMode.MULTI_POINT resetMeasurement()showInstruction("点击任意点,双击结束测量")}// 清除按钮 findViewById<Button>(R.id.btn_clear).setOnClickListener{resetMeasurement()}// 保存按钮 findViewById<Button>(R.id.btn_save).setOnClickListener{saveMeasurement()}// 校准按钮 findViewById<Button>(R.id.btn_calibrate).setOnClickListener{calibrateMeasurement()}}/** * 处理测量点击 */privatefunhandleMeasurementTap(x: Float, y: Float){// 执行光线投射val hitResults = arSessionManager.performRayCast(x, y) hitResults?.firstOrNull{ hit -> hit.trackable is Plane &&(hit.trackable as Plane).isPoseInPolygon(hit.hitPose)}?.let{ validHit ->// 获取世界坐标val worldPoint =Vector3( validHit.hitPose.tx(), validHit.hitPose.ty(), validHit.hitPose.tz())// 创建测量点val measurementPoint =MeasurementPoint( worldPosition = worldPoint, screenPosition =PointF(x, y), timestamp = System.currentTimeMillis())// 根据当前模式处理点when(currentMode){ MeasureMode.LENGTH ->handleLengthMeasurement(measurementPoint) MeasureMode.AREA ->handleAreaMeasurement(measurementPoint) MeasureMode.VOLUME ->handleVolumeMeasurement(measurementPoint) MeasureMode.ANGLE ->handleAngleMeasurement(measurementPoint) MeasureMode.MULTI_POINT ->handleMultiPointMeasurement(measurementPoint)}// 更新UIupdateMeasurementDisplay()}?: run {showToast("请点击在检测到的平面上")}}/** * 处理长度测量(两点) */privatefunhandleLengthMeasurement(point: MeasurementPoint){ measurementPoints.add(point)if(measurementPoints.size ==2){// 计算距离val distance = coordinateTransformer.calculateDistance( Pose.makeTranslation( measurementPoints[0].worldPosition.x, measurementPoints[0].worldPosition.y, measurementPoints[0].worldPosition.z ), Pose.makeTranslation( measurementPoints[1].worldPosition.x, measurementPoints[1].worldPosition.y, measurementPoints[1].worldPosition.z ))// 显示结果showResult("长度: ${String.format("%.2f", distance)} 米")// 重置为下一次测量resetMeasurement()}}/** * 处理面积测量(三点确定矩形) */privatefunhandleAreaMeasurement(point: MeasurementPoint){ measurementPoints.add(point)if(measurementPoints.size ==3){val calculator =GeometryCalculator()val area = calculator.rectangleArea( measurementPoints[0].worldPosition, measurementPoints[1].worldPosition, measurementPoints[2].worldPosition )showResult("面积: ${String.format("%.2f", area)} 平方米")resetMeasurement()}}/** * 处理角度测量(三点确定角度) */privatefunhandleAngleMeasurement(point: MeasurementPoint){ measurementPoints.add(point)if(measurementPoints.size ==3){val calculator =GeometryCalculator()val angle = calculator.calculateAngle( measurementPoints[1].worldPosition,// 顶点 measurementPoints[0].worldPosition,// 边1 measurementPoints[2].worldPosition // 边2)showResult("角度: ${String.format("%.1f", angle)}°")resetMeasurement()}}/** * 更新测量显示 */privatefunupdateMeasurementDisplay(){ measurementView.updatePoints(measurementPoints.map{ it.screenPosition })// 根据模式绘制不同的连线when(currentMode){ MeasureMode.LENGTH ->{if(measurementPoints.size >=2){ measurementView.drawLine( measurementPoints[0].screenPosition, measurementPoints[1].screenPosition )}} MeasureMode.AREA ->{if(measurementPoints.size >=3){ measurementView.drawPolygon( measurementPoints.take(3).map{ it.screenPosition })}}// 其他模式的绘制逻辑...}}dataclassMeasurementPoint(val worldPosition: Vector3,val screenPosition: PointF,val timestamp: Long )}

5.2 测量标注视图

class MeasurementOverlayView @JvmOverloadsconstructor( context: Context, attrs: AttributeSet?=null, defStyleAttr: Int =0):View(context, attrs, defStyleAttr){privateval measurementPaint =Paint().apply{ color = Color.GREEN strokeWidth =4f style = Paint.Style.STROKE isAntiAlias =true}privateval pointPaint =Paint().apply{ color = Color.RED style = Paint.Style.FILL isAntiAlias =true}privateval textPaint =Paint().apply{ color = Color.WHITE textSize =48f isAntiAlias =true typeface = Typeface.DEFAULT_BOLD }privateval pathPaint =Paint().apply{ color = Color.argb(100,0,255,0) style = Paint.Style.FILL isAntiAlias =true}privateval measurementPoints = mutableListOf<PointF>()privateval measurementLines = mutableListOf<Pair<PointF, PointF>>()privateval measurementPolygons = mutableListOf<List<PointF>>()privateval measurementTexts = mutableListOf<TextAnnotation>()privatevar currentPath: Path?=nullfunupdatePoints(points: List<PointF>){ measurementPoints.clear() measurementPoints.addAll(points)invalidate()}fundrawLine(start: PointF, end: PointF){ measurementLines.add(Pair(start, end))invalidate()}fundrawPolygon(points: List<PointF>){ measurementPolygons.add(points)invalidate()}fundrawText(text: String, position: PointF){ measurementTexts.add(TextAnnotation(text, position))invalidate()}funclear(){ measurementPoints.clear() measurementLines.clear() measurementPolygons.clear() measurementTexts.clear() currentPath =nullinvalidate()}overridefunonDraw(canvas: Canvas){super.onDraw(canvas)// 绘制点 measurementPoints.forEach{ point -> canvas.drawCircle(point.x, point.y,15f, pointPaint)// 绘制点编号val index = measurementPoints.indexOf(point) canvas.drawText("${index +1}", point.x +20f, point.y -20f, textPaint )}// 绘制线 measurementLines.forEach{(start, end)-> canvas.drawLine(start.x, start.y, end.x, end.y, measurementPaint)// 在线段中点显示长度val midX =(start.x + end.x)/2val midY =(start.y + end.y)/2// 计算长度(像素),实际应用中应该转换真实世界长度val lengthPx =sqrt((end.x - start.x).pow(2)+(end.y - start.y).pow(2)) canvas.drawText("${String.format("%.1f", lengthPx)}px", midX, midY -10f, textPaint )}// 绘制多边形 measurementPolygons.forEach{ polygon ->if(polygon.size >=3){val path =Path() path.moveTo(polygon[0].x, polygon[0].y)for(i in1 until polygon.size){ path.lineTo(polygon[i].x, polygon[i].y)} path.close()// 绘制填充 canvas.drawPath(path, pathPaint)// 绘制边框 canvas.drawPath(path, measurementPaint)}}// 绘制文字标注 measurementTexts.forEach{ textAnnotation -> canvas.drawText( textAnnotation.text, textAnnotation.position.x, textAnnotation.position.y, textPaint )}// 绘制当前路径 currentPath?.let{ canvas.drawPath(it, measurementPaint)}}funstartNewPath(startPoint: PointF){ currentPath =Path().apply{moveTo(startPoint.x, startPoint.y)}invalidate()}funaddToPath(point: PointF){ currentPath?.lineTo(point.x, point.y)invalidate()}funclosePath(){ currentPath?.close() currentPath?.let{ path -> measurementPolygons.add(extractPointsFromPath(path))} currentPath =nullinvalidate()}privatefunextractPointsFromPath(path: Path): List<PointF>{val points = mutableListOf<PointF>()val pathMeasure =PathMeasure(path,false)val length = pathMeasure.length var distance =0fval step = length /20// 采样20个点while(distance < length){val coords =FloatArray(2) pathMeasure.getPosTan(distance, coords,null) points.add(PointF(coords[0], coords[1])) distance += step }return points }dataclassTextAnnotation(val text: String,val position: PointF)}

第六章:精度优化与校准

6.1 测量精度优化算法

class MeasurementOptimizer {/** * 卡尔曼滤波器 - 用于平滑测量结果 */classKalmanFilter(privateval processNoise: Float =0.01f,privateval measurementNoise: Float =0.1f,privateval estimationError: Float =1f){privatevar currentEstimate: Float =0fprivatevar currentError: Float = estimationError funupdate(measurement: Float): Float {// 预测步骤val predictedError = currentError + processNoise // 更新步骤val kalmanGain = predictedError /(predictedError + measurementNoise) currentEstimate = currentEstimate + kalmanGain *(measurement - currentEstimate) currentError =(1- kalmanGain)* predictedError return currentEstimate }funreset(){ currentEstimate =0f currentError = estimationError }}/** * 多帧平均 - 提高测量稳定性 */classMultiFrameAverager(privateval windowSize: Int =10){privateval measurements = ArrayDeque<Float>()funaddMeasurement(measurement: Float): Float { measurements.addLast(measurement)if(measurements.size > windowSize){ measurements.removeFirst()}return measurements.average().toFloat()}funclear(){ measurements.clear()}}/** * 移动平均滤波器 */funmovingAverageFilter( measurements: List<Float>, windowSize: Int =5): List<Float>{if(measurements.size < windowSize){return measurements }val result = mutableListOf<Float>()for(i in measurements.indices){val start =max(0, i - windowSize /2)val end =min(measurements.size -1, i + windowSize /2)val window = measurements.subList(start, end +1) result.add(window.average().toFloat())}return result }/** * 异常值检测与剔除 */funremoveOutliers( measurements: List<Float>, threshold: Float =2.0f): List<Float>{if(measurements.size <3)return measurements val mean = measurements.average().toFloat()val stdDev =calculateStandardDeviation(measurements, mean)return measurements.filter{ value ->abs(value - mean)<= threshold * stdDev }}privatefuncalculateStandardDeviation(values: List<Float>, mean: Float): Float {val variance = values.map{(it - mean).pow(2)}.average().toFloat()returnsqrt(variance)}/** * 基于传感器数据的精度补偿 */funcompensateWithSensorData( measurement: Float, gyroData: GyroData, accelerometerData: AccelerometerData, magneticData: MagneticData ): Float {// 1. 设备移动补偿val movementCompensation =calculateMovementCompensation(gyroData, accelerometerData)// 2. 方向补偿(考虑设备倾斜)val orientationCompensation =calculateOrientationCompensation( accelerometerData, magneticData )// 3. 环境光补偿(如果可用)val lightCompensation =calculateLightCompensation()// 应用补偿val compensated = measurement * movementCompensation * orientationCompensation * lightCompensation returnmax(0f, compensated)}privatefuncalculateMovementCompensation( gyroData: GyroData, accelerometerData: AccelerometerData ): Float {// 计算设备移动速度val angularSpeed =sqrt( gyroData.x.pow(2)+ gyroData.y.pow(2)+ gyroData.z.pow(2))val linearAcceleration =sqrt( accelerometerData.x.pow(2)+ accelerometerData.y.pow(2)+ accelerometerData.z.pow(2))// 移动越大,补偿越多(误差越大)val movementFactor =1.0f+ angularSpeed *0.1f+ linearAcceleration *0.05freturn1.0f/ movementFactor }privatefuncalculateOrientationCompensation( accelerometerData: AccelerometerData, magneticData: MagneticData ): Float {// 计算设备倾斜角度val gravity =Vector3( accelerometerData.x, accelerometerData.y, accelerometerData.z ).normalize()val tiltAngle =acos(gravity.dot(Vector3(0f,0f,1f)))// 角度越大(设备越倾斜),补偿越多val tiltCompensation =1.0f+abs(sin(tiltAngle))*0.2freturn1.0f/ tiltCompensation }privatefuncalculateLightCompensation(): Float {// 简化版本,实际应该使用光照传感器return1.0f}dataclassGyroData(val x: Float,val y: Float,val z: Float,val timestamp: Long)dataclassAccelerometerData(val x: Float,val y: Float,val z: Float,val timestamp: Long)dataclassMagneticData(val x: Float,val y: Float,val z: Float,val timestamp: Long)}

6.2 校准系统实现

classCalibrationSystem(privateval context: Context,privateval arSessionManager: ARSessionManager ){companionobject{// 已知长度的参考物体(单位:米)privateval REFERENCE_OBJECTS =mapOf("A4纸"toPair(0.297f,0.210f),// A4纸尺寸"信用卡"toPair(0.0856f,0.0539f),// 信用卡尺寸"iPhone 14"toPair(0.1467f,0.0715f)// iPhone 14尺寸)}privatevar calibrationFactor =1.0fprivatevar calibrationHistory = mutableListOf<CalibrationRecord>()privatevar isCalibrating =false/** * 开始校准流程 */funstartCalibration(referenceObjectName: String): CalibrationResult {val referenceSize = REFERENCE_OBJECTS[referenceObjectName]?:return CalibrationResult.error("未知的参考物体") isCalibrating =true calibrationHistory.clear()return CalibrationResult.success( message ="请测量 ${referenceObjectName} 的${referenceSize.first}米边", expectedLength = referenceSize.first, objectName = referenceObjectName )}/** * 添加校准测量数据 */funaddCalibrationMeasurement( measuredLength: Float, expectedLength: Float, confidence: Float ): CalibrationResult {if(!isCalibrating){return CalibrationResult.error("未开始校准")}// 计算校准因子val factor = expectedLength / measuredLength // 保存校准记录val record =CalibrationRecord( measuredLength = measuredLength, expectedLength = expectedLength, factor = factor, confidence = confidence, timestamp = System.currentTimeMillis()) calibrationHistory.add(record)// 计算平均校准因子(加权平均)val totalWeight = calibrationHistory.sumOf{ it.confidence.toDouble()}val weightedSum = calibrationHistory.sumOf{(it.factor * it.confidence).toDouble()} calibrationFactor =(weightedSum / totalWeight).toFloat()return CalibrationResult.success( message ="校准进度: ${calibrationHistory.size}/3", calibrationFactor = calibrationFactor, confidence = calibrationHistory.map{ it.confidence }.average().toFloat())}/** * 完成校准 */funfinishCalibration(): CalibrationResult {if(!isCalibrating || calibrationHistory.isEmpty()){return CalibrationResult.error("没有校准数据")} isCalibrating =false// 保存校准结果saveCalibrationData()return CalibrationResult.success( message ="校准完成", calibrationFactor = calibrationFactor, confidence =calculateOverallConfidence())}/** * 应用校准到测量值 */funapplyCalibration(rawMeasurement: Float): Float {return rawMeasurement * calibrationFactor }/** * 验证校准准确性 */funverifyCalibration(knownLength: Float, measuredLength: Float): VerificationResult {val calibratedLength =applyCalibration(measuredLength)val error =abs(calibratedLength - knownLength)val errorPercentage =(error / knownLength)*100val accuracy =when{ errorPercentage <1->"优秀 (<1%)" errorPercentage <3->"良好 (1-3%)" errorPercentage <5->"一般 (3-5%)"else->"较差 (>5%)"}returnVerificationResult( expectedLength = knownLength, measuredLength = measuredLength, calibratedLength = calibratedLength, error = error, errorPercentage = errorPercentage, accuracy = accuracy, isAcceptable = errorPercentage <5)}/** * 保存校准数据 */privatefunsaveCalibrationData(){val sharedPrefs = context.getSharedPreferences("ar_calibration", Context.MODE_PRIVATE)with(sharedPrefs.edit()){putFloat("calibration_factor", calibrationFactor)putLong("calibration_time", System.currentTimeMillis())putInt("calibration_count", calibrationHistory.size)// 保存历史记录(JSON格式)val historyJson =Gson().toJson(calibrationHistory)putString("calibration_history", historyJson)apply()}}/** * 加载校准数据 */funloadCalibrationData(): Boolean {val sharedPrefs = context.getSharedPreferences("ar_calibration", Context.MODE_PRIVATE) calibrationFactor = sharedPrefs.getFloat("calibration_factor",1.0f)// 检查校准是否过期(30天)val calibrationTime = sharedPrefs.getLong("calibration_time",0)val daysSinceCalibration =(System.currentTimeMillis()- calibrationTime)/(1000*60*60*24)return daysSinceCalibration <30}privatefuncalculateOverallConfidence(): Float {if(calibrationHistory.isEmpty())return0f// 置信度基于:测量次数、单个测量置信度、测量一致性val countConfidence =min(1.0f, calibrationHistory.size /5.0f)val avgConfidence = calibrationHistory.map{ it.confidence }.average().toFloat()// 计算测量一致性(方差)val variance = calibrationHistory.map{ it.factor }.let{ factors ->val mean = factors.average().toFloat() factors.map{(it - mean).pow(2)}.average().toFloat()}val consistency =1.0f/(1.0f+ variance *10)return(countConfidence *0.3f+ avgConfidence *0.4f+ consistency *0.3f)}dataclassCalibrationRecord(val measuredLength: Float,val expectedLength: Float,val factor: Float,val confidence: Float,val timestamp: Long )dataclassCalibrationResult(val success: Boolean,val message: String,val calibrationFactor: Float =1.0f,val confidence: Float =0f,val expectedLength: Float =0f,val objectName: String =""){companionobject{funsuccess( message: String, calibrationFactor: Float =1.0f, confidence: Float =0f, expectedLength: Float =0f, objectName: String =""): CalibrationResult {returnCalibrationResult( success =true, message = message, calibrationFactor = calibrationFactor, confidence = confidence, expectedLength = expectedLength, objectName = objectName )}funerror(message: String): CalibrationResult {returnCalibrationResult( success =false, message = message )}}}dataclassVerificationResult(val expectedLength: Float,val measuredLength: Float,val calibratedLength: Float,val error: Float,val errorPercentage: Float,val accuracy: String,val isAcceptable: Boolean )}

第七章:高级功能扩展

7.1 面积与体积测量

class AdvancedMeasurement {/** * 多边形面积测量 */class AreaMeasurer {funmeasurePolygonArea(points: List<Vector3>): AreaResult {if(points.size <3){return AreaResult.error("至少需要3个点来测量面积")}val calculator =GeometryCalculator()// 检查点是否共面if(!arePointsCoplanar(points)){return AreaResult.error("点不在同一平面上")}// 计算面积val area = calculator.polygonArea(points)// 计算周长val perimeter =calculatePerimeter(points)// 计算中心点val centroid =calculateCentroid(points)return AreaResult.success( area = area, perimeter = perimeter, centroid = centroid, pointCount = points.size, shapeType =classifyShape(points))}privatefunarePointsCoplanar(points: List<Vector3>, tolerance: Float =0.01f): Boolean {if(points.size <4)returntrue// 使用前三个点确定平面val plane =GeometryCalculator().bestFitPlane(points.take(3))// 检查其他点是否在平面上return points.all{ point -> plane.distanceToPoint(point)< tolerance }}privatefuncalculatePerimeter(points: List<Vector3>): Float {var perimeter =0ffor(i in points.indices){val current = points[i]val next = points[(i +1)% points.size] perimeter += current.distanceTo(next)}return perimeter }privatefuncalculateCentroid(points: List<Vector3>): Vector3 {val sumX = points.sumOf{ it.x.toDouble()}val sumY = points.sumOf{ it.y.toDouble()}val sumZ = points.sumOf{ it.z.toDouble()}val count = points.size.toDouble()returnVector3((sumX / count).toFloat(),(sumY / count).toFloat(),(sumZ / count).toFloat())}privatefunclassifyShape(points: List<Vector3>): String {returnwhen(points.size){3->"三角形"4->classifyQuadrilateral(points)5->"五边形"6->"六边形"else->"多边形 (${points.size}边)"}}privatefunclassifyQuadrilateral(points: List<Vector3>): String {if(points.size !=4)return"未知"val sides =listOf( points[0].distanceTo(points[1]), points[1].distanceTo(points[2]), points[2].distanceTo(points[3]), points[3].distanceTo(points[0]))val angles =listOf(GeometryCalculator().calculateAngle(points[1], points[0], points[2]),GeometryCalculator().calculateAngle(points[2], points[1], points[3]),GeometryCalculator().calculateAngle(points[3], points[2], points[0]),GeometryCalculator().calculateAngle(points[0], points[3], points[1]))// 检查是否为矩形val isRectangle = angles.all{abs(it -90f)<5f}// 检查是否为正方形val sideVariance = sides.map{(it - sides.average()).pow(2)}.average()val isSquare = isRectangle && sideVariance <0.001freturnwhen{ isSquare ->"正方形" isRectangle ->"矩形"else->"四边形"}}}/** * 体积测量 */class VolumeMeasurer {funmeasureCuboidVolume( basePoints: List<Vector3>,// 底面多边形(至少3个点) height: Float // 高度): VolumeResult {if(basePoints.size <3){return VolumeResult.error("至少需要3个点定义底面")}val areaMeasurer =AreaMeasurer()val baseAreaResult = areaMeasurer.measurePolygonArea(basePoints)if(!baseAreaResult.success){return VolumeResult.error("底面面积计算失败: ${baseAreaResult.message}")}val volume = baseAreaResult.area * height // 计算表面积val lateralArea =calculateLateralArea(basePoints, height)val totalArea = baseAreaResult.area *2+ lateralArea return VolumeResult.success( volume = volume, baseArea = baseAreaResult.area, height = height, surfaceArea = totalArea, shapeType ="${baseAreaResult.shapeType}柱体")}funmeasureIrregularVolume( bottomPoints: List<Vector3>, topPoints: List<Vector3>): VolumeResult {if(bottomPoints.size != topPoints.size){return VolumeResult.error("上下底面点数不一致")}if(bottomPoints.size <3){return VolumeResult.error("至少需要3个点定义底面")}// 使用棱台体积公式val bottomArea =AreaMeasurer().measurePolygonArea(bottomPoints).area val topArea =AreaMeasurer().measurePolygonArea(topPoints).area // 计算平均高度val heights = bottomPoints.indices.map{ i -> bottomPoints[i].distanceTo(topPoints[i])}val avgHeight = heights.average()// 棱台体积公式: V = (h/3) * (A1 + A2 + sqrt(A1*A2))val volume =(avgHeight /3)*(bottomArea + topArea +sqrt(bottomArea * topArea))return VolumeResult.success( volume = volume.toFloat(), baseArea = bottomArea, topArea = topArea, avgHeight = avgHeight.toFloat(), shapeType ="棱台")}privatefuncalculateLateralArea(polygon: List<Vector3>, height: Float): Float {var lateralArea =0ffor(i in polygon.indices){val current = polygon[i]val next = polygon[(i +1)% polygon.size]val sideLength = current.distanceTo(next) lateralArea += sideLength * height }return lateralArea }}dataclassAreaResult(val success: Boolean,val area: Float =0f,val perimeter: Float =0f,val centroid: Vector3?=null,val pointCount: Int =0,val shapeType: String ="",val message: String =""){companionobject{funsuccess( area: Float, perimeter: Float, centroid: Vector3, pointCount: Int, shapeType: String ): AreaResult {returnAreaResult( success =true, area = area, perimeter = perimeter, centroid = centroid, pointCount = pointCount, shapeType = shapeType, message ="测量成功")}funerror(message: String): AreaResult {returnAreaResult( success =false, message = message )}}}dataclassVolumeResult(val success: Boolean,val volume: Float =0f,val baseArea: Float =0f,val topArea: Float =0f,val height: Float =0f,val avgHeight: Float =0f,val surfaceArea: Float =0f,val shapeType: String ="",val message: String =""){companionobject{funsuccess( volume: Float, baseArea: Float =0f, height: Float =0f, surfaceArea: Float =0f, shapeType: String, topArea: Float =0f, avgHeight: Float =0f): VolumeResult {returnVolumeResult( success =true, volume = volume, baseArea = baseArea, topArea = topArea, height = height, avgHeight = avgHeight, surfaceArea = surfaceArea, shapeType = shapeType, message ="测量成功")}funerror(message: String): VolumeResult {returnVolumeResult( success =false, message = message )}}}}

7.2 测量历史与数据管理

classMeasurementHistoryManager(privateval context: Context){privateval measurements = mutableListOf<MeasurementRecord>()privateval MAX_HISTORY_SIZE =100/** * 保存测量记录 */funsaveMeasurement(record: MeasurementRecord): Boolean { measurements.add(record)// 限制历史记录数量if(measurements.size > MAX_HISTORY_SIZE){ measurements.removeAt(0)}// 保存到数据库returnsaveToDatabase(record)}/** * 获取所有测量记录 */fungetAllMeasurements(): List<MeasurementRecord>{// 优先从内存读取,如果为空则从数据库加载if(measurements.isEmpty()){loadFromDatabase()}return measurements.toList()}/** * 按类型筛选测量记录 */fungetMeasurementsByType(type: MeasurementType): List<MeasurementRecord>{return measurements.filter{ it.type == type }}/** * 搜索测量记录 */funsearchMeasurements(query: String): List<MeasurementRecord>{return measurements.filter{ record -> record.name.contains(query, ignoreCase =true)|| record.tags.any{ it.contains(query, ignoreCase =true)}|| record.notes?.contains(query, ignoreCase =true)?:false}}/** * 导出测量数据 */funexportMeasurements(format: ExportFormat): ExportResult {returnwhen(format){ ExportFormat.JSON ->exportToJson() ExportFormat.CSV ->exportToCsv() ExportFormat.PDF ->exportToPdf()}}/** * 生成测量报告 */fungenerateReport(record: MeasurementRecord): Report {returnReport( title ="测量报告 - ${record.name}", timestamp = record.timestamp, content =buildReportContent(record), summary =generateSummary(record))}privatefunsaveToDatabase(record: MeasurementRecord): Boolean {val dbHelper =MeasurementDbHelper(context)val db = dbHelper.writableDatabase returntry{val values =ContentValues().apply{put(MeasurementContract.MeasurementEntry.COLUMN_NAME_NAME, record.name)put(MeasurementContract.MeasurementEntry.COLUMN_NAME_TYPE, record.type.name)put(MeasurementContract.MeasurementEntry.COLUMN_NAME_DATA,Gson().toJson(record.data))put(MeasurementContract.MeasurementEntry.COLUMN_NAME_TIMESTAMP, record.timestamp)put(MeasurementContract.MeasurementEntry.COLUMN_NAME_TAGS, record.tags.joinToString(","))put(MeasurementContract.MeasurementEntry.COLUMN_NAME_NOTES, record.notes)put(MeasurementContract.MeasurementEntry.COLUMN_NAME_LOCATION,Gson().toJson(record.location))} db.insert(MeasurementContract.MeasurementEntry.TABLE_NAME,null, values)true}catch(e: Exception){false}finally{ db.close()}}privatefunloadFromDatabase(){ measurements.clear()val dbHelper =MeasurementDbHelper(context)val db = dbHelper.readableDatabase val projection =arrayOf( MeasurementContract.MeasurementEntry.COLUMN_NAME_NAME, MeasurementContract.MeasurementEntry.COLUMN_NAME_TYPE, MeasurementContract.MeasurementEntry.COLUMN_NAME_DATA, MeasurementContract.MeasurementEntry.COLUMN_NAME_TIMESTAMP, MeasurementContract.MeasurementEntry.COLUMN_NAME_TAGS, MeasurementContract.MeasurementEntry.COLUMN_NAME_NOTES, MeasurementContract.MeasurementEntry.COLUMN_NAME_LOCATION )val cursor = db.query( MeasurementContract.MeasurementEntry.TABLE_NAME, projection,null,null,null,null,"${MeasurementContract.MeasurementEntry.COLUMN_NAME_TIMESTAMP} DESC")with(cursor){while(moveToNext()){val name =getString(getColumnIndexOrThrow( MeasurementContract.MeasurementEntry.COLUMN_NAME_NAME))val type = MeasurementType.valueOf(getString(getColumnIndexOrThrow( MeasurementContract.MeasurementEntry.COLUMN_NAME_TYPE)))val dataJson =getString(getColumnIndexOrThrow( MeasurementContract.MeasurementEntry.COLUMN_NAME_DATA))val timestamp =getLong(getColumnIndexOrThrow( MeasurementContract.MeasurementEntry.COLUMN_NAME_TIMESTAMP))val tagsStr =getString(getColumnIndexOrThrow( MeasurementContract.MeasurementEntry.COLUMN_NAME_TAGS))val notes =getString(getColumnIndexOrThrow( MeasurementContract.MeasurementEntry.COLUMN_NAME_NOTES))val locationJson =getString(getColumnIndexOrThrow( MeasurementContract.MeasurementEntry.COLUMN_NAME_LOCATION))val record =MeasurementRecord( name = name, type = type,data=Gson().fromJson(dataJson, MeasurementData::class.java), timestamp = timestamp, tags = tagsStr.split(","), notes = notes, location =Gson().fromJson(locationJson, LocationData::class.java)) measurements.add(record)}close()} db.close()}privatefunbuildReportContent(record: MeasurementRecord): String {return buildString {appendLine("=== 测量详情 ===")appendLine("名称: ${record.name}")appendLine("类型: ${record.type.displayName}")appendLine("时间: ${Date(record.timestamp)}")appendLine()when(record.type){ MeasurementType.LENGTH ->{valdata= record.data as LengthData appendLine("长度: ${String.format("%.3f",data.length)} 米")appendLine("起点: (${String.format("%.3f",data.start.x)}, "+"${String.format("%.3f",data.start.y)}, "+"${String.format("%.3f",data.start.z)})")appendLine("终点: (${String.format("%.3f",data.end.x)}, "+"${String.format("%.3f",data.end.y)}, "+"${String.format("%.3f",data.end.z)})")} MeasurementType.AREA ->{valdata= record.data as AreaData appendLine("面积: ${String.format("%.3f",data.area)} 平方米")appendLine("周长: ${String.format("%.3f",data.perimeter)} 米")appendLine("形状: ${data.shapeType}")appendLine("点数: ${data.pointCount}")} MeasurementType.VOLUME ->{valdata= record.data as VolumeData appendLine("体积: ${String.format("%.3f",data.volume)} 立方米")appendLine("底面积: ${String.format("%.3f",data.baseArea)} 平方米")appendLine("高度: ${String.format("%.3f",data.height)} 米")appendLine("形状: ${data.shapeType}")} MeasurementType.ANGLE ->{valdata= record.data as AngleData appendLine("角度: ${String.format("%.1f",data.angle)}°")appendLine("顶点: (${String.format("%.3f",data.vertex.x)}, "+"${String.format("%.3f",data.vertex.y)}, "+"${String.format("%.3f",data.vertex.z)})")}}if(record.notes?.isNotEmpty()==true){appendLine()appendLine("备注: ${record.notes}")}if(record.tags.isNotEmpty()){appendLine()appendLine("标签: ${record.tags.joinToString(", ")}")}}}sealedclassMeasurementType(val displayName: String){object LENGTH :MeasurementType("长度")object AREA :MeasurementType("面积")object VOLUME :MeasurementType("体积")object ANGLE :MeasurementType("角度")object MULTI_POINT :MeasurementType("多点")}dataclassMeasurementRecord(val name: String,val type: MeasurementType,valdata: MeasurementData,val timestamp: Long = System.currentTimeMillis(),val tags: List<String>=emptyList(),val notes: String?=null,val location: LocationData?=null)sealedclass MeasurementData dataclassLengthData(val length: Float,val start: Vector3,val end: Vector3,val confidence: Float =1.0f):MeasurementData()dataclassAreaData(val area: Float,val perimeter: Float,val shapeType: String,val pointCount: Int,val points: List<Vector3>=emptyList()):MeasurementData()dataclassVolumeData(val volume: Float,val baseArea: Float,val height: Float,val surfaceArea: Float =0f,val shapeType: String ):MeasurementData()dataclassAngleData(val angle: Float,val vertex: Vector3,val arm1: Vector3,val arm2: Vector3 ):MeasurementData()dataclassLocationData(val latitude: Double,val longitude: Double,val altitude: Double?=null,val accuracy: Float?=null)enumclass ExportFormat { JSON, CSV, PDF }dataclassExportResult(val success: Boolean,val filePath: String?=null,val error: String?=null)dataclassReport(val title: String,val timestamp: Long,val content: String,val summary: String )}

第八章:性能优化与用户体验

8.1 实时性能监控

class PerformanceMonitor {privateval frameTimes = ArrayDeque<Long>()privateval measurementTimes = ArrayDeque<Long>()privateval memoryUsage = ArrayDeque<Long>()privatevar isMonitoring =falseprivateval maxSamples =60// 保留最近60个样本/** * 开始性能监控 */funstartMonitoring(){ isMonitoring =true// 启动监控线程 Thread {while(isMonitoring){// 监控帧率monitorFrameRate()// 监控内存monitorMemoryUsage()// 监控CPUmonitorCpuUsage() Thread.sleep(1000)// 每秒采样一次}}.start()}/** * 记录帧时间 */funrecordFrameTime(frameTime: Long){if(!isMonitoring)return frameTimes.addLast(frameTime)if(frameTimes.size > maxSamples){ frameTimes.removeFirst()}}/** * 记录测量时间 */funrecordMeasurementTime(measurementTime: Long){ measurementTimes.addLast(measurementTime)if(measurementTimes.size > maxSamples){ measurementTimes.removeFirst()}}/** * 获取性能报告 */fungetPerformanceReport(): PerformanceReport {val fps =calculateFPS()val avgMeasurementTime =if(measurementTimes.isNotEmpty()){ measurementTimes.average().toLong()}else0Lval currentMemory =getCurrentMemoryUsage()val memoryTrend =analyzeMemoryTrend()returnPerformanceReport( fps = fps, frameTimeStats =calculateFrameTimeStats(), measurementTimeStats =calculateMeasurementTimeStats(), memoryUsage =MemoryUsage( current = currentMemory, max = memoryUsage.maxOrNull()?:0L, average =if(memoryUsage.isNotEmpty()) memoryUsage.average().toLong()else0L, trend = memoryTrend ), recommendations =generateRecommendations(fps, currentMemory))}privatefuncalculateFPS(): Float {if(frameTimes.size <2)return0fval totalTime = frameTimes.sum()val avgFrameTime = totalTime.toFloat()/ frameTimes.size return1000f/ avgFrameTime // 转换为FPS}privatefuncalculateFrameTimeStats(): FrameTimeStats {if(frameTimes.isEmpty())returnFrameTimeStats()returnFrameTimeStats( min = frameTimes.min(), max = frameTimes.max(), average = frameTimes.average().toLong(), percentile95 =calculatePercentile95(frameTimes))}privatefuncalculateMeasurementTimeStats(): MeasurementTimeStats {if(measurementTimes.isEmpty())returnMeasurementTimeStats()returnMeasurementTimeStats( min = measurementTimes.min(), max = measurementTimes.max(), average = measurementTimes.average().toLong(), percentile95 =calculatePercentile95(measurementTimes))}privatefuncalculatePercentile95(times: Deque<Long>): Long {if(times.isEmpty())return0Lval sorted = times.sorted()val index =(sorted.size *0.95).toInt()return sorted[min(index, sorted.size -1)]}privatefunmonitorMemoryUsage(){val runtime = Runtime.getRuntime()val usedMemory = runtime.totalMemory()- runtime.freeMemory() memoryUsage.addLast(usedMemory)if(memoryUsage.size > maxSamples){ memoryUsage.removeFirst()}}privatefungetCurrentMemoryUsage(): Long {val runtime = Runtime.getRuntime()return runtime.totalMemory()- runtime.freeMemory()}privatefunanalyzeMemoryTrend(): MemoryTrend {if(memoryUsage.size <5)return MemoryTrend.STABLE val recent = memoryUsage.takeLast(5).toList()val oldest = memoryUsage.take(5).toList()val recentAvg = recent.average()val oldestAvg = oldest.average()returnwhen{ recentAvg > oldestAvg *1.2-> MemoryTrend.INCREASING recentAvg < oldestAvg *0.8-> MemoryTrend.DECREASING else-> MemoryTrend.STABLE }}privatefunmonitorCpuUsage(){// 实现CPU使用率监控// 注意:Android上获取准确的CPU使用率比较复杂}privatefungenerateRecommendations(fps: Float, memory: Long): List<String>{val recommendations = mutableListOf<String>()// 帧率建议when{ fps <20-> recommendations.add("帧率过低,建议关闭不必要的AR特效") fps <30-> recommendations.add("帧率较低,建议简化场景") fps >=60-> recommendations.add("帧率优秀")}// 内存建议val maxMemory = Runtime.getRuntime().maxMemory()val memoryUsagePercent = memory.toFloat()/ maxMemory.toFloat()when{ memoryUsagePercent >0.8-> recommendations.add("内存使用过高,建议清理缓存") memoryUsagePercent >0.6-> recommendations.add("内存使用较高,注意监控") memoryUsagePercent <0.3-> recommendations.add("内存使用良好")}return recommendations }/** * 停止监控 */funstopMonitoring(){ isMonitoring =false frameTimes.clear() measurementTimes.clear() memoryUsage.clear()}dataclassPerformanceReport(val fps: Float =0f,val frameTimeStats: FrameTimeStats =FrameTimeStats(),val measurementTimeStats: MeasurementTimeStats =MeasurementTimeStats(),val memoryUsage: MemoryUsage =MemoryUsage(),val recommendations: List<String>=emptyList())dataclassFrameTimeStats(val min: Long =0L,val max: Long =0L,val average: Long =0L,val percentile95: Long =0L)dataclassMeasurementTimeStats(val min: Long =0L,val max: Long =0L,val average: Long =0L,val percentile95: Long =0L)dataclassMemoryUsage(val current: Long =0L,val max: Long =0L,val average: Long =0L,val trend: MemoryTrend = MemoryTrend.STABLE )enumclass MemoryTrend { INCREASING, DECREASING, STABLE }}

第九章:测试与验证

9.1 测量准确性测试

class MeasurementAccuracyTest {/** * 测试已知长度的物体 */funtestKnownObject( referenceObject: ReferenceObject, measuredLength: Float, numberOfTrials: Int =10): AccuracyTestResult {val measurements = mutableListOf<Float>()val errors = mutableListOf<Float>()val errorPercentages = mutableListOf<Float>()// 进行多次测量repeat(numberOfTrials){ trial ->// 模拟测量(实际应用中应从AR测量获取)val measurement =simulateMeasurement(referenceObject.actualLength) measurements.add(measurement)// 计算误差val error =abs(measurement - referenceObject.actualLength) errors.add(error)// 计算误差百分比val errorPercentage =(error / referenceObject.actualLength)*100 errorPercentages.add(errorPercentage) Log.d("AccuracyTest","试验 ${trial +1}: 测量=${String.format("%.3f", measurement)}m, "+"误差=${String.format("%.3f", error)}m (${String.format("%.1f", errorPercentage)}%)")}// 计算统计指标val avgMeasurement = measurements.average().toFloat()val avgError = errors.average().toFloat()val stdDev =calculateStandardDeviation(measurements)// 计算准确度评级val accuracyRating =calculateAccuracyRating(avgError, referenceObject.actualLength)returnAccuracyTestResult( referenceObject = referenceObject, numberOfTrials = numberOfTrials, measurements = measurements, averageMeasurement = avgMeasurement, averageError = avgError, standardDeviation = stdDev, errorPercentages = errorPercentages, accuracyRating = accuracyRating, confidenceLevel =calculateConfidenceLevel(stdDev, numberOfTrials), isAcceptable = avgError <= referenceObject.maxAcceptableError )}/** * 测试不同距离的测量准确性 */funtestDistanceAccuracy( testDistances: List<Float>,// 测试距离列表(米) numberOfTrials: Int =5): DistanceAccuracyReport {val resultsByDistance = mutableMapOf<Float, AccuracyTestResult>() testDistances.forEach{ distance ->val referenceObject =ReferenceObject( name ="测试距离 $distance 米", actualLength = distance, maxAcceptableError = distance *0.05f// 5%误差可接受)val result =testKnownObject(referenceObject, distance, numberOfTrials) resultsByDistance[distance]= result }// 分析距离与误差的关系val distanceErrorPairs = resultsByDistance.map{(distance, result)->Pair(distance, result.averageError)}// 拟合误差曲线:误差 = a * 距离 + bval(a, b)=linearRegression(distanceErrorPairs)returnDistanceAccuracyReport( resultsByDistance = resultsByDistance, distanceErrorRelationship =DistanceErrorRelationship(a, b), overallAccuracy =calculateOverallAccuracy(resultsByDistance.values), recommendations =generateDistanceRecommendations(resultsByDistance))}/** * 环境因素影响测试 */funtestEnvironmentalFactors(): EnvironmentalTestReport {val testConditions =listOf(TestCondition("理想光照", lighting = LightingCondition.GOOD),TestCondition("低光照", lighting = LightingCondition.LOW),TestCondition("强光照", lighting = LightingCondition.HIGH),TestCondition("纹理丰富表面", surface = SurfaceType.TEXTURED),TestCondition("光滑表面", surface = SurfaceType.SMOOTH),TestCondition("移动环境", stability = Stability.MOVING))val results = mutableListOf<ConditionalTestResult>() testConditions.forEach{ condition ->// 在每种条件下进行测试val measurement =simulateMeasurementWithCondition(10.0f, condition)val reference =ReferenceObject("测试物体",10.0f,0.5f)val result =testKnownObject(reference, measurement,3) results.add(ConditionalTestResult(condition, result))}returnEnvironmentalTestReport( results = results, mostFavorableCondition = results.minByOrNull{ it.result.averageError }?.condition, leastFavorableCondition = results.maxByOrNull{ it.result.averageError }?.condition, environmentalImpact =analyzeEnvironmentalImpact(results))}privatefunsimulateMeasurement(actualLength: Float): Float {// 模拟测量误差:实际值 + 随机误差val randomError =(Random.nextFloat()-0.5f)*0.1f// ±5cm随机误差val systematicError =0.02f// 2cm系统误差return actualLength + systematicError + randomError }privatefunsimulateMeasurementWithCondition( actualLength: Float, condition: TestCondition ): Float {var measurement =simulateMeasurement(actualLength)// 根据环境条件调整误差when(condition.lighting){ LightingCondition.LOW -> measurement +=0.05f// 低光增加5cm误差 LightingCondition.HIGH -> measurement +=0.03f// 强光增加3cm误差else->{}// 理想光照不额外增加误差}when(condition.surface){ SurfaceType.SMOOTH -> measurement +=0.08f// 光滑表面增加8cm误差else->{}// 纹理丰富表面不额外增加误差}when(condition.stability){ Stability.MOVING -> measurement +=0.10f// 移动环境增加10cm误差else->{}// 稳定环境不额外增加误差}return measurement }privatefuncalculateAccuracyRating(avgError: Float, actualLength: Float): String {val errorPercentage =(avgError / actualLength)*100returnwhen{ errorPercentage <1->"优秀 (<1%)" errorPercentage <3->"良好 (1-3%)" errorPercentage <5->"一般 (3-5%)" errorPercentage <10->"较差 (5-10%)"else->"很差 (>10%)"}}privatefuncalculateConfidenceLevel(stdDev: Float, sampleSize: Int): Float {// 简化的置信度计算val confidence =1.0f-(stdDev /0.1f)// 假设0.1m为标准差基准// 考虑样本量val sampleFactor =min(1.0f, sampleSize /30.0f)returnmax(0f, confidence * sampleFactor)}dataclassReferenceObject(val name: String,val actualLength: Float,// 实际长度(米)val maxAcceptableError: Float // 最大可接受误差(米))dataclassAccuracyTestResult(val referenceObject: ReferenceObject,val numberOfTrials: Int,val measurements: List<Float>,val averageMeasurement: Float,val averageError: Float,val standardDeviation: Float,val errorPercentages: List<Float>,val accuracyRating: String,val confidenceLevel: Float,val isAcceptable: Boolean ){val minError = measurements.minOrNull()?:0fval maxError = measurements.maxOrNull()?:0f}dataclassDistanceAccuracyReport(val resultsByDistance: Map<Float, AccuracyTestResult>,val distanceErrorRelationship: DistanceErrorRelationship,val overallAccuracy: String,val recommendations: List<String>)dataclassDistanceErrorRelationship(val slope: Float,// 误差随距离增长的斜率val intercept: Float // 距离为0时的误差){funpredictError(distance: Float): Float {return slope * distance + intercept }}dataclassEnvironmentalTestReport(val results: List<ConditionalTestResult>,val mostFavorableCondition: TestCondition?,val leastFavorableCondition: TestCondition?,val environmentalImpact: Map<String, Float>// 各因素对误差的影响程度)dataclassConditionalTestResult(val condition: TestCondition,val result: AccuracyTestResult )dataclassTestCondition(val name: String,val lighting: LightingCondition = LightingCondition.GOOD,val surface: SurfaceType = SurfaceType.TEXTURED,val stability: Stability = Stability.STABLE )enumclass LightingCondition { GOOD, LOW, HIGH }enumclass SurfaceType { TEXTURED, SMOOTH }enumclass Stability { STABLE, MOVING }}

第十章:完整应用集成

10.1 应用主界面集成

class MainActivity :AppCompatActivity(){privatelateinitvar binding: ActivityMainBinding privatelateinitvar arSessionManager: ARSessionManager privatelateinitvar measurementManager: MeasurementManager privatelateinitvar calibrationSystem: CalibrationSystem privatevar currentMeasurement: Measurement?=nulloverridefunonCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)// 初始化AR组件initializeARComponents()// 设置UI监听器setupUIListeners()// 检查AR Core可用性checkARAvailability()// 加载校准数据 calibrationSystem.loadCalibrationData()}privatefuninitializeARComponents(){// 初始化AR会话管理器 arSessionManager =ARSessionManager(this, binding.arSceneView)// 初始化测量管理器 measurementManager =MeasurementManager(this, arSessionManager)// 初始化校准系统 calibrationSystem =CalibrationSystem(this, arSessionManager)// 设置测量回调 measurementManager.onMeasurementComplete ={ measurement ->handleMeasurementComplete(measurement)} measurementManager.onMeasurementUpdate ={ progress ->updateMeasurementProgress(progress)}}privatefunsetupUIListeners(){// 测量模式选择 binding.btnLength.setOnClickListener{startMeasurement(MeasurementType.LENGTH)} binding.btnArea.setOnClickListener{startMeasurement(MeasurementType.AREA)} binding.btnVolume.setOnClickListener{startMeasurement(MeasurementType.VOLUME)} binding.btnAngle.setOnClickListener{startMeasurement(MeasurementType.ANGLE)}// 控制按钮 binding.btnClear.setOnClickListener{clearCurrentMeasurement()} binding.btnSave.setOnClickListener{saveCurrentMeasurement()} binding.btnCalibrate.setOnClickListener{showCalibrationDialog()} binding.btnHistory.setOnClickListener{showMeasurementHistory()} binding.btnSettings.setOnClickListener{showSettings()}// AR SceneView触摸监听 binding.arSceneView.setOnTouchListener{ _, event ->handleARTouch(event)}}privatefunhandleARTouch(event: MotionEvent): Boolean {returnwhen(event.action){ MotionEvent.ACTION_DOWN ->{// 如果正在测量,处理测量点 currentMeasurement?.let{ measurement ->val screenX = event.x val screenY = event.y measurementManager.addMeasurementPoint(screenX, screenY)true}?:false} MotionEvent.ACTION_MOVE ->{// 处理拖动(如移动测量点)true} MotionEvent.ACTION_UP ->{// 处理点击结束true}else->false}}privatefunstartMeasurement(type: MeasurementType){// 检查AR会话状态if(!arSessionManager.isSessionReady()){showToast("AR会话未就绪,请等待平面检测")return}// 创建新测量 currentMeasurement = measurementManager.createMeasurement(type)// 更新UI状态updateUIForMeasurement(type)// 显示指导信息showMeasurementInstructions(type)}privatefunupdateUIForMeasurement(type: MeasurementType){// 高亮当前模式按钮resetButtonColors()when(type){ MeasurementType.LENGTH -> binding.btnLength.setBackgroundColor(Color.GREEN) MeasurementType.AREA -> binding.btnArea.setBackgroundColor(Color.GREEN) MeasurementType.VOLUME -> binding.btnVolume.setBackgroundColor(Color.GREEN) MeasurementType.ANGLE -> binding.btnAngle.setBackgroundColor(Color.GREEN)}// 显示测量进度 binding.progressBar.visibility = View.VISIBLE binding.tvInstruction.visibility = View.VISIBLE }privatefunresetButtonColors(){val defaultColor = ContextCompat.getColor(this, R.color.button_default) binding.btnLength.setBackgroundColor(defaultColor) binding.btnArea.setBackgroundColor(defaultColor) binding.btnVolume.setBackgroundColor(defaultColor) binding.btnAngle.setBackgroundColor(defaultColor)}privatefunhandleMeasurementComplete(measurement: Measurement){// 应用校准val calibratedValue = calibrationSystem.applyCalibration(measurement.value)// 显示结果showMeasurementResult(measurement.type, calibratedValue)// 保存到历史saveMeasurementToHistory(measurement.copy(value = calibratedValue))// 重置当前测量 currentMeasurement =nullresetUIAfterMeasurement()}privatefunshowMeasurementResult(type: MeasurementType, value: Float){val unit =when(type){ MeasurementType.LENGTH ->"米" MeasurementType.AREA ->"平方米" MeasurementType.VOLUME ->"立方米" MeasurementType.ANGLE ->"°"}val message ="${type.displayName}: ${String.format("%.3f", value)}$unit" AlertDialog.Builder(this).setTitle("测量完成").setMessage(message).setPositiveButton("确定"){ _, _ ->}.setNegativeButton("重新测量"){ _, _ ->startMeasurement(type)}.show()}privatefunshowMeasurementInstructions(type: MeasurementType){val instructions =when(type){ MeasurementType.LENGTH ->"请点击起点和终点" MeasurementType.AREA ->"请点击三个点定义矩形" MeasurementType.VOLUME ->"请点击四个点定义长方体" MeasurementType.ANGLE ->"请点击三个点测量角度"} binding.tvInstruction.text = instructions binding.tvInstruction.visibility = View.VISIBLE }privatefunupdateMeasurementProgress(progress: MeasurementProgress){ binding.progressBar.progress = progress.percentage binding.tvProgress.text ="进度: ${progress.step}/${progress.totalSteps}"}privatefunclearCurrentMeasurement(){ measurementManager.clearCurrentMeasurement() currentMeasurement =nullresetUIAfterMeasurement()showToast("测量已清除")}privatefunresetUIAfterMeasurement(){ binding.progressBar.visibility = View.GONE binding.tvInstruction.visibility = View.GONE binding.tvProgress.text =""resetButtonColors()}overridefunonResume(){super.onResume() arSessionManager.resume()}overridefunonPause(){super.onPause() arSessionManager.pause()}overridefunonDestroy(){super.onDestroy() arSessionManager.destroy()}}

结语:AR测量的未来展望

AR测量技术正在快速发展,从简单的长度测量到复杂的3D重建,从消费级应用到工业级解决方案。通过结合CameraX的高质量图像采集和AR Core的环境理解能力,我们能够创建出真正实用的测量工具。

技术发展趋势

  1. 精度提升 - 深度传感器和AI算法的结合将极大提高测量精度
  2. 实时性增强 - 5G和边缘计算将实现实时的大范围测量
  3. 多设备协同 - 多台设备协同工作,实现更复杂的测量任务
  4. 行业融合 - 与BIM、CAD等专业软件的无缝对接

给开发者的建议

  1. 关注用户体验 - 技术再先进,如果用户不会用也是徒劳
  2. 持续优化精度 - 测量工具的核心价值在于准确性
  3. 考虑实际场景 - 在真实环境中测试和优化
  4. 保持开放心态 - AR技术仍在快速发展,保持学习和适应

通过本文的完整实现方案,你已经掌握了AR测量应用的核心技术。现在,是时候动手实践,创造出属于你自己的AR测量应用了!


资源链接

问题反馈
如果您在实现过程中遇到任何问题,或者有改进建议,欢迎在评论区交流哦。
代码示例可自由使用,但需保留署名哈。

Read more

软件解耦与扩展:插件式开发方式(基于 C++ 与 C# 的实现)

软件解耦与扩展:插件式开发方式(基于 C++ 与 C# 的实现)

软件解耦与扩展:插件式开发方式 * 🤔 什么是插件式开发? * 🧩 为何选择插件式开发?—— 解耦与扩展的艺术 * 1. 高度解耦 * 2. 极致的扩展性 * 3. 增强可维护性 * 4. 支持动态加载与卸载 * 🏗️ 插件系统的核心架构 * 💻 实践篇:C# 下的插件式开发 * 1. 定义插件契约 * 2. 实现一个具体插件 * 3. 构建宿主程序(插件加载器) * 应用案例:可扩展的日志系统 * ⚙️ 实践篇:C++ 下的插件式开发 * 1. 定义插件契约 * 2. 实现一个具体插件 * 3. 构建宿主程序(插件加载器) * 📊 C# 与 C++ 实现对比 * ⚠️ 挑战与注意事项 * 🎯 总结:何时使用插件式架构? 🚀在软件工程的漫长演进中,我们始终在追求一个核心目标:构建稳定而灵活的系统。一个优秀的软件架构,如同人体的骨骼,既要坚实稳固,又要具备生长与适应的能力。

By Ne0inhk
C++ 多线程同步之条件变量(condition_variable)实战

C++ 多线程同步之条件变量(condition_variable)实战

C++ 多线程同步之条件变量(condition_variable)实战 💡 学习目标:掌握 C++ 标准库中条件变量的使用方法,理解条件变量与互斥锁的协同工作机制,能够解决多线程间的等待-通知问题。 💡 学习重点:std::condition_variable 的核心接口、wait() 与 notify_one()/notify_all() 的配合使用、生产者-消费者模型的实现。 49.1 条件变量的引入场景 在多线程编程中,我们经常会遇到线程需要等待某个条件满足后再执行的场景。 比如生产者线程生产数据后,消费者线程才能消费;队列不为空时,消费者才能从中取数据。 如果仅用互斥锁实现,消费者线程只能不断轮询检查条件,这会造成 CPU 资源的浪费。 ⚠️ 注意事项:单纯的轮询会导致 CPU 空转,降低程序运行效率,条件变量就是为解决这类问题而生的。 举个简单的轮询反例,消费者不断检查队列是否有数据: #include<iostream>

By Ne0inhk
图文教程 | 2024年IDEA安装使用教程,JDK简易下载方法

图文教程 | 2024年IDEA安装使用教程,JDK简易下载方法

前言 📢博客主页:程序源⠀-ZEEKLOG博客 📢欢迎点赞👍收藏⭐留言📝如有错误敬请指正!  目录 一、IDEA安装 二、激活 三、JDK安装 四、JDK环境配置 五、验证 一、IDEA安装 进入官网下载:  Other Versions - IntelliJ IDEAGet past releases and previous versions of IntelliJ IDEA.https://www.jetbrains.com/idea/download/other.html 24年新版本的也不错 打开安装程序进行安装 直接安装即可 下载完成后先不要打开IDEA! 下载完成后先不要打开IDEA! 下载完成后先不要打开IDEA! 二、

By Ne0inhk
C++微服务实战中好友管理子服务的全面解析

C++微服务实战中好友管理子服务的全面解析

【C++ 微服务实战】IM 好友管理子服务全解析:从 Proto 定义到高可用部署 在即时通讯(IM)系统中,好友管理子服务是连接 “用户社交关系” 与 “聊天会话” 的核心枢纽 —— 它既要处理好友申请、关系维护,也要管理单聊 / 群聊会话的创建与成员维护。本文基于实际项目代码(C++/brpc/Protobuf/ODB),从 “接口设计”“数据模型”“核心逻辑”“高可用部署” 四个维度,完整拆解好友管理子服务的实现细节,带你理解如何构建一个解耦、可靠的微服务。 一、服务定位与技术栈 在 IM 微服务架构中,好友管理子服务(Friend Server)的核心职责是 **“管理用户社交关系” 与 “维护聊天会话容器”**,向上对接网关服务接收客户端请求,向下依赖 MySQL/ES 存储数据,

By Ne0inhk