Как эффективно управлять кадром YUV Camera на лету в Android? - PullRequest
0 голосов
/ 03 января 2019

Я добавляю черный (0) отступ вокруг области интереса (в центре) кадра NV21, полученного от обратных вызовов Android CameraPreview в потоке.

Чтобы избежать издержек преобразования в RGB / Bitmap и наоборот, я пытаюсь манипулировать байтовым массивом NV21 напрямую, но это включает в себя вложенные циклы, что также замедляет предварительный просмотр / обработку.

Это мой run() метод, отправляющий кадры детектору после вызова метода blackNonROI .

public void run() {
    Frame outputFrame;
    ByteBuffer data;
    while (true) {
        synchronized (mLock) {

            while (mActive && (mPendingFrameData == null))
                try{ mLock.wait(); }catch(InterruptedException e){ return; }

            if (!mActive) { return; }

            // Region of Interest
            mPendingFrameData = blackNonROI(mPendingFrameData.array(),mPreviewSize.getWidth(),mPreviewSize.getHeight(),300,300);

            outputFrame = new Frame.Builder().setImageData(mPendingFrameData, mPreviewSize.getWidth(),mPreviewSize.getHeight(), ImageFormat.NV21).setId(mPendingFrameId).setTimestampMillis(mPendingTimeMillis).setRotation(mRotation).build();

            data = mPendingFrameData;
            mPendingFrameData = null;

        }

        try {
            mDetector.receiveFrame(outputFrame);
        } catch (Throwable t) {
        } finally {
            mCamera.addCallbackBuffer(data.array());
        }
    }
}

Ниже приводится метод blackNonROI

private ByteBuffer blackNonROI(byte[] yuvData, int width, int height, int roiWidth, int roiHeight){

    int hozMargin = (width - roiWidth) / 2;
    int verMargin = (height - roiHeight) / 2;

    // top/bottom of center
    for(int x=0; x<width; x++){
        for(int y=0; y<verMargin; y++)
            yuvData[y * width + x] = 0;
        for(int y=height-verMargin; y<height; y++)
            yuvData[y * width + x] = 0;
    }

    // left/right of center
    for(int y=verMargin; y<height-verMargin; y++){
        for (int x = 0; x < hozMargin; x++)
            yuvData[y * width + x] = 0;
        for (int x = width-hozMargin; x < width; x++)
            yuvData[y * width + x] = 0;
    }

    return ByteBuffer.wrap(yuvData);
}

Пример выходного кадра

Обратите внимание, что я не обрезаю изображение, а просто добавляю черные пиксели вокруг указанного центра изображения, чтобы сохранить координацию дальнейших действий. Это работает так, как должно, но недостаточно быстро и приводит к задержкам при предварительном просмотре и обработке кадров.

  1. Могу ли я еще улучшить обновление массива байтов?
  2. Время / место для звонка blackNonROI отлично?
  3. Есть ли другой способ / lib для более эффективного выполнения?
  4. Моя простая итерация в пикселях настолько медленная, как библиотеки YUV / Bitmap так быстро делают сложные вещи? они используют графический процессор?

Edit:

Я заменил оба цикла for следующим кодом, и теперь он работает довольно быстро (подробности см. В ответе greeble31):

    // full top padding
    from = 0;
    to = (verMargin-1)*width + width;
    Arrays.fill(yuvData,from,to,(byte)1);

    // full bottom padding
    from = (height-verMargin)*width;
    to = (height-1)*width + width;
    Arrays.fill(yuvData,from,to,(byte)1);

    for(int y=verMargin; y<height-verMargin; y++) {
        // left-middle padding
        from = y*width;
        to = y*width + hozMargin;
        Arrays.fill(yuvData,from,to,(byte)1);

        // right-middle padding
        from = y*width + width-hozMargin;
        to = y*width + width;
        Arrays.fill(yuvData,from,to,(byte)1);
    }

Ответы [ 2 ]

0 голосов
/ 05 января 2019

Очевидно, что наиболее эффективным способом передачи изображения для обнаружения будет передача прямоугольника ROI детектору.Все наши функции обработки изображений принимают ограничивающий прямоугольник в качестве параметра.

Если для отображения используется черное поле, рассмотрите возможность использования черной оверлейной маски для макета предварительного просмотра вместо манипулирования пикселями.

Если манипулирование пикселяминеизбежно, проверьте, можете ли вы ограничить его до Y ОК, вы уже делаете это!

Если ваш детектор работает с уменьшенным изображением (как это делает мой механизм распознавания лиц), это может бытьразумно применить черный цвет к рамке с измененным размером.

В любом случае, держите ваши циклы в чистоте и порядке, удалите все повторяющиеся вычисления.Использование операций Arrays.fill () может помочь значительно, но не сильно.

