web-dev-qa-db-fra.com

Aperçu de la caméra de recadrage pour TextureView

J'ai un TextureView avec une largeur et une hauteur fixes et je veux montrer un aperçu de la caméra à l'intérieur. Je dois rogner l'aperçu de la caméra pour qu'il ne semble pas trop étiré dans TextureView. Comment faire le recadrage? Si je dois utiliser OpenGL, comment lier la texture de surface à OpenGL et comment effectuer le recadrage avec OpenGL?

public class MyActivity extends Activity implements TextureView.SurfaceTextureListener 
{

   private Camera mCamera;
   private TextureView mTextureView;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_options);

    mTextureView = (TextureView) findViewById(R.id.camera_preview);
    mTextureView.setSurfaceTextureListener(this);
}

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    mCamera = Camera.open();

    try 
    {
        mCamera.setPreviewTexture(surface);
        mCamera.startPreview();
    } catch (IOException ioe) {
        // Something bad happened
    }
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    mCamera.stopPreview();
    mCamera.release();
    return true;
}

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}

@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface)
 {
    // Invoked every time there's a new Camera preview frame
 }
}

De plus, après avoir correctement effectué la prévisualisation, je dois pouvoir lire en temps réel les pixels situés au centre de l'image recadrée.

22
Catalin Morosan

La solution fournie précédemment par @Romanski fonctionne bien, mais elle évolue avec le recadrage. Si vous avez besoin d'une mise à l'échelle pour l'adapter, utilisez la solution suivante. Appelez updateTextureMatrix chaque fois que la vue de la surface est modifiée: c'est-à-dire dans les méthodes onSurfaceTextureAvailable et onSurfaceTextureSizeChanged. Notez également que cette solution repose sur le fait que l'activité ignore les modifications de configuration (c'est-à-dire Android: configChanges = "orientation | screenSize | keyboardHidden" ou quelque chose comme ça):

private void updateTextureMatrix(int width, int height)
{
    boolean isPortrait = false;

    Display display = getWindowManager().getDefaultDisplay();
    if (display.getRotation() == Surface.ROTATION_0 || display.getRotation() == Surface.ROTATION_180) isPortrait = true;
    else if (display.getRotation() == Surface.ROTATION_90 || display.getRotation() == Surface.ROTATION_270) isPortrait = false;

    int previewWidth = orgPreviewWidth;
    int previewHeight = orgPreviewHeight;

    if (isPortrait)
    {
        previewWidth = orgPreviewHeight;
        previewHeight = orgPreviewWidth;
    }

    float ratioSurface = (float) width / height;
    float ratioPreview = (float) previewWidth / previewHeight;

    float scaleX;
    float scaleY;

    if (ratioSurface > ratioPreview)
    {
        scaleX = (float) height / previewHeight;
        scaleY = 1;
    }
    else
    {
        scaleX = 1;
        scaleY = (float) width / previewWidth;
    }

    Matrix matrix = new Matrix();

    matrix.setScale(scaleX, scaleY);
    textureView.setTransform(matrix);

    float scaledWidth = width * scaleX;
    float scaledHeight = height * scaleY;

    float dx = (width - scaledWidth) / 2;
    float dy = (height - scaledHeight) / 2;
    textureView.setTranslationX(dx);
    textureView.setTranslationY(dy);
}

Aussi, vous avez besoin des champs suivants:

private int orgPreviewWidth;
private int orgPreviewHeight;

initialisez-le dans la méthode onSurfaceTextureAvailable avant d'appeler updateTextureMatrix:

Camera.Parameters parameters = camera.getParameters();
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);

Pair<Integer, Integer> size = getMaxSize(parameters.getSupportedPreviewSizes());
parameters.setPreviewSize(size.first, size.second);

orgPreviewWidth = size.first;
orgPreviewHeight = size.second;

camera.setParameters(parameters);

méthode getMaxSize:

private static Pair<Integer, Integer> getMaxSize(List<Camera.Size> list)
{
    int width = 0;
    int height = 0;

    for (Camera.Size size : list) {
        if (size.width * size.height > width * height)
        {
            width = size.width;
            height = size.height;
        }
    }

    return new Pair<Integer, Integer>(width, height);
}

Et dernière chose - vous devez corriger la rotation de la caméra. Appelez donc la méthode setCameraDisplayOrientation dans la méthode Activity onConfigurationChanged (et effectuez également l'appel initial dans la méthode onSurfaceTextureAvailable):

public static void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera)
{
    Camera.CameraInfo info = new Camera.CameraInfo();
    Camera.getCameraInfo(cameraId, info);
    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int degrees = 0;
    switch (rotation)
    {
        case Surface.ROTATION_0:
            degrees = 0;
            break;
        case Surface.ROTATION_90:
            degrees = 90;
            break;
        case Surface.ROTATION_180:
            degrees = 180;
            break;
        case Surface.ROTATION_270:
            degrees = 270;
            break;
    }

    int result;
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
    {
        result = (info.orientation + degrees) % 360;
        result = (360 - result) % 360;  // compensate the mirror
    }
    else
    {  // back-facing
        result = (info.orientation - degrees + 360) % 360;
    }
    camera.setDisplayOrientation(result);

    Camera.Parameters params = camera.getParameters();
    params.setRotation(result);
    camera.setParameters(params);
}
31
Ruslan Yanchyshyn

