Table of Contents

Создание расширений ввода изображений

Перед началом

Создание класса внешнего источника данных кадров

Наследуйте ExternalImageStreamFrameSource, чтобы создать расширение ввода изображений. Это подкласс MonoBehaviour, а имя файла должно совпадать с именем класса.

Например:

public class MyFrameSource : ExternalImageStreamFrameSource
{
}

Пример Workflow_FrameSource_ExternalImageStream представляет собой реализацию расширения ввода изображений, основанную на видео, записанном ARCore на телефоне. Это видео было получено через callback камеры ARCore на Pixel2 (это не запись экрана).

Определение устройства

Переопределите IsCameraUnderControl и верните true.

Переопределите IsHMD, чтобы определить, является ли устройство головным дисплеем.

Например, при использовании видео в качестве входных данных задайте false.

protected override bool IsHMD => false;

Переопределите Display, чтобы определить дисплей устройства.

Например, если приложение запускается только на телефоне, можно использовать Display.DefaultSystemDisplay; его значение поворота автоматически изменяется в соответствии с текущим состоянием дисплея операционной системы.

protected override IDisplay Display => easyar.Display.DefaultSystemDisplay;

Доступность

Переопределите IsAvailable, чтобы определить, доступно ли устройство.

Например, при использовании видео в качестве входных данных оно всегда доступно:

protected override Optional<bool> IsAvailable => true;

Если IsAvailable нельзя определить во время сборки session, можно переопределить корутину CheckAvailability(), чтобы заблокировать процесс сборки до тех пор, пока доступность не будет определена.

Виртуальная камера

Переопределите Camera, чтобы предоставить виртуальную камеру.

Например, иногда можно использовать Camera.main как виртуальную камеру session:

protected override Camera Camera => Camera.main;

Физическая камера

Переопределите DeviceCameras с типом FrameSourceCamera, чтобы предоставить информацию о физической камере устройства. Эти данные используются при вводе данных кадра камеры. Создание должно быть завершено, когда CameraFrameStarted равно true.

Например, при использовании видео из примера Workflow_FrameSource_ExternalImageStream:

private FrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };

{
    var size = new Vector2Int(640, 360);
    var cameraType = CameraDeviceType.Back;
    var cameraOrientation = 90;
    deviceCamera = new FrameSourceCamera(cameraType, cameraOrientation, size, new Vector2(30, 30));
    started = true;
}
Осторожно

Несколько входных параметров здесь нужно задавать в соответствии с фактически используемым видео. Параметры в приведенном выше коде применимы только к видео из примера.

Переопределите CameraFrameStarted, чтобы предоставить признак начала ввода кадров камеры.

Например:

protected override bool CameraFrameStarted => started;

Запуск и остановка session

Переопределите OnSessionStart(ARSession) и выполните инициализацию, специфичную для AR. Нужно убедиться, что сначала вызывается base.OnSessionStart.

Например:

protected override void OnSessionStart(ARSession session)
{
    base.OnSessionStart(session);
    ...
}

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

Кроме того, это подходящее место для запуска цикла ввода данных. Этот цикл также можно написать в Update() или других методах, особенно если данные нужно получать в определенный момент порядка выполнения Unity. Не вводите данные до готовности session (ready).

При необходимости можно также пропустить процесс запуска и выполнять проверку данных при каждом обновлении; это полностью зависит от конкретных требований.

Например, при использовании видео в качестве входных данных здесь можно начать воспроизведение видео и запустить цикл ввода данных:

protected override void OnSessionStart(ARSession session)
{
    base.OnSessionStart(session);
    ...
    player.Play();
    StartCoroutine(VideoDataToInputFrames());
}

Переопределите OnSessionStop() и освободите ресурсы. Нужно убедиться, что вызывается base.OnSessionStop.

Например, при использовании видео в качестве входных данных здесь можно остановить воспроизведение видео и освободить связанные ресурсы:

protected override void OnSessionStop()
{
    base.OnSessionStop();

    StopAllCoroutines();
    player.Stop();
    if (renderTexture) { Destroy(renderTexture); }
    cameraParameters?.Dispose();
    cameraParameters = null;
    frameIndex = -1;
    started = false;
    deviceCamera?.Dispose();
    deviceCamera = null;
}

Получение данных кадра камеры с устройства или из файла

Изображение можно получать из системной камеры, USB-камеры, видеофайла, сети и любых других источников. Достаточно преобразовать данные в формат, требуемый Image. Способы получения данных с этих устройств или из файлов различаются, поэтому нужно обращаться к инструкциям по использованию соответствующего устройства или файла.

Например, при использовании видео в качестве входных данных можно использовать Texture2D.ReadPixels(Rect, int, int, bool), чтобы получить данные кадра камеры из RenderTexture видеоплеера, а затем скопировать данные Texture2D.GetRawTextureData() в Buffer:

