Практическое использование Advanced Camera API

в 12:48, , рубрики: Lumia, Блог компании Nokia, метки:

Недавно мы начали рассматривать API, выход которых был анонсирован на выставке MWC — 2013. В сегодняшней статье мы рассмотрим практические примеры применения Advanced Camera API.

Практическое использование Advanced Camera API

Windows Phone 8 Advanced Photo Capture API

Windows Phone 8 позволяет очень тонко конфигурировать параметры камеры. Основной входной точкой для новых возможностей камеры является класс Windows.Phone.Media.Capture.PhotoCaptureDevice, который предоставляет следующие методы:

— Запрос и регулирование настроек поддерживаемых параметров камеры.

static CameraCapturePropertyRange SupportedPropertyRange(CameraSensorLocation sensor, Guid propertyId)
static IReadOnlyList<object> GetSupportedPropertyValues(CameraSensorLocation sensor, Guid propertyId)
object GetProperty(Guid propertyId)
void SetProperty(Guid propertyId, object value)

— Выполнение автофокусировки, настройка баланса белого и автовыдержки.

static bool IsFocusSupported(CameraSensorLocation sensor)
static bool IsFocusRegionSupported(CameraSensorLocation sensor)
IAsyncOperation<CameraFocusStatus> FocusAsync()
IAsyncOperation<CameraFocusStatus> ResetFocusAsync()

— Проверка и установка разрешения предпросмотра и конечного снимка.

static IReadOnlyList<Size> GetAvailablePreviewResolutions()
static IReadOnlyList<Size> GetAvailableCaptureResolutions()
IAsyncAction SetPreviewResolutionAsync(Size value)
IAsyncAction SetCaptureResolutionAsync(Size value)

— Считывание буфера предпросмотра.

event TypedEventHandler<ICameraCaptureDevice, Object> PreviewFrameAvailable
void GetPreviewBufferArgb(out int[] pixels)
void GetPreviewBufferY(out byte[] pixels)
void GetPreviewBufferYCbCr(out byte[] pixels)

— Создание серии снимков.

CameraCaptureSequence CreateCaptureSequence(uint numberOfFrames)
IAsyncAction PrepareCaptureSequenceAsync(CameraCaptureSequence sequence)

Доступ к настройкам камеры производится в основном тремя способами — в зависимости от параметров. Разрешение предпросмотра и снимка задается и считывается с помощью специального асинхронного метода. Аналогично специальные методы в API существуют и для операций фокусировки. Специфические параметры фотографии регулируются с помощью GUID. Он определяет их с помощью методов, которые сообщают поддерживаемый диапазон или набор значений в запросе и метод для активации одного из поддерживаемых значений.
Стоит иметь в виду, что приложению требуется ID_CAP_ISV_CAMERA в WMAppManifest.xml для работы вышеописанных функций. Дополнительно необходима ID_CAP_MEDIALIB_PHOTO для возможности сохранения в библиотеку медиа.

Свойства камеры в смартфонах Lumia

Ниже приведена таблица наиболее важных параметров камеры в Nokia Lumia 820 и 920.
Практическое использование Advanced Camera API

Программа Camera Explorer — пример того, как может быть использован PhotoCaptureDevice на практике. Приложение включает в себя видоискатель, страницу настроек с некоторым количеством регулируемых параметров и страницу просмотра готового снимка с возможностью сохранить сделанный кадр.

Практическое использование Advanced Camera APIПрактическое использование Advanced Camera APIПрактическое использование Advanced Camera API

Прежде чем смотреть исходный код приложения, давайте посмотрим на архитектуру Camera Explorer.

Практическое использование Advanced Camera API

В дополнение к четырем основным страницам приложения Windows Phone есть еще синглтон DataContext для владения широко используемыми объектами, такими как экземпляр Settings или экземпляр PhotoCaptureDevice.
Самая интересная часть приложения — это инициализация кода PhotoCaptureDevice в DataContext, параметры, которые были созданы для этого приложения, для доступа к свойствам PhotoCaptureDevice, а также процесс захвата изображения, помещенный в MainPage, куда входит живой видоискатель.

Для описания в этой статье из всех настроек в приложении Camera Explorer мы выбрали параметры двух разных типов.
Давайте посмотрим, как PhotoCaptureDevice может быть использован на практике, используя выдержки из кода Camera Explorer. Стоит учитывать, что в данной статье не описывается весь код приложения, а потому некоторые части кода сознательно упрощены для целей статьи. С полным кодом приложения можно ознакомиться на странице проекта.

