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

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

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

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

1.1 AR测量技术栈架构

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

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

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

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

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

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

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

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

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

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

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

2.1 项目依赖配置

gradle 复制代码
// 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 权限与特性声明

xml 复制代码
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.armeasure">

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

</manifest>

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

3.1 CameraX相机初始化

kotlin 复制代码
class ARCameraManager(
    private val context: Context,
    private val surfaceProvider: Preview.SurfaceProvider
) {
    
    private lateinit var cameraProvider: ProcessCameraProvider
    private lateinit var preview: Preview
    private var camera: Camera? = null
    
    // 相机配置
    data class CameraConfig(
        val targetResolution: Size = Size(1920, 1080),
        val focusMode: Int = CameraSelector.LENS_FACING_BACK,
        val enableAutoFocus: Boolean = true,
        val frameRate: IntRange = 30..30
    )
    
    /**
     * 初始化CameraX相机
     */
    fun initializeCamera(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测量需要稳定对焦)
     */
    private fun setupAutoFocus() {
        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坐标转换)
     */
    fun getCameraIntrinsics(): 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
            }
        }
    }
    
    data class CameraIntrinsics(
        val focalLength: Float,      // 焦距(毫米)
        val sensorWidth: Float,      // 传感器宽度(毫米)
        val sensorHeight: Float,     // 传感器高度(毫米)
        val imageWidth: Int,         // 图像宽度(像素)
        val imageHeight: Int         // 图像高度(像素)
    ) {
        // 计算焦距像素值
        fun focalLengthPixels(): Pair<Float, Float> {
            val fx = (focalLength * imageWidth) / sensorWidth
            val fy = (focalLength * imageHeight) / sensorHeight
            return Pair(fx, fy)
        }
        
        // 计算主点坐标(通常为图像中心)
        fun principalPoint(): Pair<Float, Float> {
            val cx = imageWidth / 2f
            val cy = imageHeight / 2f
            return Pair(cx, cy)
        }
    }
}

3.2 AR Core会话管理