0 голосов
/ 03 января 2019

1. Да.Чтобы понять, почему, давайте взглянем на байт-код, который Android Studio создает для вашего вложенного цикла «влево / вправо от центра»:

(аннотированная выдержка из сборки выпуска blackNonROI, AS 3.2.1):

:goto_27
sub-int v2, p2, p4         ;for(int y=verMargin; y<height-verMargin; y++)
if-ge v1, v2, :cond_45
const/4 v2, 0x0
:goto_2c
if-ge v2, p3, :cond_36     ;for (int x = 0; x < hozMargin; x++)
mul-int v3, v1, p1
add-int/2addr v3, v2
.line 759
aput-byte v0, p0, v3
add-int/lit8 v2, v2, 0x1
goto :goto_2c
:cond_36
sub-int v2, p1, p3 
:goto_38
if-ge v2, p1, :cond_42     ;for (int x = width-hozMargin; x < width; x++)
mul-int v3, v1, p1
add-int/2addr v3, v2
.line 761
aput-byte v0, p0, v3
add-int/lit8 v2, v2, 0x1
goto :goto_38
:cond_42
add-int/lit8 v1, v1, 0x1
goto :goto_27
.line 764
:cond_45                   ;all done with the for loops!

Не потрудившись расшифровать всю эту вещь построчно, ясно, что каждый из ваших маленьких внутренних циклов выполняет:

  • 1 сравнение
  • 1 целочисленное умножение
  • 1 сложение
  • 1 магазин
  • 1 goto

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

Более того, некоторые из этих байт-кодов требуют реализации нескольких машинных инструкций, поэтому я не удивлюсь, если вы ищетев течение более 20 циклов, просто чтобы сделать одну итерацию одного из внутренних циклов.(Я не проверял, как выглядит этот код после его компиляции виртуальной машиной Dalvik, но я искренне сомневаюсь, что он достаточно умен, чтобы оптимизировать умножения из этих циклов.)

ВОЗМОЖНЫЕ ИСПРАВЛЕНИЯ

Вы можете улучшить производительность, исключив некоторые избыточные вычисления.Например, каждый внутренний цикл пересчитывает y * width каждый раз .Вместо этого вы можете предварительно рассчитать это смещение, сохранить его в локальной переменной (во внешнем цикле) и использовать его при расчете индексов.

Когда производительность абсолютно критична, я иногда буду делать такого родаманипулирование буфером в нативном коде.Если вы можете быть достаточно уверены, что mPendingFrameData является DirectByteBuffer, это еще более привлекательный вариант.Недостатки: 1.) более высокая сложность и 2.) меньшая «сетка безопасности», если что-то пойдет не так / вылетит.

САМОЕ СООТВЕТСТВУЮЩЕЕ ИСПРАВЛЕНИЕ

В этом случае наиболее подходящим решением, вероятно, является просто использование Arrays.fill(), которое, скорее всего, будет реализовано оптимизированным способом.

Обратите внимание, что верхний и нижний блоки представляют собой большие непрерывные куски памяти и могутобрабатывается по одному Arrays.fill() каждому:

Arrays.fill(yuvData, 0, verMargin * width, 0);   //top
Arrays.fill(yuvData, width * height - verMargin * width, width * height, 0);    //bottom

И тогда стороны могут обрабатываться примерно так:

for(int y=verMargin; y<height-verMargin; y++){
    int offset = y * width;
    Arrays.fill(yuvData, offset, offset + hozMargin, 0);  //left
    Arrays.fill(yuvData, offset + width, offset + width - hozMargin, 0);   //right
}

Здесь есть больше возможностей для оптимизации, но мыуже на грани убывающей отдачи.Например, поскольку конец каждой строки смежен с началом следующей (в памяти), вы можете фактически объединить два меньших вызова fill() в один больший, который охватывает как правую часть строки N, так и левую.сторона ряда N + 1. И пр.

2. Не уверен.Если ваш предварительный просмотр отображается без каких-либо повреждений / разрывов, то это, вероятно, безопасное место для вызова функции (с точки зрения безопасности потока), и поэтому оно, вероятно, такое же хорошее место, как и любое другое.

3 и 4. Могут быть библиотеки для выполнения этой задачи;Я не знаю ничего лишнего, для основанных на Java фреймов NV21.Вы должны будете сделать некоторые преобразования формата, и я не думаю, что это того стоит.Использование графического процессора для выполнения этой работы, на мой взгляд, является чрезмерной чрезмерной оптимизацией, но может быть целесообразным для некоторых специализированных приложений.Я хотел бы подумать о переходе на JNI (собственный код), прежде чем когда-либо подумать об использовании графического процессора.

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

...