Software de Procesamiento de Imágenes y Video Introducción Para poder implementar el procesamiento de imágenes en el CPSI, nos vimos en la necesidad de diseñar un software escalable que permitiese incorporar nuevas herramientas para aplicaciones futuras. El software se diseño a través del concepto de solución que brinda la plataforma .NET . La solución está compuesta por los siguientes módulos: reproducción de video, preprocesamiento de video, procesamiento de imágenes. Descripción de los módulos Reproducción de video: este módulo nos permite visualizar el archivo de video obtenido a través de la adquisición de datos. A su vez, permite variar las características de la reproducción como ser el tamaño y la velocidad. Preprocesamiento de video: este módulo es el encargado de brindar información acerca del video (ancho, alto, cantidad de cuadros, frame rate) y permitir la extracción de los cuadros en imágenes. A su vez, ofrece la opción de visualizar cada uno de los cuadros en forma individual y realizar un zoom sobre cada uno de ellos. Procesamiento de imágenes: este módulo nos permite visualizar cada una de las imágenes extraídas por el módulo anterior y modificar sus características. En general, las imágenes a procesar se encuentran en escala de grises o pueden ser llevadas a esta característica como así también lograr el efecto de pixelado. Implementación del módulo de reproducción de video Este módulo está basado en un ejemplo de Daniel Strigl (programador) donde se le adiciono las opciones de controlar la velocidad y el tamaño de la reproducción. Para la realización del módulo se utilizó DirectShow. DirectShow es una interface que permite la captura y reproducción de video y sonido de alta calidad. Soporta una gran cantidad de formatos incluyendo mpg, avi, mp3 y wav entre otros. Funciones de apertura y cierre clipFileFilters es utilizado para obtener a través de un filtro solamente archivos de video. ClipFile es un string que almacena el nombre del archivo que se desea abrir. UpdatePlayBackMenu es una función que una vez abierto el archivo, habilita el uso de las distintas acciones, como ser el tamaño o la velocidad de reproducción del video. UpdateMainTitle es una función que muestra el nombre del archivo que se esta ejecutando. private void archivoAbrir_Click(object sender, System.EventArgs e) { OpenFileDialog af = new OpenFileDialog(); af.Title = "Abrir archivo de video ..."; af.InitialDirectory = AppDomain.CurrentDomain.BaseDirectory; af.Filter = clipFileFilters; if( af.ShowDialog() != DialogResult.OK ) return; archivoCerrar_Click( null, null ); clipFile = af.FileName; if( ! PlayClip() ) archivoCerrar_Click( null, null ); UpdatePlaybackMenu(); UpdateMainTitle(); } private void archivoCerrar_Click(object sender, System.EventArgs e) { clipFile = null; clipType = ClipType.None; CloseInterfaces(); UpdatePlaybackMenu(); UpdateMainTitle(); InitPlayerWindow(); this.Refresh(); } Funciones de reproducción private void controlesReproducirPausa_Click(object sender, System.EventArgs e) { if( mediaCtrl == null ) return; if( (playState == PlayState.Paused) || (playState == PlayState.Stopped) ) { if( mediaCtrl.Run() == 0 ) playState = PlayState.Running; } else if( playState == PlayState.Running ) { if( mediaCtrl.Pause() == 0 ) playState = PlayState.Paused; } UpdateMainTitle(); } private void controlesDetener_Click(object sender, System.EventArgs e) { if( (mediaCtrl == null) || (mediaSeek == null) ) return; if( (playState != PlayState.Paused) && (playState != PlayState.Running) ) return; int hr = mediaCtrl.Stop(); playState = PlayState.Stopped; DsOptInt64 pos = new DsOptInt64( 0 ); hr = mediaSeek.SetPositions( pos, SeekingFlags.AbsolutePositioning, null, SeekingFlags.NoPositioning ); hr = mediaCtrl.Pause(); UpdateMainTitle(); } Implementación del módulo de preprocesamiento de video Este módulo es el encargado de procesar el archivo de video informando sobre sus características. A su vez permite convertir el video en n imágenes. Para lo cual, se debe seleccionar una carpeta de almacenamiento de las imágenes y especificar si la extracción será total o parcial. Funciones de apertura y preprocesamiento A través de estas funciones seleccionamos el archivo de video haciendo uso de la función GetFileName. Luego se informa sobre las características del video y permite la visualización de los frames. La función GetFileName hace uso de una función auxiliar para obtener el path del archivo que seleccionamos. private void seleccionarVideo_Click(object sender, System.EventArgs e) { nombreAvi.Text =""; String fileName = GetFileName("Archivos de video (*.avi)|*.avi"); if(fileName != null) nombreAvi.Text = fileName; informacion(); verCuadros(); } private String GetFileName(String filter) { OpenFileDialog dlg = new OpenFileDialog(); dlg.Filter = filter; dlg.RestoreDirectory = true; if(nombreAvi.Text.Length > 0) { dlg.InitialDirectory = GetCurrentFilePath(); } if(dlg.ShowDialog(this) == DialogResult.OK) { return dlg.FileName; } else { return null; } } private String GetCurrentFilePath() { return nombreAvi.Text.Substring(0, ombreAvi.Text.LastIndexOf("\\")+1); } Funciones de extracción total y parcial Estas funciones basan su funcionamiento en una clase llamada AviManager que permite manipular los archivos de video. La clase mencionada permite llevar el archivo de video a un stream. Para lo cual se debe abrir un stream, trabajar con él, es decir obtener su información, realizar la extracción de imágenes y luego cerrarlo. private void seleccionarCarpeta_Click(object sender, System.EventArgs e) { string folderName; FolderBrowserDialog dlg = new FolderBrowserDialog(); if( dlg.ShowDialog(this) == DialogResult.OK ) { folderName = dlg.SelectedPath + "\\" ; extraerFrame(folderName); } } private void extraerFrame(string folder) { AviManager aviManager = new AviManager(nombreAvi.Text, true); VideoStream stream = aviManager.GetVideoStream(); stream.GetFrameOpen(); String path = @folder; for(int n=0; n<stream.CountFrames; n++) { stream.ExportBitmap(n, path+n.ToString()+".bmp"); } stream.GetFrameClose(); aviManager.Close(); } Las funciones de extracción total y parcial se diferencian ya que en esta última se permite el ingreso de un intervalo. private void extraerFrameParcial(string nombreAvi, string folder, int comenzar, int finalizar) { AviManager aviManager = new AviManager(nombreAvi, true); VideoStream stream = aviManager.GetVideoStream(); stream.GetFrameOpen(); String path = @folder; for(int i=comenzar; i<= finalizar; i++) { stream.ExportBitmap(i, path+i.ToString()+".bmp"); comenzar++; } stream.GetFrameClose(); aviManager.Close(); } Implementación del módulo de visualización de imágenes Este módulo permite la visualización de las imágenes extraídas previamente por el módulo de preprocesamiento de video. Cuenta con la posibilidad de manipular imágenes desde su apertura, pasando por una transformación a escala de grises, regulación de brillo, contraste, guardar el archivo modificado, etc. Función de apertura El archivo abierto se implementa como un bitmap. A través de AutoScrollMinSize se regulará el tamaño de la imagen y con el método on paint se implementará el zoom. private void ArchivoAbrir_Click(object sender, System.EventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.InitialDirectory = "c:\\" ; openFileDialog.Filter = "Bitmap (*.bmp)|*.bmp|Jpeg (*.jpg)|*.jpg|Todos los archivos|*.*"; openFileDialog.FilterIndex = 2 ; openFileDialog.RestoreDirectory = true ; if(DialogResult.OK == openFileDialog.ShowDialog()) { m_Bitmap = (Bitmap)Bitmap.FromFile(openFileDialog.FileName, false); this.AutoScroll = true; this.AutoScrollMinSize = new Size ((int)(m_Bitmap.Width * Zoom), (int)(m_Bitmap.Height * Zoom)); this.Invalidate(); } } Función de zoom private void OnZoom200(object sender, System.EventArgs e) { Zoom = 2.0; this.AutoScrollMinSize = new Size ((int)(m_Bitmap.Width * Zoom), (int)(m_Bitmap.Height * Zoom)); this.Invalidate(); } protected override void OnPaint (PaintEventArgs e) { Graphics g = e.Graphics; g.DrawImage(m_Bitmap, new Rectangle(this.AutoScrollPosition.X, this.AutoScrollPosition.Y, (int)(m_Bitmap.Width*Zoom), (int)(m_Bitmap.Height * Zoom))); } A través de la clase BitmapFilter, se implementan las funciones de escala de grises, contraste, brillo, etc. Esta clase trabaja con punteros para lo cual debemos utilizar el tipo de directiva unsafe. Para trabajar con la imagen, debemos utilizar los métodos LockBits y UnlockBits. BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); Bitmap.LockBits es un método que bloquea un objeto Bitmap en la memoria del sistema. Parámetros rect : Estructura Rectangle que especifica la parte del objeto Bitmap que se va a bloquear. flags : Enumeración ImageLockMode que especifica el nivel de acceso (lectura y escritura) para el objeto Bitmap. format : Enumeración PixelFormat que especifica el formato de datos de este objeto Bitmap. Valor devuelto Objeto BitmapData que contiene información sobre esta operación de bloqueo. Comentarios El objeto BitmapData especifica los atributos del objeto Bitmap, como el tamaño, formato de píxeles, la dirección de inicio de los datos de píxeles en la memoria y el largo de cada línea de exploración (paso). Función escala de grises private void EfectosGrises_Click(object sender, System.EventArgs e) { m_Undo = (Bitmap)m_Bitmap.Clone(); if(BitmapFilter.GrayScale(m_Bitmap)) this.Invalidate(); }