Il suffit de calculer les proportions, de générer une matrice de mise à l’échelle et de l’appliquer à TextureView. Selon le rapport de format de la surface et le rapport de format de l'image d'aperçu, l'image d'aperçu est recadrée en haut et en bas ou à gauche et à droite ... Une autre solution que j'ai découverte est que si vous ouvrez l'appareil photo avant la SurfaceTexture est disponible, la prévisualisation est déjà mise à l'échelle automatiquement. Essayez simplement de déplacer mCamera = Camera.open (); à votre fonction onCreate après avoir défini le SurfaceTextureListener. Cela a fonctionné pour moi sur la N4. Avec cette solution, vous aurez probablement des problèmes lorsque vous passez de portrait en paysage. Si vous avez besoin de l’aide de portrait et de paysage, prenez la solution avec la matrice d’échelle!

private void initPreview(SurfaceTexture surface, int width, int height) {
    try {
        camera.setPreviewTexture(surface);
    } catch (Throwable t) {
        Log.e("CameraManager", "Exception in setPreviewTexture()", t);
    }

    Camera.Parameters parameters = camera.getParameters();
    previewSize = parameters.getSupportedPreviewSizes().get(0);

    float ratioSurface = width > height ? (float) width / height : (float) height / width;
    float ratioPreview = (float) previewSize.width / previewSize.height;

    int scaledHeight = 0;
    int scaledWidth = 0;
    float scaleX = 1f;
    float scaleY = 1f;

    boolean isPortrait = false;

    if (previewSize != null) {
        parameters.setPreviewSize(previewSize.width, previewSize.height);
        if (display.getRotation() == Surface.ROTATION_0 || display.getRotation() == Surface.ROTATION_180) {
            camera.setDisplayOrientation(display.getRotation() == Surface.ROTATION_0 ? 90 : 270);
            isPortrait = true;
        } else if (display.getRotation() == Surface.ROTATION_90 || display.getRotation() == Surface.ROTATION_270) {
            camera.setDisplayOrientation(display.getRotation() == Surface.ROTATION_90 ? 0 : 180);
            isPortrait = false;
        }
        if (isPortrait && ratioPreview > ratioSurface) {
            scaledWidth = width;
            scaledHeight = (int) (((float) previewSize.width / previewSize.height) * width);
            scaleX = 1f;
            scaleY = (float) scaledHeight / height;
        } else if (isPortrait && ratioPreview < ratioSurface) {
            scaledWidth = (int) (height / ((float) previewSize.width / previewSize.height));
            scaledHeight = height;
            scaleX = (float) scaledWidth / width;
            scaleY = 1f;
        } else if (!isPortrait && ratioPreview < ratioSurface) {
            scaledWidth = width;
            scaledHeight = (int) (width / ((float) previewSize.width / previewSize.height));
            scaleX = 1f;
            scaleY = (float) scaledHeight / height;
        } else if (!isPortrait && ratioPreview > ratioSurface) {
            scaledWidth = (int) (((float) previewSize.width / previewSize.height) * width);
            scaledHeight = height;
            scaleX = (float) scaledWidth / width;
            scaleY = 1f;
        }           
        camera.setParameters(parameters);
    }

    // calculate transformation matrix
    Matrix matrix = new Matrix();

    matrix.setScale(scaleX, scaleY);
    textureView.setTransform(matrix);
}
11
Romanski

vous pouvez manipuler le byte[] data à partir de onPreview().

Je pense que vous devrez:

  • le mettre dans un bitmap
  • faire le recadrage dans la Bitmap
  • faire un peu d'étirement/redimensionnement
  • et passez la Bitmap à votre SurfaceView

Ce n'est pas une manière très performante. Vous pouvez peut-être manipuler le byte[] directement, mais vous devrez gérer des formats d'image tels que NV21.

2
sschrass

Je viens de créer une application fonctionnelle dans laquelle je devais afficher un aperçu avec x2 de l'entrée réelle sans obtenir une apparence pixellisée. C'est à dire. J'avais besoin de montrer la partie centrale d'un aperçu en direct 1280x720 dans un TextureView 640x360.

Voici ce que j'ai fait.

Réglez l'aperçu de la caméra sur une résolution x2 de ce dont j'avais besoin:

params.setPreviewSize(1280, 720);

Et puis redimensionnez la vue de la texture en conséquence:

this.captureView.setScaleX(2f);
this.captureView.setScaleY(2f);

Cela fonctionne sans problème sur les petits appareils.

1
slott

La réponse donnée par @SatteliteSD est la plus appropriée. Chaque caméra ne prend en charge que certains formats de prévisualisation définis sur HAL. Par conséquent, si les tailles de prévisualisation disponibles ne suffisent pas, vous devez extraire les données de onPreview.

1
blganesh101

OpenCV pour Android est parfaitement adapté aux manipulations en temps réel des images de prévisualisation de la caméra. Vous trouverez tous les exemples nécessaires ici: http://opencv.org/platforms/Android/opencv4Android-samples.html , et comme vous le verrez, cela fonctionne parfaitement en temps réel.

Clause de non-responsabilité: configurer une librairie d'open sur Android peut être un vrai casse-tête, selon votre expérience de l'utilisation de c ++/opencv/ndk. Dans tous les cas, écrire du code opencv n’est jamais simple, mais c’est une bibliothèque très puissante.

0
C4stor