public void LearningTest() { MathTextBitmap bitmap = new MathTextBitmap(new FloatBitmap(5,5), new Gdk.Point(0,0)); bitmap.ProcessImage(new List<BitmapProcesses.BitmapProcess>()); CharacteristicTreeDatabase database = new CharacteristicTreeDatabase(); MathSymbol aSymbol = new MathSymbol("a"); MathSymbol bSymbol = new MathSymbol("b"); database.Learn(bitmap, aSymbol); List<MathSymbol> symbols = database.Match(bitmap); bool a1Learned = symbols.Count == 1 && symbols[0] == aSymbol; bool a2Learned = true; a2Learned = database.Learn(bitmap, aSymbol); database.Learn(bitmap, bSymbol); symbols = database.Match(bitmap); bool b1Learned = symbols.Count == 2; Assert.IsTrue(a1Learned, "Fallo el aprender la primera a"); Assert.IsFalse(a2Learned, "No se detecto el conflicto de la segunda a"); Assert.IsTrue(b1Learned, "Fallo el aprender la b"); }
/// <summary> /// <c>SegmentedNode</c>'s constructor /// </summary> /// <param name="bitmap"> /// A <see cref="MathTextBitmap"/> /// </param> /// <param name="view"> /// The parent node's <c>NodeView</c>. /// </param> public SegmentedNode(MathTextBitmap bitmap, NodeView view) { this.view = view; this.bitmap =bitmap; this.position = String.Format("({0}, {1})", bitmap.Position.X, bitmap.Position.Y); }
public override bool Apply (FloatBitmap image) { MathTextBitmap bitmap = new MathTextBitmap(image, new Gdk.Point(0,0)); foreach (BitmapSegmenter segmenter in segmenters) { if(segmenter.Segment(bitmap).Count>1) { return true; } } return false; }
/// <summary> /// Creates the projection /// </summary> /// <param name="image"> /// The <see cref="MathTextBitmap"/> to be projected. /// </param> protected override void CreateProjection(MathTextBitmap image) { Values=new int [image.Width]; for(int i=0;i<image.Width;i++) { for(int j=0;j<image.Height;j++) { if(image[i,j]!=FloatBitmap.White) { Values[i]++; } } } }
/// <summary> /// El constructor de <code>FormulaNode</code> /// </summary> /// <param name="name"> /// El texto que aparecera en el nodo. /// </param> /// <param name="bitmap"> /// El objeto <code>MathTextBitmap</code> asociado al nodo. /// </param> /// <param name="view"> /// El árbol al que añadimos el nodo. /// </param> public SegmentedNode(string name, MathTextBitmap bitmap, NodeView view) : base() { this.name=name; this.label=""; this.bitmap=bitmap; position = String.Format("({0}, {1})", bitmap.Position.X, bitmap.Position.Y); this.view = view; this.symbols = new List<MathSymbol>(); }
/// <summary> /// Creates BitmapProjection instances. /// </summary> /// <param name="mode"> /// The <see cref="ProjectionMode"/> used for the projection. /// </param> /// <param name="mtb"> /// The <see cref="MathTextBitmap"/> to be projected. /// </param> /// <returns> /// The <see cref="BitmapProjection"/> created. /// </returns> public static BitmapProjection CreateProjection(ProjectionMode mode, MathTextBitmap mtb) { BitmapProjection res=null; switch(mode) { case(ProjectionMode.Horizontal): res=new HorizontalBitmapProjection(mtb); break; case(ProjectionMode.Vertical): res=new VerticalBitmapProjection(mtb); break; default: throw new ArgumentException( "No puede usar None para crear una nueva projección"); } return res; }
/// <summary> /// Con este metodos almacenamos un nuevo simbolo en la base de /// datos. /// /// Lanza <c>DuplicateSymbolException</c> si ya existe un simbolo /// con la misma etiqueta y caracteristicas binarias en la base de datos. /// </summary> /// <param name="bitmap"> /// La imagen cuyas caracteristicas aprenderemos. /// </param> /// <param name="symbol"> /// El simbolo que representa a la imagen. ///</param> public override bool Learn(MathTextBitmap bitmap,MathSymbol symbol) { if(characteristics == null) characteristics=CharacteristicFactory.CreateCharacteristicList(); CharacteristicNode node=rootNode; bool characteristicValue; FloatBitmap processedBitmap = bitmap.LastProcessedImage; // Recorremos las caracteristicas, y vamos creando el arbol segun // vamos necesitando nodos. foreach(BinaryCharacteristic bc in characteristics) { if(characteristicValue=bc.Apply(processedBitmap)) { if(node.TrueTree==null) { node.TrueTree=new CharacteristicNode(); } node=node.TrueTree; } else { if(node.FalseTree==null) { node.FalseTree=new CharacteristicNode(); } node=node.FalseTree; } StepDoneArgs a = CreateStepDoneArgs(bc,characteristicValue,null); this.StepDoneInvoker(a); } return node.AddSymbol(symbol); }
/// <summary> /// Este metodo realiza la descomposicion de la imagen descomponiendola por /// los bordes de los huecos presentes en la proyeccion. /// Distintas implementaciones difieren en si se eliminan alguno de estos huecos /// de forma que se agrupan ciertas subimagenes para ser tratadas como una sola. /// </summary> /// <param name="image">La imagen que deseamos descomponer.</param> /// <returns> /// Un array con las distintas partes en que hemos descompuesto /// la imagen de entrada. /// </returns> public override List<MathTextBitmap> Segment(MathTextBitmap image) { //Creamos la proyeccion BitmapProjection proj=BitmapProjection.CreateProjection(mode,image); List<Hole> holes=proj.Holes; //Aqui decidimos a partir de que tamao de hueco vamos a cortar la imagen int threshold = GetImageCutThreshold(holes); //Eliminamos los huecos que tengan menor tamaño que el umbral int i=1; while(i<holes.Count-1){ if(((Hole)holes[i]).Size<threshold){ holes.Remove(holes[i]); }else{ i++; } } return ImageCut(image,holes); }
/// <summary> /// Sets the image to be processed. /// </summary> /// <param name="image"> /// The initial image's path. /// </param> private void SetInitialImage(string filename) { segmentationStore.Clear(); Gdk.Pixbuf imageOriginal = new Gdk.Pixbuf(filename); originalImageArea.Image=imageOriginal; // Generamos el MaxtTextBitmap inicial, y lo añadimos como // un nodo al arbol. MathTextBitmap mtb = new MathTextBitmap(imageOriginal); SegmentedNode node = new SegmentedNode(System.IO.Path.GetFileNameWithoutExtension(filename), mtb, null); segmentationStore.AddNode(node); ocrController.StartNode = node; Log("¡Archivo de imagen «{0}» cargado correctamente!", filename); unassistedProcessBtn.Sensitive = true; }
/// <summary> /// Con este metodos almacenamos un nuevo simbolo en la base de /// datos. /// </summary> /// <param name="bitmap"> /// La imagen que aprenderemos. /// </param> /// <param name="symbol"> /// El simbolo que representa a la imagen. /// </param> /// <returns> /// If the symbol was learned or there was a conflict. /// </returns> public abstract bool Learn(MathTextBitmap bitmap,MathSymbol symbol);
internal HorizontalBitmapProjection(MathTextBitmap image) : base(image) { }
internal VerticalBitmapProjection(MathTextBitmap image) : base(image) { }
/// <summary> /// Segmenta una imagen. /// </summary> /// <param name="image"> /// La imagen a segmentar. /// </param> /// <returns> /// Una lista con las imagenes que se han obtenido. /// </returns> private List<MathTextBitmap> CreateChildren(MathTextBitmap image) { List<MathTextBitmap> children = new List<MathTextBitmap>(); bool segmented = false; for(int i=0; i< segmenters.Count && !segmented; i++) { // Intentamos segmentar. children = segmenters[i].Segment(image); if(children.Count > 1) segmented = true; else if(children.Count == 1) { throw new Exception("MathTextBitmap incorrectly segmented"); } } return children; }
/// <summary> /// El metodo que sera invocado para intentar descomponer /// una imagen. /// </summary> /// <param name="mtb">La imagen que queremos descomponer.</param> /// <returns> /// Un array con las distintas partes en que se /// ha descompuesto la imagen. /// </returns> public abstract List<MathTextBitmap> Segment(MathTextBitmap mtb);
/// <summary> /// Este metodo intenta recuperar los simbolos de la base de datos /// correspondiente a una imagen. /// </summary> /// <param name="image"> /// La imagen cuyos simbolos asociados queremos encontrar. /// </param> /// <returns> /// Los simbolos que se puedieron asociar con la imagen. /// </returns> public override List<MathSymbol> Match(MathTextBitmap image) { if(characteristics == null) characteristics=CharacteristicFactory.CreateCharacteristicList(); List<MathSymbol> res = new List<MathSymbol>(); CharacteristicNode nodo=rootNode; BinaryCharacteristic bc; bool exists=true; bool characteristicValue; List<bool> vector=new List<bool>(); FloatBitmap processedImage = image.LastProcessedImage; for(int i=0;i<characteristics.Count && exists;i++) { bc=(BinaryCharacteristic)(characteristics[i]); if(characteristicValue=bc.Apply(processedImage)) { if(nodo.TrueTree==null) { exists=false; } nodo=nodo.TrueTree; } else { if(nodo.FalseTree==null) { exists=false; } nodo=nodo.FalseTree; } StepDoneArgs args; //Avisamos de que hemos dado un paso if(nodo!=null) { args =CreateStepDoneArgs(bc, characteristicValue, nodo.ChildrenSymbols); } else { args =CreateStepDoneArgs(bc, characteristicValue, null); } StepDoneInvoker(args); } if(exists) { res=nodo.Symbols; } return res; }
/// <summary> /// Establece la imagen inicial para segmentar y reconocer sus /// caracteres. /// </summary> /// <param name="image"> /// La imagen inicial. /// </param> private void SetInitialImage(string filename) { imageOriginal = new Pixbuf(filename); imageAreaOriginal.Image=imageOriginal; store.Clear(); // Generamos el MaxtTextBitmap inical, y lo añadimos como // un nodo al arbol. MathTextBitmap mtb = new MathTextBitmap(imageOriginal); SegmentedNode node = new SegmentedNode(System.IO.Path.GetFileNameWithoutExtension(filename), mtb, treeview); store.AddNode(node); rootNode = node; controller.StartNode = node; Log("¡Archivo de imagen «{0}» cargado correctamente!", filename); alignNextButtons.Sensitive=true; segmentBtn.Sensitive = true; gotoTokenizerBtn.Sensitive = false; buttonsNB.Page = 0; }
/// <summary> /// Creates the projection. /// </summary> /// <param name="image"> /// The <see cref="MathTextBitmap"/> being projected. /// </param> protected abstract void CreateProjection(MathTextBitmap image);
/// <summary> /// Metodo que obtiene las subimagenes a partir de los huecos. /// </summary> /// <param name="image">La imagen a segmentar.</param> /// <param name="holes"> /// Los huecos considerados para separar las partes de la imagen. /// </param> /// <returns>Un array con las subimagenes obtenidas al segmentar.</returns> private List<MathTextBitmap> ImageCut(MathTextBitmap image, List<Hole> holes) { List<MathTextBitmap> newBitmaps=new List<MathTextBitmap>(); int start,size; int xpos=image.Position.X; int ypos=image.Position.Y; for(int i=0;i<holes.Count-1;i++) { //El texto esta entre el final de un hueco, y el inicio del siguiente; start=((Hole)holes[i]).EndPixel; size=((Hole)holes[i+1]).StartPixel-start+1; int x0,y0; int width0, height0; if(mode == ProjectionMode.Horizontal) { x0 = start; y0 = 0; width0 = size; height0 = image.Height; } else { x0 = 0; y0 = start; width0 = image.Width; height0 = size; } // Recortamos FloatBitmap cutImage= image.FloatImage.SubImage(x0, y0, width0,height0); // Encuadramos Gdk.Point pd; Gdk.Size sd; GetEdges(cutImage, out pd, out sd); cutImage = cutImage.SubImage(pd.X, pd.Y, sd.Width,sd.Height); // Montamos el bitmap MathTextBitmap newBitmap = new MathTextBitmap(cutImage, new Point(xpos + x0 + pd.X, ypos + y0+ pd.Y)); newBitmaps.Add(newBitmap); } if (newBitmaps.Count == 1) { newBitmaps.Clear(); } return newBitmaps; }
/// <summary> /// Este metodo intenta recuperar los simbolos de la base de datos /// correspondiente a una imagen. /// </summary> /// <param name="image"> /// La imagen cuyos simbolos asociados queremos encontrar. /// </param> /// <returns> /// Los simbolos que se puedieron asociar con la imagen. /// </returns> public override List<MathSymbol> Match(MathTextBitmap image) { List<MathSymbol> res = new List<MathSymbol>(); FloatBitmap processedImage = image.LastProcessedImage; TristateCheckVector vector = CreateVector(processedImage); // We have this image vector, now we will compare with the stored ones. foreach(TristateCheckVector storedVector in symbolsDict) { // We consider a threshold. int threshold = (int)((storedVector.Length - storedVector.DontCares) * epsilon); // If the distance is below the threshold, we consider it valid. if(storedVector.Distance(vector) <= threshold) { foreach(MathSymbol symbol in storedVector.Symbols) { // We don't want duplicated symbols. if(!res.Contains(symbol)) { res.Add(symbol); } } string msg = String.Format("Distancia({0}, {1}) <= {2}", vector.ToString(), storedVector.ToString(), threshold); msg += "\nSe añadieron los símbolos almacenados."; StepDoneInvoker(new StepDoneArgs(msg)); } else { string msg = String.Format("Distancia({0}, {1}) > {2}", vector.ToString(), storedVector.ToString(), threshold); StepDoneInvoker(new StepDoneArgs(msg)); } } return res; }
/// <summary> /// This method is used to add new symbols to the database. /// </summary> /// <param name="bitmap"> /// The image containing the symbol. /// </param> /// <param name="symbol"> /// The symbol contained in the image. /// </param> public override bool Learn(MathTextBitmap bitmap,MathSymbol symbol) { FloatBitmap processedBitmap = bitmap.LastProcessedImage; TristateCheckVector newVector = CreateVector(processedBitmap); TristateCheckVector found =null; foreach (TristateCheckVector vector in symbolsDict) { if(vector.Symbols.Contains(symbol)) { found = vector; break; } } if(found ==null) { // The symbol wasnt present in the database. newVector.Symbols.Add(symbol); symbolsDict.Add(newVector); } else { // The symbol is present, we may have to retrain the database. if(newVector.Equals(found)) { // It's the same one, so there is a conflict. return false; } else { // We have to find the differnces, and change them to // don't care values. for(int i=0; i< found.Length; i++) { if(found[i] != TristateValue.DontCare && found[i] != newVector[i]) { // If the value is different from what we had learned, // then we make the vector more general in that point. found[i] = TristateValue.DontCare; } } } } return true; }
/// <summary> /// Con este metodos almacenamos un nuevo simbolo en la base de /// datos. /// /// Lanza <c>DuplicateSymbolException</c> si ya existe un simbolo /// con la misma etiqueta y caracteristicas binarias en la base de datos. /// </summary> /// <param name="bitmap"> /// La imagen cuyas caracteristicas aprenderemos. /// </param> /// <param name="symbol"> /// El simbolo que representa a la imagen. /// </param> public override bool Learn(MathTextBitmap bitmap,MathSymbol symbol) { if(characteristics == null) characteristics=CharacteristicFactory.CreateCharacteristicList(); FloatBitmap processedBitmap = bitmap.LastProcessedImage; CheckVector vector = CreateVector(processedBitmap); int position = symbolsDict.IndexOf(vector); if(position >=0) { // The vector exists in the list List<MathSymbol> symbols = symbolsDict[position].Symbols; if(symbols.Contains(symbol)) { return false; } else { symbols.Add(symbol); } } else { vector.Symbols.Add(symbol); symbolsDict.Add(vector); } return true; }
/// <summary> /// Constructor de la clase. /// </summary> /// <param name="bitmap">La imagen que se ha comenzado a reconocer.</param> public BitmapProcessedArgs(MathTextBitmap bitmap) :base() { b=bitmap; }
private void OnUnassistedProcessBtnClicked(object sender, EventArgs args) { // We try to free memory. GC.Collect(); segmentationStore.Clear(); MainRecognizerWindow.ProcessItemsSensitive = true; unassistedControlHBB.Sensitive = false; unassistedTaskNameLabel.Text = "Segmentación y OCR"; unassistedGlobalProgressBar.Fraction = 0; // We create the image to be recognized. Bitmap bitmap = handwritingArea.Bitmap; FloatBitmap floatBitmap = new FloatBitmap(bitmap.Width, bitmap.Height); for(int i=0; i< bitmap.Width; i++) { for(int j = 0; j < bitmap.Height; j++) { floatBitmap[i,j] = bitmap.GetPixel(i,j).GetBrightness(); } } // The original image is set. originalImage = floatBitmap.CreatePixbuf(); MathTextBitmap mtb = new MathTextBitmap(originalImage); SegmentedNode node = new SegmentedNode("Raíz", mtb, null); segmentationStore.AddNode(node); ocrController.StartNode = node; ocrController.SearchDatabase = false; ocrController.Databases = Config.RecognizerConfig.Instance.Databases; ocrController.Next(ControllerStepMode.UntilEnd); }
/// <summary> /// Reconoce una imagen dada una imagen. /// </summary> /// <param name="image"> /// La imagen a intentar asociar al caracter. /// </param> /// <returns> /// Los símbolos que se han podido asociar a la imagen. /// </returns> public List<MathSymbol> Recognize(MathTextBitmap image) { return database.Match(image); }
/// <summary> /// Este método intenta asignar a una imagen un simbolo segun los parametros /// almacenados para la misma. /// </summary> /// <param name="image"> /// La imagen que a la que queremos asociar un simbolo. /// </param> /// <returns> /// Una lista de simbolos cuyos parametros coinciden con los de la /// imagen. /// </returns> public override List<MathSymbol> Match (MathTextBitmap image) { return null; }
/// <summary> /// Busca una imagen en la base de datos. /// </summary> /// <param name="image"> /// La imagen que se procesará y buscará en la base de datos. /// </param> /// <returns> /// Una lista con todos los simbolos que coincidan con los parametros /// asociados a la imagen. /// </returns> public abstract List<MathSymbol> Match(MathTextBitmap image);
/// <summary> /// Metodo que se encarga de dibujar un recuadro sobre la imagen original /// para señalar la zona que estamos tratando. /// </summary> /// <param name="mtb">La subimagen que estamos tratando.</param> private void MarkImage(MathTextBitmap mtb) { Pixbuf originalMarked= imageOriginal.Copy(); // We tint the copy in red. originalMarked = originalMarked.CompositeColorSimple(originalMarked.Width, originalMarked.Height, InterpType.Bilinear, 100,1, 0xAAAAAA,0xAAAAAA); // Over the red tinted copy, we place the piece we want to be // normal. imageOriginal.CopyArea(mtb.Position.X, mtb.Position.Y, mtb.Width, mtb.Height, originalMarked, mtb.Position.X, mtb.Position.Y); imageAreaOriginal.Image=originalMarked; }
/// <summary> /// Envolvemos el lanzamiento del evento BitmapBeingRecognized, por comodidad. /// </summary> /// <param name="bitmap"> /// La imagen que hemos comenzado a reconocer, que sera enviada como /// argumentod del evento. /// </param> protected void BitmapProcessedByDatabaseInvoker(MathTextBitmap bitmap) { if(BitmapProcessedByDatabase!=null) { BitmapProcessedByDatabase(this,new BitmapProcessedArgs(bitmap)); } }
/// <summary> /// <see cref="BitmapProjection"/>'s constructor /// </summary> /// <param name="image"> /// The <see cref="MathTextBitmap"/> to be projected. /// </param> protected BitmapProjection(MathTextBitmap image) { CreateProjection(image); }
/// <summary> /// Con este metodos almacenamos un nuevo simbolo en la base de /// datos. /// /// Lanza <c>DuplicateSymbolException</c> si ya hay un simbolo con las /// mismas propiedades y etiqueta en la base de datos. /// </summary> /// <param name="bitmap"> /// La imagen que queremos añadir a la base de datos. /// </param> /// <param name="symbol"> /// El simbolo que representa a la imagen. /// </param> public bool Learn(MathTextBitmap bitmap,MathSymbol symbol) { return database.Learn(bitmap,symbol); }