Android Camera2 API裁剪预览框区域存图异常问题排查
问题分析与解决方案
嘿,我看了你的代码和问题,核心问题出在两个关键地方:Rect构造参数顺序错误,以及对Camera2裁剪区域的坐标空间理解偏差。咱们一步步来拆解修复:
1. 最直接的错误:Rect构造参数顺序搞反了
Camera2要求SCALER_CROP_REGION对应的Rect必须遵循left, top, right, bottom的顺序,而且必须满足right > left、bottom > top。你代码里写的:
Rect cropRect = new Rect(topX,topY,x,y);
这里x和y比topX、topY小,导致right < left、bottom < top,系统会自动修正这个无效的矩形,但修正后的区域完全不是你想要的,这是导致裁切和缩小的直接原因。
正确的写法应该是先定义区域的左上和右下坐标:
// 假设你想要的区域是从(x,y)开始,宽度为250/4,高度为250/5 int left = x; int top = y; int right = x + (250 / 4); int bottom = y + (250 / 5); Rect cropRect = new Rect(left, top, right, bottom);
2. 裁剪区域的坐标空间是传感器原生坐标,不是输出尺寸
Camera2的SCALER_CROP_REGION坐标基于相机传感器的原生分辨率,而不是你设置的输出图片尺寸(比如640x480或jpegSizes里的尺寸)。你直接用输出尺寸计算x、y,会导致裁剪区域和预览里的视点矩形完全不匹配。
解决方法:
- 先获取传感器的原生有效尺寸:
Size sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); int sensorWidth = sensorSize.getWidth(); int sensorHeight = sensorSize.getHeight(); - 把预览界面上的视点矩形(基于预览Surface的坐标)转换为传感器坐标空间,需要考虑预览的缩放比例和相机旋转方向(比如竖屏时预览坐标和传感器坐标是旋转90度的):
// 替换成你实际的预览Surface尺寸和预览里的视点矩形 Size previewSize = /* 你的预览Surface尺寸,比如从预览CaptureRequest中获取 */; Rect previewCropRect = /* 预览界面上显示的视点矩形 */; // 计算缩放比例 float scaleX = (float)sensorWidth / previewSize.getWidth(); float scaleY = (float)sensorHeight / previewSize.getHeight(); // 转换为传感器坐标 int sensorLeft = Math.round(previewCropRect.left * scaleX); int sensorTop = Math.round(previewCropRect.top * scaleY); int sensorRight = Math.round(previewCropRect.right * scaleX); int sensorBottom = Math.round(previewCropRect.bottom * scaleY); // 确保裁剪区域在传感器有效范围内 sensorLeft = Math.max(0, sensorLeft); sensorTop = Math.max(0, sensorTop); sensorRight = Math.min(sensorWidth, sensorRight); sensorBottom = Math.min(sensorHeight, sensorBottom); Rect cropRect = new Rect(sensorLeft, sensorTop, sensorRight, sensorBottom);
3. 输出尺寸与裁剪区域的匹配问题
当你设置SCALER_CROP_REGION后,相机会先裁剪传感器帧到指定区域,再缩放到ImageReader的尺寸。如果你希望保存的图片就是裁剪区域的原始大小,需要把ImageReader的尺寸设置为裁剪区域的尺寸:
int cropWidth = sensorRight - sensorLeft; int cropHeight = sensorBottom - sensorTop; ImageReader mImageReader = ImageReader.newInstance(cropWidth, cropHeight, ImageFormat.JPEG, 1);
否则,裁剪后的帧会被强制拉伸/缩放到ImageReader的尺寸,导致你看到的“缩小”或变形。
修正后的核心代码片段
这里给你调整后的关键代码部分:
private void captureStillPicture() { try { final Activity activity = getActivity(); if (null == activity || null == mCameraDevice) { return; } final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE); CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraDevice.getId()); // 获取传感器原生尺寸 Size sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); int sensorWidth = sensorSize.getWidth(); int sensorHeight = sensorSize.getHeight(); // 替换成你实际的预览尺寸和预览视点矩形 Size previewSize = /* 你的预览Surface尺寸 */; Rect previewCropRect = /* 预览界面上的视点矩形 */; // 转换预览坐标到传感器坐标 float scaleX = (float)sensorWidth / previewSize.getWidth(); float scaleY = (float)sensorHeight / previewSize.getHeight(); int sensorLeft = Math.round(previewCropRect.left * scaleX); int sensorTop = Math.round(previewCropRect.top * scaleY); int sensorRight = Math.round(previewCropRect.right * scaleX); int sensorBottom = Math.round(previewCropRect.bottom * scaleY); // 确保裁剪区域有效 sensorLeft = Math.max(0, sensorLeft); sensorTop = Math.max(0, sensorTop); sensorRight = Math.min(sensorWidth, sensorRight); sensorBottom = Math.min(sensorHeight, sensorBottom); Rect cropRect = new Rect(sensorLeft, sensorTop, sensorRight, sensorBottom); // 设置ImageReader为裁剪区域尺寸 int cropWidth = sensorRight - sensorLeft; int cropHeight = sensorBottom - sensorTop; ImageReader mImageReader = ImageReader.newInstance(cropWidth, cropHeight, ImageFormat.JPEG, 1); captureBuilder.addTarget(mImageReader.getSurface()); captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, cropRect); // 其他原有设置保持不变 captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); setAutoFlash(captureBuilder); int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { showToast("Saved: " + mFile); Log.d(TAG, mFile.toString()); unlockFocus(); } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } }
内容的提问来源于stack exchange,提问作者Monica Das
相关产品推荐
相关产品推荐

