Обработка данных изображения предварительного просмотра камеры с Android L и Camera2 API

Я работаю над приложением для Android, которое обрабатывает входное изображение с камеры и отображает его пользователю. Это довольно просто, я регистрирую PreviewCallback на объекте камеры с помощью setPreviewCallbackWithBuffer. Это просто и гладко работает со старым API камеры.

public void onPreviewFrame(byte[] data, Camera cam) {
    // custom image data processing
}

Я пытаюсь перенести свое приложение, чтобы воспользоваться преимуществами нового API Camera2, и не знаю, как именно это сделать. Я следил за примерами Camera2Video in L Preview, которые позволяют записывать видео. Однако в образце отсутствует прямая передача данных изображения, поэтому я не понимаю, где именно взять данные пикселей изображения и как их обработать.

Может ли кто-нибудь помочь мне или подсказать, как можно получить функциональность PreviewCallback в android L или как можно обрабатывать данные предварительного просмотра с камеры перед их отображением на экране? (для объекта камеры нет обратного вызова предварительного просмотра)

Спасибо!


person bubo    schedule 23.08.2014    source источник
comment
Вы разобрались с этой проблемой.   -  person user1154390    schedule 20.12.2015
comment
Да. Проверьте ответ VP, а также Camera2Basic и Camera2Video из образцов Android. Вам необходимо создать ImageReader и использовать setOnImageAvailableListener для получения нового изображения при захвате. Чтобы нарисовать изображение, я создал поверхность OpenGL, которая отображает текстуру и шейдер, конвертирующий YUV_420_888 в RGB.   -  person bubo    schedule 20.12.2015
comment
Спасибо, я проверил и репозиторий, и ответ вице-президента. Когда я устанавливаю addTarget (mImageReader.getSurface ()); Он дает только три кадра onImageAvailable, после чего замораживает предварительный просмотр.   -  person user1154390    schedule 20.12.2015
comment
Это (или что-то подобное) может произойти, если вы не читаете / не закрываете изображение из ImageReader. Убедитесь, что в слушателе onImageAvailable(...) вы действительно читаете и закрываете изображение. Слушатель не может быть пустым, даже если вы не используете изображение, необходимое для его чтения (например, с reader.acquireNextImage()).   -  person bubo    schedule 25.12.2015
comment
Спасибо @bubo. Мне потребовалось время, чтобы выяснить это поведение, потому что оно не упоминалось в документации.   -  person user1154390    schedule 04.01.2016


Ответы (5)


Поскольку Camera2 API сильно отличается от текущего Camera API, может быть полезно просмотреть документацию.

Хорошей отправной точкой является camera2basic пример. В нем показано, как использовать Camera2 API и настроить ImageReader для получения изображений JPEG и зарегистрировать ImageReader.OnImageAvailableListener для получения этих изображений.

Чтобы получать кадры предварительного просмотра, вам нужно добавить поверхность вашего ImageReader в CaptureRequest.Builder setRepeatingRequest.

Кроме того, вы должны установить для формата ImageReader значение YUV_420_888, что даст вам 30 кадров в секунду при 8MP (документация гарантирует 30 кадров в секунду при 8MP для Nexus 5).

person VP.    schedule 21.09.2014
comment
Привет, @VP. Не могли бы вы посоветовать мне, как преобразовать изображение YUV_420_888, которое я получаю в прослушивателе onImageAvailable, в Bitmap? - person mol; 09.02.2015
comment
Вот и все: Самый большой размер = Collections.max (Arrays.asList (map.getOutputSizes (ImageFormat.YUV_420_888)), new CompareSizesByArea ()); Вместо .JPEG - это из репозитория camera2Basic. - person Sipty; 27.03.2015
comment
@Ruban Я добавил ответ с кодом ниже на основе ответа вице-президента, чтобы прояснить эту реализацию: stackoverflow.com/a/43564630/630996 - person AngeloS; 23.04.2017
comment
Даже при использовании YUV_420_888 предварительный просмотр камеры значительно отстает (по сравнению с PreviewCallback камеры Camera1) и дает не более 10 кадров в секунду на Moto G3 (при этом дает более 30 кадров в секунду с тем же разрешением на Camera1). Это известная проблема? - person Dmitry Zaytsev; 27.04.2017
comment
Если у вас низкая частота кадров из-за Camera2, проверьте этот ответ stackoverflow.com/a/51083567/2606068 удачи. @ DmitryZaytsev - person wdanxna; 28.06.2018
comment
ImageReader для получения JPEG и ImageReader для формата YUV_420_888 не имеет смысла - person user25; 29.07.2018
comment
@VP Я последовал твоему подходу, но все равно безуспешно. Я также пробовал TEMPLATE_RECORD и TEMPLATE_PREVIEW. - person Bipin Vayalu; 08.01.2019
comment
Я настраиваю 2 поверхности, одна из которых - Preview, а другая - ImageReader (с форматом YUV_420_888). Как и когда я начну предварительный просмотр. Предварительный просмотр приостановлен и получил 5-7 обратных вызовов для onImageAvailable. - person Bipin Vayalu; 08.01.2019

