news 2026/5/10 15:12:34

手把手教你用Camera2 API实现一个带手动对焦和RAW拍摄的专业相机Demo

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你用Camera2 API实现一个带手动对焦和RAW拍摄的专业相机Demo

从零构建专业级Android相机:Camera2 API高阶开发实战

在移动摄影技术飞速发展的今天,系统默认相机应用的功能往往无法满足专业用户的需求。本文将带领Android开发者深入Camera2 API的高级应用,打造一个支持手动对焦和RAW格式拍摄的专业相机Demo。不同于基础教程,我们聚焦于三个核心技术点:实时触摸对焦控制、DNG原始图像处理,以及基于元数据的图像增强。

1. 环境准备与架构设计

1.1 项目初始化配置

首先在AndroidManifest.xml中添加必要权限:

<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.camera2" />

关键依赖版本要求:

组件最低版本推荐版本
Android SDKAPI 21API 26+
Camera2支持库androidx.camera:camera-core:1.1.0

1.2 Camera2核心组件解析

Camera2 API采用管道式架构,主要包含以下关键对象:

  • CameraManager:设备入口,负责枚举和打开摄像头
  • CameraCharacteristics:设备能力描述(如支持的分辨率、RAW输出等)
  • CameraDevice:物理摄像头抽象
  • CaptureRequest:包含所有拍摄参数的请求模板
  • CameraCaptureSession:处理连续帧捕获的会话

提示:现代Android设备通常支持多个逻辑摄像头(广角、长焦等),可通过CameraCharacteristics.LENS_FACING区分

2. 实现触摸对焦功能

2.1 对焦区域映射原理

触摸对焦的核心是将屏幕坐标转换为传感器坐标系统。关键转换步骤:

  1. 获取TextureView的触摸坐标(x,y)
  2. 计算相对于预览View的标准化坐标(0-1范围)
  3. 通过CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE映射到传感器区域
private MeteringRectangle calculateFocusArea(float x, float y) { // 转换为相对坐标 float nx = x / textureView.getWidth(); float ny = y / textureView.getHeight(); // 获取传感器有效区域 Rect sensorArray = characteristics.get( CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); // 转换为传感器坐标 int focusX = (int)(nx * sensorArray.width()); int focusY = (int)(ny * sensorArray.height()); int halfWidth = 100; // 对焦区域宽度 return new MeteringRectangle( focusX - halfWidth, focusY - halfWidth, halfWidth * 2, halfWidth * 2, MeteringRectangle.METERING_WEIGHT_MAX); }

2.2 对焦请求构建

创建对焦CaptureRequest的关键参数设置:

private void triggerAutoFocus(MeteringRectangle focusArea) { try { CaptureRequest.Builder builder = cameraDevice.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW); // 设置对焦区域 builder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{focusArea}); // 设置测光区域 builder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{focusArea}); // 设置对焦模式为自动 builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); // 触发对焦 builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); cameraCaptureSession.capture(builder.build(), focusCaptureCallback, null); } catch (CameraAccessException e) { Log.e(TAG, "对焦失败", e); } }

对焦状态机处理:

private CameraCaptureSession.CaptureCallback focusCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(...) { Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); if (afState == null) return; switch (afState) { case CONTROL_AF_STATE_FOCUSED_LOCKED: showFocusSuccess(); break; case CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: showFocusFailed(); break; } } };

3. RAW图像捕获与处理

3.1 DNG格式配置

配置ImageReader接收RAW数据:

// 检查设备是否支持RAW boolean isRawSupported = characteristics.get( CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) .contains(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_RAW); if (isRawSupported) { // 创建DNG格式的ImageReader rawImageReader = ImageReader.newInstance( rawSize.getWidth(), rawSize.getHeight(), ImageFormat.RAW_SENSOR, 2); rawImageReader.setOnImageAvailableListener( rawImageAvailableListener, backgroundHandler); }

3.2 DNG文件生成

DNG文件包含传感器原始数据和丰富的元信息:

private void saveRawImage(Image image) { File rawFile = new File(getExternalFilesDir(null), "IMG_" + System.currentTimeMillis() + ".dng"); try (FileOutputStream output = new FileOutputStream(rawFile)) { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); // 添加DNG头部信息 DngCreator creator = new DngCreator(characteristics, metadata); creator.writeByteBuffer(output, new Size(image.getWidth(), image.getHeight()), buffer, 0); } }

RAW与JPEG同时捕获配置:

CaptureRequest.Builder builder = cameraDevice.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE); builder.addTarget(rawImageReader.getSurface()); builder.addTarget(jpegImageReader.getSurface()); builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); cameraCaptureSession.capture(builder.build(), captureCallback, null);