kotlin 复制代码
class ARSessionManager(
    private val context: Context,
    private val arSceneView: ArSceneView
) {
    
    private var arSession: Session? = null
    private var arConfig: Config? = null
    private var isSessionCreated = false
    
    // AR会话状态
    enum class SessionState {
        NOT_INITIALIZED,
        INITIALIZING,
        TRACKING,
        PAUSED,
        STOPPED,
        ERROR
    }
    
    private var currentState = SessionState.NOT_INITIALIZED
    
    /**
     * 创建AR会话(关键步骤)
     */
    fun createARSession(): SessionState {
        if (isSessionCreated) {
            return currentState
        }
        
        try {
            currentState = SessionState.INITIALIZING
            
            // 1. 检查AR Core可用性
            val availability = ArCoreApk.getInstance().checkAvailability(context)
            if (!availability.isSupported) {
                throw ARNotSupportedException("设备不支持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
    }
    
    /**
     * 设置平面检测回调
     */
    private fun setupPlaneDetection() {
        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坐标)
     */
    fun performRayCast(x: Float, y: Float): List<HitResult>? {
        val frame = arSession?.update() ?: return null
        
        return try {
            // 执行光线投射
            frame.hitTest(x, y)
        } catch (e: Exception) {
            Log.e("ARSessionManager", "光线投射失败", e)
            null
        }
    }
    
    /**
     * 计算两个3D点之间的距离
     */
    fun calculateDistance(point1: Pose, point2: Pose): Float {
        // 使用欧几里得距离公式
        val dx = point1.tx() - point2.tx()
        val dy = point1.ty() - point2.ty()
        val dz = point1.tz() - point2.tz()
        
        return sqrt(dx * dx + dy * dy + dz * dz)
    }
    
    /**
     * 暂停AR会话
     */
    fun pause() {
        arSession?.pause()
        currentState = SessionState.PAUSED
    }
    
    /**
     * 恢复AR会话
     */
    fun resume() {
        arSession?.resume()
        currentState = SessionState.TRACKING
    }
    
    /**
     * 销毁AR会话
     */
    fun destroy() {
        arSession?.close()
        arSession = null
        isSessionCreated = false
        currentState = SessionState.STOPPED
    }
    
    // 回调接口
    var onPlaneTapped: ((HitResult, Plane, MotionEvent) -> Unit)? = null
    var onPlaneUpdated: ((Plane) -> Unit)? = null
    
    class ARNotSupportedException(message: String) : Exception(message)
}

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

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

kotlin 复制代码
class CoordinateTransformer(
    private val cameraIntrinsics: ARCameraManager.CameraIntrinsics
) {
    
    /**
     * 屏幕坐标转相机标准化坐标(NDC)
     */
    fun screenToNDC(screenX: Float, screenY: Float, screenWidth: Int, screenHeight: Int): Pair<Float, Float> {
        // 归一化设备坐标(-1到1之间)
        val ndcX = (2.0f * screenX / screenWidth) - 1.0f
        val ndcY = 1.0f - (2.0f * screenY / screenHeight)  // Y轴反向
        
        return Pair(ndcX, ndcY)
    }
    
    /**
     * NDC坐标转相机空间坐标
     */
    fun ndcToCamera(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  // 假设深度为1
        
        return Vector3(cameraX, cameraY, cameraZ)
    }
    
    /**
     * 相机空间坐标转世界坐标
     */
    fun cameraToWorld(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
    }
    
    /**
     * 完整转换:屏幕坐标 -> 世界坐标
     */
    fun screenToWorld(
        screenX: Float, 
        screenY: Float,
        screenWidth: Int,
        screenHeight: Int,
        cameraPose: Pose,
        hitDepth: Float? = null
    ): Vector3? {
        try {
            // 步骤1:屏幕坐标 -> NDC
            val (ndcX, ndcY) = screenToNDC(screenX, screenY, screenWidth, screenHeight)
            
            // 步骤2:NDC -> 相机坐标
            var cameraPoint = ndcToCamera(ndcX, ndcY)
            
            // 如果有深度信息,调整Z值
            hitDepth?.let {
                cameraPoint = cameraPoint.normalize().multiply(it)
            }
            
            // 步骤3:相机坐标 -> 世界坐标
            return cameraToWorld(cameraPoint, cameraPose)
            
        } catch (e: Exception) {
            Log.e("CoordinateTransformer", "坐标转换失败", e)
            return null
        }
    }
    
    /**
     * 计算测量误差(基于设备移动)
     */
    fun calculateMeasurementError(
        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.03f
        
        return baseError + movementError + distanceError
    }
}

// 数学工具类
data class Vector3(val x: Float, val y: Float, val z: Float) {
    fun add(other: Vector3): Vector3 {
        return Vector3(x + other.x, y + other.y, z + other.z)
    }
    
    fun subtract(other: Vector3): Vector3 {
        return Vector3(x - other.x, y - other.y, z - other.z)
    }
    
    fun multiply(scalar: Float): Vector3 {
        return Vector3(x * scalar, y * scalar, z * scalar)
    }
    
    fun normalize(): Vector3 {
        val length = sqrt(x * x + y * y + z * z)
        return if (length > 0) Vector3(x / length, y / length, z / length) else this
    }
    
    fun distanceTo(other: Vector3): Float {
        val dx = x - other.x
        val dy = y - other.y
        val dz = z - other.z
        return sqrt(dx * dx + dy * dy + dz * dz)
    }
    
    fun dot(other: Vector3): Float {
        return x * other.x + y * other.y + z * other.z
    }
    
    fun cross(other: Vector3): Vector3 {
        return Vector3(
            y * other.z - z * other.y,
            z * other.x - x * other.z,
            x * other.y - y * other.x
        )
    }
}

class Matrix3x3 private constructor(private val data: FloatArray) {
    companion object {
        fun fromArray(array: FloatArray): Matrix3x3 {
            // 提取3x3旋转矩阵(忽略平移部分)
            return Matrix3x3(floatArrayOf(
                array[0], array[1], array[2],
                array[4], array[5], array[6],
                array[8], array[9], array[10]
            ))
        }
        
        fun identity(): Matrix3x3 {
            return Matrix3x3(floatArrayOf(
                1f, 0f, 0f,
                0f, 1f, 0f,
                0f, 0f, 1f
            ))
        }
    }
    
    fun multiply(vector: Vector3): Vector3 {
        return Vector3(
            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 多点测量与几何计算

kotlin 复制代码
class GeometryCalculator {
    
    /**
     * 计算点到直线的距离
     */
    fun pointToLineDistance(
        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))
    }
    
    /**
     * 计算三角形的面积(海伦公式)
     */
    fun triangleArea(
        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) / 2f
        return sqrt(s * (s - sideAB) * (s - sideBC) * (s - sideCA))
    }
    
    /**
     * 计算多边形的面积(适用于平面多边形)
     */
    fun polygonArea(points: List<Vector3>): Float {
        if (points.size < 3) return 0f
        
        var 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)
        }
        
        return abs(area) / 2f
    }
    
    /**
     * 计算矩形的面积
     */
    fun rectangleArea(
        corner1: Vector3,
        corner2: Vector3,
        corner3: Vector3
    ): Float {
        val width = corner1.distanceTo(corner2)
        val height = corner2.distanceTo(corner3)
        
        return width * height
    }
    
    /**
     * 计算体积(长方体)
     */
    fun cuboidVolume(
        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
    }
    
    /**
     * 计算点到平面的距离
     */
    fun pointToPlaneDistance(
        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)
        return abs(vectorToPoint.dot(normal))
    }
    
    /**
     * 计算角度(三点法)
     */
    fun calculateAngle(
        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()
    }
    
    /**
     * 检查点是否共线
     */
    fun arePointsCollinear(
        point1: Vector3,
        point2: Vector3,
        point3: Vector3,
        tolerance: Float = 0.01f
    ): Boolean {
        val area = triangleArea(point1, point2, point3)
        return area < tolerance
    }
    
    /**
     * 计算最佳拟合平面(最小二乘法)
     */
    fun bestFitPlane(points: List<Vector3>): PlaneEquation {
        if (points.size < 3) {
            throw IllegalArgumentException("至少需要3个点来计算平面")
        }
        
        // 计算重心
        val centroid = Vector3(
            points.map { it.x }.average().toFloat(),
            points.map { it.y }.average().toFloat(),
            points.map { it.z }.average().toFloat()
        )
        
        // 构建协方差矩阵
        var xx = 0f
        var xy = 0f
        var xz = 0f
        var yy = 0f
        var yz = 0f
        var zz = 0f
        
        for (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 = 0
        val d = -(normal.x * centroid.x + normal.y * centroid.y + normal.z * centroid.z)
        
        return PlaneEquation(normal.x, normal.y, normal.z, d)
    }
    
    data class PlaneEquation(val a: Float, val b: Float, val c: Float, val d: Float) {
        fun distanceToPoint(point: Vector3): Float {
            return abs(a * point.x + b * point.y + c * point.z + d) / 
                   sqrt(a * a + b * b + c * c)
        }
    }
}

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

5.1 测量界面实现

kotlin 复制代码
class MeasureActivity : AppCompatActivity() {
    
    private lateinit var arSceneView: ArSceneView
    private lateinit var cameraPreviewView: PreviewView
    private lateinit var controlPanel: LinearLayout
    private lateinit var measurementView: MeasurementOverlayView
    
    private lateinit var arCameraManager: ARCameraManager
    private lateinit var arSessionManager: ARSessionManager
    private lateinit var coordinateTransformer: CoordinateTransformer
    
    // 测量状态管理
    private enum class MeasureMode {
        LENGTH,     // 长度测量
        AREA,       // 面积测量
        VOLUME,     // 体积测量
        ANGLE,      // 角度测量
        MULTI_POINT // 多点测量
    }
    
    private var currentMode = MeasureMode.LENGTH
    private val measurementPoints = mutableListOf<MeasurementPoint>()
    private var isMeasuring = false
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_measure)
        
        // 初始化视图
        initViews()
        
        // 请求权限
        requestPermissions()
        
        // 初始化AR组件
        initARComponents()
        
        // 设置交互监听
        setupInteractionListeners()
    }
    
    private fun initViews() {
        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))
            )
        }
    }
    
    private fun initARComponents() {
        // 初始化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)
        }
    }
    
    private fun setupInteractionListeners() {
        // AR SceneView触摸监听
        arSceneView.setOnTouchListener { _, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    if (isMeasuring) {
                        handleMeasurementTap(event.x, event.y)
                        true
                    } else {
                        false
                    }
                }
                else -> false
            }
        }
        
        // 控制按钮监听
        setupControlButtons()
    }
    
    private fun setupControlButtons() {
        // 长度测量按钮
        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()
        }
    }
    
    /**
     * 处理测量点击
     */
    private fun handleMeasurementTap(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)
            }
            
            // 更新UI
            updateMeasurementDisplay()
        } ?: run {
            showToast("请点击在检测到的平面上")
        }
    }
    
    /**
     * 处理长度测量(两点)
     */
    private fun handleLengthMeasurement(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()
        }
    }
    
    /**
     * 处理面积测量(三点确定矩形)
     */
    private fun handleAreaMeasurement(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()
        }
    }
    
    /**
     * 处理角度测量(三点确定角度)
     */
    private fun handleAngleMeasurement(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()
        }
    }
    
    /**
     * 更新测量显示
     */
    private fun updateMeasurementDisplay() {
        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 }
                    )
                }
            }
            // 其他模式的绘制逻辑...
        }
    }
    
    data class MeasurementPoint(
        val worldPosition: Vector3,
        val screenPosition: PointF,
        val timestamp: Long
    )
}

