Предварительный просмотр камеры кадрирования для 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's a new Camera preview frame
 }
}

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


person Catalin Morosan    schedule 10.06.2013    source источник
comment
Я не уверен, что это поможет вам, но вы можете попробовать с меньшим размером предварительного просмотра. Изначально камера дает больший размер предварительного просмотра по умолчанию.   -  person Amrendra    schedule 10.06.2013
comment
Я знаю о размерах предварительного просмотра. Это не помогает мне, потому что вид, который показывает предварительный просмотр камеры, имеет размеры, отличные от тех, которые доступны в качестве размеров предварительного просмотра.   -  person Catalin Morosan    schedule 10.06.2013
comment
Вы можете попробовать с фрагментом на экране.   -  person Amrendra    schedule 10.06.2013
comment
хорошо, чтобы получить лучший размер предварительного просмотра, я согласен с @Amrendra, выполнив некоторые математические действия, чтобы получить размер предварительного просмотра, максимально близкий к размеру вашего представления. и я думаю, что это самое близкое, что вы можете достичь   -  person Diogo Bento    schedule 20.06.2013
comment
На некоторых устройствах очень мало размеров предварительного просмотра, и ни один из них не очень близок к нужному мне размеру. Так что это не вариант.   -  person Catalin Morosan    schedule 20.06.2013
comment
если вы не хотите обрабатывать изображение, вы можете поместить представление текстуры внутрь linearlayout и добавить layout_gravity = center или center_vertically к вашему представлению textrure. здесь для получения дополнительной информации: stackoverflow.com/questions/18051472/   -  person bh_earth0    schedule 23.02.2018


Ответы (6)


Предоставленное ранее решение @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 перед вызовом 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);
}
person Ruslan Yanchyshyn    schedule 07.02.2014
comment
ПОСТАВЬТЕ ЭТОМУ ЧЕЛОВЕКУ ПОДТВЕРЖДЕНИЕ! большое спасибо за это Руслан. Я пытался атаковать это с помощью SurfaceViews, SurfaceTextures и миллиона других вещей уже около недели. Я знал, что должен быть какой-то вид, на который я мог бы установить матрицу масштабирования, но я просто не мог его найти. это потрясающе. Я думаю, что должна быть ссылка на этот ответ из многих других вопросов о обрезке предварительного просмотра камеры, которые там есть. - person Sam; 28.04.2014
comment
редактировать: в этом коде есть небольшая логическая проблема, которую romanski, похоже, решил, я попытаюсь обновить этот ответ, как только я выясню, в чем проблема - person Sam; 28.04.2014
comment
Пример того, как сделать это непосредственно с помощью SurfaceTexture и GLES, см. в текстуре активности камеры в Grafika (github.com/google. /графика). Действие масштабирования выполняет центральную обрезку и масштабирование, манипулируя координатами текстуры. TextureView делает что-то подобное. Преимущество TextureView в том, что его намного проще использовать. Необработанный подход GLES намного сложнее, но гораздо более гибкий. - person fadden; 28.11.2014
comment
Привет, спасибо за полезный пост. Это помогло мне решить проблему, после которой у меня появилась другая: при перемещении камеры вид не плавный. Похоже на плохую трансформацию или что-то в этом роде. Не могли бы вы мне помочь ? любые идеи для такого поведения? - person Vardges; 16.12.2014
comment
У меня есть небольшие проблемы с пониманием этого. Я получаю ошибку setParameters failed. Я следовал этому руководству, но думаю, что and also make initial call in onSurfaceTextureAvailable method немного запутался. Это должно быть до того, как я установил Camera camera = Camera.open(); ? - person Jonas Borggren; 26.01.2015
comment
Серьезная логическая проблема в верхнем ответе. Предположим, у нас есть вид 720 * 720, а предварительный просмотр камеры 1280 * 720. Я получил совершенно неправильный коэффициент масштабирования. - person user1154390; 04.03.2015
comment
Это все еще работает для уровней API выше 20? У меня проблемы с переводом на устройствах Android с уровня API 21. - person Philippe Creytens; 08.02.2017

Просто рассчитайте соотношение сторон, сгенерируйте матрицу масштабирования и примените ее к TextureView. В зависимости от соотношения сторон поверхности и соотношения сторон изображения предварительного просмотра изображение предварительного просмотра обрезается сверху и снизу или слева и справа. Еще одно решение, которое я обнаружил, заключается в том, что если вы открываете камеру до того, как 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);
}
person Romanski    schedule 26.07.2013
comment
Я не могу заставить его обрезать превью. Вы уверены, что можно обрезать превью внутри TextureView? - person Catalin Morosan; 28.08.2013
comment
У меня так работает. В основном это не кадрирование, а масштабирование. Предварительный просмотр камеры всегда размещается внутри TextureView, даже если соотношение TextureView и соотношения предварительного просмотра камеры не совпадают. С матрицей преобразования вы можете установить масштабирование для компенсации искажения. - person Romanski; 29.08.2013
comment
Я знаю, что опоздал, но как бы вы адаптировали вращение к API Camera2? Нет setDisplayOrientation. Я тестирую с matrix.setRotate/postRotate/preRotate, но никогда не получаю желаемого результата. - person Csharpest; 26.03.2018
comment
На Camera2 я использую другой подход - вычисляю матрицу с помощью setRectToRect (размер изображения и размер поверхности), а затем на основе текущей позиции отображения я использую postScale/postRotate на матрице. Извините, но я не могу предоставить код для этого - person Romanski; 27.03.2018

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

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

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

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

person sschrass    schedule 25.06.2013

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

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

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

params.setPreviewSize(1280, 720);

И затем соответствующим образом масштабируйте вид текстуры:

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

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

person slott    schedule 28.02.2014
comment
Большое спасибо. Ты спас меня. - person YeeKhin; 29.10.2015

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

person blganesh101    schedule 25.06.2013

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

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

person C4stor    schedule 26.06.2013
comment
Я сам смотрел на JavaCameraView, и, похоже, он выполняет преобразование yuv->rgb в собственном коде на процессоре, а не на графическом процессоре ... я думаю, что это приводит к реальному замедлению кадров камеры, доставляемых на экран (задержка и плохой fps) - person Sam; 28.04.2014