基于Camera2 API:如何从onCaptureProgressed的CaptureResult取byte[]数据?
解决Camera2 API实时获取图像byte[]的问题
嘿,我来帮你理清这个问题——其实onCaptureProgressed()里的CaptureResult根本不是用来拿图像像素数据的,它只负责返回捕获过程中的元数据(比如对焦状态、曝光参数这些)。要实时获取图像的byte[],你得用ImageReader来做,这才是Camera2 API里获取帧数据的正确姿势。
核心思路
Camera2 API是通过Surface来输出图像数据的,你需要把ImageReader的Surface添加到捕获会话的输出列表中,当每帧图像捕获完成时,ImageReader会回调通知你获取图像,再把图像转换成byte[]。
具体实现步骤
创建ImageReader实例
选择合适的图像格式(比如ImageFormat.YUV_420_888,这是Camera2预览常用格式,适合实时处理),指定和预览一致的尺寸,设置最大缓存图像数(一般设2-3帧就够):// 假设预览尺寸是previewSize,比如new Size(1920, 1080) ImageReader imageReader = ImageReader.newInstance( previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2 // 最大缓存图像数 );设置ImageReader的图像可用监听器
这里是你获取图像并转换成byte[]的关键地方:imageReader.setOnImageAvailableListener(reader -> { try (Image image = reader.acquireNextImage()) { if (image == null) return; // 将Image对象转换成byte[],这里以YUV_420_888为例 byte[] imageBytes = convertImageToByteArray(image); // 在这里处理你的实时图像处理逻辑,比如传入算法模型等 processImageBytes(imageBytes); } catch (Exception e) { e.printStackTrace(); } }, new Handler(Looper.getMainLooper()));将ImageReader的Surface加入捕获会话
在创建CameraCaptureSession时,把imageReader.getSurface()和预览的Surface(比如TextureView的Surface)一起加入输出列表:List<Surface> outputSurfaces = new ArrayList<>(); outputSurfaces.add(previewSurface); // 预览的Surface outputSurfaces.add(imageReader.getSurface()); // ImageReader的Surface cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { // 配置完成后,启动预览捕获请求 try { CaptureRequest.Builder previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewRequestBuilder.addTarget(previewSurface); previewRequestBuilder.addTarget(imageReader.getSurface()); // 把ImageReader的Surface加入请求目标 session.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { // 处理配置失败逻辑,比如弹出提示 } }, backgroundHandler);实现Image到byte[]的转换方法
针对YUV_420_888格式,需要分别提取Y、U、V三个平面的字节数据,再合并(或者根据你的需求单独处理):private byte[] convertImageToByteArray(Image image) { Image.Plane[] planes = image.getPlanes(); ByteBuffer yBuffer = planes[0].getBuffer(); ByteBuffer uBuffer = planes[1].getBuffer(); ByteBuffer vBuffer = planes[2].getBuffer(); int ySize = yBuffer.remaining(); int uSize = uBuffer.remaining(); int vSize = vBuffer.remaining(); byte[] imageBytes = new byte[ySize + uSize + vSize]; yBuffer.get(imageBytes, 0, ySize); uBuffer.get(imageBytes, ySize, uSize); vBuffer.get(imageBytes, ySize + uSize, vSize); return imageBytes; }
关键注意事项
- 一定要在
try-with-resources里使用Image对象,或者用完后手动调用image.close(),否则会导致内存泄漏,相机很快就会停止输出图像。 - 如果你的实时处理需要RGB格式,可以把YUV数据转换成RGB再处理,但这个转换会有一定性能开销,建议尽量在YUV格式下完成处理。
onCaptureProgressed()方法确实会每帧调用,但它的作用是告诉你捕获的进度状态,比如自动对焦的进度,不要指望从这里拿图像像素数据。
内容的提问来源于stack exchange,提问作者Mayur Dabhi




