Refactored post processing to improve accuracy of alternative results

This commit is contained in:
psaintjust
2015-06-28 10:31:33 -04:00
parent 99afc9e2b6
commit e2a302d8f8
3 changed files with 131 additions and 206 deletions

View File

@@ -194,13 +194,7 @@ namespace alpr
for (unsigned int pp = 0; pp < ppResults.size(); pp++) for (unsigned int pp = 0; pp < ppResults.size(); pp++)
{ {
if (pp >= topN)
break;
int plate_char_length = utf8::distance(ppResults[pp].letters.begin(), ppResults[pp].letters.end());
if (plate_char_length >= config->postProcessMinCharacters &&
plate_char_length <= config->postProcessMaxCharacters)
{
// Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match // Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match
if (bestPlateIndex == 0 && ppResults[pp].matchesTemplate) if (bestPlateIndex == 0 && ppResults[pp].matchesTemplate)
bestPlateIndex = plateResult.topNPlates.size(); bestPlateIndex = plateResult.topNPlates.size();
@@ -223,7 +217,6 @@ namespace alpr
aplate.character_details.push_back(character_details); aplate.character_details.push_back(character_details);
} }
plateResult.topNPlates.push_back(aplate); plateResult.topNPlates.push_back(aplate);
}
} }

View File

@@ -24,7 +24,6 @@ using namespace std;
namespace alpr namespace alpr
{ {
PostProcess::PostProcess(Config* config) PostProcess::PostProcess(Config* config)
{ {
this->config = config; this->config = config;
@@ -143,6 +142,7 @@ namespace alpr
unknownCharPositions.clear(); unknownCharPositions.clear();
unknownCharPositions.resize(0); unknownCharPositions.resize(0);
allPossibilities.clear(); allPossibilities.clear();
allPossibilitiesLetters.clear();
//allPossibilities.resize(0); //allPossibilities.resize(0);
bestChars = ""; bestChars = "";
@@ -183,68 +183,22 @@ namespace alpr
} }
} }
// Prune the letters based on the topN value. timespec permutationStartTime;
// If our topN value is 3, for example, we can get rid of a lot of low scoring letters getTimeMonotonic(&permutationStartTime);
// because it would be impossible for them to be a part of our topN results.
vector<int> maxDepth = getMaxDepth(topn);
for (int i = 0; i < letters.size(); i++) findAllPermutations(templateregion, topn);
{
for (int k = letters[i].size() - 1; k > maxDepth[i]; k--)
{
letters[i].erase(letters[i].begin() + k);
}
}
//getTopN();
vector<Letter> tmp;
findAllPermutations(tmp, 0, config->postProcessMaxSubstitutions);
timespec sortStartTime;
getTimeMonotonic(&sortStartTime);
int numelements = topn;
if (allPossibilities.size() < topn)
numelements = allPossibilities.size() - 1;
partial_sort( allPossibilities.begin(), allPossibilities.begin() + numelements, allPossibilities.end(), wordCompare );
if (config->debugTiming) if (config->debugTiming)
{ {
timespec sortEndTime; timespec permutationEndTime;
getTimeMonotonic(&sortEndTime); getTimeMonotonic(&permutationEndTime);
cout << " -- PostProcess Sort Time: " << diffclock(sortStartTime, sortEndTime) << "ms." << endl; cout << " -- PostProcess Permutation Time: " << diffclock(permutationStartTime, permutationEndTime) << "ms." << endl;
} }
matchesTemplate = false; if (allPossibilities.size() > 0)
{
if (templateregion != "") bestChars = allPossibilities[0].letters;
{
vector<RegexRule*> regionRules = rules[templateregion];
for (int i = 0; i < allPossibilities.size(); i++)
{
for (int j = 0; j < regionRules.size(); j++)
{
allPossibilities[i].matchesTemplate = regionRules[j]->match(allPossibilities[i].letters);
if (allPossibilities[i].matchesTemplate)
{
allPossibilities[i].letters = regionRules[j]->filterSkips(allPossibilities[i].letters);
//bestChars = regionRules[j]->filterSkips(allPossibilities[i].letters);
matchesTemplate = true;
break;
}
}
if (i >= topn - 1)
break;
//if (matchesTemplate || i >= TOP_N - 1)
//break;
}
}
if (matchesTemplate)
{
for (int z = 0; z < allPossibilities.size(); z++) for (int z = 0; z < allPossibilities.size(); z++)
{ {
if (allPossibilities[z].matchesTemplate) if (allPossibilities[z].matchesTemplate)
@@ -253,15 +207,8 @@ namespace alpr
break; break;
} }
} }
}
else
{
bestChars = allPossibilities[0].letters;
}
// Now adjust the confidence scores to a percentage value // Now adjust the confidence scores to a percentage value
if (allPossibilities.size() > 0)
{
float maxPercentScore = calculateMaxConfidenceScore(); float maxPercentScore = calculateMaxConfidenceScore();
float highestRelativeScore = (float) allPossibilities[0].totalscore; float highestRelativeScore = (float) allPossibilities[0].totalscore;
@@ -280,9 +227,6 @@ namespace alpr
if (allPossibilities[i].letters == bestChars) if (allPossibilities[i].letters == bestChars)
cout << " <--- "; cout << " <--- ";
cout << endl; cout << endl;
if (i >= topn - 1)
break;
} }
cout << allPossibilities.size() << " total permutations" << endl; cout << allPossibilities.size() << " total permutations" << endl;
} }
@@ -325,128 +269,117 @@ namespace alpr
return totalScore / ((float) numScores); return totalScore / ((float) numScores);
} }
// Finds the minimum number of letters to include in the recursive sorting algorithm.
// For example, if I have letters
// A-200 B-100 C-100
// X-99 Y-95 Z-90
// Q-55 R-80
// And my topN value was 3, this would return:
// 0, 1, 1
// Which represents:
// A-200 B-100 C-100
// Y-95 Z-90
vector<int> PostProcess::getMaxDepth(int topn)
{
vector<int> depth;
for (int i = 0; i < letters.size(); i++)
depth.push_back(0);
int nextLeastDropCharPos = getNextLeastDrop(depth);
while (nextLeastDropCharPos != -1)
{
if (getPermutationCount(depth) >= topn)
break;
depth[nextLeastDropCharPos] = depth[nextLeastDropCharPos] + 1;
nextLeastDropCharPos = getNextLeastDrop(depth);
}
return depth;
}
int PostProcess::getPermutationCount(vector<int> depth)
{
int permutationCount = 1;
for (int i = 0; i < depth.size(); i++)
{
permutationCount *= (depth[i] + 1);
}
return permutationCount;
}
int PostProcess::getNextLeastDrop(vector<int> depth)
{
int nextLeastDropCharPos = -1;
float leastNextDrop = 99999999999;
for (int i = 0; i < letters.size(); i++)
{
if (depth[i] + 1 >= letters[i].size())
continue;
float drop = letters[i][depth[i]].totalscore - letters[i][depth[i]+1].totalscore;
if (drop < leastNextDrop)
{
nextLeastDropCharPos = i;
leastNextDrop = drop;
}
}
return nextLeastDropCharPos;
}
const vector<PPResult> PostProcess::getResults() const vector<PPResult> PostProcess::getResults()
{ {
return this->allPossibilities; return this->allPossibilities;
} }
void PostProcess::findAllPermutations(vector<Letter> prevletters, int charPos, int substitutionsLeft) struct PermutationCompare {
bool operator() (pair<float,vector<int> > &a, pair<float,vector<int> > &b)
{ {
if (substitutionsLeft < 0) return (a.first < b.first);
return; }
};
// Add my letter to the chain and recurse void PostProcess::findAllPermutations(string templateregion, int topn) {
for (int i = 0; i < letters[charPos].size(); i++)
// use a priority queue to process permutations in highest scoring order
priority_queue<pair<float,vector<int> >, vector<pair<float,vector<int> > >, PermutationCompare> permutations;
set<float> permutationHashes;
// push the first word onto the queue
float totalscore = 0;
for (int i=0; i<letters.size(); i++)
{ {
if (charPos == letters.size() - 1) if (letters[i].size() > 0)
totalscore += letters[i][0].totalscore;
}
vector<int> v(letters.size());
permutations.push(make_pair(totalscore, v));
while (permutations.size() > 0)
{
// get the top permutation and analyze
pair<float, vector<int> > topPermutation = permutations.top();
analyzePermutation(topPermutation.second, templateregion, topn);
permutations.pop();
if (allPossibilities.size() >= topn) {
break;
}
// add child permutations to queue
for (int i=0; i<letters.size(); i++)
{
// no more permutations with this letter
if (topPermutation.second[i]+1 >= letters[i].size())
continue;
pair<float, vector<int> > childPermutation = topPermutation;
childPermutation.first -= letters[i][topPermutation.second[i]].totalscore - letters[i][topPermutation.second[i] + 1].totalscore;
childPermutation.second[i] += 1;
// ignore permutations that have already been visited (assume that score is a good hash for permutation)
if (permutationHashes.end() != permutationHashes.find(childPermutation.first))
continue;
permutations.push(childPermutation);
permutationHashes.insert(childPermutation.first);
}
}
}
void PostProcess::analyzePermutation(vector<int> letterIndices, string templateregion, int topn)
{ {
// Last letter, add the word
PPResult possibility; PPResult possibility;
possibility.letters = ""; possibility.letters = "";
possibility.totalscore = 0; possibility.totalscore = 0;
possibility.matchesTemplate = false; possibility.matchesTemplate = false;
for (int z = 0; z < prevletters.size(); z++) int plate_char_length = 0;
for (int i = 0; i < letters.size(); i++)
{ {
if (prevletters[z].letter != SKIP_CHAR) if (letters[i].size() == 0)
continue;
Letter letter = letters[i][letterIndices[i]];
if (letter.letter != SKIP_CHAR)
{ {
possibility.letters = possibility.letters + prevletters[z].letter; possibility.letters = possibility.letters + letter.letter;
possibility.letter_details.push_back(prevletters[z]); possibility.letter_details.push_back(letter);
plate_char_length += 1;
} }
possibility.totalscore = possibility.totalscore + prevletters[z].totalscore; possibility.totalscore = possibility.totalscore + letter.totalscore;
} }
if (letters[charPos][i].letter != SKIP_CHAR) // ignore plates that don't fit the length requirements
if (plate_char_length < config->postProcessMinCharacters ||
plate_char_length > config->postProcessMaxCharacters)
return;
// Apply templates
if (templateregion != "")
{ {
possibility.letters = possibility.letters + letters[charPos][i].letter; vector<RegexRule*> regionRules = rules[templateregion];
possibility.letter_details.push_back(letters[charPos][i]);
for (int i = 0; i < regionRules.size(); i++)
{
possibility.matchesTemplate = regionRules[i]->match(possibility.letters);
if (possibility.matchesTemplate)
{
possibility.letters = regionRules[i]->filterSkips(possibility.letters);
break;
} }
possibility.totalscore = possibility.totalscore +letters[charPos][i].totalscore; }
}
// ignore duplicate words
if (allPossibilitiesLetters.end() != allPossibilitiesLetters.find(possibility.letters))
return;
allPossibilities.push_back(possibility); allPossibilities.push_back(possibility);
} allPossibilitiesLetters.insert(possibility.letters);
else
{
prevletters.push_back(letters[charPos][i]);
float scorePercentDiff = abs( letters[charPos][0].totalscore - letters[charPos][i].totalscore ) / letters[charPos][0].totalscore;
if (i != 0 && letters[charPos][i].letter != SKIP_CHAR && scorePercentDiff > 0.10f )
findAllPermutations(prevletters, charPos + 1, substitutionsLeft - 1);
else
findAllPermutations(prevletters, charPos + 1, substitutionsLeft);
prevletters.pop_back();
}
}
if (letters[charPos].size() == 0)
{
// No letters for this char position...
// Just pass it along
findAllPermutations(prevletters, charPos + 1, substitutionsLeft);
}
} }
bool wordCompare( const PPResult &left, const PPResult &right ) bool wordCompare( const PPResult &left, const PPResult &right )

View File

@@ -26,7 +26,9 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <stdio.h> #include <stdio.h>
#include <queue>
#include <vector> #include <vector>
#include <set>
#include "config.h" #include "config.h"
@@ -76,7 +78,8 @@ namespace alpr
private: private:
Config* config; Config* config;
//void getTopN(); //void getTopN();
void findAllPermutations(std::vector<Letter> prevletters, int charPos, int substitutionsLeft); void findAllPermutations(std::string templateregion, int topn);
void analyzePermutation(std::vector<int> letterIndices, std::string templateregion, int topn);
void insertLetter(std::string letter, int charPosition, float score); void insertLetter(std::string letter, int charPosition, float score);
@@ -88,11 +91,7 @@ namespace alpr
std::vector<int> unknownCharPositions; std::vector<int> unknownCharPositions;
std::vector<PPResult> allPossibilities; std::vector<PPResult> allPossibilities;
std::set<std::string> allPossibilitiesLetters;
// Functions used to prune the list of letters (based on topn) to improve performance
std::vector<int> getMaxDepth(int topn);
int getPermutationCount(std::vector<int> depth);
int getNextLeastDrop(std::vector<int> depth);
}; };
} }