Windows Phone 8.1: Cómo usar la cámara, primeros pasos


Hace poco comencé con un proyecto bastante interesante el cual incluía un módulo de reconocer caracteres en una imagen, al lograr eso se podrían desplegar varios tipos de aplicaciones; sin embargo el primer obstaculo que encontré es que controlar la cámara del dispositivo no es como se hacía con sus antecesores (WP7 y WP8), es un poco más cercano a como se maneja en WindowsRT.

Por lo que primero que tenía que hacer era domar este dispositivo que accede a un nivel muy bajo en cuanto a Hardware (se me crasheó el móvil más de 30 veces!!!!), por lo cual decido compartirles esta serie de artículos hasta reconocer caracteres en Windows Phone 8.1, mecanismo que se denomina OCR (Optical Character Recognition).

Como estos artículos son nivel medio supondré que ya tienen creado el proyecto Windows Phone 8.1 XAML con C#.

Declarando los manifiestos

En el manifiesto de la App ir a la sección de Capabilities, seleccionar “Microphone” y “Webcam”

Front-end

En la vista elegida declarar un elemento tipo CaptureElement, en mi caso MainPage.xaml

Code-behind

Ahora si!, manos a la obra.

En el constructor de la clase declarar dos manejadores de eventos, el primero para controlar el botón de atrás que se usará para salir de la aplicación y el segundo para cuando la vista se haya cargado correctamente

El cual el primero tendrá la siguiente definición:

Como dije en un principio, el acceso a la cámara es a muy bajo nivel y por lo tanto si no se trabaja correctamente podremos dejar nuestra aplicación fuera de uso y de paso el móvil por completo, en una ocasión se me cerró hasta Visual Studio ya que este intentaba comunicarse con el móvil y hacer todo lo que tiene hacer en el Debug y fue un caos, por lo cual es buena práctica liberar el manejador de la cámara correctamente.

Hay medios más elegantes de cerrar este handler como hacerlo en el evento OnSuspending declarado en App.xaml.cs pero por fines educativos voy a dejar este método en esta clase.

Para que todo armonice, entre el code-behind y el front-end  es necesario declarar un objeto tipo MediaCapture, quien es el encargado de hacer todo el trabajo sucio, es sucio… pero alguien tiene que hacerlo jaja, no mentiras, es quien se encarga de administrar la memoria y proporcionar toda las funcionalidades como el Zoom, configuraciones, Pan, Exposición, etc, etc.

Ahora si, se declara el método encargado de hacerle un Dispose a la cámara:

Lo siguiente es declarar el otro controlador de evento, el que contiene definidos dos métodos. El primero para cambiar de posición el móvil y el segundo para inicializar la cámara:

Ahora, cuando estaba experimentando los controles pude percibir que la imagen resultante era un poco extraña y se veía en ciertos ángulos distorsionada, por lo cual entendí el motivo de que en los teléfonos Lumia y sus productos que tienen que ver con la cámara están en modo Landscape, para renderizar de manera adecuada la imagen resultante. Sin embargo lo que se usa para dar la sensación de que se soporta Portrait solo cambian de posición los iconos.

Por lo cual declaro este método para forzar la posición del equipo a Landscape:

Ahora solo queda configurar la cámara y todos sus elementos:

En el método anterior declarado se cuenta con otro método muy especial que es para detectar la cámara trasera, ya que en mi teléfono (Lumia 920) siempre me traía por defecto la cámara delantera, era necesario cambiar siempre la cámara y como esto se maneja a través de identificadores numéricos puede caerse en el error de que en otro teléfono los Id estén inversos o simplemente no existan, por lo cual se declara el siguiente método.

Lo siguiente es hacer que la cámara se encienda y comience a funcionar, posterior a esto hacer el resto de toda la lógica de negocio.

Y para esto se declara el método:

En el método de la lógica de negocio llamado TakeAndProcess lo que hace es que cada determinado tiempo tome una foto y la procese, en este caso tomará una foto cada 5 segundos que es el tiempo máximo que tiene el sistema operativo para capturar una imagen, procesarla y codificarla en algún formato específico.

En cada iteración del Dispatcher ejecutaré la lógica de negocio y tomaré la imagen:

Para el alcance de este artículo solo me limitaré a explicar el método de CreateBitmapFromCaptureElement el cual se declara de la siguiente manera, donde CapturePhotoToStreamAsync es el método encargado de tomar el frame:

Ya por último queda decir que la variable ras contiene toda la información de la imagen capturada, el cual podremos usar para almacenar en un archivo, mostrarla en un elemento Image, guardarla en una base de datos y en este caso particular, analizar los caracteres contenidos en la misma.

