mirror of
https://github.com/kerberos-io/openalpr-base.git
synced 2025-10-06 01:27:03 +08:00
Refactored post processing to improve accuracy of alternative results
This commit is contained in:
@@ -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);
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 )
|
||||||
|
@@ -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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user