Как получить растровое изображение из camerax? - PullRequest
0 голосов
/ 19 июня 2020

Мне нужно правильно получить растровое изображение из ImageProxy, и я попробовал несколько методов. Я пробовал следующий метод преобразования ImageProxy в растровое изображение:

class CustomBitmapConverter(context: Context) {
private val rs = RenderScript.create(context)
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private val scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))

private var pixelCount: Int = -1
private lateinit var yuvBuffer: ByteBuffer
private lateinit var inputAllocation: Allocation
private lateinit var outputAllocation: Allocation

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@Synchronized
fun convertYuvToBitmap(image: Image): Bitmap {

    val conf = Bitmap.Config.ARGB_8888
    val output = Bitmap.createBitmap(image.width, image.height, conf)

    // Ensure that the intermediate output byte buffer is allocated
    if (!::yuvBuffer.isInitialized) {
        pixelCount = image.cropRect.width() * image.cropRect.height()
        yuvBuffer = ByteBuffer.allocateDirect(
                pixelCount * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8)
    }

    // Get the YUV data in byte array form
    imageToByteBuffer(image, yuvBuffer)

    // Ensure that the RenderScript inputs and outputs are allocated
    if (!::inputAllocation.isInitialized) {
        inputAllocation = Allocation.createSized(rs, Element.U8(rs), yuvBuffer.array().size)
    }
    if (!::outputAllocation.isInitialized) {
        outputAllocation = Allocation.createFromBitmap(rs, output)
    }

    // Convert YUV to RGB
    inputAllocation.copyFrom(yuvBuffer.array())
    scriptYuvToRgb.setInput(inputAllocation)
    scriptYuvToRgb.forEach(outputAllocation)
    outputAllocation.copyTo(output)

    return output
}

@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun imageToByteBuffer(image: Image, outputBuffer: ByteBuffer) {
    assert(image.format == ImageFormat.YUV_420_888)

    val imageCrop = image.cropRect
    val imagePlanes = image.planes
    val rowData = ByteArray(imagePlanes.first().rowStride)

    imagePlanes.forEachIndexed { planeIndex, plane ->

        val outputStride: Int
        var outputOffset: Int

        when (planeIndex) {
            0 -> {
                outputStride = 1
                outputOffset = 0
            }
            1 -> {
                outputStride = 2
                outputOffset = pixelCount + 1
            }
            2 -> {
                outputStride = 2
                outputOffset = pixelCount
            }
            else -> {
                return@forEachIndexed
            }
        }

        val buffer = plane.buffer
        val rowStride = plane.rowStride
        val pixelStride = plane.pixelStride

        // We have to divide the width and height by two if it's not the Y plane
        val planeCrop = if (planeIndex == 0) {
            imageCrop
        } else {
            Rect(
                    imageCrop.left / 2,
                    imageCrop.top / 2,
                    imageCrop.right / 2,
                    imageCrop.bottom / 2
            )
        }

        val planeWidth = planeCrop.width()
        val planeHeight = planeCrop.height()

        buffer.position(rowStride * planeCrop.top + pixelStride * planeCrop.left)
        for (row in 0 until planeHeight) {
            val length: Int
            if (pixelStride == 1 && outputStride == 1) {
                // When there is a single stride value for pixel and output, we can just copy
                // the entire row in a single step
                length = planeWidth
                buffer.get(outputBuffer.array(), outputOffset, length)
                outputOffset += length
            } else {
                // When either pixel or output have a stride > 1 we must copy pixel by pixel
                length = (planeWidth - 1) * pixelStride + 1
                buffer.get(rowData, 0, length)
                for (col in 0 until planeWidth) {
                    outputBuffer.array()[outputOffset] = rowData[col * pixelStride]
                    outputOffset += outputStride
                }
            }

            if (row < planeHeight - 1) {
                buffer.position(buffer.position() + rowStride - length)
            }
        }
    }
}

Однако это значительно снижает качество, и вы изображение теряет резкость . Затем я попробовал следующее преобразование:

 // Image → JPEG
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static byte[] imageToByteArray(Image image) {
    byte[] data = null;
    if (image.getFormat() == ImageFormat.JPEG) {
        Image.Plane[] planes = image.getPlanes();
        ByteBuffer buffer = planes[0].getBuffer();
        data = new byte[buffer.capacity()];
        buffer.get(data);
        return data;
    } else if (image.getFormat() == ImageFormat.YUV_420_888) {
        data = NV21toJPEG(YUV_420_888toNV21(image),
                image.getWidth(), image.getHeight());
    }
    return data;
}

// YUV_420_888 → NV21
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private static byte[] YUV_420_888toNV21(Image image) {
    byte[] nv21;
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
    int ySize = yBuffer.remaining();
    int uSize = uBuffer.remaining();
    int vSize = vBuffer.remaining();
    nv21 = new byte[ySize + uSize + vSize];
    yBuffer.get(nv21, 0, ySize);
    vBuffer.get(nv21, ySize, vSize);
    uBuffer.get(nv21, ySize + vSize, uSize);
    return nv21;
}

// NV21 → JPEG
private static byte[] NV21toJPEG(byte[] nv21, int width, int height) {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
    yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
    return out.toByteArray();
}

Это сохраняет все пропорции, но на таком устройстве, как Xioami Mi A2, изображение имеет красный / синий цвета по всей поверхности , поэтому вы даже не можете видеть что-нибудь там ... В ImageProxy определенно есть какая-то ошибка.

Тогда я сделал что-то более простое. Я получаю растровое изображение прямо из PreviewView . Я использую этот код:

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@SuppressLint("UnsafeExperimentalUsageError")
@Override
public void analyze(@NonNull ImageProxy image) {
      final Bitmap bitmap = previewView.getBitmap();
            if(bitmap==null){
                return;
            }
            int rotation = CameraUtils.INSTANCE.getRotationDegrees(previewView.getDisplay().getRotation());
  
    }

Тогда мне даже не нужно обрабатывать поворот, потому что возвращаемое растровое изображение имеет точно такое же вращение, как я установил его с помощью конструктора предварительного просмотра:

 internal fun buildCameraXPreview(screenAspectRatio: Int, cameraRotation: Int): Preview {
    return Preview.Builder()
            .setTargetAspectRatio(screenAspectRatio)
            .setTargetRotation(cameraRotation)
            .build()
}

Работает хорошо во всех случаях. Качество, размер такие же, как при предварительном просмотре. Никаких зависаний и эт c.

Теперь мне нужен ваш отзыв. Это жизнеспособное решение? Есть ли альтернативы? Возможно, кому-то удалось исправить преобразование из Proxy в растровое изображение ...

EDIT Я смотрел "рекомендуемый" подход в одном из репозиториев от Google здесь

Однако это даже не кажется рекомендуемым подходом, потому что они буквально пометили это как экспериментальное и только для демонстрации ...

...