/* If the user selects a card, returns the card, otherwise null */ private Card AskUserToConfirm(Bitmap targetImage, Hashtable possibleMatches) { /* * if (possibleMatches.Count == 0) * { * Globals.Director.WriteDebug(WRITE_DEBUG,"Warning: We should ask the user to confirm an image, but there are no possible matches..."); * return null; * }*/ CardMatchSelectDialog dialog = new CardMatchSelectDialog(); dialog.Location = cardMatchDialogSpawnLocation; dialog.DisplayImageToMatch(targetImage); // Create list List <String> orderedFilenames = new List <String>(); foreach (String cardMatchFile in possibleMatches.Keys) { orderedFilenames.Add(cardMatchFile); Globals.Director.WriteDebug(WRITE_DEBUG, cardMatchFile + " has similarity of " + possibleMatches[cardMatchFile]); } orderedFilenames.Sort((delegate(String file1, String file2) { return(((double)possibleMatches[file2]).CompareTo((double)possibleMatches[file1])); })); // Create cards from filenames const int MAX_CARDS_TO_DISPLAY = 5; int i = 0; foreach (String filename in orderedFilenames) { dialog.AddPossibleCardMatch(Card.CreateFromPath(filename)); i++; if (i == MAX_CARDS_TO_DISPLAY) { break; } } if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { // The user selected a card Card userCard = dialog.SelectedCard; /* Before returning to the caller, we replace the image associated with the card * with the targetimage. This process allows us to slowly replace the common card images templates * with images that come directly from the poker client, allowing us to achieve an almost perfect match * the next time the same card is compared */ ReplaceTemplateImageWith(userCard, targetImage); return(userCard); } return(null); }
/* It returns null if there are no good matches * If training mode is enabled, a window might ask the user to aid the algorithm find the best match */ public Card MatchCard(Bitmap image, string player_card = null, double perfectMatchHistogramThreshold = 0, double possibleMatchTemplateThreshold = 0, double allowableSimilarityThreshold = 0) { if (image == null) { return(null); } if (0.0d == perfectMatchHistogramThreshold) { perfectMatchHistogramThreshold = PERFECT_MATCH_HISTOGRAM_THRESHOLD; } if (0.0d == possibleMatchTemplateThreshold) { possibleMatchTemplateThreshold = POSSIBLE_MATCH_TEMPLATE_THRESHOLD; } if (0.0d == allowableSimilarityThreshold) { allowableSimilarityThreshold = ALLOWABLE_MATCH_TEMPLATE_THRESHOLD; } if (player_card == null) { player_card = "unk"; } double minDifference = Double.MaxValue; double maxSimilarity = 0.0d; String bestMatchFilename = ""; /* We keep a hashtable of possible matches (filename => match %) * so that if training mode is enabled we can access it to figure out which * are the most likely candidates */ Hashtable possibleMatches = new Hashtable(); foreach (String cardMatchFile in cardMatchFiles) { Bitmap candidateImage = new Bitmap(cardMatchFile); bool sizesMatch = candidateImage.Height == image.Height && candidateImage.Width == image.Width; // For template matching template should be smaller than image candidateImage = ScaleIfBiggerThan(image, candidateImage); String name = cardMatchFile.Substring(cardMatchFile.Length - 9) + "\n"; Globals.Director.WriteDebug(WRITE_DEBUG, " --- STARTING COMP: " + name + " for: " + player_card); double difference = HistogramBitmapDifference(image, candidateImage); double similarity = TemplateMatchingSimilarity(image, candidateImage); Globals.Director.WriteDebug(WRITE_DEBUG, "--- " + player_card + " : " + name + " difference: (" + difference + ") similarity: (" + similarity + ")"); if (difference < minDifference) { Globals.Director.WriteDebug(WRITE_DEBUG, " ---- setting the minDifference to: " + difference); minDifference = difference; bestMatchFilename = cardMatchFile; } if (similarity > maxSimilarity) { Globals.Director.WriteDebug(WRITE_DEBUG, " ---- setting the maxSimilarity to: " + similarity); maxSimilarity = similarity; } /* We use histogram difference to match perfect copies of the image (after the training phase is over) * but if they are different, we use the template matching as an indicator of similarity */ if (difference > perfectMatchHistogramThreshold && similarity > possibleMatchTemplateThreshold) { /* Our set of common cards is typically larger than the actual client matches * (although there could be rare exceptions that would not influence negatively the behavior of the algorithm). * So, given that same size images have already been matched by the user during training mode, * we can pretty safely ignore them, since * they are likely to be perfectly matched with another target (and this block would have not been executed) */ // if (!sizesMatch) // { // Globals.Director.WriteDebug(WRITE_DEBUG,"\n\n\n --- Adding " + name + " to possible matches"); possibleMatches.Add(cardMatchFile, similarity); // } } candidateImage.Dispose(); } Card matchedCard = null; // Hold the return value /* If we have a possible match, but we are not too sure about it, we can ask the user to confirm our guesses */ if (Globals.UserSettings.TrainingModeEnabled) { if (minDifference > perfectMatchHistogramThreshold && maxSimilarity > possibleMatchTemplateThreshold) { Globals.Director.WriteDebug(WRITE_DEBUG, "Min difference too high (" + minDifference + ") and max similarity (" + maxSimilarity + ") above threshold, asking user to confirm our guesses"); Card userCard = null; Globals.Director.RunFromGUIThread((Action) delegate() { userCard = AskUserToConfirm(image, possibleMatches); }, false ); if (userCard != null) { matchedCard = userCard; } } } Globals.Director.WriteDebug(WRITE_DEBUG, "\n\t Card Position: " + player_card + "\n\t"); Globals.Director.WriteDebug(WRITE_DEBUG, "Matched " + bestMatchFilename.Substring(bestMatchFilename.Length - 5) + " (Difference: " + minDifference + ")" + " (Similarity: " + maxSimilarity + ")"); Globals.Director.WriteDebug(WRITE_DEBUG, "\n\t bestMatchFile: " + bestMatchFilename + "\n\t"); Globals.Director.WriteDebug(WRITE_DEBUG, "\n\t allowableSimilarityThreshold: " + allowableSimilarityThreshold + "\n\t"); // If the user has selected a card, matchedCard is an object and this is skipped if (minDifference < perfectMatchHistogramThreshold && matchedCard == null && bestMatchFilename != "") { Globals.Director.WriteDebug(WRITE_DEBUG, "\t --- creating from minDiff"); matchedCard = Card.CreateFromPath(bestMatchFilename); } if (maxSimilarity > allowableSimilarityThreshold && matchedCard == null && bestMatchFilename != "") { Globals.Director.WriteDebug(WRITE_DEBUG, "\t --- creating from maxSim: " + maxSimilarity); matchedCard = Card.CreateFromPath(bestMatchFilename); } return(matchedCard); }
/* It returns null if there are no good matches * If training mode is enabled, a window might ask the user to aid the algorithm find the best match */ public Card MatchCard(Bitmap image) { if (image == null) { return(null); } double minDifference = Double.MaxValue; double maxSimilarity = 0.0d; String bestMatchFilename = ""; /* We keep a hashtable of possible matches (filename => match %) * so that if training mode is enabled we can access it to figure out which * are the most likely candidates */ Hashtable possibleMatches = new Hashtable(); foreach (String cardMatchFile in cardMatchFiles) { Bitmap candidateImage = new Bitmap(cardMatchFile); bool sizesMatch = candidateImage.Height == image.Height && candidateImage.Width == image.Width; // For template matching template should be smaller than image candidateImage = ScaleIfBiggerThan(image, candidateImage); double difference = HistogramBitmapDifference(image, candidateImage); double similarity = TemplateMatchingSimilarity(image, candidateImage); if (difference < minDifference) { minDifference = difference; bestMatchFilename = cardMatchFile; } if (similarity > maxSimilarity) { maxSimilarity = similarity; } /* We use histogram difference to match perfect copies of the image (after the training phase is over) * but if they are different, we use the template matching as an indicator of similarity */ if (difference > PERFECT_MATCH_HISTOGRAM_THRESHOLD && similarity > POSSIBLE_MATCH_TEMPLATE_THRESHOLD) { /* Our set of common cards is typically larger than the actual client matches * (although there could be rare exceptions that would not influence negatively the behavior of the algorithm). * So, given that same size images have already been matched by the user during training mode, * we can pretty safely ignore them, since * they are likely to be perfectly matched with another target (and this block would have not been executed) */ if (!sizesMatch) { possibleMatches.Add(cardMatchFile, similarity); } } candidateImage.Dispose(); } //Trace.WriteLine("Matched " + bestMatchFilename + " (Difference: " + minDifference + ")"); Card matchedCard = null; // Hold the return value /* If we have a possible match, but we are not too sure about it, we can ask the user to confirm our guesses */ if (Globals.UserSettings.TrainingModeEnabled) { if (minDifference > PERFECT_MATCH_HISTOGRAM_THRESHOLD && maxSimilarity > POSSIBLE_MATCH_TEMPLATE_THRESHOLD) { Trace.WriteLine("Min difference too high (" + minDifference + ") and max similarity above threshold, asking user to confirm our guesses"); Card userCard = null; Globals.Director.RunFromGUIThread((Action) delegate() { userCard = AskUserToConfirm(image, possibleMatches); }, false ); if (userCard != null) { matchedCard = userCard; } } } // If the user has selected a card, matchedCard is an object and this is skipped if (minDifference < PERFECT_MATCH_HISTOGRAM_THRESHOLD && matchedCard == null && bestMatchFilename != "") { matchedCard = Card.CreateFromPath(bestMatchFilename); } return(matchedCard); }