Имейте в виду DataContext, который содержит примеры, необходимые на нескольких разных разделах.

...

class DataContext : INotifyPropertyChanged
{
    ...

    public static DataContext Singleton { ... }

    public ObservableCollection<Parameter> Parameters { ... }

    public PhotoCaptureDevice Device { ... }

    public MemoryStream ImageStream { ... }
}

Захват изображения камерой

Рассмотрим, как PhotoCaptureDevice может быть запущен с использованием живого видоискателя и возможностью сделать снимок. В MainPage xaml есть элемент Canvas с элементом VideoBrush, который рендерит фон.

<phone:PhoneApplicationPage x:Class="CameraExplorer.MainPage" ... >
    ...

    <Grid x:Name="LayoutRoot" ... >
        ...

        <Grid x:Name="ContentPanel" ... >
            <Canvas x:Name="VideoCanvas">
                <Canvas.Background>
                    <VideoBrush x:Name="videoBrush"/>
                </Canvas.Background>
            </Canvas>

            ...
        </Grid>
    </Grid>

    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar>
            <shell:ApplicationBarIconButton Text="capture" Click="captureButton_Click" ... />
     
            ...
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

    ...
</phone:PhoneApplicationPage>

В коде C# MainPage PhotoCaptureDevice запускается методом InitializeCamera, примеры камеры при этом сохраняются в DataContext. В обработчике событий для кнопки съемки, который был заявлен в XAML, мы видим, как фото может быть снято.

...

public partial class MainPage : PhoneApplicationPage
{
    private CameraExplorer.DataContext _dataContext = CameraExplorer.DataContext.Singleton;

    ...

    protected async override void OnNavigatedTo(NavigationEventArgs e)
    {
        if (_dataContext.Device == null)
        {
            ...

            await InitializeCamera(CameraSensorLocation.Back);

            ...
        }

        videoBrush.RelativeTransform = new CompositeTransform()
        {
            CenterX = 0.5,
            CenterY = 0.5,
            Rotation = _dataContext.Device.SensorLocation == CameraSensorLocation.Back ?
                       _dataContext.Device.SensorRotationInDegrees :
                     - _dataContext.Device.SensorRotationInDegrees
        };

        videoBrush.SetSource(_dataContext.Device);

        ...
    }

    private async Task InitializeCamera(CameraSensorLocation sensorLocation)
    {
        Windows.Foundation.Size initialResolution = new Windows.Foundation.Size(640, 480);
        Windows.Foundation.Size previewResolution = new Windows.Foundation.Size(640, 480);
        Windows.Foundation.Size captureResolution = new Windows.Foundation.Size(640, 480);

        PhotoCaptureDevice d = await PhotoCaptureDevice.OpenAsync(sensorLocation, initialResolution);

        await d.SetPreviewResolutionAsync(previewResolution);
        await d.SetCaptureResolutionAsync(captureResolution);

        d.SetProperty(KnownCameraGeneralProperties.EncodeWithOrientation,
                      d.SensorLocation == CameraSensorLocation.Back ?
                      d.SensorRotationInDegrees : -d.SensorRotationInDegrees);

        _dataContext.Device = d;
    }

    private async void captureButton_Click(object sender, EventArgs e)
    {
        if (!_manuallyFocused)
        {
            await AutoFocus();
        }
        await Capture();
    }

    private async Task AutoFocus()
    {
        if (!_capturing && PhotoCaptureDevice.IsFocusSupported(_dataContext.Device.SensorLocation))
        {
            ...
            await _dataContext.Device.FocusAsync();
            ...
            _capturing = false;
        }
    }
    
    private async Task Capture()
    {
        if (!_capturing)
        {
            _capturing = true;
            MemoryStream stream = new MemoryStream();
            CameraCaptureSequence sequence = _dataContext.Device.CreateCaptureSequence(1);
            sequence.Frames[0].CaptureStream = stream.AsOutputStream();
            await _dataContext.Device.PrepareCaptureSequenceAsync(sequence);
            await sequence.StartCaptureAsync();
            _dataContext.ImageStream = stream;
            ...
        }
        ...
    }

    ...
}

Показываем предпросмотр и сохраняем в альбом камеры

Отрисовка предпросмотра сделанного снимка довольно проста. Приложение Camera Explorer делает это, используя элемент XAML Image с объектом BitmapImage. Сохранить сделанный снимок в альбом камеры тоже достаточно просто, стоит только помнить, что поток изображений, являющийся аргументом для void MediaLibrary.SavePictureToCameraRoll(string name, Stream source), должен располагаться перед началом данных изображения.

