Camera 2 API 会降低拍摄后的质量

Posted

技术标签:

【中文标题】Camera 2 API 会降低拍摄后的质量【英文标题】:Camera 2 API reduces the quality after capture 【发布时间】:2019-10-06 23:33:54 【问题描述】:

我在我的应用程序中使用相机拍摄身份证照片,我有一个矩形叠加层,图像将被裁剪。问题是一旦捕获图像,图像质量就会降低。

我无法弄清楚它到底发生在哪里。在 cutImage 方法中,我正在剪切图像,但我认为我没有对那里的图像分辨率做任何事情。

谁能建议质量可能下降的地方。

当用户点击拍照时调用takePicture。 拍照后,会出现一个“使用图片”按钮,此时调用 usePicture。

cutImage 方法用于根据预览裁剪图像。

任何关于如何阻止分辨率下降的建议都会非常有帮助

protected void takePicture() 
    Log.e(TAG, "takePicture started");
    if(null == cameraDevice) 
        Log.e(TAG, "cameraDevice is null");
        return;
    
    try 
        ImageReader reader = ImageReader.newInstance(textureViewWidth, textureViewHeight, ImageFormat.JPEG, 1);
        List<Surface> outputSurfaces = new ArrayList<Surface>(2);
        outputSurfaces.add(reader.getSurface());
        outputSurfaces.add(new Surface(textureView.getSurfaceTexture()));
        final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(reader.getSurface());
        captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
        // Orientation
        int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
        ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() 
            @Override
            public void onImageAvailable(ImageReader reader) 
                Image image = null;
                try 
                    image = reader.acquireLatestImage();
                    ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                    byte[] bytes = new byte[buffer.capacity()];
                    buffer.get(bytes);
                    takenPictureBytes = bytes;
                    Log.d(TAG, "takenPictureBytes length - " + takenPictureBytes.length);

                 catch (Exception e) 
                    Log.d(TAG, " onImageAvailable exception ");
                    e.printStackTrace();
                 finally 
                    if (image != null) 
                        Log.d(TAG, " image closing");
                        image.close();
                    
                
            
        ;
        reader.setOnImageAvailableListener(readerListener, mBackgroundHandler);
        final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() 
            @Override
            public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) 
                super.onCaptureCompleted(session, request, result);
                Log.d(TAG, "takePicture - camera capture session");
                switchPanels(true);
                progress.setVisibility(View.GONE);
            
        ;
        cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() 
            @Override
            public void onConfigured(CameraCaptureSession session) 
                try 
                    session.capture(captureBuilder.build(), captureListener, mBackgroundHandler);
                 catch (CameraAccessException e) 
                    Log.d(TAG, "takePicture - onConfigured- camera access exception ");
                    e.printStackTrace();
                
            
            @Override
            public void onConfigureFailed(CameraCaptureSession session) 
                Log.d(TAG, "takePicture - onConfigureFailed");

            
        , mBackgroundHandler);
     catch (CameraAccessException e) 
        Log.d(TAG, "takePicture - CameraAccessException ");
        e.printStackTrace();
    


private void usePicture() 
    Log.d(TAG, "usePicture - started     ");

    if(null != takenPictureBytes )
        try
            String imagePath = null;

            Bitmap bitmap = BitmapFactory.decodeByteArray(takenPictureBytes, 0, takenPictureBytes.length);
            int bitmapByteCountUsePic = byteSizeOf(bitmap);
            Matrix matrix = new Matrix();
            matrix.postRotate(90);
            Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

            if (isFrameMode) 
                float withRatio = (float) rotatedBitmap.getWidth() / (float) textureViewWidth;
                float heightRatio = (float) rotatedBitmap.getHeight() / (float) textureViewHeight;

                Bitmap newImage = cutImage(rotatedBitmap, (int) (photoFrameView.getWidth() * withRatio), (int) (photoFrameView.getHeight() * heightRatio), withRatio);
                int bitmapByteCountNewImage = byteSizeOf(newImage);
                imagePath = saveBitmap(newImage);
             else 
                imagePath = saveBitmap(rotatedBitmap);
            
            TakePhotoFragment.TakePhotoFragmentEvent takePhotoFragmentEvent = new TakePhotoFragment.TakePhotoFragmentEvent();
            takePhotoFragmentEvent.setImagePath(imagePath);
            // send rxjava
            //pop backstack
            RxBus.getInstance().post(takePhotoFragmentEvent);
            getActivity().getSupportFragmentManager().popBackStack();
        catch (Exception e)
            Log.d(TAG, "usePicture - exception     ");
            e.printStackTrace();
        
    else
        Log.d(TAG, "usePicture - takenPictureBytes is null");
        DialogUtil.showErrorSnackBar(getView(), R.string.retake_photo );
    


