Android CameraX拍照部分设备图像旋转问题排查与修正
CameraX拍照图像旋转不一致问题解决
问题说明
在Android平台使用CameraX实现点击拍照并转换为指定尺寸正方形的功能时,不同设备拍照结果存在差异:部分设备捕获图像与预览画面一致,部分设备的捕获图像出现旋转,需要让捕获图像与预览画面完全一致(无旋转)。
核心原因
- 硬编码设置
setTargetRotation(ROTATION_0),未适配设备的屏幕旋转和相机传感器方向 - 转换
ImageProxy为Bitmap时,忽略了CameraX提供的图像旋转信息,导致图像方向与预览不匹配
修复步骤及代码修改
1. 修正CameraX用例的旋转设置
移除Preview和ImageCapture构建时硬编码的旋转设置,改用预览View的实际旋转值,让CameraX自动适配设备方向:
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) { Log.d(TAG, "bindPreview: width " + width); Log.d(TAG, "bindPreview: height " + height); Preview preview = new Preview.Builder() .setTargetResolution(new Size(width, height)) // 适配预览View的实际旋转 .setTargetRotation(previewView.getDisplay().getRotation()) .build(); previewView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange( View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { Size newViewFinderDimens = new Size(right - left, bottom - top); // 传入实际屏幕旋转角度 updateTransform(previewView.getDisplay().getRotation(), MainActivity.bufferDimens, newViewFinderDimens); } }); imageCapture = new ImageCapture.Builder() .setTargetResolution(new Size(width, height)) // 适配预览View的实际旋转 .setTargetRotation(previewView.getDisplay().getRotation()) .build(); CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build(); preview.setSurfaceProvider(previewView.getSurfaceProvider()); UseCaseGroup useCaseGroup = new UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(imageCapture) .build(); cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, useCaseGroup); previewHeight = previewView.getHeight(); previewWidth = previewView.getWidth(); }
2. 拍照成功时修正图像旋转
在onCaptureSuccess中,获取CameraX计算好的旋转角度,转换Bitmap后进行方向修正,确保与预览画面一致:
bGetImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { final Bitmap[] bitmap = {null}; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { imageCapture.takePicture(getMainExecutor(),new ImageCapture.OnImageCapturedCallback() { @Override public void onCaptureSuccess(@NonNull ImageProxy image) { super.onCaptureSuccess(image); Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.width()); Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.height()); OutputTransform source = previewView.getOutputTransform(); OutputTransform target = new ImageProxyTransformFactory().getOutputTransform(image); Log.d(TAG, "onCaptureSuccess: target " + target.getMatrix().toString()); CoordinateTransform coordinateTransform = new CoordinateTransform(source, target); coordinateTransform.mapRect(testview.innerRectangle); Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.width()); Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.height()); // 获取CameraX计算的旋转角度 int rotationDegrees = image.getImageInfo().getRotationDegrees(); bitmap[0] = imageProxytoBitmap(image); // 根据旋转角度修正Bitmap方向 if (rotationDegrees != 0) { bitmap[0] = rotateImage(bitmap[0], rotationDegrees); } Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getWidth()); Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getHeight()); // 裁剪为指定正方形 bitmap[0] = Bitmap.createBitmap(bitmap[0],(int)testview.innerRectangle.left,(int)testview.innerRectangle.top,(int)testview.innerRectangle.width(),(int)testview.innerRectangle.height()); try { Uri uri = getUri(bitmap[0], getContentResolver()); Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getWidth()); Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getHeight()); String path = uri.toString(); moveToScanActivity(path); } catch (IOException e) { Log.e(TAG, "onCaptureSuccess: ",e); } finally { // 关闭ImageProxy避免资源泄漏 image.close(); } } }); } } });
3. 优化ImageProxy转Bitmap的方法
确保正确处理图像格式,避免潜在的格式兼容问题:
private Bitmap imageProxytoBitmap(ImageProxy image) { Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); }
4. 修复预览变换逻辑
确保预览的变换矩阵与实际旋转匹配:
public void updateTransform(int rotation, Size newBufferDimens, Size newViewFinderDimens) { if (rotation == viewFinderRotation && newBufferDimens.equals(bufferDimens) && newViewFinderDimens.equals(viewFinderDimens)) { return; } if (newBufferDimens.getWidth() == 0 || newBufferDimens.getHeight() == 0) { return; } else { bufferDimens = newBufferDimens; } if (newViewFinderDimens.getWidth() == 0 || newViewFinderDimens.getHeight() == 0) { return; } else { viewFinderDimens = newViewFinderDimens; } Matrix matrix = new Matrix(); Log.d(TAG, "Applying output transformation.\n" + "View finder size: " + viewFinderDimens + "\n" + "Preview output size: " + bufferDimens + "\n" + "View finder rotation: " + rotation + "\n" + "Preview output rotation: " + 0); float centerX = previewView.getWidth() / 2f; float centerY = previewView.getHeight() / 2f; // 根据传入的旋转角度修正预览 float rotationDegrees; switch (rotation) { case Surface.ROTATION_0: rotationDegrees = 0f; break; case Surface.ROTATION_90: rotationDegrees = 90f; break; case Surface.ROTATION_180: rotationDegrees = 180f; break; case Surface.ROTATION_270: rotationDegrees = 270f; break; default: return; } matrix.postRotate(-rotationDegrees, centerX, centerY); float bufferRatio = (float) bufferDimens.getWidth() / bufferDimens.getHeight(); int scaledWidth; int scaledHeight; if (previewView.getWidth() > previewView.getHeight()) { scaledWidth = viewFinderDimens.getWidth(); scaledHeight = Math.round(viewFinderDimens.getWidth() / bufferRatio); } else { scaledHeight = viewFinderDimens.getHeight(); scaledWidth = Math.round(viewFinderDimens.getHeight() * bufferRatio); } float xScale = (float) scaledWidth / viewFinderDimens.getWidth(); float yScale = (float) scaledHeight / viewFinderDimens.getHeight(); matrix.preScale(xScale, yScale, centerX, centerY); // 将变换矩阵应用到预览View previewView.setTransform(matrix); viewFinderRotation = rotation; }
关键注意事项
- 必须在
onCaptureSuccess的最后调用image.close(),避免CameraX资源泄漏 - 禁止硬编码旋转角度,始终使用CameraX提供的
getRotationDegrees()或预览View的实际旋转值 - 裁剪正方形时,需确保旋转后的Bitmap尺寸正确,避免裁剪区域偏移
内容的提问来源于stack exchange,提问作者Lal Bihari Pandey
相关产品推荐
相关产品推荐

