//returns score & score after removing erroenously small rows and cols (null if segmentation algorithm doesn't support this) private static Tuple<double, double?> EvaluateByNumRowsAndCols(List<WordsearchImage> wordsearchImages, SegmentationAlgorithm segAlgorithm) { DefaultLog.Info("Evaluating Wordsearch Image Segmentation by number of rows and cols returned . . ."); int numCorrect = 0; int numCorrectRemoveSmallRowsAndCols = 0; //Test the algorithm on each Wordsearch Image foreach(WordsearchImage wordsearchImage in wordsearchImages) { //Register an interest in the Bitmap of the Wordsearch Image wordsearchImage.RegisterInterestInBitmap(); Segmentation proposedSegmentation = segAlgorithm.Segment(wordsearchImage.Bitmap); //Keep track of the number of correct if(proposedSegmentation.NumRows == wordsearchImage.Rows && proposedSegmentation.NumCols == wordsearchImage.Cols) { numCorrect++; } //If this segmentation algorithm performs its segmentation with start & end character indices & can therefore have // small rows and cols removed, do it if(segAlgorithm is SegmentationAlgorithmByStartEndIndices) { Segmentation segRemoveSmallRowsAndCols = proposedSegmentation.RemoveSmallRowsAndCols(); if(segRemoveSmallRowsAndCols.NumRows == wordsearchImage.Rows && segRemoveSmallRowsAndCols.NumCols == wordsearchImage.Cols) { numCorrectRemoveSmallRowsAndCols++; } } //Clean Up wordsearchImage.DeregisterInterestInBitmap(); } DefaultLog.Info("Returned {0}/{1} Wordsearch Segmentations Correctly", numCorrect, wordsearchImages.Count); double score = (double)numCorrect / wordsearchImages.Count; double? scoreRemoveSmallRowsAndCols = null; if(segAlgorithm is SegmentationAlgorithmByStartEndIndices) { scoreRemoveSmallRowsAndCols = (double)numCorrectRemoveSmallRowsAndCols / wordsearchImages.Count; DefaultLog.Info("Returned {0}/{1} Wordsearch Segmentations Correctly after removing small rows and cols", numCorrectRemoveSmallRowsAndCols, wordsearchImages.Count); } DefaultLog.Info("Wordsearch Image Segmentation Evaluation Completed"); return Tuple.Create(score, scoreRemoveSmallRowsAndCols); }
//Constructors public AlgorithmCombination(SegmentationAlgorithm detectionSegmentationAlgorithm, bool detectionSegmentationRemoveSmallRowsAndCols, SegmentationAlgorithm segmentationAlgorithm, bool segmentationRemoveSmallRowsAndCols, EvaluateFullSystem.SegmentationMethod segmentationMethod, Classifier probabilisticRotationCorrectionClassifier, Classifier classifier, Solver wordsearchSolver) { this.DetectionSegmentationAlgorithm = detectionSegmentationAlgorithm; this.DetectionSegmentationRemoveSmallRowsAndCols = detectionSegmentationRemoveSmallRowsAndCols; this.SegmentationAlgorithm = segmentationAlgorithm; this.SegmentationRemoveSmallRowsAndCols = segmentationRemoveSmallRowsAndCols; this.SegmentationMethod = segmentationMethod; this.ProbabilisticRotationCorrectionClassifier = probabilisticRotationCorrectionClassifier; this.Classifier = classifier; this.WordsearchSolver = wordsearchSolver; }
private static double EvaluateReturnsWordsearch(List<Image> images, SegmentationAlgorithm segAlgorithm, bool removeSmallRowsAndCols) { DefaultLog.Info("Evaluating Wordsearch Detection by best wordsearch returned . . ."); int numCorrect = 0; //Test the algorithm on each Image foreach (Image image in images) { //Register an interest in the Bitmap of the Image image.RegisterInterestInBitmap(); Tuple<List<IntPoint>, Bitmap> bestCandidate = DetectionAlgorithm.ExtractBestWordsearch(image.Bitmap, segAlgorithm, removeSmallRowsAndCols); //If we found a valid best candidate if (bestCandidate != null) { //Check if the best candidate is a wordsearch in this image List<IntPoint> points = bestCandidate.Item1; if(IsWordsearch(points, image)) { numCorrect++; } else { //Console.WriteLine("Incorrect"); //Debug Point (for viewing incorrect Bitmaps) } } else //Otherwise we couldn't find anything that resembeled a quadrilateral (and could therefore be a wordsearch) { } //Clean up bestCandidate.Item2.Dispose(); image.DeregisterInterestInBitmap(); } DefaultLog.Info("Found a Wordsearch for {0} / {1} Images correctly", numCorrect, images.Count); DefaultLog.Info("Wordsearch Detection Evaluation Completed"); return (double)numCorrect / images.Count; }
internal static WordsearchSolutionEvaluator EvaluateWordsearchBitmap(Bitmap wordsearchBitmap, string[] wordsToFind, Dictionary<string, List<WordPosition>> correctSolutions, SegmentationAlgorithm segmentationAlgorithm, bool segmentationRemoveSmallRowsAndCols, SegmentationMethod segmentationMethod, Classifier probabilisticRotationCorrectionClassifier, Classifier classifier, Solver wordsearchSolver) { /* * Wordsearch Segmentation */ Segmentation segmentation = segmentationAlgorithm.Segment(wordsearchBitmap); //Remove erroneously small rows and columns from the segmentation if that option is specified if(segmentationRemoveSmallRowsAndCols) { segmentation = segmentation.RemoveSmallRowsAndCols(); } /* * Wordsearch Rotation Correction */ WordsearchRotation originalRotation; //If we're using fixed row & col width if (segmentationMethod == SegmentationMethod.FixedWidth) { originalRotation = new WordsearchRotation(wordsearchBitmap, segmentation.NumRows, segmentation.NumCols); } else //Otherwise we're using varied row/col width segmentation, use the Segmentation object { originalRotation = new WordsearchRotation(wordsearchBitmap, segmentation); } WordsearchRotation rotatedWordsearch = WordsearchRotationCorrection.CorrectOrientation(originalRotation, probabilisticRotationCorrectionClassifier); Bitmap rotatedImage = rotatedWordsearch.Bitmap; //If the wordsearch has been rotated if (rotatedImage != wordsearchBitmap) { //Update the segmentation //If the wordsearch rotation won't have been passed a segmentation if (segmentationMethod == SegmentationMethod.FixedWidth) { //Make a new fixed width segmentation from the WordsearchRotation segmentation = new Segmentation(rotatedWordsearch.Rows, rotatedWordsearch.Cols, rotatedImage.Width, rotatedImage.Height); } else { //Use the rotated segmentation segmentation = rotatedWordsearch.Segmentation; } } /* * Classification */ //Split image up into individual characters Bitmap[,] rawCharImgs = null; //If we're using fixed row & col width if (segmentationMethod == SegmentationMethod.FixedWidth) { ResizeBicubic resize = new ResizeBicubic(Constants.CHAR_WITH_WHITESPACE_WIDTH * segmentation.NumCols, Constants.CHAR_WITH_WHITESPACE_HEIGHT * segmentation.NumRows); Bitmap resizedImage = resize.Apply(rotatedImage); rawCharImgs = SplitImage.Grid(resizedImage, segmentation.NumRows, segmentation.NumCols); //Resized image no longer required resizedImage.Dispose(); } else //Otherwise we're using varied row/col width segmentation { rawCharImgs = SplitImage.Segment(rotatedImage, segmentation); //If the Segmentation Method is to resize the raw char imgs, resize them if (segmentationMethod == SegmentationMethod.VariedWidthWithResize) { ResizeBicubic resize = new ResizeBicubic(Constants.CHAR_WITH_WHITESPACE_WIDTH, Constants.CHAR_WITH_WHITESPACE_HEIGHT); for (int i = 0; i < rawCharImgs.GetLength(0); i++) { for (int j = 0; j < rawCharImgs.GetLength(1); j++) { //Only do the resize if it isn't already that size if (rawCharImgs[i, j].Width != Constants.CHAR_WITH_WHITESPACE_WIDTH || rawCharImgs[i, j].Height != Constants.CHAR_WITH_WHITESPACE_HEIGHT) { Bitmap orig = rawCharImgs[i, j]; rawCharImgs[i, j] = resize.Apply(orig); //Remove the now unnecessary original/not resized image orig.Dispose(); } } } } } //Full sized rotated image no longer required rotatedImage.Dispose(); //Get the part of the image that actually contains the character (without any whitespace) Bitmap[,] charImgs = CharImgExtractor.ExtractAll(rawCharImgs); //Raw char img's are no longer required rawCharImgs.ToSingleDimension().DisposeAll(); //Perform the classification on all of the images (returns probabilities for each possible class) double[][][] classifierOutput = classifier.Classify(charImgs); //Actual images of the characters are no longer required charImgs.ToSingleDimension().DisposeAll(); /* * Solve Wordsearch */ Solution solution = wordsearchSolver.Solve(classifierOutput, wordsToFind); /* * Evaluate the Proposed Solution */ WordsearchSolutionEvaluator evaluator = new WordsearchSolutionEvaluator(solution, correctSolutions); return evaluator; }
private static double Evaluate(List<Image> images, SegmentationAlgorithm detectionSegmentationAlgorithm, bool detectionSegmentationRemoveSmallRowsAndCols, SegmentationAlgorithm segmentationAlgorithm, bool segmentationRemoveSmallRowsAndCols, SegmentationMethod segmentationMethod, Classifier probabilisticRotationCorrectionClassifier, Classifier classifier, Solver wordsearchSolver) { DefaultLog.Info("Evaluating Full System . . ."); int numCorrect = 0; List<WordsearchSolutionEvaluator> evaluators = new List<WordsearchSolutionEvaluator>(); foreach(Image image in images) { //Register an interest in the Bitmap of the image image.RegisterInterestInBitmap(); /* * Wordsearch Detection */ Tuple<List<IntPoint>, Bitmap> wordsearchImageTuple = DetectionAlgorithm.ExtractBestWordsearch(image.Bitmap, detectionSegmentationAlgorithm, detectionSegmentationRemoveSmallRowsAndCols); //Original wordsearch image is no longer required image.DeregisterInterestInBitmap(); //If the system failed to find anything remotely resembling a wordsearch, fail now if(wordsearchImageTuple == null) { continue; } //Get the words to look for later from this image & the correct solutions string[] wordsToFind = null; //Requires default, but won't even get used Dictionary<string, List<WordPosition>> correctSolutions = null; //If the image contains more than one wordsearch, we need to work out which one has been found if(image.WordsearchImages.Length > 1) { List<IntPoint> coordinates = wordsearchImageTuple.Item1; bool found = false; //Select the wordsearch found using the algorithm for checking if the returned wordsearch is correct in EvaluateWordsearchDetection foreach(WordsearchImage wordsearchImage in image.WordsearchImages) { //If it's this wordsearch if(EvaluateWordsearchDetection.IsWordsearch(coordinates, wordsearchImage)) { wordsToFind = wordsearchImage.Wordsearch.Words; correctSolutions = wordsearchImage.Wordsearch.Solutions; found = true; break; } } //If this isn't one of the wordsearches in the image, then fail now if(!found) { //Clean up wordsearchImageTuple.Item2.Dispose(); continue; } } else //Otherwise just use the one wordsearch that's in the image { wordsToFind = image.WordsearchImages[0].Wordsearch.Words; correctSolutions = image.WordsearchImages[0].Wordsearch.Solutions; } Bitmap extractedImage = wordsearchImageTuple.Item2; /* * Image Segmentation onwards happen in EvaluateWordsearchBitmap */ WordsearchSolutionEvaluator evaluator = EvaluateWordsearchBitmap(extractedImage, wordsToFind, correctSolutions, segmentationAlgorithm, segmentationRemoveSmallRowsAndCols, segmentationMethod, probabilisticRotationCorrectionClassifier, classifier, wordsearchSolver); //Clean up extractedImage.Dispose(); //Log Evaluation evaluators.Add(evaluator); DefaultLog.Info(evaluator.ToString()); if(evaluator.Correct) { numCorrect++; } } DefaultLog.Info("System found all words correctly for {0} / {1} Images correctly", numCorrect, images.Count); //Calculate some extra statistics int numWordsearchesNoWordsFound = 0; int numDidntReachEvaluation = images.Count - evaluators.Count; double fMeasureSum = 0; int numValidFMeasures = 0; foreach (WordsearchSolutionEvaluator evaluator in evaluators) { //If no words were found correctly if(evaluator.TruePositive == 0) { numWordsearchesNoWordsFound++; } //If there was a valid F-Measure if(!double.IsNaN(evaluator.FMeasure)) { fMeasureSum += evaluator.FMeasure; numValidFMeasures++; } } DefaultLog.Info("In {0} wordsearches no words were found correctly at all", numWordsearchesNoWordsFound); DefaultLog.Info("{0} wordsearch images got discarded before reaching the evaluation stage", numDidntReachEvaluation); DefaultLog.Info("Average F-Measure (when not NaN): {0}", fMeasureSum / numValidFMeasures); DefaultLog.Info("Full System Evaluation Completed"); return (double)numCorrect / images.Count; }
//Method to extract a Bitmap of the best match for a wordsearch in an image using a specified Wordsearch Segmentation Algorithm //returns null if no Wordsearch candidate could be found in the image public static Tuple<List<IntPoint>, Bitmap> ExtractBestWordsearch(Bitmap image, SegmentationAlgorithm segAlg, bool removeSmallRowsAndCols) { double blobMinDimensionDbl = BLOB_MIN_DIMENSION_PERCENTAGE / 100; int minWidth = (int)Math.Ceiling(image.Width * blobMinDimensionDbl); //Round up, so that the integer comaprison minimum will always be correct int minHeight = (int)Math.Ceiling(image.Height * blobMinDimensionDbl); List<List<IntPoint>> quads = ShapeFinder.Quadrilaterals(image, minWidth, minHeight); //Check that there are some quads found to search through for the best wordsearch candidate if(quads.Count != 0) { double bestScore = double.NegativeInfinity; List<IntPoint> bestCoords = null; Bitmap bestBitmap = null; //Search for the Bitmap that yields the best score foreach (List<IntPoint> quad in quads) { //Extract the Bitmap of this quad QuadrilateralTransformation quadTransform = new QuadrilateralTransformation(quad); Bitmap quadBitmap = quadTransform.Apply(image); //Score this wordsearch candidate double score; //If an InvalidRowsAndCols Exception gets throw by the segmentation // (due to there being 0 rows/cols in the returned segmentation) // this is obviously not a good wordsearch candidate try { Segmentation segmentation = segAlg.Segment(quadBitmap); //If removing erroneously small rows and cols before scoring the segmentation, do so now if(removeSmallRowsAndCols) { segmentation = segmentation.RemoveSmallRowsAndCols(); } CandidateScorer scorer = new CandidateScorer(segmentation); score = scorer.WordsearchRecognitionScore; } catch(InvalidRowsAndColsException) { //This is slightly better than the default score of Negative Infinity as any candidate // (even one with no rows or cols found in it) is better than no candidate whatsoever score = double.MinValue; } //If this score is better than the previous best (don't // override equal scores as the list is size ordered and // we'll default to the biggest wordsearch as being better) if(score > bestScore) { bestScore = score; bestCoords = quad; //Dispose of the previously best Bitmap resource if(bestBitmap != null) { bestBitmap.Dispose(); } //Update the ptr to the new one bestBitmap = quadBitmap; } else { //Clean up quadBitmap.Dispose(); } } return Tuple.Create(bestCoords, bestBitmap); } else //Otherwise there are no quads to search through { return null; } }
public static Tuple<List<IntPoint>, Bitmap> ExtractBestWordsearch(Bitmap image, SegmentationAlgorithm segAlg) { return ExtractBestWordsearch(image, segAlg, false); //Don't remove the erroneously small rows and cols by default }