/// <summary> /// Calculates the match between the test-array and the templateArray, represented in a float from 0 to 1. /// </summary> /// <param name="test">The array to test</param> /// <param name="templateArray">The existing template to which the test will be compared.</param> /// <param name="match">match, 0 no equal pixels, 1 all pixels are identical.</param> /// <param name="offset">0 no shift. 1 the test is shifted one pixel to the right. -1 the test is shifted one pixel to the left.</param> public static void letterMatch(uint[] test, uint[] templateArray, out float match, out int offset) { match = 0; offset = 0; // test letter and also shifted by one pixel (offset) for (int currentOffset = -1; currentOffset < 2; currentOffset++) { int testOffset = currentOffset > 0 ? currentOffset : 0; int templateOffset = currentOffset < 0 ? -currentOffset : 0; uint HammingDiff = 0; int maxTestRange = Math.Min(test.Length, templateArray.Length); for (int y = 1; y < maxTestRange; y++) { HammingDiff += HammingWeight.HWeight((test[y] << testOffset) ^ templateArray[y] << templateOffset); } if (test.Length > templateArray.Length) { for (int y = maxTestRange; y < test.Length; y++) { HammingDiff += HammingWeight.HWeight((test[y] << testOffset)); } } else if (test.Length < templateArray.Length) { for (int y = maxTestRange; y < templateArray.Length; y++) { HammingDiff += HammingWeight.HWeight(templateArray[y] << templateOffset); } } long total = (Math.Max(test.Length, templateArray.Length) - 1) * Math.Max(test[0], templateArray[0]); float newMatch; if (total > 10) { newMatch = (float)(total - HammingDiff) / total; } else { newMatch = 1 - HammingDiff / 10f; } if (newMatch > match) { match = newMatch; offset = currentOffset; } } }
public double[] DoOcr(out string OcrText, out string dinoName, out string species, out string ownerName, out string tribeName, out Sex sex, string useImageFilePath = "", bool changeForegroundWindow = true) { string finishedText = string.Empty; dinoName = string.Empty; species = string.Empty; ownerName = string.Empty; tribeName = string.Empty; sex = Sex.Unknown; double[] finalValues = { 0 }; if (ocrConfig == null) { OcrText = "Error: OCR not configured"; return(finalValues); } Bitmap screenShotBmp; _ocrControl.debugPanel.Controls.Clear(); _ocrControl.ClearLists(); if (File.Exists(useImageFilePath)) { screenShotBmp = (Bitmap)Image.FromFile(useImageFilePath); } else { // grab screen shot from ark screenShotBmp = Win32API.GetScreenshotOfProcess(screenCaptureApplicationName, waitBeforeScreenCapture, true); } if (screenShotBmp == null) { OcrText = "Error: no image for OCR. Is ARK running?"; return(finalValues); } if (!CheckResolutionSupportedByOcr(screenShotBmp)) { OcrText = "Error while calibrating: The game-resolution is not supported by the currently loaded OCR-configuration.\n" + $"The tested image has a resolution of {screenShotBmp.Width} × {screenShotBmp.Height} px,\n" + $"the resolution of the loaded ocr-config is {ocrConfig.resolutionWidth} × {ocrConfig.resolutionHeight} px.\n\n" + "Load or create a ocr-config file with the resolution of the game to make it work."; return(finalValues); } // TODO resize image according to resize-factor. used for large screenshots if (ocrConfig.resize != 1 && ocrConfig.resize > 0) { Bitmap resized = new Bitmap((int)(ocrConfig.resize * ocrConfig.resolutionWidth), (int)(ocrConfig.resize * ocrConfig.resolutionHeight)); using (var graphics = Graphics.FromImage(resized)) { graphics.CompositingMode = CompositingMode.SourceCopy; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; using (var wrapMode = new ImageAttributes()) { wrapMode.SetWrapMode(WrapMode.TileFlipXY); graphics.DrawImage(screenShotBmp, new Rectangle(0, 0, resized.Width, resized.Height), 0, 0, screenShotBmp.Width, screenShotBmp.Height, GraphicsUnit.Pixel, wrapMode); } screenShotBmp.Dispose(); screenShotBmp = resized; } } if (enableOutput) { _ocrControl?.DisplayBmpInOcrControl(screenShotBmp); } finalValues = new double[ocrConfig.labelRectangles.Length]; finalValues[8] = -1; // set imprinting to -1 to mark it as unknown and to set a difference to a creature with 0% imprinting. if (changeForegroundWindow) { Win32API.SetForegroundWindow(Application.OpenForms[0].Handle); } HammingWeight.InitializeBitCounts(); // TODO OCR performance measurement //var sw = new Stopwatch(); //sw.Start(); var whiteThreshold = Properties.Settings.Default.OCRWhiteThreshold; bool wild = false; // todo: set to true and find out if the creature is wild in the first loop int stI = -1; var labels = (OcrTemplate.OcrLabels[])Enum.GetValues(typeof(OcrTemplate.OcrLabels)); for (int lbI = 0; lbI < labels.Length; lbI++) { stI++; if (lbI == 8) { stI = 8; } var label = labels[stI]; switch (label) { case OcrTemplate.OcrLabels.NameSpecies: if (ocrConfig.RecognitionPatterns.TrainingSettings.SkipName) { dinoName = string.Empty; continue; } break; case OcrTemplate.OcrLabels.Tribe: if (ocrConfig.RecognitionPatterns.TrainingSettings.SkipTribe) { tribeName = string.Empty; continue; } break; case OcrTemplate.OcrLabels.Owner: if (ocrConfig.RecognitionPatterns.TrainingSettings.SkipOwner) { ownerName = string.Empty; continue; } break; } Rectangle rec = ocrConfig.labelRectangles[lbI]; // wild creatures don't have the xp-bar, all stats are moved one row up if (wild && stI < 9) { rec.Offset(0, ocrConfig.labelRectangles[0].Top - ocrConfig.labelRectangles[1].Top); } Bitmap testbmp = SubImage(screenShotBmp, rec.X, rec.Y, rec.Width, rec.Height); //AddBitmapToDebug(testbmp); string statOcr; try { if (label == OcrTemplate.OcrLabels.NameSpecies) { statOcr = PatternOcr.ReadImageOcr(testbmp, false, whiteThreshold, rec.X, rec.Y, _ocrControl); } else if (label == OcrTemplate.OcrLabels.Level) { statOcr = PatternOcr.ReadImageOcr(testbmp, true, whiteThreshold, rec.X, rec.Y, _ocrControl).Replace(".", ": "); } else if (label == OcrTemplate.OcrLabels.Tribe || label == OcrTemplate.OcrLabels.Owner) { statOcr = PatternOcr.ReadImageOcr(testbmp, false, whiteThreshold, rec.X, rec.Y, _ocrControl); } else { statOcr = PatternOcr.ReadImageOcr(testbmp, true, whiteThreshold, rec.X, rec.Y, _ocrControl).Trim('.'); // statValues are only numbers } } catch (OperationCanceledException) { OcrText = "Canceled"; return(finalValues); } if (statOcr == string.Empty && (label == OcrTemplate.OcrLabels.Health || label == OcrTemplate.OcrLabels.Imprinting || label == OcrTemplate.OcrLabels.Tribe || label == OcrTemplate.OcrLabels.Owner)) { if (wild && label == OcrTemplate.OcrLabels.Health) { stI--; wild = false; } continue; // these can be missing, it's fine } finishedText += $"{(finishedText.Length == 0 ? string.Empty : "\r\n")}{label}:\t{statOcr}"; // parse the OCR String var r = new Regex(@"^[_\/\\]*(.*?)[_\/\\]*$"); statOcr = r.Replace(statOcr, "$1"); if (label == OcrTemplate.OcrLabels.NameSpecies) { r = new Regex(@".*?([♂♀])?[_.,-\/\\]*([^♂♀]+?)(?:[\(\[]([^\[\(\]\)]+)[\)\]]$|$)"); } else if (label == OcrTemplate.OcrLabels.Owner || label == OcrTemplate.OcrLabels.Tribe) { r = new Regex(@"(.*)"); } else if (label == OcrTemplate.OcrLabels.Level) { r = new Regex(@".*\D(\d+)"); } else { r = new Regex(@"(?:[\d.,%\/]*\/)?(\d+[\.,']?\d?)(%)?\.?"); // only the second numbers is interesting after the current weight is not shown anymore } MatchCollection mc = r.Matches(statOcr); if (mc.Count == 0) { if (label == OcrTemplate.OcrLabels.NameSpecies || label == OcrTemplate.OcrLabels.Owner || label == OcrTemplate.OcrLabels.Tribe) { continue; } //if (statName == "Torpor") //{ // // probably it's a wild creature // // todo //} //else //{ finishedText += $"error reading stat {label}"; finalValues[stI] = 0; continue; //} } if (label == OcrTemplate.OcrLabels.NameSpecies || label == OcrTemplate.OcrLabels.Owner || label == OcrTemplate.OcrLabels.Tribe) { if (label == OcrTemplate.OcrLabels.NameSpecies && mc[0].Groups.Count > 0) { if (mc[0].Groups[1].Value == "♀") { sex = Sex.Female; } else if (mc[0].Groups[1].Value == "♂") { sex = Sex.Male; } dinoName = mc[0].Groups[2].Value; species = mc[0].Groups[3].Value; if (species.Length == 0) { species = dinoName; } // remove non-letter chars r = new Regex(@"[^a-zA-Z]"); species = r.Replace(species, string.Empty); // replace capital I with lower l (common misrecognition) r = new Regex(@"(?<=[a-z])I(?=[a-z])"); species = r.Replace(species, "l"); // readd spaces before capital letters //r = new Regex("(?<=[a-z])(?=[A-Z])"); //species = r.Replace(species, " "); finishedText += $"\t→ {sex}, {species}"; dinoName = RemoveUnrecognizedCharacters(dinoName); species = RemoveUnrecognizedCharacters(species); } else if (label == OcrTemplate.OcrLabels.Owner && mc[0].Groups.Count > 0) { ownerName = mc[0].Groups[0].Value; finishedText += $"\t→ {ownerName}"; ownerName = RemoveUnrecognizedCharacters(ownerName); } else if (label == OcrTemplate.OcrLabels.Tribe && mc[0].Groups.Count > 0) { tribeName = mc[0].Groups[0].Value; finishedText += $"\t→ {tribeName}"; tribeName = RemoveUnrecognizedCharacters(tribeName); } continue; } if (mc[0].Groups.Count > 2 && mc[0].Groups[2].Value == "%" && label == OcrTemplate.OcrLabels.Weight) { // first stat with a '%' is damage, if oxygen is missing, shift all stats by one finalValues[4] = finalValues[3]; // shift food to weight finalValues[3] = finalValues[2]; // shift oxygen to food finalValues[2] = 0; // set oxygen (which wasn't there) to 0 stI++; } var splitRes = statOcr.Split('/', ',', ':'); var ocrValue = splitRes[splitRes.Length - 1] == "%" ? splitRes[splitRes.Length - 2] : splitRes[splitRes.Length - 1]; ocrValue = PatternOcr.RemoveNonNumeric(ocrValue); double.TryParse(ocrValue, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.GetCultureInfo("en-US"), out double v); // common substitutions: comma and apostrophe to dot, finishedText += label == OcrTemplate.OcrLabels.Level ? $"\t→ {v:F0}" : $"\t→ {v:F1}"; // TODO: test here that the read stat name corresponds to the stat supposed to be read finalValues[stI] = v; string RemoveUnrecognizedCharacters(string s) => s.Replace("�", string.Empty); } OcrText = finishedText; // TODO OCR performance output //sw.Stop(); //Debug.WriteLine($"OCR took {sw.ElapsedMilliseconds} ms."); // TODO reorder stats to match 12-stats-order return(finalValues); }