5.2 测量标注视图

kotlin 复制代码
class MeasurementOverlayView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    
    private val measurementPaint = Paint().apply {
        color = Color.GREEN
        strokeWidth = 4f
        style = Paint.Style.STROKE
        isAntiAlias = true
    }
    
    private val pointPaint = Paint().apply {
        color = Color.RED
        style = Paint.Style.FILL
        isAntiAlias = true
    }
    
    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = 48f
        isAntiAlias = true
        typeface = Typeface.DEFAULT_BOLD
    }
    
    private val pathPaint = Paint().apply {
        color = Color.argb(100, 0, 255, 0)
        style = Paint.Style.FILL
        isAntiAlias = true
    }
    
    private val measurementPoints = mutableListOf<PointF>()
    private val measurementLines = mutableListOf<Pair<PointF, PointF>>()
    private val measurementPolygons = mutableListOf<List<PointF>>()
    private val measurementTexts = mutableListOf<TextAnnotation>()
    
    private var currentPath: Path? = null
    
    fun updatePoints(points: List<PointF>) {
        measurementPoints.clear()
        measurementPoints.addAll(points)
        invalidate()
    }
    
    fun drawLine(start: PointF, end: PointF) {
        measurementLines.add(Pair(start, end))
        invalidate()
    }
    
    fun drawPolygon(points: List<PointF>) {
        measurementPolygons.add(points)
        invalidate()
    }
    
    fun drawText(text: String, position: PointF) {
        measurementTexts.add(TextAnnotation(text, position))
        invalidate()
    }
    
    fun clear() {
        measurementPoints.clear()
        measurementLines.clear()
        measurementPolygons.clear()
        measurementTexts.clear()
        currentPath = null
        invalidate()
    }
    
    override fun onDraw(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) / 2
            val 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 in 1 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)
        }
    }
    
    fun startNewPath(startPoint: PointF) {
        currentPath = Path().apply {
            moveTo(startPoint.x, startPoint.y)
        }
        invalidate()
    }
    
    fun addToPath(point: PointF) {
        currentPath?.lineTo(point.x, point.y)
        invalidate()
    }
    
    fun closePath() {
        currentPath?.close()
        currentPath?.let { path ->
            measurementPolygons.add(extractPointsFromPath(path))
        }
        currentPath = null
        invalidate()
    }
    
    private fun extractPointsFromPath(path: Path): List<PointF> {
        val points = mutableListOf<PointF>()
        val pathMeasure = PathMeasure(path, false)
        val length = pathMeasure.length
        
        var distance = 0f
        val 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
    }
    
    data class TextAnnotation(val text: String, val position: PointF)
}

第六章:精度优化与校准

6.1 测量精度优化算法

kotlin 复制代码
class MeasurementOptimizer {
    
    /**
     * 卡尔曼滤波器 - 用于平滑测量结果
     */
    class KalmanFilter(
        private val processNoise: Float = 0.01f,
        private val measurementNoise: Float = 0.1f,
        private val estimationError: Float = 1f
    ) {
        private var currentEstimate: Float = 0f
        private var currentError: Float = estimationError
        
        fun update(measurement: Float): Float {
            // 预测步骤
            val predictedError = currentError + processNoise
            
            // 更新步骤
            val kalmanGain = predictedError / (predictedError + measurementNoise)
            currentEstimate = currentEstimate + kalmanGain * (measurement - currentEstimate)
            currentError = (1 - kalmanGain) * predictedError
            
            return currentEstimate
        }
        
        fun reset() {
            currentEstimate = 0f
            currentError = estimationError
        }
    }
    
