mirror of
https://github.com/kerberos-io/openalpr-base.git
synced 2025-10-06 19:52:50 +08:00
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:
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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)
|
||||||
{
|
{
|
||||||
|
@@ -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);
|
||||||
|
190
src/openalpr/result_aggregator.cpp
Normal file
190
src/openalpr/result_aggregator.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
62
src/openalpr/result_aggregator.h
Normal file
62
src/openalpr/result_aggregator.h
Normal 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
|
@@ -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 <rim(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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -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 <rim(std::string &s);
|
||||||
|
std::string &rtrim(std::string &s);
|
||||||
|
std::string &trim(std::string &s);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // OPENALPR_UTILITY_H
|
#endif // OPENALPR_UTILITY_H
|
||||||
|
@@ -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
53
src/tests/test_config.cpp
Normal 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");
|
||||||
|
//}
|
Reference in New Issue
Block a user