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

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

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

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

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

Спасибо!

Ответ 1

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

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

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

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

Ответ 2

Объединяя несколько ответов в более удобоваримый, потому что ответ @VP, в то время как технически ясный, трудно понять, если вы в первый раз переходите от камеры к 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();
    }

Ответ 3

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

      Image image =  imageReader.acquireNextImage();
      ByteBuffer buffer = image.getPlanes()[0].getBuffer();
      byte[] bytes = new byte[buffer.remaining()];
      buffer.get(bytes);
      image.close();

Ответ 4

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

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();
        }
    };