public Bitmap cutImage(final Bitmap bitmap, final int pixepWidth, final int pixelsHeight, float widthRatio) 
    int bitmapByteCountCutImage = byteSizeOf(bitmap);
    Bitmap output = createBitmap(pixepWidth, pixelsHeight, Bitmap.Config.ARGB_8888);
    Bitmap original = bitmap;

    final Paint paint = new Paint();
    Canvas canvas = new Canvas(output);
    int padding = (int) ((float) getResources().getDimensionPixelSize(R.dimen.double_padding) * widthRatio);

    Rect rect = new Rect(padding, (original.getHeight() - pixelsHeight) / 2, padding + pixepWidth, original.getHeight() - (original.getHeight() - pixelsHeight) / 2);

    final RectF cutedRect = new RectF(0, 0, pixepWidth, pixelsHeight);
    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    canvas.drawBitmap(original, rect, cutedRect, paint);
    return output;


   private String saveBitmap(Bitmap bitmap) 
    File pictureFileDir = getDir();

    if (!pictureFileDir.exists() && !pictureFileDir.mkdirs()) 
        Toast.makeText(getActivity(), "Can't create directory to save image.", Toast.LENGTH_LONG).show();
        return null;

    

    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmmssSSS");
    String date = dateFormat.format(new Date());
    String photoFile = "Picture_" + date + ".jpg";

    String filename = pictureFileDir.getPath() + File.separator + photoFile;

    File pictureFile = new File(filename);

    try 
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);

        FileOutputStream fos = new FileOutputStream(pictureFile);
        fos.write(stream.toByteArray());
        fos.close();

        return pictureFile.getAbsolutePath();

     catch (Exception error) 
        Log.d(TAG, "File" + filename + "not saved: " + error.getMessage());
    

    return null;

【问题讨论】:

按照帖子中的建议,不要使用位图使用 Uri。 ***.com/questions/34609275/… 好的,关于如何修改我的代码有什么建议吗?哪些方法需要改变? 【参考方案1】:

您正在更改此代码中的位图大小/分辨率:

            float withRatio = (float) rotatedBitmap.getWidth() / (float) textureViewWidth;
        float heightRatio = (float) rotatedBitmap.getHeight() / (float) textureViewHeight;

        Bitmap newImage = cutImage(rotatedBitmap, (int) (photoFrameView.getWidth() * withRatio), (int) (photoFrameView.getHeight() * heightRatio), withRatio);
        int bitmapByteCountNewImage = byteSizeOf(newImage);
        imagePath = saveBitmap(newImage);

放一个断点,看看新的 heightRatio 和 widthRatio 是什么,以及 photoFrameView.getWidth() * withRatio 的值是什么。我想你会发现它与原始图像相比很小。我不确定您为什么要使用 textureViewWidth/Height 计算比率,您不必这样做。无论您在其中显示图像,都应该能够“填充”而无需更改底层位图的大小,从而降低分辨率。

你可以看看这个方法:

rawBitmap = ((BitmapDrawable)imageToLoad.getDrawable()).getBitmap();
theBitmap = Bitmap.createScaledBitmap(rawBitmap, 285, 313, false);

【讨论】:

我知道有一种方法可以将位图的一部分剪辑到一个新位图中,您可以在其中指定左上角的 x、y 坐标,然后指定宽度和高度,该矩形将是复制到新的位图,根本不改变分辨率,但我手边没有任何代码示例。我做了一些“适合”风格的尺寸调整,如果我有一个非正方形的图像,我会寻找最小的边、高度或宽度,然后让它适合正方形,然后截断较长的尺寸。这可以防止图像被挤压或拉伸以适应。

以上是关于Camera 2 API 会降低拍摄后的质量的主要内容,如果未能解决你的问题,请参考以下文章

缩放图像并在图像视图中设置会降低图像质量并挤压它

Android使用camera2复制内置视频录制质量和帧率

如何在 Android 中提高 Camera 2 的图像质量?

缩放数据会降低聚类的质量

HUAWEI P9 双摄像头 API (CAMERA API 2) - 拍摄黑白视频

淘宝首页serverless升级后的质量保障方案