private Image DrawFoundCharOnPicture(Image img, RecognizedCharData charData) { using (var db = new DirectBitmap(img.Width, img.Height)) { using (var graphics = Graphics.FromImage(db.Bitmap)) { graphics.DrawImage(img, Point.Empty); } using (var graphics = Graphics.FromImage(db.Bitmap)) { graphics.DrawImage(img, Point.Empty); } var xMax = charData.Coords.X + charData.Pattern.GetLength(0); var yMax = charData.Coords.Y + charData.Pattern.GetLength(1); var x = 0; for (var i = charData.Coords.X; i < xMax; i++, x++) { var y = 0; for (var j = charData.Coords.Y; j < yMax; j++, y++) { if (charData.Pattern[x, y]) { db.SetPixel(i, j, Color.Red); } } } return(db.ToBitmap()); } }
public RecognitionTrainingForm(RecognizedCharData charData, bool[,] image) { InitializeComponent(); DrawPattern(charData.Pattern); Image adjustedOriginalPicture = DrawFoundCharOnPicture(image, charData); pictureBox2.Image = adjustedOriginalPicture; _selectedText = string.Empty; }
private string AddNewPattern(RecognizedCharData sym, string manualChar, bool[,] curPattern) { var pat = Texts.FirstOrDefault(x => x.Text == manualChar); if (pat != null) { pat.Patterns.Add(curPattern); } else { Texts.Add(sym.ToCharData(manualChar)); } Save?.Invoke(); return(manualChar); }
private Image DrawFoundCharOnPicture(bool[,] image, RecognizedCharData charData) { var w = image.GetLength(0); var h = image.GetLength(1); var charWidth = charData.Pattern.GetLength(0); var charYMax = charData.Coords.Y + charData.Pattern.GetLength(1); using (var db = new DirectBitmap(w, h)) { for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (image[x, y]) { db.Bits[y * w + x] = -1; // white } else { db.Bits[y * w + x] = -16777216; // black } } if (y >= charData.Coords.Y && y < charYMax) { var charY = y - charData.Coords.Y; for (int x = 0; x < charWidth; x++) { if (charData.Pattern[x, charY]) { db.Bits[y * w + x + charData.Coords.X] = -65536; // red } } } } return(db.ToBitmap()); } }
private static void SplitIn2Chars(List <RecognizedCharData> ret, RecognizedCharData charData) { var xSize = charData.Pattern.GetLength(0); var maxX = xSize / 2; var maxY = charData.Pattern.GetLength(1); var c1 = new bool[maxX, maxY]; var c2 = new bool[maxX, maxY]; for (int x = 0; x < maxX; x++) { for (int y = 0; y < maxY; y++) { c1[x, y] = charData.Pattern[x, y]; } } var start = xSize - maxX; for (int x = xSize - maxX; x < xSize; x++) { for (int y = 0; y < maxY; y++) { c2[x - start, y] = charData.Pattern[x, y]; } } ret.Add(new RecognizedCharData(charData.Coords.X, charData.Coords.Y, (byte)charData.Coords.Y) { Pattern = c1 }); ret.Add(new RecognizedCharData(start, charData.Coords.Y, (byte)charData.Coords.Y) { Pattern = c2 }); }
private static void SplitIn2Chars(List <RecognizedCharData> ret, RecognizedCharData charData) { var xSize = charData.Pattern.GetLength(0); var maxX = xSize / 2; var maxY = charData.Pattern.GetLength(1); var c1 = new bool[maxX, maxY]; var c2 = new bool[maxX, maxY]; for (int i = 0; i < maxX; i++) { for (int j = 0; j < maxY; j++) { c1[i, j] = charData.Pattern[i, j]; } } var start = xSize - maxX; for (int i = xSize - maxX; i < xSize; i++) { for (int j = 0; j < maxY; j++) { c2[i - start, j] = charData.Pattern[i, j]; } } ret.Add(new RecognizedCharData(charData.Coords.X, charData.Coords.Y) { Pattern = c1 }); ret.Add(new RecognizedCharData(start, charData.Coords.Y) { Pattern = c2 }); }
/// <summary> /// /// </summary> /// <param name="sym"></param> /// <param name="image">Used to display the context when entering a character.</param> /// <param name="tolerance"></param> /// <param name="onlyNumbers"></param> /// <returns></returns> public string FindMatchingChar(RecognizedCharData sym, bool[,] image, float tolerance = 0.3f, bool onlyNumbers = false) { var recognizedPattern = new Pattern(sym.Pattern, sym.YOffset); //// debug //Boolean2DimArrayConverter.ToDebugLog(recognizedPattern.Data); //var letterWasSortedOutByAperture = false; //string aperturesString = string.Empty; //for (int i = 0; i < 8; i++) //{ // if (i % 2 == 0) // aperturesString += "|"; // aperturesString += ((recognizedPattern.Apertures >> i) & 1) == 1 ? "▬" : " "; //} //Debug.WriteLine($"Apertures: {aperturesString}"); var widthRecognized = recognizedPattern.Width; var heightRecognized = recognizedPattern.Height; var recognizedHeightWithOffset = heightRecognized + sym.Coords.Y; float bestMatchDifference = float.MaxValue; string bestMatch = null; int maxAllowedSet = (int)(recognizedPattern.SetPixels * (1 + tolerance)) + 1; int minAllowedSet = (int)(recognizedPattern.SetPixels * (1 - tolerance)); foreach (var c in Texts) { // if only numbers are expected, skip non numerical patterns if (onlyNumbers && !OnlyNumbersChars.Contains(c.Text)) { continue; } foreach (var pattern in c.Patterns) { if (HammingWeight.SetBitCount((byte)(pattern.Apertures ^ recognizedPattern.Apertures)) > 1 || Math.Abs(pattern.YOffset - recognizedPattern.YOffset) > 3 ) { continue; } //var currentLetterWasSortedOutByAperture = pattern.Apertures != recognizedPattern.Apertures; int minWidth = Math.Min(pattern.Width, widthRecognized); int maxWidth = Math.Max(pattern.Width, widthRecognized); var widthDiff = maxWidth - minWidth; if (pattern.SetPixels > maxAllowedSet || pattern.SetPixels < minAllowedSet || (widthDiff > 2 && widthDiff > maxWidth * 0.2)) { continue; // if dimensions is too different ignore pattern } int minHeight = Math.Min(pattern.Height, heightRecognized); int maxHeight = Math.Max(pattern.Height, heightRecognized); var heightDiff = maxHeight - minHeight; if (heightDiff > 2 && heightDiff > maxHeight * 0.2) { continue; } var allowedDifference = pattern.SetPixels * 2 * tolerance; // Attempted to do offset shifting here but got too many false recognitions here, might need some tweaking. //var minOffsetX = xSizeFound > 2 ? -1 : 0; //var maxOffsetX = xSizeFound > 2 ? 1 : 0; //var minOffsetY = xSizeFound > 2 ? -1 : 0; //var maxOffsetY = xSizeFound > 2 ? 1 : 0; //for (var offSetX = minOffsetX; offSetX <= maxOffsetX; offSetX++) //{ // for (var offSetY = minOffsetY; offSetY <= maxOffsetY; offSetY++) // { var dif = 0f; var fail = false; // y offset. Small character at the baseline like dots have mostly empty pixels. var yStart = Math.Min(sym.Coords.Y, pattern.YOffset); var patternHeightWithOffset = pattern.Height + pattern.YOffset; Pattern overlappingPattern; // the pattern that is more than 1 px larger than the other. testing that margin is simpler. int widthTesting; if (widthRecognized > pattern.Width) { widthTesting = widthRecognized; overlappingPattern = recognizedPattern; } else { widthTesting = pattern.Width; overlappingPattern = pattern; } var widthMinPlusOne = Math.Min(widthRecognized, pattern.Width) + 1; // pixels too far outside of the narrower pattern never have a match or a possible neighbor in the other one, they can be sorted out fast if (widthTesting - widthMinPlusOne > 0) { for (int y = 0; !fail && y < overlappingPattern.Height; y++) { for (var x = widthMinPlusOne; x < widthTesting; x++) { if (overlappingPattern[x, y]) { dif += 1; if (dif > allowedDifference) { fail = true; break; } } } } } for (var y = yStart; !fail && y < recognizedHeightWithOffset && y < patternHeightWithOffset; y++) { //var curPatternY = y;// + offSetY; var patternYIndex = y - pattern.YOffset; var recognizedYIndex = y - sym.Coords.Y; for (var x = 0; x < widthMinPlusOne; x++) { //var curPatternX = x;// + offSetX; //if (curPatternX < 0 || curPatternY < 0) continue; //if (y >= heightRecognized || x >= widthRecognized) continue; var cHave = recognizedYIndex >= 0 && x < widthRecognized && recognizedPattern[x, recognizedYIndex]; var pHave = patternYIndex >= 0 && x < pattern.Width && pattern[x, patternYIndex]; // if the bits are different, check if the total number of different bits is too large for a match and if to ignore this pattern if (cHave != pHave) { // tolerance of difference if a nearby bit is equal dif += IsNearby(cHave ? pattern.Data : recognizedPattern.Data, x, cHave ? patternYIndex : recognizedYIndex) ? 0.4f : 1f; if (dif > allowedDifference) { fail = true; break; } } } } if (!fail && bestMatchDifference > dif) { if (dif == 0) { //Debug.WriteLine($"matched with {c.Text} (dif: {dif})"); //if (currentLetterWasSortedOutByAperture) // Debug.WriteLine("Would have been sorted out by Aperture"); return(c.Text); // there is no better match } //letterWasSortedOutByAperture = currentLetterWasSortedOutByAperture; bestMatchDifference = dif; bestMatch = c.Text; } // } //} } } if (!string.IsNullOrEmpty(bestMatch)) { //Debug.WriteLine($"matched with {bestMatch} (dif: {bestMatchDifference})"); //if (letterWasSortedOutByAperture) // Debug.WriteLine("Would have been sorted out by Aperture"); return(bestMatch); } // no match was found if (!TrainingSettings.IsTrainingEnabled) { return("�"); //string.Empty; } var manualChar = new RecognitionTrainingForm(sym, image).Prompt(); if (string.IsNullOrEmpty(manualChar)) { return(manualChar); } return(AddNewPattern(recognizedPattern, manualChar)); }
public string FindMatchingChar(RecognizedCharData sym, Image originalImg, float tolerance = 0.15f, bool onlyNumbers = false) { var curPattern = sym.Pattern; var xSizeFound = curPattern.GetLength(0); var ySizeFound = curPattern.GetLength(1); float bestMatchDifference = float.MaxValue; string bestMatch = null; foreach (var c in Texts) { // if only numbers are expected, skip non numerical patterns if (onlyNumbers && !"0123456789.,%/:LEVEL".Contains(c.Text)) { continue; } foreach (var pattern in c.Patterns) { int minWidth = Math.Min(pattern.Width, ySizeFound); int maxWidth = Math.Max(pattern.Width, ySizeFound); if (maxWidth - minWidth > 3 && (maxWidth >> 1) > minWidth) { continue; // if width is too different ignore pattern } var possibleDif = ((pattern.Length + sym.Pattern.Length) / 2) * tolerance; if (Math.Abs(pattern.Length - curPattern.Length) > possibleDif) { continue; } possibleDif = pattern.SetPixels * 4 * tolerance; // Attempted to do offset shifting here but got too many false recognitions here, might need some tweaking. //var minOffsetX = xSizeFound > 2 ? -1 : 0; //var maxOffsetX = xSizeFound > 2 ? 1 : 0; //var minOffsetY = xSizeFound > 2 ? -1 : 0; //var maxOffsetY = xSizeFound > 2 ? 1 : 0; //for (var offSetX = minOffsetX; offSetX <= maxOffsetX; offSetX++) //{ // for (var offSetY = minOffsetY; offSetY <= maxOffsetY; offSetY++) // { var dif = 0f; var fail = false; // TODO sort out small recognized patterns that would match 100 % of their size with a lot of patterns, e.g. dots for (var x = 0; !fail && x < xSizeFound && x < pattern.Width; x++) { for (var y = 0; y < ySizeFound && y < pattern.Height; y++) { var curPatternX = x; // + offSetX; var curPatternY = y; // + offSetY; if (curPatternX >= 0 && curPatternY >= 0 && curPatternY < ySizeFound && curPatternX < xSizeFound) { var cHave = curPattern[curPatternX, curPatternY]; var pHave = pattern[x, y]; // if the bits are different, check if the total number of different bits is too large for a match and if to ignore this pattern if (cHave != pHave) { // tolerance of difference if a nearby bit is equal dif += IsNearby(cHave ? pattern.Data : curPattern, x, y) ? 0.4f : 1f; if (dif > possibleDif) { fail = true; break; } } } } } if (!fail && bestMatchDifference > dif) { if (dif == 0) { return(c.Text); // there is no better match } bestMatchDifference = dif; bestMatch = c.Text; } // } //} } } if (!string.IsNullOrEmpty(bestMatch)) { return(bestMatch); } // no match was found if (!TrainingSettings.IsTrainingEnabled) { return("�"); //string.Empty; } var manualChar = new RecognitionTrainingForm(sym, originalImg).Prompt(); if (string.IsNullOrEmpty(manualChar)) { return(manualChar); } return(AddNewPattern(sym, manualChar, curPattern)); }