Предварительный просмотр камеры для TextureView

У меня есть TextureView с фиксированной шириной и высотой, и я хочу показать предварительный просмотр камеры внутри него. Мне нужно обрезать предварительный просмотр камеры, чтобы он не выглядел растянутым внутри моего TextureView. Как сделать обрезку? Если мне нужно использовать OpenGL, как связать текстуру поверхности с OpenGL и как сделать обрезку с помощью 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 a new Camera preview frame
 }
}

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

Ответ 1

При условии, что более раннее решение от @Romanski работает отлично, но оно масштабируется с обрезкой. Если вам нужно масштабировать, чтобы соответствовать, используйте следующее решение. Вызывайте updateTextureMatrix каждый раз при изменении вида поверхности: т.е. В onSurfaceTextureAvailable и в методах onSurfaceTextureSizeChanged. Также обратите внимание, что это решение полагается, что активность игнорирует изменения конфигурации (т.е. Android: configChanges = "orientation | screenSize | keyboardHidden" или что-то в этом роде):

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

Также вам нужны следующие поля:

private int orgPreviewWidth;
private int orgPreviewHeight;

инициализировать его в onSurfaceTextureAvailable mathod перед вызовом 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);

Метод 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);
}

И последнее - нужно исправить поворот камеры. Поэтому вызовите метод setCameraDisplayOrientation в Activity onConfigurationChanged (а также сделайте начальный вызов методом 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);
}

Ответ 2

Просто вычислите соотношение сторон, создайте матрицу масштабирования и примените ее к текстуру. Основываясь на соотношении сторон поверхности и соотношении сторон изображения предварительного просмотра, изображение предварительного просмотра обрезается сверху, снизу или слева и справа. Еще одно решение, которое я выяснил, заключается в том, что если вы открываете камеру до, SurfaceTexture доступна, предварительный просмотр уже масштабируется автоматически. Просто попробуйте переместить mCamera = Camera.open(); к вашей функции onCreate после установки SurfaceTextureListener. Это сработало для меня на N4. С помощью этого решения вы, вероятно, столкнетесь с проблемами при повороте с портрета на пейзаж. Если вам нужна портретная и ландшафтная поддержка, тогда возьмите решение с матрицей масштаба!

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

Ответ 3

вы можете манипулировать byte[] data с onPreview().

Думаю, вам придется:

  • поместите его в растровое изображение
  • выполните обрезку в Bitmap
  • сделать немного растяжения/изменения размера
  • и передайте Bitmap на ваш SurfaceView

Это не очень эффективный способ. Возможно, вы можете напрямую манипулировать byte[], но вам придется иметь дело с форматами изображений, такими как NV21.

Ответ 4

Ответ, данный @SatteliteSD, является наиболее подходящим. Каждая камера поддерживает только определенные размеры предварительного просмотра, которые установлены в HAL. Следовательно, если доступных размеров предварительного просмотра недостаточно, вам необходимо извлечь данные из onPreview

Ответ 5

Я только что создал рабочее приложение, где мне нужно было показать предварительный просмотр с x2 фактического ввода, не получив пиксельного вида. То есть. Мне нужно было показать центральную часть предварительного просмотра 1280x720 в режиме TextureView 640x360.

Вот что я сделал.

Установите предварительный просмотр камеры на x2, что мне нужно:

params.setPreviewSize(1280, 720);

И затем соответствующим образом измените представление текстуры:

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

Это выполняется без каких-либо икота на небольших устройствах.

Ответ 6

Для обработки изображений в режиме реального времени камеры OpenCV для Android отлично адаптируется. Вы найдете здесь каждый образец: http://opencv.org/platforms/android/opencv4android-samples.html, и, как вы увидите, он работает хорошо в режиме реального времени.

Отказ от ответственности: настройка opencv lib на Android может быть довольно головной болью в зависимости от того, как вы экспериментируете с С++/opencv/ndk. Во всех случаях писать opencv-код никогда не бывает простым, но, с другой стороны, это очень мощная библиотека.