基于Kalman Filter的安卓实时相机尺度估算及花园面积计算咨询
方案与代码示例
一、安卓相机预览物体尺度归一化(保持显示尺寸一致)
核心原理
相机成像遵循透视投影规律:物体的像素尺寸与相机到物体的距离成反比。要固定显示尺寸,需通过相机内参+物体真实尺寸/距离信息计算实时缩放因子,对预览帧或物体检测结果进行缩放。
实现步骤
- 步骤1:获取相机内参
用CameraX(安卓推荐方案)获取焦距、传感器物理尺寸,转换为像素单位的焦距:val cameraCharacteristics = cameraInfo.cameraCharacteristics // 物理焦距(mm) val focalLengthMm = cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_FOCAL_LENGTH)!! // 传感器物理尺寸(mm) val sensorSize = cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)!! // 预览帧宽度(像素) val previewWidth = previewView.width // 像素焦距 = 物理焦距 * 预览宽度 / 传感器宽度 val focalLengthPixel = focalLengthMm * previewWidth / sensorSize.width - 步骤2:检测物体并计算缩放因子
假设已知目标物体的真实宽度(比如花园花盆真实宽度为20cm),通过ML Kit/OpenCV检测出物体的像素宽度detectedPixelWidth,计算相机到物体的距离,再推导缩放因子:val realObjectWidthCm = 20.0 // 距离(cm)= (真实宽度 * 像素焦距) / 检测到的像素宽度 val distanceCm = (realObjectWidthCm * focalLengthPixel) / detectedPixelWidth // 目标固定显示像素宽度(比如100px) val targetPixelWidth = 100.0 // 实时缩放因子:让物体显示为目标宽度 val scaleFactor = targetPixelWidth / detectedPixelWidth - 步骤3:应用缩放
在预览帧的绘制回调中,对物体边界框或帧本身应用缩放:previewView.setOnDrawListener { canvas -> // 绘制检测到的物体框,应用缩放因子 val scaledRect = RectF( detectedRect.left * scaleFactor, detectedRect.top * scaleFactor, detectedRect.right * scaleFactor, detectedRect.bottom * scaleFactor ) canvas.drawRect(scaledRect, Paint().apply { color = Color.RED style = Paint.Style.STROKE strokeWidth = 3f }) }
补充说明
如果没有已知真实尺寸的物体,可通过场景参考物(比如标准尺寸的地面瓷砖)校准;或使用双目/TOF传感器直接获取距离。
二、卡尔曼滤波实现花园面积平滑计算
核心思路
花园面积计算易受边缘检测噪声、相机抖动影响,卡尔曼滤波通过预测-更新循环,融合历史状态与当前观测值,输出平滑的面积估计值。
卡尔曼滤波实现(Kotlin)
class KalmanFilter { // 状态向量:当前估计面积 private var x: Double = 0.0 // 状态协方差矩阵(衡量估计不确定性) private var p: Double = 1.0 // 过程噪声协方差(面积变化的不确定性) private val q: Double = 0.01 // 观测噪声协方差(检测误差的不确定性) private val r: Double = 0.1 // 状态转移矩阵(假设面积变化缓慢) private val a: Double = 1.0 // 观测矩阵(观测值直接对应状态) private val h: Double = 1.0 fun update(measurement: Double): Double { // 预测步骤:根据历史状态预测当前状态 val xPred = a * x val pPred = a * p * a + q // 更新步骤:用观测值修正预测结果 val k = pPred * h / (h * pPred * h + r) x = xPred + k * (measurement - h * xPred) p = (1 - k * h) * pPred return x } }
结合面积计算的流程
- 提取花园轮廓并计算像素面积:用OpenCV处理预览帧:
fun calculateGardenPixelArea(frame: Mat): Double { val gray = Mat() Imgproc.cvtColor(frame, gray, Imgproc.COLOR_BGR2GRAY) val blurred = Mat() Imgproc.GaussianBlur(gray, blurred, Size(5.0,5.0), 0.0) val edges = Mat() Imgproc.Canny(blurred, edges, 50.0, 150.0) val contours = mutableListOf<MatOfPoint>() val hierarchy = Mat() Imgproc.findContours(edges, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE) // 取最大轮廓作为花园区域 val maxContour = contours.maxByOrNull { Imgproc.contourArea(it) } ?: return 0.0 return Imgproc.contourArea(maxContour) } - 像素面积转真实面积:结合相机内参和距离转换为平方米:
fun pixelToRealArea(pixelArea: Double, distanceCm: Double, focalLengthPixel: Double, sensorWidthMm: Double): Double { val previewWidth = previewView.width // 计算每像素对应的真实长度(米) val pixelPerMm = previewWidth / sensorWidthMm val realMeterPerPixel = (distanceCm / (focalLengthPixel / pixelPerMm)) / 100 // 真实面积 = 像素面积 * 单像素真实长度^2 return pixelArea * realMeterPerPixel * realMeterPerPixel } - 应用卡尔曼滤波平滑结果:
val kalmanFilter = KalmanFilter() // 每一帧处理逻辑 fun processFrame(frame: Mat) { val pixelArea = calculateGardenPixelArea(frame) val realArea = pixelToRealArea(pixelArea, distanceCm, focalLengthPixel, sensorSize.width) val smoothedArea = kalmanFilter.update(realArea) // 显示平滑后的面积 textView.text = "花园面积:${String.format("%.2f", smoothedArea)} ㎡" }
调优建议
- 根据实际噪声情况调整
q和r:噪声大时增大r,面积变化慢时减小q; - 若相机移动导致面积变化有规律,可扩展状态向量为
[面积, 面积变化率],提升预测精度。
内容的提问来源于stack exchange,提问作者Hemant Rawat
相关产品推荐
相关产品推荐