    /**
     * 多帧平均 - 提高测量稳定性
     */
    class MultiFrameAverager(private val windowSize: Int = 10) {
        private val measurements = ArrayDeque<Float>()
        
        fun addMeasurement(measurement: Float): Float {
            measurements.addLast(measurement)
            
            if (measurements.size > windowSize) {
                measurements.removeFirst()
            }
            
            return measurements.average().toFloat()
        }
        
        fun clear() {
            measurements.clear()
        }
    }
    
    /**
     * 移动平均滤波器
     */
    fun movingAverageFilter(
        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
    }
    
    /**
     * 异常值检测与剔除
     */
    fun removeOutliers(
        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
        }
    }
    
    private fun calculateStandardDeviation(values: List<Float>, mean: Float): Float {
        val variance = values.map { (it - mean).pow(2) }.average().toFloat()
        return sqrt(variance)
    }
    
    /**
     * 基于传感器数据的精度补偿
     */
    fun compensateWithSensorData(
        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
        
        return max(0f, compensated)
    }
    
    private fun calculateMovementCompensation(
        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.05f
        
        return 1.0f / movementFactor
    }
    
    private fun calculateOrientationCompensation(
        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.2f
        
        return 1.0f / tiltCompensation
    }
    
    private fun calculateLightCompensation(): Float {
        // 简化版本,实际应该使用光照传感器
        return 1.0f
    }
    
    data class GyroData(val x: Float, val y: Float, val z: Float, val timestamp: Long)
    data class AccelerometerData(val x: Float, val y: Float, val z: Float, val timestamp: Long)
    data class MagneticData(val x: Float, val y: Float, val z: Float, val timestamp: Long)
}

6.2 校准系统实现

kotlin 复制代码
class CalibrationSystem(
    private val context: Context,
    private val arSessionManager: ARSessionManager
) {
    
    companion object {
        // 已知长度的参考物体(单位:米)
        private val REFERENCE_OBJECTS = mapOf(
            "A4纸" to Pair(0.297f, 0.210f),     // A4纸尺寸
            "信用卡" to Pair(0.0856f, 0.0539f),  // 信用卡尺寸
            "iPhone 14" to Pair(0.1467f, 0.0715f) // iPhone 14尺寸
        )
    }
    
    private var calibrationFactor = 1.0f
    private var calibrationHistory = mutableListOf<CalibrationRecord>()
    private var isCalibrating = false
    
    /**
     * 开始校准流程
     */
    fun startCalibration(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
        )
    }
    
    /**
     * 添加校准测量数据
     */
    fun addCalibrationMeasurement(
        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()
        )
    }
    
    /**
     * 完成校准
     */
    fun finishCalibration(): CalibrationResult {
        if (!isCalibrating || calibrationHistory.isEmpty()) {
            return CalibrationResult.error("没有校准数据")
        }
        
        isCalibrating = false
        
        // 保存校准结果
        saveCalibrationData()
        
        return CalibrationResult.success(
            message = "校准完成",
            calibrationFactor = calibrationFactor,
            confidence = calculateOverallConfidence()
        )
    }
    
    /**
     * 应用校准到测量值
     */
    fun applyCalibration(rawMeasurement: Float): Float {
        return rawMeasurement * calibrationFactor
    }
    
    /**
     * 验证校准准确性
     */
    fun verifyCalibration(knownLength: Float, measuredLength: Float): VerificationResult {
        val calibratedLength = applyCalibration(measuredLength)
        val error = abs(calibratedLength - knownLength)
        val errorPercentage = (error / knownLength) * 100
        
        val accuracy = when {
            errorPercentage < 1 -> "优秀 (<1%)"
            errorPercentage < 3 -> "良好 (1-3%)"
            errorPercentage < 5 -> "一般 (3-5%)"
            else -> "较差 (>5%)"
        }
        
        return VerificationResult(
            expectedLength = knownLength,
            measuredLength = measuredLength,
            calibratedLength = calibratedLength,
            error = error,
            errorPercentage = errorPercentage,
            accuracy = accuracy,
            isAcceptable = errorPercentage < 5
        )
    }
    
    /**
     * 保存校准数据
     */
    private fun saveCalibrationData() {
        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()
        }
    }
    
    /**
     * 加载校准数据
     */
    fun loadCalibrationData(): 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
    }
    
    private fun calculateOverallConfidence(): Float {
        if (calibrationHistory.isEmpty()) return 0f
        
        // 置信度基于:测量次数、单个测量置信度、测量一致性
        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)
    }
    
    data class CalibrationRecord(
        val measuredLength: Float,
        val expectedLength: Float,
        val factor: Float,
        val confidence: Float,
        val timestamp: Long
    )
    
    data class CalibrationResult(
        val success: Boolean,
        val message: String,
        val calibrationFactor: Float = 1.0f,
        val confidence: Float = 0f,
        val expectedLength: Float = 0f,
        val objectName: String = ""
    ) {
        companion object {
            fun success(
                message: String,
                calibrationFactor: Float = 1.0f,
                confidence: Float = 0f,
                expectedLength: Float = 0f,
                objectName: String = ""
            ): CalibrationResult {
                return CalibrationResult(
                    success = true,
                    message = message,
                    calibrationFactor = calibrationFactor,
                    confidence = confidence,
                    expectedLength = expectedLength,
                    objectName = objectName
                )
            }
            
            fun error(message: String): CalibrationResult {
                return CalibrationResult(
                    success = false,
                    message = message
                )
            }
        }
    }
    
    data class VerificationResult(
        val expectedLength: Float,
        val measuredLength: Float,
        val calibratedLength: Float,
        val error: Float,
        val errorPercentage: Float,
        val accuracy: String,
        val isAcceptable: Boolean
    )
}

第七章:高级功能扩展

7.1 面积与体积测量