void VideoDataToInputFrames()
{
    ...
    RenderTexture.active = renderTexture;
    var pixelSize = new Vector2Int((int)player.width, (int)player.height);
    var texture = new Texture2D(pixelSize.x, pixelSize.y, TextureFormat.RGB24, false);
    texture.ReadPixels(new Rect(0, 0, pixelSize.x, pixelSize.y), 0, 0);
    texture.Apply();
    RenderTexture.active = null;
    ...
    CopyRawTextureData(buffer, texture.GetRawTextureData<byte>(), pixelSize);
} 

static unsafe void CopyRawTextureData(Buffer buffer, Unity.Collections.NativeArray<byte> data, Vector2Int size)
{
    int oneLineLength = size.x * 3;
    int totalLength = oneLineLength * size.y;
    var ptr = new IntPtr(data.GetUnsafeReadOnlyPtr());
    for (int i = 0; i < size.y; i++)
    {
        buffer.tryCopyFrom(ptr, oneLineLength * i, totalLength - oneLineLength * (i + 1), oneLineLength);
    }
}
Осторожно

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

При получении изображения также нужно получить калибровочные данные камеры или эквивалентной камеры и создать экземпляр CameraParameters.

Если исходный источник данных — callback камеры телефона и данные не были вручную обрезаны, можно напрямую использовать калибровочные данные камеры телефона. При получении данных callback камеры через такие интерфейсы, как ARCore или ARKit, можно обратиться к соответствующей документации, чтобы получить внутренние параметры камеры. Если требуемая функция AR — отслеживание изображений или отслеживание объектов, в такой ситуации также можно создать внутренние параметры камеры с помощью CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int); это немного повлияет на результат алгоритма, но обычно влияние невелико.

Если данные поступают из USB-камеры, видеофайла, созданного не через callback камеры, или других источников, нужно откалибровать камеру или видеокадры, чтобы получить корректные внутренние параметры.

Осторожно

Данные callback камеры нельзя обрезать; после обрезки нужно заново вычислить внутренние параметры. Если данные получены, например, через запись экрана, обычно нельзя использовать калибровочные данные камеры телефона; в этом случае также нужно откалибровать камеру или видеокадры, чтобы получить корректные внутренние параметры.

Некорректные внутренние параметры приведут к тому, что функции AR не смогут работать нормально; распространенные проявления — виртуальное содержимое не совпадает с реальными объектами, а AR-отслеживание трудно запускается или легко теряется.

Например, для видео из примера Workflow_FrameSource_ExternalImageStream соответствующие внутренние параметры камеры и процесс создания CameraParameters выглядят так:

var size = new Vector2Int(640, 360);
var cameraType = CameraDeviceType.Back;
var cameraOrientation = 90;
cameraParameters = new CameraParameters(size.ToEasyARVector(), new Vec2F(506.085f, 505.3105f), new Vec2F(318.1032f, 177.6514f), cameraType, cameraOrientation);
Осторожно

Параметры в приведенном выше коде применимы только к видео из примера; эти внутренние параметры камеры и видео были получены одновременно. Если нужно использовать данные другого видео или устройства, обязательно одновременно получите внутренние параметры устройства или выполните ручную калибровку.

Ввод данных кадра камеры

После получения обновления данных кадра камеры вызовите HandleCameraFrameData(double, Image, CameraParameters) для ввода данных кадра камеры.

Например, при использовании видео в качестве входных данных реализация выглядит так:

IEnumerator VideoDataToInputFrames()
{
    yield return new WaitUntil(() => player.isPrepared);
    var pixelSize = new Vector2Int((int)player.width, (int)player.height);
    ...
    yield return new WaitUntil(() => player.isPlaying && player.frame >= 0);
    while (true)
    {
        yield return null;
        if (frameIndex == player.frame) { continue; }
        frameIndex = player.frame;
        ...
        var pixelFormat = PixelFormat.RGB888;
        var bufferO = TryAcquireBuffer(pixelSize.x * pixelSize.y * 3);
        if (bufferO.OnNone) { continue; }

        var buffer = bufferO.Value;
        CopyRawTextureData(buffer, texture.GetRawTextureData<byte>(), pixelSize);

        using (buffer)
        using (var image = Image.create(buffer, pixelFormat, pixelSize.x, pixelSize.y, pixelSize.x, pixelSize.y))
        {
            HandleCameraFrameData(player.time, image, cameraParameters);
        }
    }
}
Осторожно

Не забывайте после использования вызывать Dispose() или освобождать Image , Buffer и другие связанные данные через такие механизмы, как using. Иначе возникнут серьезные утечки памяти, а получение buffer из buffer pool также может завершаться ошибкой.

Связанные темы