Снимок, сделанный камерой2 - преобразование из YUV_420_888 в NV21 - PullRequest
0 голосов
/ 09 октября 2018

Через API Camera2 мы получаем объект Image в формате YUV_420_888 .Затем мы используем следующую функцию для преобразования в NV21 :

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];

    //U and V are swapped
    yBuffer.get(nv21, 0, ySize);
    vBuffer.get(nv21, ySize, vSize);
    uBuffer.get(nv21, ySize + vSize, uSize);

    return nv21;
}

Хотя эта функция отлично работает с cameraCaptureSessions.setRepeatingRequest, мы получаем ошибку сегментации при дальнейшей обработке (на стороне JNI) при звонке cameraCaptureSessions.capture.Оба запрашивают формат YUV_420_888 через ImageReader.

Почему результат для обоих вызовов функций различен, а запрошенный тип одинаков?

Обновление: Как указано в комментарияхЯ получаю это поведение из-за разных размеров изображения (гораздо большее измерение для запроса захвата).Но наши дальнейшие операции обработки на стороне JNI одинаковы для обоих запросов и не зависят от размеров изображения (только от соотношения сторон, которое в обоих случаях одинаково).

1 Ответ

0 голосов
/ 10 октября 2018

Ваш код вернет правильный NV21 только в том случае, если заполнение вообще отсутствует, а равнины U и V перекрываются и фактически представляют чересстрочные значения VU.Это происходит довольно часто для предварительного просмотра, но в этом случае вы выделяете дополнительные w * h / 4 байта для вашего массива (что, вероятно, не является проблемой).Возможно, для захваченного изображения вам нужна более надежная реализация, например,

private static byte[] YUV_420_888toNV21(Image image) {

    int width = image.getWidth();
    int height = image.getHeight(); 
    int ySize = width*height;
    int uvSize = width*height/4;

    byte[] nv21 = new byte[ySize + uvSize*2];

    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); // Y
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); // U
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); // V

    int rowStride = image.getPlanes()[0].getRowStride();
    assert(image.getPlanes()[0].getPixelStride() == 1);

    int pos = 0;

    if (rowStride == width) { // likely
        yBuffer.get(nv21, 0, ySize);
        pos += ySize;
    }
    else {
        long yBufferPos = width - rowStride; // not an actual position
        for (; pos<ySize; pos+=width) {
            yBufferPos += rowStride - width;
            yBuffer.position(yBufferPos);
            yBuffer.get(nv21, pos, width);
        }
    }

    rowStride = image.getPlanes()[2].getRowStride();
    int pixelStride = image.getPlanes()[2].getPixelStride();

    assert(rowStride == image.getPlanes()[1].getRowStride());
    assert(pixelStride == image.getPlanes()[1].getPixelStride());

    if (pixelStride == 2 && rowStride == width && uBuffer.get(0) == vBuffer.get(1)) {
        // maybe V an U planes overlap as per NV21, which means vBuffer[1] is alias of uBuffer[0]
        byte savePixel = vBuffer.get(1);
        vBuffer.put(1, (byte)0);
        if (uBuffer.get(0) == 0) {
            vBuffer.put(1, (byte)255);
            if (uBuffer.get(0) == 255) {
                vBuffer.put(1, savePixel);
                vBuffer.get(nv21, ySize, uvSize);

                return nv21; // shortcut
            }
        }

        // unfortunately, the check failed. We must save U and V pixel by pixel
        vBuffer.put(1, savePixel);
    }

    // other optimizations could check if (pixelStride == 1) or (pixelStride == 2), 
    // but performance gain would be less significant

    for (int row=0; row<height/2; row++) {
        for (int col=0; col<width/2; col++) {
            int vuPos = col*pixelStride + row*rowStride;
            nv21[pos++] = vBuffer.get(vuPos);
            nv21[pos++] = uBuffer.get(vuPos);
        }
    }

    return nv21;
}

Если вы все равно намереваетесь передать полученный массив в C ++, вы можете воспользоваться фактом , что

возвращенный буфер всегда будет иметь возвращаемое значение isDirect true, поэтому базовые данные могут быть отображены как указатель в JNI без каких-либо копий с GetDirectBufferAddress.

Это означает, что одно и то же преобразование можетбыть сделано в C ++ с минимальными издержками.В C ++ вы можете даже обнаружить, что фактическое расположение пикселей уже составляет NV21!

PS На самом деле, это может быть сделано в Java с незначительными накладными расходами, см. Строку if (pixelStride == 2 && … выше.

...