Y de esta manera se pueden obtener imágenes de la manera más fácil y segura en Apps Windows Phone 8.1.

Código completo:

        MediaCapture captureManager;
        private DispatcherTimer dispatcherTimer;

        public MainPage()
        {
            this.InitializeComponent();

            this.NavigationCacheMode = NavigationCacheMode.Required;

            HardwareButtons.BackPressed += HardwareButtons_BackPressed;

            this.Loaded += MainPage_Loaded;
        }

        async void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
        {
            await CleanUpCamera();
        }

        async void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            ChangeScreenState();
            CameraInitalize();
        }

        private void ChangeScreenState()
        {
            DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;
        }

        async private void StartCapture()
        {
            myCaptureElement.Source = captureManager;
            await captureManager.StartPreviewAsync();

            TakeAndProcess();
        }

        async private void CameraInitalize()
        {
            // Get back camera, every times
            var cameraID = await GetCameraID(Windows.Devices.Enumeration.Panel.Back);

            // Initialize the MediaCapture element
            captureManager = new MediaCapture();

            // Setting up the MediaCapture, no support Audio
            // because this app is to use for capture the video not the audio
            await captureManager.InitializeAsync(new MediaCaptureInitializationSettings
            {
                StreamingCaptureMode = StreamingCaptureMode.Video,
                PhotoCaptureSource = PhotoCaptureSource.VideoPreview,
                AudioDeviceId = string.Empty,
                VideoDeviceId = cameraID.Id
            });            

            // To set the Auto Focus 
            captureManager.VideoDeviceController.Focus.TrySetAuto(true);

            // To set the Auto Brightness
            captureManager.VideoDeviceController.Brightness.TrySetAuto(true);

            // To set the Auto Contrast
            captureManager.VideoDeviceController.Contrast.TrySetAuto(true);

            // To turn off the flash control, affect the result with OCR Analysis
            captureManager.VideoDeviceController.FlashControl.Auto = false;

            captureManager.VideoDeviceController.PhotoConfirmationControl.Enabled = true;

            // Try to get the maximum resolution on device
            var maxResolution = captureManager.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo)
                .Aggregate((i1, i2) => 
                    (i1 as VideoEncodingProperties).Width > (i2 as VideoEncodingProperties).Width ? i1 : i2);
            
            // Setting up the max resolution
            await captureManager.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, maxResolution);

            // Initiate to get frames
            StartCapture();
        }

        private static async Task<DeviceInformation> GetCameraID(Windows.Devices.Enumeration.Panel desiredCamera)
        {
            // get available devices for capturing pictures
            DeviceInformation deviceID = (await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture))
                .FirstOrDefault(x => x.EnclosureLocation != null && x.EnclosureLocation.Panel == desiredCamera);

            if (deviceID != null) return deviceID;
            else throw new Exception(string.Format("Camera of type {0} doesn't exist.", desiredCamera));
        }

        private async Task CleanUpCamera()
        {
            await captureManager.StopPreviewAsync();
            myCaptureElement.Source = null;
            captureManager.Dispose();
        }

        private async void TakeAndProcess()
        {
            dispatcherTimer = new DispatcherTimer();
            dispatcherTimer.Tick += dispatcherTimer_Tick;
            dispatcherTimer.Interval = new TimeSpan(0, 0, 5);
            dispatcherTimer.Start();
        }

        async void dispatcherTimer_Tick(object sender, object e)
        {
            
            string result = await CreateBitmapFromCaptureElement();
            // 
            // Update the UI thread by using the UI core dispatcher.
            // 
            await Dispatcher.RunAsync(CoreDispatcherPriority.High,
                () =>
                {
                    OcrText.Text = result;
                });
        }

        private async Task<string> CreateBitmapFromCaptureElement()
        {
            
            ImageEncodingProperties properties = ImageEncodingProperties.CreateJpeg();
            // Setting up dimensions
            properties.Height = 720;
            properties.Width = 1280;

            try
            {
                using (IRandomAccessStream ras = new InMemoryRandomAccessStream())
                {
                    await this.captureManager.CapturePhotoToStreamAsync(properties, ras);
                    await ras.FlushAsync();

                    ras.Seek(0);

                    return await ReadText(ras);
                }
            }
            catch (Exception)
            {
                CleanUpCamera();
            }
            return String.Empty;
        }

Un comentario en “Windows Phone 8.1: Cómo usar la cámara, primeros pasos

Los comentarios están cerrados.