//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); }
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; }
//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; } }