跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
KotlinAI大前端算法

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

综述由AI生成AR 测量技术结合手机摄像头与 AR 引擎实现非接触式测量。基于 AR Core 和 CameraX 框架,详解了从环境搭建、相机初始化、坐标转换算法到 UI 交互的完整实现流程。涵盖长度、面积、体积及角度测量功能,包含精度优化校准系统及性能监控方案。通过光线投射与平面检测技术,解决了 2D 屏幕坐标至 3D 世界坐标的映射难题,并提供多帧平均与卡尔曼滤波等误差修正策略,最终构建出具备实用价值的 AR 测量应用架构。

漫步发布于 2026/4/8更新于 2026/5/2217 浏览

引言

在现实世界中,我们经常需要测量物体的大小。通过手机摄像头和 AR 技术,可以实现所见即所得的智能测量。本文将阐述 AR 测量的核心原理,并实现一个完整的 AR 测量应用。

技术基础 - 理解 AR 测量的核心原理

AR 测量技术栈架构

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

AR 测量与普通测量的对比

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

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

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

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

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

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

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

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

环境搭建与基础配置

项目依赖配置

// 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'
}

权限与特性声明

<!-- 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 的协同工作

CameraX 相机初始化

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)
        }
    }
}

AR Core 会话管理

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 测量核心算法实现

屏幕到世界坐标转换算法

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
        )
    }
}

多点测量与几何计算

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)
    }
}

用户界面与交互设计

测量界面实现

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
)

测量标注视图

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)

精度优化与校准

测量精度优化算法

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)

校准系统实现

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", 0L)
        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
)

高级功能扩展

面积与体积测量

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)
        }
    }
}

测量历史与数据管理

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
)

性能优化与用户体验

实时性能监控

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 }

测试与验证

测量准确性测试

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 }

结语

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

技术发展趋势:

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

给开发者的建议:

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

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

目录

  1. 引言
  2. 技术基础 - 理解 AR 测量的核心原理
  3. AR 测量技术栈架构
  4. AR 测量与普通测量的对比
  5. AR 测量的核心挑战与解决方案
  6. 环境搭建与基础配置
  7. 项目依赖配置
  8. 权限与特性声明
  9. CameraX 与 AR Core 的协同工作
  10. CameraX 相机初始化
  11. AR Core 会话管理
  12. AR 测量核心算法实现
  13. 屏幕到世界坐标转换算法
  14. 多点测量与几何计算
  15. 用户界面与交互设计
  16. 测量界面实现
  17. 测量标注视图
  18. 精度优化与校准
  19. 测量精度优化算法
  20. 校准系统实现
  21. 高级功能扩展
  22. 面积与体积测量
  23. 测量历史与数据管理
  24. 性能优化与用户体验
  25. 实时性能监控
  26. 测试与验证
  27. 测量准确性测试
  28. 结语
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Qwen-Image-2512 技术亮点解析与 ComfyUI 部署实战
  • 使用码云 Gitee 登录 Ruoyi-Vue-Pro 配置指南
  • C++ STL 哈希表原理与模拟实现
  • Flutter与Web混合开发实践
  • 前端新势力:Svelte、SolidJS 等框架深度解析与对比
  • Python 入门指南:基于爬虫技术的零基础学习路径
  • ToClaw:基于 OpenClaw 的零门槛 AI 桌面自动化方案
  • Web 虚拟卡销售平台架构设计与核心实现
  • AI 产品经理面试 20 个核心问题及备考策略
  • 2026 年上半年主流 AIGC 长文本写作软件实测:5 款头部工具优缺点解析
  • ToClaw:基于 OpenClaw 的云端 AI 桌面自动化助手
  • Verilog 描述半加器:FPGA 硬件入门实战
  • 美团 EvoCUA 刷新开源 SOTA:具备持续进化能力的计算机操作智能体
  • Spring AI:Java 生态的 AI 赋能与企业级智能应用开发
  • 前端内存泄露检测与排查指南
  • Qwen2.5-7B-Instruct 模型本地运行与 Ollama 实践
  • FAIR plus 机器人全产业链接会:聚焦全产业链技术与创新
  • AI 赋能数据库运维:金仓 KES 的智能化未来
  • macOS 完整卸载 OpenClaw 指南(含深度清理)
  • stable-diffusion-webui 照片艺术化风格迁移指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online