web-dev-qa-db-fra.com

Traitement des données d'image de prévisualisation de l'appareil photo avec Android L et API Camera2

Je travaille sur une application Android qui traite l'image d'entrée de la caméra et l'affiche à l'utilisateur. C'est assez simple, j'enregistre un PreviewCallback sur la caméra avec le setPreviewCallbackWithBuffer. C'est facile et fonctionne bien avec l'ancienne API de la caméra

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

J'essaie de porter mon application pour profiter de la nouvelle API Camera2 et je ne sais pas exactement comment je dois le faire. J'ai suivi les échantillons Camera2Video en L Preview qui permettent d'enregistrer une vidéo. Cependant, il n'y a pas de transfert direct de données d'image dans l'échantillon, donc je ne comprends pas exactement où dois-je obtenir les données de pixel d'image et comment les traiter.

Quelqu'un pourrait-il m'aider ou suggérer comment obtenir les fonctionnalités de PreviewCallback dans Android L, ou comment il est possible de traiter les données d'aperçu de la caméra avant de les afficher sur l'écran? (il n'y a pas de rappel de prévisualisation sur l'objet caméra)

Merci!

39
bubo

Depuis le Camera2 L'API est très différente de l'API Camera actuelle, il pourrait être utile de parcourir la documentation.

Un bon point de départ est camera2basic Exemple. Il montre comment utiliser Camera2 API et configurez ImageReader pour obtenir des images JPEG et vous inscrire ImageReader.OnImageAvailableListener pour recevoir ces images

Pour recevoir des images d'aperçu, vous devez ajouter la surface de votre ImageReader à setRepeatingRequest's CaptureRequest.Builder.

Vous devez également définir le format de ImageReader sur YUV_420_888, qui vous donnera 30fps à 8MP (La documentation garantit 30fps à 8MP pour Nexus 5).

27
VP.

Combiner quelques réponses en une réponse plus digeste car la réponse de @ VP, bien que techniquement claire, est difficile à comprendre si c'est la première fois que vous passez d'une caméra à une caméra2:

En utilisant https://github.com/googlesamples/Android-Camera2Basic comme point de départ, modifiez ce qui suit:

Dans createCameraPreviewSession() initiez un nouveau Surface à partir de mImageReader

Surface mImageSurface = mImageReader.getSurface();

Ajoutez cette nouvelle surface comme cible de sortie de votre variable CaptureRequest.Builder. À l'aide de l'exemple Camera2Basic, la variable sera mPreviewRequestBuilder

mPreviewRequestBuilder.addTarget(mImageSurface);

Voici l'extrait avec les nouvelles lignes (voir mes commentaires @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);

        ...

Ensuite, dans setUpCameraOutputs(), changez le format de ImageFormat.JPEG En ImageFormat.YUV_420_888 Lorsque vous lancez votre ImageReader. (PS, je recommande également de supprimer la taille de votre aperçu pour un fonctionnement plus fluide - une fonctionnalité intéressante de Camera2)

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

Enfin, dans votre méthode onImageAvailable() de ImageReader.OnImageAvailableListener, Assurez-vous d'utiliser la suggestion de @ Kamala car l'aperçu s'arrêtera après quelques images si vous ne le fermez pas

    @Override
    public void onImageAvailable(ImageReader reader) {

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

        Image image =  reader.acquireNextImage();

        ...

        if (image != null)
            image.close();
    }
23
AngeloS

Dans la classe ImageReader.OnImageAvailableListener, fermez l'image après la lecture comme indiqué ci-dessous (cela libérera le tampon pour la prochaine capture). Vous devrez gérer l'exception à la fermeture

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

J'avais besoin de la même chose, j'ai donc utilisé leur exemple et ajouté un appel à une nouvelle fonction lorsque la caméra est en état d'aperçu.

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

La savePreviewShot() est simplement une version recyclée de la captureStillPicture() originale adaptée pour utiliser le modèle d'aperçu.

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

Il est préférable d'initier ImageReader avec le tampon d'image max est 2 Puis d'utiliser reader.acquireLatestImage() à l'intérieur de onImageAvailable().

Parce que acquireLatestImage() va acquérir la dernière image de la file d'attente d'ImageReader, en supprimant l'ancienne. Il est recommandé d'utiliser cette fonction sur acquireNextImage() pour la plupart des cas d'utilisation, car elle convient mieux au traitement en temps réel. Notez que le tampon d'image max doit être d'au moins 2.

Et n'oubliez pas de close() votre image après traitement.

2
nhoxbypass