<phone:PhoneApplicationPage x:Class="CameraExplorer.PreviewPage" ... >
    ...

    <Grid x:Name="LayoutRoot" ... >
        ...

        <Grid x:Name="ContentPanel" ... >
            <Image x:Name="image" Stretch="UniformToFill"/>
        </Grid>
    </Grid>

    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar>
            <shell:ApplicationBarIconButton Text="save" Click="saveButton_Click" ... />
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

    ...
</phone:PhoneApplicationPage>
...

public partial class PreviewPage : PhoneApplicationPage
{
    private CameraExplorer.DataContext _dataContext = CameraExplorer.DataContext.Singleton;
    private BitmapImage _bitmap = new BitmapImage();

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        ...

        _bitmap.SetSource(_dataContext.ImageStream);

        image.Source = _bitmap;

        ...
    }

    private void saveButton_Click(object sender, EventArgs e)
    {
        try
        {
            _dataContext.ImageStream.Position = 0;

            MediaLibrary library = new MediaLibrary();

            library.SavePictureToCameraRoll("CameraExplorer_" + DateTime.Now.ToString() + ".jpg",
                                            _dataContext.ImageStream);
        }
        catch (Exception)
        {
            ...
        }

        ...
    }
}

Архитектура приложения Camera Explorer такова, что параметры камеры делятся на ArrayParameters и RangeParameters, производные от базового класса Parameters. Такая архитектура класса позволяет легко и гибко управлять параметрами и отображать их на экране, связывая объекты параметров напрямую с элементами управления интерфейса, такими как ComboBox, Slider и Text. В данной статье не рассматривается сопоставление, но вы можете найти примеры на странице проекта.
Вот упрощенный пример базового класса Parameter с несколькими элементами, соответствующими обоим типам параметров.

...

public abstract class Parameter : INotifyPropertyChanged
{    
    ...
   
    protected Parameter(PhotoCaptureDevice device, string name)
    {
        ...
    }
 
    public PhotoCaptureDevice Device { ... }

    public string Name { ... }

    public bool Supported { ... }

    public abstract void Refresh();

    ...
}

Теперь давайте посмотрим, как можно работать с массивом свойств. Чтение минимума и максимума массива для свойства определяется _propertyId, который выполняется запросом метода static CameraCapturePropertyRange PhotoCaptureDevice.GetSupportedPropertyRange(CameraSensorLocation sensor, Guid propertyId) и проверкой полученного значения. Это значение свойства читается запросом к методу примера object PhotoCaptureDevice.GetProperty(Guid propertyId). Аналогично установка нового значения для свойства может быть произведена вызовом void PhotoCaptureDevice.SetProperty(Guid propertyId, object value).

...

public abstract class RangeParameter<T> : Parameter
{
    private Guid _propertyId;
    private T _value;

    ...

    protected RangeParameter(PhotoCaptureDevice device, Guid propertyId, string name)
        : base(device, name)  
    {
        ...
    }

    public override void Refresh()
    {
        try
        {
            CameraCapturePropertyRange range = PhotoCaptureDevice.GetSupportedPropertyRange(
                Device.SensorLocation, _propertyId);
            
            if (range == null)
            {
                Supported = false;
            }
            else
            {
                Minimum = (T)range.Min;
                Maximum = (T)range.Max;
                
                _value = (T)Device.GetProperty(_propertyId);
                
                Supported = true;
            }
        }
        catch (Exception)
        {
            Supported = false;
        }

        ... 
    }
    
    public T Minimum { ... }

    public T Maximum { ... }

    public T Value
    {
        ...

        set
        {
            if (!_value.Equals(value))
            {
                try
                {
                    Device.SetProperty(_propertyId, (T)value);
  
                    _value = value;

                    ...
                }
                catch (Exception)
                {
                    ...
                }
            }
        }
    }

    ...
}

Вот определение ExposureCompensationParameter, где Guid RangeParameter установлен в KnownCameraPhotoProperties.ExposureCompensation, — значение, прописанное в платформе в качестве конкретного примера диапазона параметров. Также из документации платформы мы знаем, что конкретный тип экспокоррекции владеет Int32, так что это может быть использовано как шаблонный аргумент для реализации диапазона параметров.

...

public class ExposureCompensationParameter : RangeParameter<Int32>
{
    public ExposureCompensationParameter(PhotoCaptureDevice device)
        : base(device, KnownCameraPhotoProperties.ExposureCompensation, "Exposure compensation")
    {
    }

    ...
}

