【问题标题】:Camera.takePicture returns a rotated byteArrayCamera.takePicture 返回一个旋转的 byteArray
【发布时间】:2020-12-02 00:44:04
【问题描述】:

我正在尝试使用 hardware.camera 制作自定义相机应用。

我已经实现了一个PictureCallback,它将在拍照时写入具有特定路径的文件。写入文件的data是相机API中takePicture返回的ByteArray

所以在写入文件后,我注意到垂直拍摄的照片是水平保存的。问题不是因为 Exif 标签,而是因为 byteArray 在写入文件之前和之后都有 ORIENTATION_NORMAL

写入文件的data是相机API中takePicture返回的ByteArray

这是takePictureCamera.Java 中的样子:

    public final void takePicture(ShutterCallback shutter, PictureCallback raw,
            PictureCallback jpeg) {
        takePicture(shutter, raw, null, jpeg);
    }

这是CameraPreview 的一部分,它将捕获照片:

相机预览代码

    val imageProcessor = ImageProcessor()
    private val fileSaver = FileSaver(context)
    fun capture() {
        val callback = PictureCallback { data, _ ->
            imageProcessor.process(data)?.apply {
                val file = fileSaver.saveBitmap(this, outputFileName ?: DEFAULT_FILE_NAME)
                onCaptureTaken?.invoke(file)
            }
        }
        camera?.takePicture(null, null, callback)
    }

ImageProcessor.kt 的代码

class ImageProcessor {

    fun process(data: ByteArray): Bitmap? {
        val options = BitmapFactory.Options().apply {
            inMutable = true
        }

        val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, options)
        return fixImageRotation(data, bitmap)
    }
    private fun fixImageRotation(picture: ByteArray, bitmap: Bitmap): Bitmap? {
        return when (exifPostProcessor(picture)) {
            ExifInterface.ORIENTATION_ROTATE_90 ->
                rotateImage(bitmap, 90F)
            ExifInterface.ORIENTATION_ROTATE_180 ->
                rotateImage(bitmap, 180F)
            ExifInterface.ORIENTATION_ROTATE_270 ->
                rotateImage(
                    bitmap, 270F
                )
            ExifInterface.ORIENTATION_NORMAL -> bitmap
            else -> bitmap
        }
    }

    private fun rotateImage(source: Bitmap, angle: Float): Bitmap? {
        val matrix = Matrix()
        matrix.postRotate(angle)
        return Bitmap.createBitmap(
            source, 0, 0, source.width, source.height,
            matrix, true
        )
    }

    private fun exifPostProcessor(picture: ByteArray?): Int {
        try {
            return getExifOrientation(ByteArrayInputStream(picture))
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return -1
    }

    @Throws(IOException::class)
    private fun getExifOrientation(inputStream: InputStream): Int {
        val exif = ExifInterface(inputStream)
        return exif.getAttributeInt(
            ExifInterface.TAG_ORIENTATION,
            ExifInterface.ORIENTATION_NORMAL
        )
    }
}

FileSaver.kt 的代码

internal class FileSaver(context: Context) {