Объединение нескольких ответов в более удобоваримый, потому что ответ @ VP, хотя технически ясный, трудно понять, если вы впервые переходите с Camera на Camera2:

Используя https://github.com/googlesamples/android-Camera2Basic в качестве отправной точки, измените следующий:

В createCameraPreviewSession() инициализируйте новый Surface из mImageReader

Surface mImageSurface = mImageReader.getSurface();

Добавьте эту новую поверхность в качестве выходной цели вашей переменной CaptureRequest.Builder. Используя пример Camera2Basic, переменная будет mPreviewRequestBuilder

mPreviewRequestBuilder.addTarget(mImageSurface);

Вот фрагмент с новыми строками (см. Мои комментарии @AngeloS):

private void createCameraPreviewSession() {

    try {

        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;

        // We configure the size of default buffer to be the size of camera preview we want.
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

        // This is the output Surface we need to start preview.
        Surface surface = new Surface(texture);

        //@AngeloS - Our new output surface for preview frame data
        Surface mImageSurface = mImageReader.getSurface();

        // We set up a CaptureRequest.Builder with the output Surface.
        mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

        //@AngeloS - Add the new target to our CaptureRequest.Builder
        mPreviewRequestBuilder.addTarget(mImageSurface);

        mPreviewRequestBuilder.addTarget(surface);

        ...

Затем в setUpCameraOutputs() измените формат с ImageFormat.JPEG на ImageFormat.YUV_420_888, когда вы инициализируете свой ImageReader. (PS, я также рекомендую уменьшить размер предварительного просмотра для более плавной работы - одна приятная особенность Camera2)

mImageReader = ImageReader.newInstance(largest.getWidth() / 16, largest.getHeight() / 16, ImageFormat.YUV_420_888, 2);

Наконец, в вашем onImageAvailable() методе ImageReader.OnImageAvailableListener обязательно используйте предложение @ Kamala, потому что предварительный просмотр остановится через несколько кадров, если вы его не закроете.

    @Override
    public void onImageAvailable(ImageReader reader) {

        Log.d(TAG, "I'm an image frame!");

        Image image =  reader.acquireNextImage();

        ...

        if (image != null)
            image.close();
    }
person AngeloS    schedule 22.04.2017
comment
У меня java.lang.IllegalArgumentException: submitRequestList:216: Request targets Surface that is not part of current capture session, но я использую CameraView вместо Camera2Basic от Google. - person Sira Lam; 02.05.2018
comment
В приведенном выше фрагменте кода можно обрабатывать / изменять изображение внутри функции onImageAvailable. Однако это не приведет к отображению изображения в предварительном просмотре. Верно? Как вывести изображение на превью (TextureView) после завершения обработки? - person vvy; 19.08.2018
comment
Я пробовал ваш подход, но предварительный просмотр приостанавливается и получает только несколько обратных вызовов onImageAvailable. - person Bipin Vayalu; 08.01.2019
comment
Определенную поверхность необходимо добавить в список, отправленный в createCaptureSession - точно так же, как поверхность предварительного просмотра, которая уже существует - то есть, если у вас есть предварительный просмотр. - person slott; 09.06.2019

В классе ImageReader.OnImageAvailableListener закройте изображение после чтения, как показано ниже (это освободит буфер для следующего захвата). Вам придется обрабатывать исключение при закрытии

      Image image =  imageReader.acquireNextImage();
      ByteBuffer buffer = image.getPlanes()[0].getBuffer();
      byte[] bytes = new byte[buffer.remaining()];
      buffer.get(bytes);
      image.close();
person Kamala    schedule 22.07.2015
comment
Это очень полезный фрагмент - person twerdster; 25.05.2016
comment
Как вы можете сделать это на каждом кадре? - person Florian Mac Langlade; 30.05.2016
comment
Это верно только тогда, когда вы используете формат JPEG для создания ImageReader, иначе в изображении будет более одной плоскости. - person Charlesjean; 09.02.2018

Мне нужно было то же самое, поэтому я использовал их пример и добавил вызов новой функции, когда камера находится в состоянии предварительного просмотра.

private CameraCaptureSession.CaptureCallback mCaptureCallback
            = new CameraCaptureSession.CaptureCallback()
    private void process(CaptureResult result) {
        switch (mState) {
            case STATE_PREVIEW: {
                    if (buttonPressed){
                        savePreviewShot();
                    }
                break;
            }

savePreviewShot() - это просто переработанная версия оригинального captureStillPicture(), адаптированная для использования шаблона предварительного просмотра.

   private void savePreviewShot(){
        try {
            final Activity activity = getActivity();
            if (null == activity || null == mCameraDevice) {
                return;
            }
            // This is the CaptureRequest.Builder that we use to take a picture.
            final CaptureRequest.Builder captureBuilder =
                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            captureBuilder.addTarget(mImageReader.getSurface());

            // Orientation
            int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));

            CameraCaptureSession.CaptureCallback CaptureCallback
                    = new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                                               TotalCaptureResult result) {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss:SSS");
                    Date resultdate = new Date(System.currentTimeMillis());
                    String mFileName = sdf.format(resultdate);
                    mFile = new File(getActivity().getExternalFilesDir(null), "pic "+mFileName+" preview.jpg");

                    Log.i("Saved file", ""+mFile.toString());
                    unlockFocus();
                }
            };

            mCaptureSession.stopRepeating();
            mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    };
person panonski    schedule 20.03.2015
comment
Я использую ваше решение. Проблема в том, что приложение camera2basic (github.com/googlesamples/android-Camera2Basic) получает застрял после первого захвата изображения. Вы это решили? - person user2924714; 06.07.2015
comment
Кроме того, сохранение изображения должно выполняться вне потока пользовательского интерфейса. - person user2924714; 06.07.2015
comment
Да, я изменил строчку mCaptureSession.capture (captureBuilder.build (), CaptureCallback, null); в mCaptureSession.capture (captureBuilder.build (), mCaptureCallback, mBackgroundHandler); - person panonski; 06.07.2015
comment
действительно ли savePreviewShot сохраняет видимый кадр предварительного просмотра или захватывает следующий доступный кадр? createCaptureRequest подразумевает будущее? - person RoundSparrow hilltx; 25.04.2016
comment
Один кадр предварительного просмотра тратит очень мало времени на то, чтобы быть видимым. :) Кадры текут все время (примерно 30 кадров / сек на моем самом быстром устройстве). Однако устройству удается сохранить только несколько из них (например, 5-6), поэтому можно сказать, что сохраняется 1 из 6 кадров. - person panonski; 26.04.2016
comment
В вашем onCaptureCompleted, как вы на самом деле сохраняете данные? - person twerdster; 25.05.2016
comment
Вы не сохраните его там. Вы можете использовать ImageReader для этого. Когда изображение доступно, вы можете запустить отдельный поток для сохранения файла, чтобы ваш основной поток не был перегружен им. Вы можете найти множество примеров в Интернете. - person panonski; 25.05.2016

Лучше инициализировать ImageReader с максимальным размером буфера изображения 2, а затем использовать reader.acquireLatestImage() внутри onImageAvailable().

Потому что acquireLatestImage() получит последний образ из очереди ImageReader, отбрасывая более старый. Эту функцию рекомендуется использовать более acquireNextImage() для большинства случаев использования, так как она больше подходит для обработки в реальном времени. Обратите внимание, что максимальный размер буфера изображения должен быть не менее 2.

И не забудьте close() ваше изображение после обработки.

person nhoxbypass    schedule 27.03.2018