4. 基于元数据的图像增强

4.1 EXIF信息提取

从CaptureResult获取关键拍摄参数:

private void processMetadata(TotalCaptureResult result) { // 获取ISO值 Integer iso = result.get(CaptureResult.SENSOR_SENSITIVITY); // 获取曝光时间(纳秒) Long exposureTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME); // 获取白平衡增益 float[] wbGains = result.get(CaptureResult.COLOR_CORRECTION_GAINS); // 获取镜头光圈 Float aperture = result.get(CaptureResult.LENS_APERTURE); }

4.2 实时图像调节

利用元数据实现专业级控制界面:

fun setupManualControls() { // 曝光补偿 seekbarExposure.min = characteristics.get( CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)[0] seekbarExposure.max = characteristics.get( CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)[1] // ISO范围 val isoRange = characteristics.get( CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) seekbarIso.min = isoRange.lower seekbarIso.max = isoRange.upper // 快门速度 val exposureRange = characteristics.get( CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE) seekbarShutter.min = exposureRange.lower.toInt() seekbarShutter.max = exposureRange.upper.toInt() }

参数应用示例:

private void applyManualSettings() { CaptureRequest.Builder builder = cameraDevice.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW); // 手动曝光模式 builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF); // 设置ISO builder.set(CaptureRequest.SENSOR_SENSITIVITY, currentIso); // 设置曝光时间 builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, currentExposureTime); // 设置手动对焦距离 builder.set(CaptureRequest.LENS_FOCUS_DISTANCE, focusDistance); cameraCaptureSession.setRepeatingRequest(builder.build(), null, null); }

5. 性能优化与调试技巧

5.1 帧率优化策略

提升预览流畅度的关键配置:

// 选择最佳预览尺寸 Size optimalSize = chooseOptimalSize( map.getOutputSizes(SurfaceTexture.class), textureView.getWidth(), textureView.getHeight(), new Size(1920, 1080)); // 目标分辨率 // 设置高优先级模式 builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW); // 启用硬件级帧缓冲 builder.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);

5.2 常见问题排查

Camera2开发中的典型问题及解决方案:

问题现象可能原因解决方案
打开相机失败权限未授权或相机被占用检查权限并实现相机状态回调
预览图像变形显示比例与传感器比例不匹配使用TextureView.setTransform校正
对焦无响应AF模式设置不当确认CONTROL_AF_MODE支持情况
RAW保存失败存储权限或格式不支持检查设备能力和存储路径

调试日志建议:

adb shell setprop log.tag.Camera2Basic DEBUG adb logcat -s Camera2Basic

6. 扩展功能实现

6.1 实时滤镜处理

通过SurfaceTexture实现GPU加速处理:

// 创建OpenGL纹理 int[] textures = new int[1]; GLES20.glGenTextures(1, textures, 0); GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, textures[0]); // 设置SurfaceTexture监听 surfaceTexture.setOnFrameAvailableListener(frame -> { // 更新纹理 surfaceTexture.updateTexImage(); // 应用滤镜着色器 applyFilter(shaderProgram); // 渲染到屏幕 GLES20.glDrawArrays(...); });

6.2 多摄像头协同

双摄配置示例:

String[] cameraIds = cameraManager.getCameraIdList(); String backCameraId = null; String wideCameraId = null; for (String id : cameraIds) { CameraCharacteristics chars = cameraManager.getCameraCharacteristics(id); Integer facing = chars.get(CameraCharacteristics.LENS_FACING); if (facing == CameraCharacteristics.LENS_FACING_BACK) { // 检查是否广角镜头 Float maxZoom = chars.get( CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); if (maxZoom > 3.0f) { wideCameraId = id; } else { backCameraId = id; } } }

在完成这个专业相机Demo的开发过程中,最耗时的部分是对不同设备厂商的RAW格式兼容性处理。例如某品牌设备的DNG文件需要特殊的分段读取方式,这需要通过CameraCharacteristics中的SENSOR_INFO_COLOR_FILTER_ARRANGEMENT字段进行针对性适配。建议在实际项目中建立设备白名单机制,对已知问题设备进行特殊处理。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 15:06:21

MNN移动端推理引擎:从模型转换到部署优化的全链路实践

1. 项目概述&#xff1a;移动端推理引擎的“硬核”突围如果你在移动端或者边缘设备上折腾过AI模型部署&#xff0c;大概率经历过这样的痛苦&#xff1a;好不容易在云端训练好的模型&#xff0c;想放到手机或者嵌入式设备上跑起来&#xff0c;却发现要么速度慢如蜗牛&#xff0c…

作者头像 李华