Приложение Camera Explorer работает с массивом параметров с помощью ArrayParameter. Вот его упрощенное определение для настроек в контексте кода. Действительно интересные части — это защищенные абстрактные методы void PopulateOptions() и void SetOption(ArrayParameterOption option), в которых мы разберемся сразу после этого отрывка кода.

...

public class ArrayParameterOption
{
    ...
    
    public ArrayParameterOption(dynamic value, string name)
    {
        ...
    }

    public dynamic Value { ... }

    public string Name { ... }

    ...
}

public abstract class ArrayParameter : Parameter, IReadOnlyCollection<ArrayParameterOption>
{ 
    private List<ArrayParameterOption> _options = new List<ArrayParameterOption>();
    private ArrayParameterOption _selectedOption;

    ...

    public ArrayParameter(PhotoCaptureDevice device, string name)
        : base(device, name)
    {
        ...
    }

    public override void Refresh()
    {
        ...

        _options.Clear();

        _selectedOption = null;

        try
        {
            PopulateOptions();

            Supported = _options.Count > 0;
        }
        catch (Exception)
        {
            Supported = false;
        }

        ...
    }

    protected List<ArrayParameterOption> Options { ... }

    public ArrayParameterOption SelectedOption
    {
        ...
 
        set
        {
            if (_selectedOption != value)
            {
                ...

                if (_selectedOption != null))
                {
                    SetOption(value);
                }

                _selectedOption = value; 
            }
        }
    }

    protected abstract void PopulateOptions();

    protected abstract void SetOption(ArrayParameterOption option);

    ...
}

Теперь, когда у нас есть выгруженная основа для всех типов массива параметров, давайте посмотрим на конкретный фрагмент кода, который отвечает за считывание и установку параметров камеры в сюжетном режиме. Считывание поддерживаемых значений и выбор текущего активного значения осуществляет метод void PopulateOptions(), в то время как установка нового значения, согласно изменениям в ArrayParameterOption SelectedOption, осуществляется методом void SetOption(ArrayParameterOption option). Обратите внимание, как static IReadOnlyList<object> PhotoCaptureDevice.GetSupportedPropertyValues(CameraSensorLocation sensor, Guid propertyId) используется для считывания поддерживаемых значений, а KnownCameraPhotoProperties.SceneMode и object PhotoCaptureDevice.GetProperty(Guid propertyId) используется для считывания текущего значения. Установка нового значения сюжетного режима достигается простым запросом метода void PhotoCaptureDevice.SetProperty(Guid propertyId, object value).

...

public class SceneModeParameter : ArrayParameter
{
    public SceneModeParameter(PhotoCaptureDevice device)
        : base(device, KnownCameraPhotoProperties.SceneMode, "Scene mode")
    {
    }

    protected override void PopulateOptions()
    {
        IReadOnlyList<object> supportedValues = PhotoCaptureDevice.GetSupportedPropertyValues(
            Device.SensorLocation, PropertyId);

        object value = Device.GetProperty(PropertyId);

        foreach (dynamic i in supportedValues)
        {
            CameraSceneMode csm = (CameraSceneMode)i;

            ArrayParameterOption option = new ArrayParameterOption(csm, csm.ToString());

            Options.Add(option);

            if (i.Equals(value))
            {
                SelectedOption = option;
            }
        }
    }

    protected override void SetOption(ArrayParameterOption option)
    {
        Device.SetProperty(PropertyId, option.Value);
    }

    ...
}

Tap-to-focus

По умолчанию приложение фокусируется и рассчитывает сцену по нажатию кнопки «Съемка». Tap-to-focus является альтернативным вариантом поведения, когда касание устанавливает область фокусировки камеры, запускает саму фокусировку, экспозицию и настраивает баланс белого для следующего снимка. Индикатор фокусировки отображается, если режим Tap-to-focus активен.

Индикатор фокуса является элементом Rectangle внутри Canvas, где отображается картинка в видоискателе:

<Rectangle x:Name="FocusIndicator"
           Stroke="Red" Opacity="0.7" Width="80" Height="80" StrokeThickness="5"
           Visibility="Collapsed"/>

Свернутая видимость означает, что элемент изначально скрыт.
Касание определяется с помощью обработчика событий для события Canvas Tap:

public MainPage()
{
    ...
    VideoCanvas.Tap += new EventHandler<GestureEventArgs>(videoCanvas_Tap);
    ...
}

Обработчик событий должен произвести дополнительные действия для определения зоны фокусировки. Это связано с несовместимыми типами используемых API-интерфейсов и тем фактом, что VideoCanvas, который используется для отображения видоискателя, не соответствует ему по размеру.

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

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

