如何使用Camerax前置摄像头拍摄并保存正确旋转的图像?

3

我正在使用CameraX来开发我的安卓应用程序,在其中当我以横屏模式或竖屏模式拍照时,所拍摄的图像会被翻转。我知道前置摄像头也是这样工作的。但是,如果我想以与拍摄时相同的方式保存图片,该怎么办呢?

下面是我使用的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)
    }

请帮忙指导应该如何更改设置以正确捕获图像。 注意:相机面向前方,且处于横屏模式。
4个回答

7

您需要读取图像的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)); 

你好!有没有办法在不保存图像的情况下使用这个答案。换句话说:使用ExifInterface并从ImageProxy设置路径?如果我从这个答案中理解正确,你是先保存图像,然后使用Exif获取旋转信息对吧?如果我只想拍照而不保存,并且仍然存在旋转问题,该怎么办呢?谢谢! - DM developing
在使用img.recycle();时要小心。因为我在项目中进一步使用了src位图,所以它给我带来了一个非常奇怪和难以追踪的错误。 我得到的错误是:2021-04-28 10:47:30.073 13573-15598/APPNAME A/libc: Fatal signal 11 (SIGSEGV),code 1 (SEGV_MAPERR),fault addr 0x70 in tid 15598 (pool-7-thread-1),pid 13573 (ev.APPNAME ) - Raul Lucaciu

2
为了避免使用前置摄像头拍照时出现镜像效果,需要将元数据传递给相机。

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 {})

1

我从CameraX团队的某个成员那里得到了有关自己代码中相同问题的建议。

你的代码中缺少从保存的Jpeg中读取EXIF旋转信息,以便可以正确设置ImageView的旋转。你可以使用ExifInterface来实现这一点,或者,你可以直接使用处理此问题的库,例如Glide。官方示例CameraXBasic也使用了Glide方法

首先,他们的建议意味着您不需要为PreviewImageCapture定义setTargetRotation(screenTargetRotation)就能获得正确旋转显示的图像。

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

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

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


0

这并不是一个真正的答案,但如果您需要从byte[]中读取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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接