Android CameraX图像旋转

如何解决Android CameraX图像旋转

我已遵循Google CameraX code lab来实现自定义相机。相机预览很好,但是当我旋转图像捕获图像后拍摄图像时。我正在以人像模式拍摄图像,但是保存的图像是横向的。这是配置摄像头的方法

private fun startCamera() {

    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener(Runnable {
        // Used to bind the lifecycle of cameras to the lifecycle owner
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

        // Preview
        val preview = Preview.Builder()
            .setTargetRotation(this.windowManager.defaultDisplay.rotation)
            .build()
            .also {
                it.setSurfaceProvider(viewFinder.createSurfaceProvider())
            }

        imageCapture = ImageCapture.Builder()
            .setTargetRotation(this.windowManager.defaultDisplay.rotation)
            .build()

        val imageAnalyzer = ImageAnalysis.Builder()
            .build()
            .also {
                it.setAnalyzer(cameraExecutor,LuminosityAnalyzer { luma ->
                    Log.d(TAG,"Average luminosity: $luma")
                })
            }

        // Select back camera as a default
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        try {
            // Unbind use cases before rebinding
            cameraProvider.unbindAll()

            // Bind use cases to camera
            cameraProvider.bindToLifecycle(
                this,cameraSelector,preview,imageCapture,imageAnalyzer)

        } catch(exc: Exception) {
            Log.e(TAG,"Use case binding failed",exc)
        }

    },ContextCompat.getMainExecutor(this))
}

以下是捕获图像的方法:

private fun takePhoto() {
    val imageCapture = imageCapture ?: return

    // Create time-stamped output file to hold the image
    val photoFile = File(
        outputDirectory,SimpleDateFormat(FILENAME_FORMAT,Locale.US
        ).format(System.currentTimeMillis()) + ".jpg")

    // Create output options object which contains file + metadata
    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

    // Set up image capture listener,which is triggered after photo has
    // been taken
    imageCapture.takePicture(
        outputOptions,ContextCompat.getMainExecutor(this),object : ImageCapture.OnImageSavedCallback {
            override fun onError(exc: ImageCaptureException) {
                Log.e(TAG,"Photo capture failed: ${exc.message}",exc)
            }

            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                val savedUri = Uri.fromFile(photoFile)
                val msg = "Photo capture succeeded: $savedUri"
                val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,savedUri)
                ivCapturedImage.setImageBitmap(bitmap)
                setCaptureUI(false)
                Log.d(TAG,msg)
            }
        })
}

使用EXIF拍摄图像后,我需要自己旋转图像吗?还是可以在配置相机时对其进行修复?

解决方法

默认情况下,ImageCapture将捕获的方向设置为显示旋转。如果将映像保存到磁盘,则旋转将在EXIF中进行。

您的设备是否处于锁定人像模式?在这种情况下,显示旋转与设备的方向不匹配,因此您需要自己设置目标旋转。例子。

// The value is whatever the display rotation should be,if the device orientation is not locked.
imageCapture.setTargetRotation(...) 

或者,您可以简单地使用LifecycleCameraController API。它为您处理轮换,并以所见即所得的方式使所有用例保持一致。

,

我也遇到同样的情况。我用骇客的方式解决了这个问题。

我的解决方法是:

    fun Bitmap.rotate(degrees: Float): Bitmap {
    val matrix = Matrix().apply { postRotate(degrees) }
    return Bitmap.createBitmap(this,width,height,matrix,true)
    }

用法:

imageViewCapturedImage.setImageBitmap(bitmap?.rotate(90F))
,

我已经使用此类旋转图像

object CaptureImageHelper {

/**
 * This method is responsible for solving the rotation issue if exist. Also scale the images to
 * 1024x1024 resolution
 *
 * @param context       The current context
 * @param selectedImage The Image URI
 * @return Bitmap image results
 * @throws IOException
 */
@Throws(IOException::class)
fun handleSamplingAndRotationBitmap(context: Context,selectedImage: Uri?): Bitmap? {
    val MAX_HEIGHT = 1024
    val MAX_WIDTH = 1024

    // First decode with inJustDecodeBounds=true to check dimensions
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    var imageStream: InputStream = context.getContentResolver().openInputStream(selectedImage!!)!!
    BitmapFactory.decodeStream(imageStream,null,options)
    imageStream.close()

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options,MAX_WIDTH,MAX_HEIGHT)

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false
    imageStream = context.getContentResolver().openInputStream(selectedImage!!)!!
    var img = BitmapFactory.decodeStream(imageStream,options)
    img = rotateImageIfRequired(img!!,selectedImage)
    return img
}