private async void videoCanvas_Tap(object sender, GestureEventArgs e)
{
    System.Windows.Point uiTapPoint = e.GetPosition(VideoCanvas);
    if (PhotoCaptureDevice.IsFocusRegionSupported(_dataContext.Device.SensorLocation)
        && _focusSemaphore.WaitOne(0))
    {
        // Получаем координаты нажатия для отправной точки.
        Windows.Foundation.Point tapPoint = new Windows.Foundation.Point(uiTapPoint.X, uiTapPoint.Y);
        double xRatio = VideoCanvas.ActualWidth / _dataContext.Device.PreviewResolution.Width;
        double yRatio = VideoCanvas.ActualHeight / _dataContext.Device.PreviewResolution.Height;
        
        // Регулируем фокус, основываясь на точке касания
        Windows.Foundation.Point displayOrigin = new Windows.Foundation.Point(
            tapPoint.X - _focusRegionSize.Width / 2,
            tapPoint.Y - _focusRegionSize.Height / 2);
        
        // Регулируем разницу разрешения между картинкой предпросмотра и canvas
        Windows.Foundation.Point viewFinderOrigin = new Windows.Foundation.Point(
            displayOrigin.X / xRatio, displayOrigin.Y / yRatio);
        Windows.Foundation.Rect focusrect = new Windows.Foundation.Rect(
            viewFinderOrigin, _focusRegionSize);
        
        // Уменьшаем разрешение предпросмотра
        Windows.Foundation.Rect viewPortRect = new Windows.Foundation.Rect(
            0, 0,
            _dataContext.Device.PreviewResolution.Width,
            _dataContext.Device.PreviewResolution.Height);
        focusrect.Intersect(viewPortRect);
        _dataContext.Device.FocusRegion = focusrect;

        // Показываем индикатор фокусировки
        FocusIndicator.SetValue(Shape.StrokeProperty, _notFocusedBrush);
        FocusIndicator.SetValue(Canvas.LeftProperty, uiTapPoint.X - _focusRegionSize.Width / 2);
        FocusIndicator.SetValue(Canvas.TopProperty, uiTapPoint.Y - _focusRegionSize.Height / 2);
        FocusIndicator.SetValue(Canvas.VisibilityProperty, Visibility.Visible);

        CameraFocusStatus status = await _dataContext.Device.FocusAsync();
        if (status == CameraFocusStatus.Locked)
        {
            FocusIndicator.SetValue(Shape.StrokeProperty, _focusedBrush);
            _manuallyFocused = true;
            _dataContext.Device.SetProperty(
                KnownCameraPhotoProperties.LockedAutoFocusParameters,
                AutoFocusParameters.Exposure & AutoFocusParameters.Focus & AutoFocusParameters.WhiteBalance);
        }
        else
        {
            _manuallyFocused = false;
            _dataContext.Device.SetProperty(
                KnownCameraPhotoProperties.LockedAutoFocusParameters,
                AutoFocusParameters.None);
        }
        _focusSemaphore.Release();
    }
}

Фокусировка также блокируется семафором, так что автофокусировка происходит только один раз. Если мы попытаемся вызвать FocusAsync, пока предыдущая операция выполняется, приложение закроется с ошибкой. Нулевое значение, заданное методу семафора WaitOne, означает, что если семафор недоступен, то запрос вернет false немедленно — вместо ожидания, пока семафор станет доступен.

Интеграция выбора фотоприложений

Стандартное приложение камеры позволяет запускать дополнения, так называемые «Фотоприложения», с помощью Lens Picker. «Фотоприложения» являются полноценными приложениями для работы с камерой и обычно предоставляют более широкие возможности, чем стандартное приложение. Для того чтобы сделать приложение доступным в Lens Picker, следующий фрагмент XML должен быть добавлен в манифест приложения вместе с элементом App:

<Extensions>
    <Extension ExtensionName="Camera_Capture_App"
               ConsumerId="{5B04B775-356B-4AA0-AAF8-6491FFEA5631]"
               TaskId="_default" />
</Extensions>

Таким образом, когда приложение выбрано в Lens Picker, оно запускается на своем стартовом экране. Кроме того, иконки для Lens Picker располагаются в папке Assets. Иконка должна быть определенного размера и иметь определенное имя, о чем сказано в статье Lens design guidelines for Windows Phone.

В следующей статье цикла мы рассмотрим пример использования Maps API.

Автор: nokiaman

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js