You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:45:31