/**
 * Calculate an inSampleSize for use in a [BitmapFactory.Options] object when decoding
 * bitmaps using the decode* methods from [BitmapFactory]. This implementation calculates
 * the closest inSampleSize that will result in the final decoded bitmap having a width and
 * height equal to or larger than the requested width and height. This implementation does not
 * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
 * results in a larger bitmap which isn't as useful for caching purposes.
 *
 * @param options   An options object with out* params already populated (run through a decode*
 * method with inJustDecodeBounds==true
 * @param reqWidth  The requested width of the resulting bitmap
 * @param reqHeight The requested height of the resulting bitmap
 * @return The value to be used for inSampleSize
 */
private fun calculateInSampleSize(
    options: BitmapFactory.Options,reqWidth: Int,reqHeight: Int
): Int {
    // Raw height and width of image
    val height = options.outHeight
    val width = options.outWidth
    var inSampleSize = 1
    if (height > reqHeight || width > reqWidth) {

        // Calculate ratios of height and width to requested height and width
        val heightRatio =
            Math.round(height.toFloat() / reqHeight.toFloat())
        val widthRatio =
            Math.round(width.toFloat() / reqWidth.toFloat())

        // Choose the smallest ratio as inSampleSize value,this will guarantee a final image
        // with both dimensions larger than or equal to the requested height and width.
        inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio

        // This offers some additional logic in case the image has a strange
        // aspect ratio. For example,a panorama may have a much larger
        // width than height. In these cases the total pixels might still
        // end up being too large to fit comfortably in memory,so we should
        // be more aggressive with sample down the image (=larger inSampleSize).
        val totalPixels = width * height.toFloat()

        // Anything more than 2x the requested pixels we'll sample down further
        val totalReqPixelsCap = reqWidth * reqHeight * 2.toFloat()
        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
            inSampleSize++
        }
    }
    return inSampleSize
}

/**
 * Rotate an image if required.
 *
 * @param img           The image bitmap
 * @param selectedImage Image URI
 * @return The resulted Bitmap after manipulation
 */
@Throws(IOException::class)
private fun rotateImageIfRequired(img: Bitmap,selectedImage: Uri): Bitmap? {
    val ei = ExifInterface(selectedImage.path)
    val orientation: Int =
        ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)
    return when (orientation) {
        ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(img,90)
        ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(img,180)
        ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(img,270)
        else -> img
    }
}

private fun rotateImage(img: Bitmap,degree: Int): Bitmap? {
    val matrix = Matrix()
    matrix.postRotate(degree.toFloat())
    val rotatedImg =
        Bitmap.createBitmap(img,img.width,img.height,true)
    img.recycle()
    return rotatedImg
}

}
,

我遇到了同样的问题;据我了解,从对票证的回复中,例如thisthis,CameraX背后的团队不希望混入从硬件返回的原始图像数据,并且非常希望只是限制自己设置EXIF元数据。

所以我只是解决了这个问题,并从与您的代码非常相似的代码开始(嗯,从代码实验室的代码中获得了很大启发),我添加了以下代码:

Display d = getDisplay();
if (d != null) {
    iCapture.setTargetRotation(d.getRotation());
}

在调用iCapture.takePicture()之前(iCapture是我的ImageCapture用例实例)。这样可以确保EXIF文件元数据中的目标旋转角度与拍摄照片时的实际显示旋转方向保持一致。

然后,在接收到数据后(在我的情况下,在onImageSaved()处理程序上),我检查EXIF元数据是否旋转,在这种情况下,手动旋转图像所需的角度并保存其他文件确保没有EXIF标签保留不连贯的值。

try {
    ExifInterface ei = new ExifInterface(tempFile.getAbsolutePath());
    if (ei.getRotationDegrees() != 0) {
        actualPicture = ImageUtil.rotateDegrees(tempFile,ei.getRotationDegrees());
    }
} catch (IOException exc) {
    Log.e(TAG,"Tried to fix image rotation but could not continue: " + exc,getMessage());
}

其中ImageUtil是图像工具的自定义类,而rotateDegrees就是这样,其初始化的自定义矩阵是这样的:

//inside rotateDegrees(),degrees is the parameter to the function
Matrix m = new Matrix();
m.postRotate(degrees);

并从从原始文件导入的位图开始创建一个新的位图:

Bitmap b = Bitmap.createBitmap(sourceFile,sourceFile.getWidth(),sourceFile.getHeight(),m,true);
b.compress(Bitmap.CompressFormat.JPEG,85,/* a suitably-created output stream */);

不过,我还是希望CameraX直接处理图像旋转,而不依赖元数据(由于它们本身的承认,很少有库和工具可以读取和实际处理)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?