    private val context: Context = context.applicationContext
    fun saveBitmap(bitmap: Bitmap, fileName: String): File {
        val file = File(mkdirsCacheFolder(), fileName)
        try {
            FileOutputStream(file).use { out ->
                bitmap.compress(Bitmap.CompressFormat.JPEG, ORIGINAL_QUALITY, out)
            }
            bitmap.recycle()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return file
    }


    private fun mkdirsCacheFolder(): File {
        return File(context.externalCacheDir, CACHE_DIRECTORY).apply {
            if (!exists()) {
                mkdirs()
            }
        }
    }

    companion object {
        private const val ORIGINAL_QUALITY = 100
        private const val CACHE_DIRECTORY = "/Lens"
    }
}

有什么建议吗?

编辑: 我打印了 Exif 标签,结果是ORIENTATION_NORMAL,所以我真的不知道它是否被旋转了。

编辑 2: 示例图片以纵向模式拍摄并从文件管理器中打开 [! 并非如此,这些结果在模拟器和真正的 android 手机上都进行了测试,并且它们是相同的。 预习: Preview

从文件管理器捕获的图像: Captured image from file manager

【问题讨论】:

  • 我认为大部分都包含在代码中。输入文件名,然后使用该名称创建一个文件,然后将其写入。 @blackapps
  • @blackapps 对不起 :( 输入文件名是我传递给此函数的硬编码字符串。我应该将其包含在描述中吗?
  • @blackapps 我添加了一个编辑。感谢您的反馈
  • 嘿嘿...终于有我要的信息了。数据字节数组包含一个 jpg 文件,您正在将字节保存到文件中。请在您的帖子中输入您获得的 jpg 图像,并将其保存到文件中。并像我一样移除 cmets。
  • 在写入文件时,标签被忽略——这很奇怪。您的代码写入所有 jpeg 字节,包括 EXIF 标头。也许您的意思是您的 viewer 忽略了这些标志?我建议尝试一些好的查看器(即不是 Windows 默认)来检查这一点。如果您发现 EXIF 标头损坏,您可以修复它(请参阅ExifInterface 库)。如果您希望图像与忽略标题的查看器兼容,您别无选择,只能自己执行旋转。

标签: android android-camera


【解决方案1】:

在这个问题中很少有与这种情况重叠的问题,因此我花了很长时间才明白到底发生了什么。

你做了什么,你从相机收到了一个有效的 Jpeg ByteArray,这个流包含一些 EXIF 信息,但是它缺少方向标签。这发生在许多设备上,也发生在Xiaomi Mi

因此,您无法正确旋转位图。但是您确切地知道orientation of your Activitypreview.display.rotation。这应该告诉您在这种情况下应该如何旋转位图,但是如果您的活动被锁定为纵向,您甚至不需要检查。显示旋转可能在0…3 范围内,这些代表Surface.ROTATION_0Surface.ROTATION_90Surface.ROTATION_180Surface.ROTATION_270

要选择正确的旋转方式,您必须了解硬件的组装方式,即摄像头传感器与设备的对齐方式。这个orientation of the camera 可以是 0、90、180 或 270。

您可能在不同的来源看到过这段代码:

var degrees = 0
when (preview.display.rotation) {
    Surface.ROTATION_0 -> degrees = 0
    Surface.ROTATION_90 -> degrees = 90
    Surface.ROTATION_180 -> degrees = 180
    Surface.ROTATION_270 -> degrees = 270
}
val ci = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, ci)
if (ci.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    degrees += ci.orientation
    degrees %= 360
    degrees = 360 - degrees
} else {
    degrees = 360 - degrees
    degrees += ci.orientation
}
camera!!.setDisplayOrientation(degrees % 360)

此代码允许相机预览与您的屏幕正确对齐;你可能在你的应用程序的某个地方也有这个。如果getExifOrientation() 返回ExifInterface.ORIENTATION_UNKNOWN,则可以使用相同的代码在fixImageRotation() 中选择正确的位图旋转。

在某些情况下,您需要有关设备方向的更多详细信息,如 here 所述。

无论如何,我建议您切换到现代的CameraX API,它可以为大多数设备提供更好的支持。它允许我调用 ImageCapture.setTargetRotation() 并且生成的 Jpeg 由库为我旋转。

【讨论】:

  • 那么这个函数实际上是在纠正Exif标签,之后输出正确吗?还是应该使用degrees 旋转输出图片?
  • 顺便说一句,我的 min sdk 是 17,所以我不能使用 cameraX,因为它在 API 21 及更高版本中可用。
  • 这是你的选择:你可以保留你当前的代码并让这个函数找到正确的旋转,或者你可以使用 ExifInterface 库来设置方向标签。在后一种情况下,您不需要将所有这些将data 解码为位图的代码,使用修改后的 Exif 标头编写 Jpeg 会更快并节省内存。
  • 我将我的活动锁定为 PORTRAIT 并手动写了ORIENTATION_ROTATE_90 Exif。我认为这解决了我的问题。这是正确的做法吗?
  • @MaryJane,只要它服务于您的用例,就可以了。不要忘记ci.orientation 在某些设备上可能不是90(著名的例子是/曾经是Nexus 5X),您应该确保正确处理此类电话。
猜你喜欢
  • 2011-11-29
  • 1970-01-01
  • 2012-10-31
  • 2017-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多