kotlin 复制代码
class AdvancedMeasurement {
    
    /**
     * 多边形面积测量
     */
    class AreaMeasurer {
        fun measurePolygonArea(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)
            )
        }
        
        private fun arePointsCoplanar(points: List<Vector3>, tolerance: Float = 0.01f): Boolean {
            if (points.size < 4) return true
            
            // 使用前三个点确定平面
            val plane = GeometryCalculator().bestFitPlane(points.take(3))
            
            // 检查其他点是否在平面上
            return points.all { point ->
                plane.distanceToPoint(point) < tolerance
            }
        }
        
        private fun calculatePerimeter(points: List<Vector3>): Float {
            var perimeter = 0f
            
            for (i in points.indices) {
                val current = points[i]
                val next = points[(i + 1) % points.size]
                perimeter += current.distanceTo(next)
            }
            
            return perimeter
        }
        
        private fun calculateCentroid(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()
            
            return Vector3(
                (sumX / count).toFloat(),
                (sumY / count).toFloat(),
                (sumZ / count).toFloat()
            )
        }
        
        private fun classifyShape(points: List<Vector3>): String {
            return when (points.size) {
                3 -> "三角形"
                4 -> classifyQuadrilateral(points)
                5 -> "五边形"
                6 -> "六边形"
                else -> "多边形 (${points.size}边)"
            }
        }
        
        private fun classifyQuadrilateral(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.001f
            
            return when {
                isSquare -> "正方形"
                isRectangle -> "矩形"
                else -> "四边形"
            }
        }
    }
    
    /**
     * 体积测量
     */
    class VolumeMeasurer {
        fun measureCuboidVolume(
            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}柱体"
            )
        }
        
        fun measureIrregularVolume(
            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 = "棱台"
            )
        }
        
        private fun calculateLateralArea(polygon: List<Vector3>, height: Float): Float {
            var lateralArea = 0f
            
            for (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
        }
    }
    
    data class AreaResult(
        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 = ""
    ) {
        companion object {
            fun success(
                area: Float,
                perimeter: Float,
                centroid: Vector3,
                pointCount: Int,
                shapeType: String
            ): AreaResult {
                return AreaResult(
                    success = true,
                    area = area,
                    perimeter = perimeter,
                    centroid = centroid,
                    pointCount = pointCount,
                    shapeType = shapeType,
                    message = "测量成功"
                )
            }
            
            fun error(message: String): AreaResult {
                return AreaResult(
                    success = false,
                    message = message
                )
            }
        }
    }
    
    data class VolumeResult(
        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 = ""
    ) {
        companion object {
            fun success(
                volume: Float,
                baseArea: Float = 0f,
                height: Float = 0f,
                surfaceArea: Float = 0f,
                shapeType: String,
                topArea: Float = 0f,
                avgHeight: Float = 0f
            ): VolumeResult {
                return VolumeResult(
                    success = true,
                    volume = volume,
                    baseArea = baseArea,
                    topArea = topArea,
                    height = height,
                    avgHeight = avgHeight,
                    surfaceArea = surfaceArea,
                    shapeType = shapeType,
                    message = "测量成功"
                )
            }
            
            fun error(message: String): VolumeResult {
                return VolumeResult(
                    success = false,
                    message = message
                )
            }
        }
    }
}

7.2 测量历史与数据管理

kotlin 复制代码
class MeasurementHistoryManager(private val context: Context) {
    
    private val measurements = mutableListOf<MeasurementRecord>()
    private val MAX_HISTORY_SIZE = 100
    
    /**
     * 保存测量记录
     */
    fun saveMeasurement(record: MeasurementRecord): Boolean {
        measurements.add(record)
        
        // 限制历史记录数量
        if (measurements.size > MAX_HISTORY_SIZE) {
            measurements.removeAt(0)
        }
        
        // 保存到数据库
        return saveToDatabase(record)
    }
    
    /**
     * 获取所有测量记录
     */
    fun getAllMeasurements(): List<MeasurementRecord> {
        // 优先从内存读取,如果为空则从数据库加载
        if (measurements.isEmpty()) {
            loadFromDatabase()
        }
        return measurements.toList()
    }
    
    /**
     * 按类型筛选测量记录
     */
    fun getMeasurementsByType(type: MeasurementType): List<MeasurementRecord> {
        return measurements.filter { it.type == type }
    }
    
