Support analysis for multiple training data sets in a single pass.

Iterate over each training data set and choose the best scoring plate when there are overlaps
This is useful when countries may have multiple different plate styles (e.g., EU single line and EU multi-line)
This commit is contained in:
Matt Hill
2015-09-15 23:10:41 -04:00
parent 5889ec6109
commit 8bb3264072
11 changed files with 618 additions and 206 deletions

View File

@@ -35,6 +35,7 @@ set(lpr_source_files
pipeline_data.cpp pipeline_data.cpp
cjson.c cjson.c
motiondetector.cpp motiondetector.cpp
result_aggregator.cpp
) )

View File

@@ -18,6 +18,7 @@
*/ */
#include "alpr_impl.h" #include "alpr_impl.h"
#include "result_aggregator.h"
void plateAnalysisThread(void* arg); void plateAnalysisThread(void* arg);
@@ -34,11 +35,9 @@ namespace alpr
getTimeMonotonic(&startTime); getTimeMonotonic(&startTime);
config = new Config(country, configFile, runtimeDir); config = new Config(country, configFile, runtimeDir);
plateDetector = ALPR_NULL_PTR;
stateDetector = ALPR_NULL_PTR;
ocr = ALPR_NULL_PTR;
prewarp = ALPR_NULL_PTR; prewarp = ALPR_NULL_PTR;
// Config file or runtime dir not found. Don't process any further. // Config file or runtime dir not found. Don't process any further.
if (config->loaded == false) if (config->loaded == false)
@@ -46,8 +45,24 @@ namespace alpr
return; return;
} }
plateDetector = createDetector(config); for (unsigned int i = 0; i < config->loaded_countries.size(); i++)
ocr = new OCR(config); {
config->setCountry(config->loaded_countries[i]);
AlprRecognizers recognizer;
recognizer.plateDetector = createDetector(config);
recognizer.ocr = new OCR(config);
recognizer.stateDetector = new StateDetector(this->config->country, this->config->runtimeBaseDir);
recognizers[config->country] = recognizer;
}
setNumThreads(0); setNumThreads(0);
setDetectRegion(DEFAULT_DETECT_REGION); setDetectRegion(DEFAULT_DETECT_REGION);
@@ -62,18 +77,24 @@ namespace alpr
cout << "OpenALPR Initialization Time: " << diffclock(startTime, endTime) << "ms." << endl; cout << "OpenALPR Initialization Time: " << diffclock(startTime, endTime) << "ms." << endl;
} }
AlprImpl::~AlprImpl() AlprImpl::~AlprImpl()
{ {
delete config; delete config;
if (plateDetector != ALPR_NULL_PTR) typedef std::map<std::string, AlprRecognizers>::iterator it_type;
delete plateDetector; for(it_type iterator = recognizers.begin(); iterator != recognizers.end(); iterator++) {
if (stateDetector != ALPR_NULL_PTR) if (iterator->second.plateDetector != ALPR_NULL_PTR)
delete stateDetector; delete iterator->second.plateDetector;
if (iterator->second.stateDetector != ALPR_NULL_PTR)
delete iterator->second.stateDetector;
if (iterator->second.ocr != ALPR_NULL_PTR)
delete iterator->second.ocr;
}
if (ocr != ALPR_NULL_PTR)
delete ocr;
if (prewarp != ALPR_NULL_PTR) if (prewarp != ALPR_NULL_PTR)
delete prewarp; delete prewarp;
@@ -126,174 +147,24 @@ namespace alpr
// Warp the image if prewarp is provided // Warp the image if prewarp is provided
grayImg = prewarp->warpImage(grayImg); grayImg = prewarp->warpImage(grayImg);
warpedRegionsOfInterest = prewarp->projectRects(regionsOfInterest, grayImg.cols, grayImg.rows, false); warpedRegionsOfInterest = prewarp->projectRects(regionsOfInterest, grayImg.cols, grayImg.rows, false);
vector<PlateRegion> warpedPlateRegions; // Iterate through each country provided (typically just one)
// Find all the candidate regions // and aggregate the results if necessary
if (config->skipDetection == false) ResultAggregator aggregator;
for (unsigned int i = 0; i < config->loaded_countries.size(); i++)
{ {
warpedPlateRegions = plateDetector->detect(grayImg, warpedRegionsOfInterest); if (config->debugGeneral)
} cout << "Analyzing: " << config->loaded_countries[i] << endl;
else
{ config->setCountry(config->loaded_countries[i]);
// They have elected to skip plate detection. Instead, return a list of plate regions AlprFullDetails sub_results = analyzeSingleCountry(img, grayImg, warpedRegionsOfInterest);
// based on their regions of interest
for (unsigned int i = 0; i < warpedRegionsOfInterest.size(); i++) aggregator.addResults(sub_results);
{
PlateRegion pr;
pr.rect = cv::Rect(warpedRegionsOfInterest[i]);
warpedPlateRegions.push_back(pr);
}
} }
response = aggregator.getAggregateResults();
queue<PlateRegion> plateQueue;
for (unsigned int i = 0; i < warpedPlateRegions.size(); i++)
plateQueue.push(warpedPlateRegions[i]);
int platecount = 0;
while(!plateQueue.empty())
{
PlateRegion plateRegion = plateQueue.front();
plateQueue.pop();
PipelineData pipeline_data(img, grayImg, plateRegion.rect, config);
pipeline_data.prewarp = prewarp;
timespec platestarttime;
getTimeMonotonic(&platestarttime);
LicensePlateCandidate lp(&pipeline_data);
lp.recognize();
bool plateDetected = false;
if (pipeline_data.disqualified && config->debugGeneral)
{
cout << "Disqualify reason: " << pipeline_data.disqualify_reason << endl;
}
if (!pipeline_data.disqualified)
{
AlprPlateResult plateResult;
plateResult.region = defaultRegion;
plateResult.regionConfidence = 0;
plateResult.plate_index = platecount++;
// If using prewarp, remap the plate corners to the original image
vector<Point2f> cornerPoints = pipeline_data.plate_corners;
cornerPoints = prewarp->projectPoints(cornerPoints, true);
for (int pointidx = 0; pointidx < 4; pointidx++)
{
plateResult.plate_points[pointidx].x = (int) cornerPoints[pointidx].x;
plateResult.plate_points[pointidx].y = (int) cornerPoints[pointidx].y;
}
if (detectRegion)
{
std::vector<StateCandidate> state_candidates = stateDetector->detect(pipeline_data.color_deskewed.data,
pipeline_data.color_deskewed.elemSize(),
pipeline_data.color_deskewed.cols,
pipeline_data.color_deskewed.rows);
if (state_candidates.size() > 0)
{
plateResult.region = state_candidates[0].state_code;
plateResult.regionConfidence = (int) state_candidates[0].confidence;
}
}
if (plateResult.region.length() > 0 && ocr->postProcessor.regionIsValid(plateResult.region) == false)
{
std::cerr << "Invalid pattern provided: " << plateResult.region << std::endl;
std::cerr << "Valid patterns are located in the " << config->country << ".patterns file" << std::endl;
}
ocr->performOCR(&pipeline_data);
ocr->postProcessor.analyze(plateResult.region, topN);
timespec resultsStartTime;
getTimeMonotonic(&resultsStartTime);
const vector<PPResult> ppResults = ocr->postProcessor.getResults();
int bestPlateIndex = 0;
cv::Mat charTransformMatrix = getCharacterTransformMatrix(&pipeline_data);
bool isBestPlateSelected = false;
for (unsigned int pp = 0; pp < ppResults.size(); pp++)
{
// Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match
if (isBestPlateSelected == false && ppResults[pp].matchesTemplate){
bestPlateIndex = plateResult.topNPlates.size();
isBestPlateSelected = true;
}
AlprPlate aplate;
aplate.characters = ppResults[pp].letters;
aplate.overall_confidence = ppResults[pp].totalscore;
aplate.matches_template = ppResults[pp].matchesTemplate;
// Grab detailed results for each character
for (unsigned int c_idx = 0; c_idx < ppResults[pp].letter_details.size(); c_idx++)
{
AlprChar character_details;
character_details.character = ppResults[pp].letter_details[c_idx].letter;
character_details.confidence = ppResults[pp].letter_details[c_idx].totalscore;
cv::Rect char_rect = pipeline_data.charRegions[ppResults[pp].letter_details[c_idx].charposition];
std::vector<AlprCoordinate> charpoints = getCharacterPoints(char_rect, charTransformMatrix );
for (int cpt = 0; cpt < 4; cpt++)
character_details.corners[cpt] = charpoints[cpt];
aplate.character_details.push_back(character_details);
}
plateResult.topNPlates.push_back(aplate);
}
if (plateResult.topNPlates.size() > bestPlateIndex)
{
AlprPlate bestPlate;
bestPlate.characters = plateResult.topNPlates[bestPlateIndex].characters;
bestPlate.matches_template = plateResult.topNPlates[bestPlateIndex].matches_template;
bestPlate.overall_confidence = plateResult.topNPlates[bestPlateIndex].overall_confidence;
bestPlate.character_details = plateResult.topNPlates[bestPlateIndex].character_details;
plateResult.bestPlate = bestPlate;
}
timespec plateEndTime;
getTimeMonotonic(&plateEndTime);
plateResult.processing_time_ms = diffclock(platestarttime, plateEndTime);
if (config->debugTiming)
{
cout << "Result Generation Time: " << diffclock(resultsStartTime, plateEndTime) << "ms." << endl;
}
if (plateResult.topNPlates.size() > 0)
{
plateDetected = true;
response.results.plates.push_back(plateResult);
}
}
if (!plateDetected)
{
// Not a valid plate
// Check if this plate has any children, if so, send them back up for processing
for (unsigned int childidx = 0; childidx < plateRegion.children.size(); childidx++)
{
plateQueue.push(plateRegion.children[childidx]);
}
}
}
// Unwarp plate regions if necessary
prewarp->projectPlateRegions(warpedPlateRegions, grayImg.cols, grayImg.rows, true);
response.plateRegions = warpedPlateRegions;
timespec endTime; timespec endTime;
getTimeMonotonic(&endTime); getTimeMonotonic(&endTime);
response.results.total_processing_time_ms = diffclock(startTime, endTime);
if (config->debugTiming) if (config->debugTiming)
{ {
cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl; cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl;
@@ -352,7 +223,183 @@ namespace alpr
return response; return response;
} }
AlprFullDetails AlprImpl::analyzeSingleCountry(cv::Mat colorImg, cv::Mat grayImg, std::vector<cv::Rect> warpedRegionsOfInterest)
{
AlprFullDetails response;
AlprRecognizers country_recognizers = recognizers[config->country];
timespec startTime;
getTimeMonotonic(&startTime);
vector<PlateRegion> warpedPlateRegions;
// Find all the candidate regions
if (config->skipDetection == false)
{
warpedPlateRegions = country_recognizers.plateDetector->detect(grayImg, warpedRegionsOfInterest);
}
else
{
// They have elected to skip plate detection. Instead, return a list of plate regions
// based on their regions of interest
for (unsigned int i = 0; i < warpedRegionsOfInterest.size(); i++)
{
PlateRegion pr;
pr.rect = cv::Rect(warpedRegionsOfInterest[i]);
warpedPlateRegions.push_back(pr);
}
}
queue<PlateRegion> plateQueue;
for (unsigned int i = 0; i < warpedPlateRegions.size(); i++)
plateQueue.push(warpedPlateRegions[i]);
int platecount = 0;
while(!plateQueue.empty())
{
PlateRegion plateRegion = plateQueue.front();
plateQueue.pop();
PipelineData pipeline_data(colorImg, grayImg, plateRegion.rect, config);
pipeline_data.prewarp = prewarp;
timespec platestarttime;
getTimeMonotonic(&platestarttime);
LicensePlateCandidate lp(&pipeline_data);
lp.recognize();
bool plateDetected = false;
if (pipeline_data.disqualified && config->debugGeneral)
{
cout << "Disqualify reason: " << pipeline_data.disqualify_reason << endl;
}
if (!pipeline_data.disqualified)
{
AlprPlateResult plateResult;
plateResult.region = defaultRegion;
plateResult.regionConfidence = 0;
plateResult.plate_index = platecount++;
// If using prewarp, remap the plate corners to the original image
vector<Point2f> cornerPoints = pipeline_data.plate_corners;
cornerPoints = prewarp->projectPoints(cornerPoints, true);
for (int pointidx = 0; pointidx < 4; pointidx++)
{
plateResult.plate_points[pointidx].x = (int) cornerPoints[pointidx].x;
plateResult.plate_points[pointidx].y = (int) cornerPoints[pointidx].y;
}
if (detectRegion)
{
std::vector<StateCandidate> state_candidates = country_recognizers.stateDetector->detect(pipeline_data.color_deskewed.data,
pipeline_data.color_deskewed.elemSize(),
pipeline_data.color_deskewed.cols,
pipeline_data.color_deskewed.rows);
if (state_candidates.size() > 0)
{
plateResult.region = state_candidates[0].state_code;
plateResult.regionConfidence = (int) state_candidates[0].confidence;
}
}
if (plateResult.region.length() > 0 && country_recognizers.ocr->postProcessor.regionIsValid(plateResult.region) == false)
{
std::cerr << "Invalid pattern provided: " << plateResult.region << std::endl;
std::cerr << "Valid patterns are located in the " << config->country << ".patterns file" << std::endl;
}
country_recognizers.ocr->performOCR(&pipeline_data);
country_recognizers.ocr->postProcessor.analyze(plateResult.region, topN);
timespec resultsStartTime;
getTimeMonotonic(&resultsStartTime);
const vector<PPResult> ppResults = country_recognizers.ocr->postProcessor.getResults();
int bestPlateIndex = 0;
cv::Mat charTransformMatrix = getCharacterTransformMatrix(&pipeline_data);
bool isBestPlateSelected = false;
for (unsigned int pp = 0; pp < ppResults.size(); pp++)
{
// Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match
if (isBestPlateSelected == false && ppResults[pp].matchesTemplate){
bestPlateIndex = plateResult.topNPlates.size();
isBestPlateSelected = true;
}
AlprPlate aplate;
aplate.characters = ppResults[pp].letters;
aplate.overall_confidence = ppResults[pp].totalscore;
aplate.matches_template = ppResults[pp].matchesTemplate;
// Grab detailed results for each character
for (unsigned int c_idx = 0; c_idx < ppResults[pp].letter_details.size(); c_idx++)
{
AlprChar character_details;
character_details.character = ppResults[pp].letter_details[c_idx].letter;
character_details.confidence = ppResults[pp].letter_details[c_idx].totalscore;
cv::Rect char_rect = pipeline_data.charRegions[ppResults[pp].letter_details[c_idx].charposition];
std::vector<AlprCoordinate> charpoints = getCharacterPoints(char_rect, charTransformMatrix );
for (int cpt = 0; cpt < 4; cpt++)
character_details.corners[cpt] = charpoints[cpt];
aplate.character_details.push_back(character_details);
}
plateResult.topNPlates.push_back(aplate);
}
if (plateResult.topNPlates.size() > bestPlateIndex)
{
AlprPlate bestPlate;
bestPlate.characters = plateResult.topNPlates[bestPlateIndex].characters;
bestPlate.matches_template = plateResult.topNPlates[bestPlateIndex].matches_template;
bestPlate.overall_confidence = plateResult.topNPlates[bestPlateIndex].overall_confidence;
bestPlate.character_details = plateResult.topNPlates[bestPlateIndex].character_details;
plateResult.bestPlate = bestPlate;
}
timespec plateEndTime;
getTimeMonotonic(&plateEndTime);
plateResult.processing_time_ms = diffclock(platestarttime, plateEndTime);
if (config->debugTiming)
{
cout << "Result Generation Time: " << diffclock(resultsStartTime, plateEndTime) << "ms." << endl;
}
if (plateResult.topNPlates.size() > 0)
{
plateDetected = true;
response.results.plates.push_back(plateResult);
}
}
if (!plateDetected)
{
// Not a valid plate
// Check if this plate has any children, if so, send them back up for processing
for (unsigned int childidx = 0; childidx < plateRegion.children.size(); childidx++)
{
plateQueue.push(plateRegion.children[childidx]);
}
}
}
// Unwarp plate regions if necessary
prewarp->projectPlateRegions(warpedPlateRegions, grayImg.cols, grayImg.rows, true);
response.plateRegions = warpedPlateRegions;
timespec endTime;
getTimeMonotonic(&endTime);
response.results.total_processing_time_ms = diffclock(startTime, endTime);
return response;
}
AlprResults AlprImpl::recognize( std::vector<char> imageBytes) AlprResults AlprImpl::recognize( std::vector<char> imageBytes)
{ {
@@ -596,18 +643,6 @@ namespace alpr
{ {
this->detectRegion = detectRegion; this->detectRegion = detectRegion;
if (detectRegion && this->stateDetector == NULL)
{
timespec startTime;
getTimeMonotonic(&startTime);
this->stateDetector = new StateDetector(this->config->country, this->config->runtimeBaseDir);
timespec endTime;
getTimeMonotonic(&endTime);
if (config->debugTiming)
cout << "State Identification Initialization Time: " << diffclock(startTime, endTime) << "ms." << endl;
}
} }

View File

@@ -66,6 +66,13 @@ namespace alpr
AlprResults results; AlprResults results;
}; };
struct AlprRecognizers
{
Detector* plateDetector;
StateDetector* stateDetector;
OCR* ocr;
};
class AlprImpl class AlprImpl
{ {
@@ -83,6 +90,8 @@ namespace alpr
void applyRegionTemplate(AlprPlateResult* result, std::string region); void applyRegionTemplate(AlprPlateResult* result, std::string region);
AlprFullDetails analyzeSingleCountry(cv::Mat colorImg, cv::Mat grayImg, std::vector<cv::Rect> regionsOfInterest);
void setDetectRegion(bool detectRegion); void setDetectRegion(bool detectRegion);
void setTopN(int topn); void setTopN(int topn);
void setDefaultRegion(std::string region); void setDefaultRegion(std::string region);
@@ -99,9 +108,8 @@ namespace alpr
private: private:
Detector* plateDetector; std::map<std::string, AlprRecognizers> recognizers;
StateDetector* stateDetector;
OCR* ocr;
PreWarp* prewarp; PreWarp* prewarp;
int topN; int topN;

View File

@@ -21,6 +21,7 @@
#include "support/filesystem.h" #include "support/filesystem.h"
#include "support/platform.h" #include "support/platform.h"
#include "simpleini/simpleini.h" #include "simpleini/simpleini.h"
#include "utility.h"
using namespace std; using namespace std;
@@ -88,9 +89,6 @@ namespace alpr
} }
this->country = country;
loadCommonValues(configFile); loadCommonValues(configFile);
if (runtime_dir.compare("") != 0) if (runtime_dir.compare("") != 0)
@@ -115,21 +113,24 @@ namespace alpr
return; return;
} }
std::string country_config_file = this->runtimeBaseDir + "/config/" + country + ".conf"; this->loaded_countries = this->parse_country_string(country);
if (fileExists(country_config_file.c_str()) == false)
{
std::cerr << "--(!) Country config file '" << country_config_file << "' does not exist. Missing config for the country: '" << country<< "'!" << endl;
return;
}
loadCountryValues(country_config_file, country);
if (fileExists((this->runtimeBaseDir + "/ocr/tessdata/" + this->ocrLanguage + ".traineddata").c_str()) == false) if (this->loaded_countries.size() == 0)
{ {
std::cerr << "--(!) Runtime directory '" << this->runtimeBaseDir << "' is invalid. Missing OCR data for the country: '" << country<< "'!" << endl; std::cerr << "--(!) Country not specified." << endl;
return; return;
} }
for (unsigned int i = 0; i < loaded_countries.size(); i++)
{
bool country_loaded = setCountry(this->loaded_countries[i]);
if (!country_loaded)
{
return;
}
}
setCountry(this->loaded_countries[0]);
if (this->debugGeneral) if (this->debugGeneral)
{ {
std::cout << debug_message << endl; std::cout << debug_message << endl;
@@ -309,7 +310,42 @@ namespace alpr
} }
std::vector<std::string> Config::parse_country_string(std::string countries)
{
std::istringstream ss(countries);
std::string token;
std::vector<std::string> parsed_countries;
while(std::getline(ss, token, ',')) {
std::string trimmed_token = trim(token);
if (trimmed_token.size() > 0)
parsed_countries.push_back(trimmed_token);
}
return parsed_countries;
}
bool Config::setCountry(std::string country)
{
this->country = country;
std::string country_config_file = this->runtimeBaseDir + "/config/" + country + ".conf";
if (fileExists(country_config_file.c_str()) == false)
{
std::cerr << "--(!) Country config file '" << country_config_file << "' does not exist. Missing config for the country: '" << country<< "'!" << endl;
return false;
}
loadCountryValues(country_config_file, country);
if (fileExists((this->runtimeBaseDir + "/ocr/tessdata/" + this->ocrLanguage + ".traineddata").c_str()) == false)
{
std::cerr << "--(!) Runtime directory '" << this->runtimeBaseDir << "' is invalid. Missing OCR data for the country: '" << country<< "'!" << endl;
return false;
}
return true;
}
float getFloat(CSimpleIniA* ini, string section, string key, float defaultValue) float getFloat(CSimpleIniA* ini, string section, string key, float defaultValue)
{ {

View File

@@ -26,6 +26,7 @@
#include <stdio.h> #include <stdio.h>
#include <iostream> #include <iostream>
#include <vector>
#include <stdlib.h> /* getenv */ #include <stdlib.h> /* getenv */
#include <math.h> #include <math.h>
@@ -130,11 +131,16 @@ namespace alpr
std::string runtimeBaseDir; std::string runtimeBaseDir;
std::vector<std::string> loaded_countries;
bool setCountry(std::string country);
private: private:
float ocrImagePercent; float ocrImagePercent;
float stateIdImagePercent; float stateIdImagePercent;
std::vector<std::string> parse_country_string(std::string countries);
void loadCommonValues(std::string configFile); void loadCommonValues(std::string configFile);
void loadCountryValues(std::string configFile, std::string country); void loadCountryValues(std::string configFile, std::string country);

View File

@@ -0,0 +1,190 @@
/*
* Copyright (c) 2015 OpenALPR Technology, Inc.
* Open source Automated License Plate Recognition [http://www.openalpr.com]
*
* This file is part of OpenALPR.
*
* OpenALPR is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License
* version 3 as published by the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "result_aggregator.h"
using namespace std;
using namespace cv;
namespace alpr
{
ResultAggregator::ResultAggregator()
{
}
ResultAggregator::~ResultAggregator() {
}
void ResultAggregator::addResults(AlprFullDetails full_results)
{
all_results.push_back(full_results);
}
AlprFullDetails ResultAggregator::getAggregateResults()
{
assert(all_results.size() > 0);
if (all_results.size() == 1)
return all_results[0];
AlprFullDetails response;
// Plate regions are needed for benchmarking
// Copy all detected boxes across all results
for (unsigned int i = 0; i < all_results.size(); i++)
{
for (unsigned int k = 0; k < all_results[i].plateRegions.size(); k++)
response.plateRegions.push_back(all_results[i].plateRegions[k]);
}
response.results.epoch_time = all_results[0].results.epoch_time;
response.results.img_height = all_results[0].results.img_height;
response.results.img_width = all_results[0].results.img_width;
response.results.total_processing_time_ms = all_results[0].results.total_processing_time_ms;
response.results.regionsOfInterest = all_results[0].results.regionsOfInterest;
vector<vector<AlprPlateResult> > clusters = findClusters();
// Assume we have multiple results, one cluster for each unique train data (e.g., eu, eu2)
// Now for each cluster of plates, pick the best one
for (unsigned int i = 0; i < clusters.size(); i++)
{
float best_confidence = 0;
int best_index = 0;
for (unsigned int k = 0; k < clusters[i].size(); k++)
{
if (clusters[i][k].bestPlate.overall_confidence > best_confidence)
{
best_confidence = clusters[i][k].bestPlate.overall_confidence;
best_index = k;
}
}
response.results.plates.push_back(clusters[i][best_index]);
}
return response;
}
// Searches all_plates to find overlapping plates
// Returns an array containing "clusters" (overlapping plates)
std::vector<std::vector<AlprPlateResult> > ResultAggregator::findClusters()
{
std::vector<std::vector<AlprPlateResult> > clusters;
for (unsigned int i = 0; i < all_results.size(); i++)
{
for (unsigned int plate_id = 0; plate_id < all_results[i].results.plates.size(); plate_id++)
{
AlprPlateResult plate = all_results[i].results.plates[plate_id];
int cluster_index = overlaps(plate, clusters);
if (cluster_index < 0)
{
vector<AlprPlateResult> new_cluster;
new_cluster.push_back(plate);
clusters.push_back(new_cluster);
}
else
{
clusters[cluster_index].push_back(plate);
}
}
}
return clusters;
}
PlateShapeInfo ResultAggregator::getShapeInfo(AlprPlateResult plate)
{
int NUM_POINTS = 4;
Moments mu;
PlateShapeInfo response;
vector<Point> points;
for (int i = 0; i < NUM_POINTS; i++ )
{
cv::Point p(plate.plate_points[i].x, plate.plate_points[i].y);
points.push_back(p);
}
mu = moments( points, false );
response.center = cv::Point2f( mu.m10/mu.m00 , mu.m01/mu.m00 );
response.area = mu.m00;
Rect r = cv::boundingRect(points);
response.max_width = r.width;
response.max_height = r.height;
return response;
}
// Returns the cluster ID if the plate overlaps. Otherwise returns -1
int ResultAggregator::overlaps(AlprPlateResult plate,
std::vector<std::vector<AlprPlateResult> > clusters)
{
// Check the center positions to see how close they are to each other
// Also compare the size. If it's much much larger/smaller, treat it as a separate cluster
PlateShapeInfo psi = getShapeInfo(plate);
for (unsigned int i = 0; i < clusters.size(); i++)
{
for (unsigned int k = 0; k < clusters[i].size(); k++)
{
PlateShapeInfo cluster_shapeinfo = getShapeInfo(clusters[i][k]);
int diffx = abs(psi.center.x - cluster_shapeinfo.center.x);
int diffy = abs(psi.center.y - cluster_shapeinfo.center.y);
// divide the larger plate area by the smaller plate area to determine a match
float area_diff;
if (psi.area > cluster_shapeinfo.area)
area_diff = psi.area / cluster_shapeinfo.area;
else
area_diff = cluster_shapeinfo.area / psi.area;
int max_x_diff = (psi.max_width + cluster_shapeinfo.max_width) / 2;
int max_y_diff = (psi.max_height + cluster_shapeinfo.max_height) / 2;
float max_area_diff = 4.0;
// Consider it a match if center diffx/diffy are less than the average height
// the area is not more than 4x different
if (diffx <= max_x_diff && diffy <= max_y_diff && area_diff <= max_area_diff)
{
return i;
}
}
}
return -1;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2015 OpenALPR Technology, Inc.
* Open source Automated License Plate Recognition [http://www.openalpr.com]
*
* This file is part of OpenALPR.
*
* OpenALPR is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License
* version 3 as published by the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPENALPR_RESULTAGGREGATOR_H
#define OPENALPR_RESULTAGGREGATOR_H
#include "alpr_impl.h"
// Runs the analysis for multiple training sets, and aggregates the results into the best matches
struct PlateShapeInfo
{
cv::Point2f center;
float area;
int max_width;
int max_height;
};
namespace alpr
{
class ResultAggregator
{
public:
ResultAggregator();
virtual ~ResultAggregator();
void addResults(AlprFullDetails full_results);
AlprFullDetails getAggregateResults();
private:
std::vector<AlprFullDetails> all_results;
PlateShapeInfo getShapeInfo(AlprPlateResult plate);
std::vector<std::vector<AlprPlateResult> > findClusters();
int overlaps(AlprPlateResult plate, std::vector<std::vector<AlprPlateResult> > clusters);
};
}
#endif //OPENALPR_RESULTAGGREGATOR_H

View File

@@ -578,5 +578,22 @@ int levenshteinDistance (const std::string &s1, const std::string &s2, int max)
ss << value; ss << value;
return ss.str(); return ss.str();
} }
// trim from start
std::string &ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
return s;
}
// trim from end
std::string &rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
return s;
}
// trim from both ends
std::string &trim(std::string &s) {
return ltrim(rtrim(s));
}
} }

View File

@@ -109,6 +109,9 @@ namespace alpr
std::string toString(float value); std::string toString(float value);
std::string toString(double value); std::string toString(double value);
std::string &ltrim(std::string &s);
std::string &rtrim(std::string &s);
std::string &trim(std::string &s);
} }
#endif // OPENALPR_UTILITY_H #endif // OPENALPR_UTILITY_H

View File

@@ -2,7 +2,8 @@ enable_testing()
ADD_EXECUTABLE( unittests ADD_EXECUTABLE( unittests
test_api.cpp test_api.cpp
test_utility.cpp test_utility.cpp
test_config.cpp
test_regex.cpp test_regex.cpp
) )

53
src/tests/test_config.cpp Normal file
View File

@@ -0,0 +1,53 @@
/*
* File: utility_tests.cpp
* Author: mhill
*
* Created on October 23, 2014, 10:16 PM
*/
#include <cstdlib>
#include "catch.hpp"
#include "config.h"
using namespace std;
using namespace alpr;
Config get_config(std::string countries)
{
Config config(countries, "", "");
return config;
}
TEST_CASE( "Loading Countries", "[Config]" ) {
REQUIRE( get_config("us,eu").loaded_countries.size() == 2 );
REQUIRE( get_config("us").loaded_countries.size() == 1 );
REQUIRE( get_config("eu").loaded_countries.size() == 1 );
REQUIRE( get_config("us, eu").loaded_countries.size() == 2 );
REQUIRE( get_config("us, eu, au, kr").loaded_countries.size() == 4 );
REQUIRE( get_config("us , eu").loaded_countries.size() == 2 );
REQUIRE( get_config("us,eu,").loaded_countries.size() == 2 );
REQUIRE( get_config(",,us,eu,").loaded_countries.size() == 2 );
}
//TEST_CASE( "Modifying Countries", "[Config]" )
//{
// Config config("us,eu", "/etc/openalpr/openalpr.conf", "/usr/share/openalpr/runtime_data/");
//
// REQUIRE(config.ocrLanguage == "lus");
//
// config.setCountry("eu");
// REQUIRE(config.ocrLanguage == "leu");
//
// config.setCountry("us");
// REQUIRE(config.ocrLanguage == "lus");
//}