如何使用 camerax Front Camera 保存正确的旋转图像?

How to save the correct rotated image using camerax Front Camera?

我正在使用 CameraX 开发我的 android 应用程序,当我在横向模式或纵向模式下拍摄照片时,捕获和保存的图像是镜像。

我知道前置摄像头的工作原理相同。但是如果我想按照拍摄的方式保存图片怎么办?

这是我正在使用的 buildUseCase() 代码:

private fun buildUseCases() {

        val screenAspectRatio = Rational(width, height)
        val screenTargetRotation = display.rotation

        //Preview
        val previewConfig = PreviewConfig.Builder().apply {
            setTargetAspectRatio(screenAspectRatio)
            setTargetRotation(screenTargetRotation)
            setLensFacing(lensFacing)
        }.build()

        preview = AutoFitPreviewBuilder.build(previewConfig, this)
        //End - Preview


        // Set up the capture use case to allow users to take photos
        val imageCaptureConfig = ImageCaptureConfig.Builder().apply {
            setTargetAspectRatio(screenAspectRatio)
            setTargetRotation(screenTargetRotation)
            setLensFacing(lensFacing)
            setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
        }.build()


        imageCapture = ImageCapture(imageCaptureConfig)
    }

请帮助我更改哪些内容以获取正确的图像。

注意:相机面向前方,并且处于横向模式。

您需要读取所创建图像的 EXIF 数据,并且必须根据要求和需要编写自己的自定义控制器。在大多数 Android 和 iOS 设备中,捕获的图像旋转是很正常的,必须进行相应处理。在大多数设备中,相机的默认方向设置为横向模式,因此即使您以纵向模式拍摄照片,它也会旋转 90 度。

从 EXIF 数据中,您可以获取图像的旋转度数或镜像度数,然后您可以在后端进行处理。

要旋转图像,您可以尝试

private static Bitmap rotateImageIfRequired(Bitmap img, Uri selectedImage) throws IOException 
{
    ExifInterface ei = new ExifInterface(selectedImage.getPath());
    int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);

    switch (orientation) {
        case ExifInterface.ORIENTATION_ROTATE_90:
            return rotateImage(img, 90);
        case ExifInterface.ORIENTATION_ROTATE_180:
            return rotateImage(img, 180);
        case ExifInterface.ORIENTATION_ROTATE_270:
            return rotateImage(img, 270);
        default:
            return img;
    }
 }

private static Bitmap rotateImage(Bitmap img, int degree)
{
    Matrix matrix = new Matrix();
    matrix.postRotate(degree);
    Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
    img.recycle();
    return rotatedImg;
}

图片翻转问题可以试试这个

public static Bitmap flip(Bitmap src, int type) 
{
     // create new matrix for transformation
     Matrix matrix = new Matrix();
     matrix.preScale(-1.0f, 1.0f);

     // return transformed image
     return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
 }

然后将图片设置为ImageView为

imgPreview.setImageBitmap(flip(bitmap)); 

不是真正的答案,但如果您需要从字节 [] 而不是已经解压缩的位图中读取 EXIF 信息,可能会非常方便。

 /**
 * Returns the degrees, counted clockwise, from a byte[] instead of a already saved file.<br>
 *
 * @param jpeg the JPEG image byte[]
 * @return Exif orientation as either <b>0</b>, <b>90</b>, <b>180</b> or <b>270</b>
 */
public static int getExifOrientation(byte[] jpeg) {
    if (jpeg == null) {
        return 0;
    }

    int offset = 0;
    int length = 0;

    // ISO/IEC 10918-1:1993(E)
    while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
        int marker = jpeg[offset] & 0xFF;

        // Check if the marker is a padding.
        if (marker == 0xFF) {
            continue;
        }
        offset++;

        // Check if the marker is SOI or TEM.
        if (marker == 0xD8 || marker == 0x01) {
            continue;
        }
        // Check if the marker is EOI or SOS.
        if (marker == 0xD9 || marker == 0xDA) {
            break;
        }

        // Get the length and check if it is reasonable.
        length = pack(jpeg, offset, 2, false);
        if (length < 2 || offset + length > jpeg.length) {
            Log.e(TAG, "Invalid JPEG length");
            return 0;
        }

        // Break if the marker is EXIF in APP1.
        if (marker == 0xE1 && length >= 8 &&
                pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
                pack(jpeg, offset + 6, 2, false) == 0) {
            offset += 8;
            length -= 8;
            break;
        }

        // Skip other markers.
        offset += length;
        length = 0;
    }

    // JEITA CP-3451 Exif Version 2.2
    if (length > 8) {
        // Identify the byte order.
        int tag = pack(jpeg, offset, 4, false);
        if (tag != 0x49492A00 && tag != 0x4D4D002A) {
            Log.e(TAG, "Invalid JPEG EXIF byte order");
            return 0;
        }
        boolean littleEndian = (tag == 0x49492A00);

        // Get the offset and check if it is reasonable.
        int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
        if (count < 10 || count > length) {
            Log.e(TAG, "Invalid JPEG EXIF offset");
            return 0;
        }
        offset += count;
        length -= count;

        // Get the count and go through all the elements.
        count = pack(jpeg, offset - 2, 2, littleEndian);
        while (count-- > 0 && length >= 12) {
            // Get the tag and check if it is orientation.
            tag = pack(jpeg, offset, 2, littleEndian);
            if (tag == 0x0112) {
                // We do not really care about type and count, do we?
                int orientation = pack(jpeg, offset + 8, 2, littleEndian);
                switch (orientation) {
                    case 1:
                        return 0;
                    case 3:
                        return 180;
                    case 6:
                        return 90;
                    case 8:
                        return 270;
                }
                Log.i(TAG, "Unsupported EXIF orientation");
                return 0;
            }
            offset += 12;
            length -= 12;
        }
    }

    Log.i(TAG, "EXIF Orientation not found");
    return 0;
}

private static int pack(byte[] bytes, int offset, int length,
                        boolean littleEndian) {
    int step = 1;
    if (littleEndian) {
        offset += length - 1;
        step = -1;
    }

    int value = 0;
    while (length-- > 0) {
        value = (value << 8) | (bytes[offset] & 0xFF);
        offset += step;
    }
    return value;
}

为了避免前置摄像头拍照时照片出现镜面效果,需要将元数据传递给

ImageCapture.OutputFileOptions

根据使用的相机,这里是我所做的示例

        val metadata = ImageCapture.Metadata()
        metadata.isReversedHorizontal = cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA

        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
            .setMetadata(metadata)
            .build()

       imageCapture.takePicture(outputOptions, executor, object: ImageCapture.OnImageSavedCallback {})

收到 CameraX 团队成员关于我自己的代码中的相同问题的建议。

The thing missing from your code is reading the EXIF rotation from the saved Jpeg so you can set the rotation properly on the ImageView. You can do this with ExifInterface, or alternatively, just use a library that handles this for you, like Glide. The official sample, CameraXBasic, uses the Glide approach.

首先,他们的提案意味着您无需为 PreviewImageCapture 定义 setTargetRotation(screenTargetRotation) 即可生成以正确旋转显示的图像。

如果官方示例发生变化,这里是它如何使用 Glide:

Glide.with().load().into()

load() 接收对捕获图像的引用。如果您将其保存到 File,请提供对该 File.

的引用