    /**
     * 搜索测量记录
     */
    fun searchMeasurements(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
        }
    }
    
    /**
     * 导出测量数据
     */
    fun exportMeasurements(format: ExportFormat): ExportResult {
        return when (format) {
            ExportFormat.JSON -> exportToJson()
            ExportFormat.CSV -> exportToCsv()
            ExportFormat.PDF -> exportToPdf()
        }
    }
    
    /**
     * 生成测量报告
     */
    fun generateReport(record: MeasurementRecord): Report {
        return Report(
            title = "测量报告 - ${record.name}",
            timestamp = record.timestamp,
            content = buildReportContent(record),
            summary = generateSummary(record)
        )
    }
    
    private fun saveToDatabase(record: MeasurementRecord): Boolean {
        val dbHelper = MeasurementDbHelper(context)
        val db = dbHelper.writableDatabase
        
        return try {
            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()
        }
    }
    
    private fun loadFromDatabase() {
        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()
    }
    
    private fun buildReportContent(record: MeasurementRecord): String {
        return buildString {
            appendLine("=== 测量详情 ===")
            appendLine("名称: ${record.name}")
            appendLine("类型: ${record.type.displayName}")
            appendLine("时间: ${Date(record.timestamp)}")
            appendLine()
            
            when (record.type) {
                MeasurementType.LENGTH -> {
                    val data = 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 -> {
                    val data = record.data as AreaData
                    appendLine("面积: ${String.format("%.3f", data.area)} 平方米")
                    appendLine("周长: ${String.format("%.3f", data.perimeter)} 米")
                    appendLine("形状: ${data.shapeType}")
                    appendLine("点数: ${data.pointCount}")
                }
                MeasurementType.VOLUME -> {
                    val data = 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 -> {
                    val data = 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(", ")}")
            }
        }
    }
    
    sealed class MeasurementType(val displayName: String) {
        object LENGTH : MeasurementType("长度")
        object AREA : MeasurementType("面积")
        object VOLUME : MeasurementType("体积")
        object ANGLE : MeasurementType("角度")
        object MULTI_POINT : MeasurementType("多点")
    }
    
    data class MeasurementRecord(
        val name: String,
        val type: MeasurementType,
        val data: MeasurementData,
        val timestamp: Long = System.currentTimeMillis(),
        val tags: List<String> = emptyList(),
        val notes: String? = null,
        val location: LocationData? = null
    )
    
    sealed class MeasurementData
    data class LengthData(
        val length: Float,
        val start: Vector3,
        val end: Vector3,
        val confidence: Float = 1.0f
    ) : MeasurementData()
    
    data class AreaData(
        val area: Float,
        val perimeter: Float,
        val shapeType: String,
        val pointCount: Int,
        val points: List<Vector3> = emptyList()
    ) : MeasurementData()
    
    data class VolumeData(
        val volume: Float,
        val baseArea: Float,
        val height: Float,
        val surfaceArea: Float = 0f,
        val shapeType: String
    ) : MeasurementData()
    
    data class AngleData(
        val angle: Float,
        val vertex: Vector3,
        val arm1: Vector3,
        val arm2: Vector3
    ) : MeasurementData()
    
    data class LocationData(
        val latitude: Double,
        val longitude: Double,
        val altitude: Double? = null,
        val accuracy: Float? = null
    )
    
    enum class ExportFormat { JSON, CSV, PDF }
    
    data class ExportResult(
        val success: Boolean,
        val filePath: String? = null,
        val error: String? = null
    )
    
    data class Report(
        val title: String,
        val timestamp: Long,
        val content: String,
        val summary: String
    )
}

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

8.1 实时性能监控

kotlin 复制代码
class PerformanceMonitor {
    
    private val frameTimes = ArrayDeque<Long>()
    private val measurementTimes = ArrayDeque<Long>()
    private val memoryUsage = ArrayDeque<Long>()
    
    private var isMonitoring = false
    private val maxSamples = 60  // 保留最近60个样本
    
    /**
     * 开始性能监控
     */
    fun startMonitoring() {
        isMonitoring = true
        
        // 启动监控线程
        Thread {
            while (isMonitoring) {
                // 监控帧率
                monitorFrameRate()
                
                // 监控内存
                monitorMemoryUsage()
                
                // 监控CPU
                monitorCpuUsage()
                
                Thread.sleep(1000)  // 每秒采样一次
            }
        }.start()
    }
    
    /**
     * 记录帧时间
     */
    fun recordFrameTime(frameTime: Long) {
        if (!isMonitoring) return
        
        frameTimes.addLast(frameTime)
        
        if (frameTimes.size > maxSamples) {
            frameTimes.removeFirst()
        }
    }
    
    /**
     * 记录测量时间
     */
    fun recordMeasurementTime(measurementTime: Long) {
        measurementTimes.addLast(measurementTime)
        
        if (measurementTimes.size > maxSamples) {
            measurementTimes.removeFirst()
        }
    }
    
    /**
     * 获取性能报告
     */
    fun getPerformanceReport(): PerformanceReport {
        val fps = calculateFPS()
        val avgMeasurementTime = if (measurementTimes.isNotEmpty()) {
            measurementTimes.average().toLong()
        } else 0L
        
        val currentMemory = getCurrentMemoryUsage()
        val memoryTrend = analyzeMemoryTrend()
        
        return PerformanceReport(
            fps = fps,
            frameTimeStats = calculateFrameTimeStats(),
            measurementTimeStats = calculateMeasurementTimeStats(),
            memoryUsage = MemoryUsage(
                current = currentMemory,
                max = memoryUsage.maxOrNull() ?: 0L,
                average = if (memoryUsage.isNotEmpty()) memoryUsage.average().toLong() else 0L,
                trend = memoryTrend
            ),
            recommendations = generateRecommendations(fps, currentMemory)
        )
    }
    
    private fun calculateFPS(): Float {
        if (frameTimes.size < 2) return 0f
        
        val totalTime = frameTimes.sum()
        val avgFrameTime = totalTime.toFloat() / frameTimes.size
        
        return 1000f / avgFrameTime  // 转换为FPS
    }
    
    private fun calculateFrameTimeStats(): FrameTimeStats {
        if (frameTimes.isEmpty()) return FrameTimeStats()
        
        return FrameTimeStats(
            min = frameTimes.min(),
            max = frameTimes.max(),
            average = frameTimes.average().toLong(),
            percentile95 = calculatePercentile95(frameTimes)
        )
    }
    
    private fun calculateMeasurementTimeStats(): MeasurementTimeStats {
        if (measurementTimes.isEmpty()) return MeasurementTimeStats()
        
        return MeasurementTimeStats(
            min = measurementTimes.min(),
            max = measurementTimes.max(),
            average = measurementTimes.average().toLong(),
            percentile95 = calculatePercentile95(measurementTimes)
        )
    }
    
    private fun calculatePercentile95(times: Deque<Long>): Long {
        if (times.isEmpty()) return 0L
        
        val sorted = times.sorted()
        val index = (sorted.size * 0.95).toInt()
        
        return sorted[min(index, sorted.size - 1)]
    }
    
    private fun monitorMemoryUsage() {
        val runtime = Runtime.getRuntime()
        val usedMemory = runtime.totalMemory() - runtime.freeMemory()
        
        memoryUsage.addLast(usedMemory)
        
        if (memoryUsage.size > maxSamples) {
            memoryUsage.removeFirst()
        }
    }
    
    private fun getCurrentMemoryUsage(): Long {
        val runtime = Runtime.getRuntime()
        return runtime.totalMemory() - runtime.freeMemory()
    }
    
    private fun analyzeMemoryTrend(): 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()
        
        return when {
            recentAvg > oldestAvg * 1.2 -> MemoryTrend.INCREASING
            recentAvg < oldestAvg * 0.8 -> MemoryTrend.DECREASING
            else -> MemoryTrend.STABLE
        }
    }
    
    private fun monitorCpuUsage() {
        // 实现CPU使用率监控
        // 注意:Android上获取准确的CPU使用率比较复杂
    }
    
    private fun generateRecommendations(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
    }
    
    /**
     * 停止监控
     */
    fun stopMonitoring() {
        isMonitoring = false
        frameTimes.clear()
        measurementTimes.clear()
        memoryUsage.clear()
    }
    
    data class PerformanceReport(
        val fps: Float = 0f,
        val frameTimeStats: FrameTimeStats = FrameTimeStats(),
        val measurementTimeStats: MeasurementTimeStats = MeasurementTimeStats(),
        val memoryUsage: MemoryUsage = MemoryUsage(),
        val recommendations: List<String> = emptyList()
    )
    
    data class FrameTimeStats(
        val min: Long = 0L,
        val max: Long = 0L,
        val average: Long = 0L,
        val percentile95: Long = 0L
    )
    
    data class MeasurementTimeStats(
        val min: Long = 0L,
        val max: Long = 0L,
        val average: Long = 0L,
        val percentile95: Long = 0L
    )
    
    data class MemoryUsage(
        val current: Long = 0L,
        val max: Long = 0L,
        val average: Long = 0L,
        val trend: MemoryTrend = MemoryTrend.STABLE
    )
    
    enum class MemoryTrend { INCREASING, DECREASING, STABLE }
}

