Flutter移动端获取相机内参

0 阅读2分钟

Flutter App 中相机内参获取全流程

一句话总结
Flutter 本身无法拿到“真实相机内参”,必须通过 原生 Android / iOS API 获取,然后回传给 Flutter。
⚠️ 不同分辨率、不同摄像头、不同对焦状态,内参都会不同,需要动态获取或按比例调整。


1. 什么是相机内参

视觉里常用的内参矩阵 (K):

image.png

参数含义
fx, fy焦距(像素单位)
cx, cy主点(通常在图像中心附近)
skew一般为 0

工程实践中,基本关注 fx, fy, cx, cy 即可。


2. Flutter 层调用方法

Flutter 端通过 MethodChannel 调用原生接口。

方式 A:详细版本(用于上传 / 展示)

static const MethodChannel _channel = MethodChannel('camera_intrinsics_detailed');

Future<CameraIntrinsicsResult?> fetchCameraIntrinsics() async {
  final result = await _channel.invokeMethod('getCameraIntrinsicsDetailed');
  // 解析 fx, fy, cx, cy, method, errorMargin
}

方式 B:简单版本(仅用于文件名 / 简单用途)

static const MethodChannel _channel = MethodChannel('camera_intrinsics');

Future<CameraIntrinsics?> getCameraIntrinsics() async {
  final result = await _channel.invokeMethod('getCameraIntrinsics');
  // 只返回 fx, fy, cx, cy
}

3. Android 原生实现(Camera2 API)

1️⃣ 选择摄像头(优先后置)

val cameraManager = getSystemService(CAMERA_SERVICE) as CameraManager
val cameraIds = cameraManager.cameraIdList

var targetCameraId: String? = null
for (id in cameraIds) {
    val characteristics = cameraManager.getCameraCharacteristics(id)
    if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_BACK) {
        targetCameraId = id
        break
    }
}

2️⃣ 获取内参(优先硬件校准)

val intrinsic = characteristics.get(CameraCharacteristics.LENS_INTRINSIC_CALIBRATION)
val fx = intrinsic[0].toDouble()
val fy = intrinsic[1].toDouble()
val cx = intrinsic[2].toDouble()
val cy = intrinsic[3].toDouble()
  • 硬件校准误差约 1%
  • 若获取失败,可用传感器参数计算:
val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)
val pixelArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)
val focalLength = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)[0]

val fx = focalLength * pixelArray.width / sensorSize.width
val fy = focalLength * pixelArray.height / sensorSize.height
val cx = pixelArray.width / 2.0
val cy = pixelArray.height / 2.0

计算值误差约 5%,90% 场景够用。

⚠️ 注意坑点

  1. 分辨率变化时需要重算 fx/fy
  2. 前后摄像头内参不同
  3. CameraX 默认隐藏内参,需要 Camera2Interop

4. iOS 原生实现(AVFoundation)

1️⃣ 获取摄像头和 activeFormat

guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { return }
let format = camera.activeFormat
let dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
let width = Double(dimensions.width)
let height = Double(dimensions.height)

2️⃣ 获取内参(推荐顺序)

方法1:CameraCalibrationData(最准确,ARKit/TrueDepth 支持)
if let intrinsic = camera.cameraCalibrationData?.intrinsicMatrix {
    let fx = intrinsic.columns.0.x
    let fy = intrinsic.columns.1.y
    let cx = intrinsic.columns.2.x
    let cy = intrinsic.columns.2.y
}
方法2:FOV计算(估算)
let fovXRadians = camera.activeFormat.videoFieldOfView * .pi / 180
let fx = width / (2 * tan(fovXRadians/2))
let fy = fx * height / width
let cx = width / 2
let cy = height / 2
方法3:经验值估算(粗略)
let fx = width * 1.2
let fy = height * 1.2
let cx = width / 2
let cy = height / 2

5. Flutter 调用链总结

用户操作 / 页面启动
    │
    ├─> Flutter 层调用 fetchCameraIntrinsics()
    │   └─> MethodChannel
    │       ├─> Android: Camera2 API
    │       └─> iOS: AVCaptureDevice + CameraCalibrationData / FOV
    │
    └─> 返回 fx, fy, cx, cy,存储到 SharedPreferences / 本地缓存

6. 工程实用经验

  1. 不同分辨率:内参按比例缩放

  2. 不同摄像头:前后摄像头内参不同

  3. Flutter 端:只做存储、计算、投影,核心获取依赖原生

  4. 原型阶段 / OpenCV 算法:可用近似值

    fx ≈ fy ≈ max(imageWidth, imageHeight)
    cx ≈ imageWidth / 2
    cy ≈ imageHeight / 2
    

7. 总结

  • Flutter 本身无法直接获取相机内参
  • Android 用 Camera2 API / LENS_INTRINSIC_CALIBRATION
  • iOS 用 CameraCalibrationData / activeFormat / FOV
  • 实际使用时需考虑分辨率、摄像头、对焦状态
  • 工程上可先用近似值跑算法,后期按设备精确获取