第九章:测试与验证

9.1 测量准确性测试

kotlin 复制代码
class MeasurementAccuracyTest {
    
    /**
     * 测试已知长度的物体
     */
    fun testKnownObject(
        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)
        
        return AccuracyTestResult(
            referenceObject = referenceObject,
            numberOfTrials = numberOfTrials,
            measurements = measurements,
            averageMeasurement = avgMeasurement,
            averageError = avgError,
            standardDeviation = stdDev,
            errorPercentages = errorPercentages,
            accuracyRating = accuracyRating,
            confidenceLevel = calculateConfidenceLevel(stdDev, numberOfTrials),
            isAcceptable = avgError <= referenceObject.maxAcceptableError
        )
    }
    
    /**
     * 测试不同距离的测量准确性
     */
    fun testDistanceAccuracy(
        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 * 距离 + b
        val (a, b) = linearRegression(distanceErrorPairs)
        
        return DistanceAccuracyReport(
            resultsByDistance = resultsByDistance,
            distanceErrorRelationship = DistanceErrorRelationship(a, b),
            overallAccuracy = calculateOverallAccuracy(resultsByDistance.values),
            recommendations = generateDistanceRecommendations(resultsByDistance)
        )
    }
    
    /**
     * 环境因素影响测试
     */
    fun testEnvironmentalFactors(): 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))
        }
        
        return EnvironmentalTestReport(
            results = results,
            mostFavorableCondition = results.minByOrNull { it.result.averageError }?.condition,
            leastFavorableCondition = results.maxByOrNull { it.result.averageError }?.condition,
            environmentalImpact = analyzeEnvironmentalImpact(results)
        )
    }
    
    private fun simulateMeasurement(actualLength: Float): Float {
        // 模拟测量误差:实际值 + 随机误差
        val randomError = (Random.nextFloat() - 0.5f) * 0.1f  // ±5cm随机误差
        val systematicError = 0.02f  // 2cm系统误差
        
        return actualLength + systematicError + randomError
    }
    
    private fun simulateMeasurementWithCondition(
        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
    }
    
    private fun calculateAccuracyRating(avgError: Float, actualLength: Float): String {
        val errorPercentage = (avgError / actualLength) * 100
        
        return when {
            errorPercentage < 1 -> "优秀 (<1%)"
            errorPercentage < 3 -> "良好 (1-3%)"
            errorPercentage < 5 -> "一般 (3-5%)"
            errorPercentage < 10 -> "较差 (5-10%)"
            else -> "很差 (>10%)"
        }
    }
    
    private fun calculateConfidenceLevel(stdDev: Float, sampleSize: Int): Float {
        // 简化的置信度计算
        val confidence = 1.0f - (stdDev / 0.1f)  // 假设0.1m为标准差基准
        
        // 考虑样本量
        val sampleFactor = min(1.0f, sampleSize / 30.0f)
        
        return max(0f, confidence * sampleFactor)
    }
    
    data class ReferenceObject(
        val name: String,
        val actualLength: Float,          // 实际长度(米)
        val maxAcceptableError: Float     // 最大可接受误差(米)
    )
    
    data class AccuracyTestResult(
        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() ?: 0f
        val maxError = measurements.maxOrNull() ?: 0f
    }
    
    data class DistanceAccuracyReport(
        val resultsByDistance: Map<Float, AccuracyTestResult>,
        val distanceErrorRelationship: DistanceErrorRelationship,
        val overallAccuracy: String,
        val recommendations: List<String>
    )
    
    data class DistanceErrorRelationship(
        val slope: Float,  // 误差随距离增长的斜率
        val intercept: Float  // 距离为0时的误差
    ) {
        fun predictError(distance: Float): Float {
            return slope * distance + intercept
        }
    }
    
    data class EnvironmentalTestReport(
        val results: List<ConditionalTestResult>,
        val mostFavorableCondition: TestCondition?,
        val leastFavorableCondition: TestCondition?,
        val environmentalImpact: Map<String, Float>  // 各因素对误差的影响程度
    )
    
    data class ConditionalTestResult(
        val condition: TestCondition,
        val result: AccuracyTestResult
    )
    
    data class TestCondition(
        val name: String,
        val lighting: LightingCondition = LightingCondition.GOOD,
        val surface: SurfaceType = SurfaceType.TEXTURED,
        val stability: Stability = Stability.STABLE
    )
    
    enum class LightingCondition { GOOD, LOW, HIGH }
    enum class SurfaceType { TEXTURED, SMOOTH }
    enum class Stability { STABLE, MOVING }
}

第十章:完整应用集成

10.1 应用主界面集成

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    
    private lateinit var binding: ActivityMainBinding
    
    private lateinit var arSessionManager: ARSessionManager
    private lateinit var measurementManager: MeasurementManager
    private lateinit var calibrationSystem: CalibrationSystem
    
    private var currentMeasurement: Measurement? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        // 初始化AR组件
        initializeARComponents()
        
        // 设置UI监听器
        setupUIListeners()
        
        // 检查AR Core可用性
        checkARAvailability()
        
        // 加载校准数据
        calibrationSystem.loadCalibrationData()
    }
    
    private fun initializeARComponents() {
        // 初始化AR会话管理器
        arSessionManager = ARSessionManager(this, binding.arSceneView)
        
        // 初始化测量管理器
        measurementManager = MeasurementManager(this, arSessionManager)
        
        // 初始化校准系统
        calibrationSystem = CalibrationSystem(this, arSessionManager)
        
        // 设置测量回调
        measurementManager.onMeasurementComplete = { measurement ->
            handleMeasurementComplete(measurement)
        }
        
        measurementManager.onMeasurementUpdate = { progress ->
            updateMeasurementProgress(progress)
        }
    }
    
    private fun setupUIListeners() {
        // 测量模式选择
        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)
        }
    }
    
    private fun handleARTouch(event: MotionEvent): Boolean {
        return when (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
        }
    }
    
    private fun startMeasurement(type: MeasurementType) {
        // 检查AR会话状态
        if (!arSessionManager.isSessionReady()) {
            showToast("AR会话未就绪,请等待平面检测")
            return
        }
        
        // 创建新测量
        currentMeasurement = measurementManager.createMeasurement(type)
        
        // 更新UI状态
        updateUIForMeasurement(type)
        
        // 显示指导信息
        showMeasurementInstructions(type)
    }
    
    private fun updateUIForMeasurement(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
    }
    
    private fun resetButtonColors() {
        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)
    }
    
    private fun handleMeasurementComplete(measurement: Measurement) {
        // 应用校准
        val calibratedValue = calibrationSystem.applyCalibration(measurement.value)
        
        // 显示结果
        showMeasurementResult(measurement.type, calibratedValue)
        
        // 保存到历史
        saveMeasurementToHistory(measurement.copy(value = calibratedValue))
        
        // 重置当前测量
        currentMeasurement = null
        resetUIAfterMeasurement()
    }
    
    private fun showMeasurementResult(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()
    }
    
    private fun showMeasurementInstructions(type: MeasurementType) {
        val instructions = when (type) {
            MeasurementType.LENGTH -> "请点击起点和终点"
            MeasurementType.AREA -> "请点击三个点定义矩形"
            MeasurementType.VOLUME -> "请点击四个点定义长方体"
            MeasurementType.ANGLE -> "请点击三个点测量角度"
        }
        
        binding.tvInstruction.text = instructions
        binding.tvInstruction.visibility = View.VISIBLE
    }
    
    private fun updateMeasurementProgress(progress: MeasurementProgress) {
        binding.progressBar.progress = progress.percentage
        binding.tvProgress.text = "进度: ${progress.step}/${progress.totalSteps}"
    }
    
    private fun clearCurrentMeasurement() {
        measurementManager.clearCurrentMeasurement()
        currentMeasurement = null
        resetUIAfterMeasurement()
        showToast("测量已清除")
    }
    
    private fun resetUIAfterMeasurement() {
        binding.progressBar.visibility = View.GONE
        binding.tvInstruction.visibility = View.GONE
        binding.tvProgress.text = ""
        resetButtonColors()
    }
    
    override fun onResume() {
        super.onResume()
        arSessionManager.resume()
    }
    
    override fun onPause() {
        super.onPause()
        arSessionManager.pause()
    }
    
    override fun onDestroy() {
        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测量应用了!


资源链接

问题反馈

如果您在实现过程中遇到任何问题,或者有改进建议,欢迎在评论区交流哦。

代码示例可自由使用,但需保留署名哈。

相关推荐
方见华Richard2 小时前
全球AGI实验室梯队标准清单(2026)
人工智能·经验分享·交互·原型模式·空间计算
浅念-2 小时前
C语言文件操作
c语言·c++·经验分享·笔记·学习
方见华Richard2 小时前
世毫九实验室RAE递归对抗引擎:技术与原理全解
人工智能·经验分享·交互·原型模式·空间计算
字节跳动的猫2 小时前
2026四款AI 开源项目改造提速
经验分享
The森2 小时前
万字长文外加示例:进入内核理解Linux 文件描述符(fd) 和 “一切皆文件” 理念
linux·经验分享·笔记
June bug2 小时前
【领域知识】一个休闲游戏产品(安卓和iOS)从0到1
android·ios
zgyhc20502 小时前
【Android Audio】Android Audio有线设备插拔监听机制
android
ZHANG13HAO2 小时前
android13 系统强制wifi连接到内网,假装具有互联网能力
android
三水不滴2 小时前
23种设计模式
经验分享·笔记·设计模式