diff --git a/src/openalpr/CMakeLists.txt b/src/openalpr/CMakeLists.txt index 1424ad7..2219465 100644 --- a/src/openalpr/CMakeLists.txt +++ b/src/openalpr/CMakeLists.txt @@ -35,6 +35,7 @@ set(lpr_source_files pipeline_data.cpp cjson.c motiondetector.cpp + result_aggregator.cpp ) diff --git a/src/openalpr/alpr_impl.cpp b/src/openalpr/alpr_impl.cpp index e065f67..a0ef23f 100644 --- a/src/openalpr/alpr_impl.cpp +++ b/src/openalpr/alpr_impl.cpp @@ -18,6 +18,7 @@ */ #include "alpr_impl.h" +#include "result_aggregator.h" void plateAnalysisThread(void* arg); @@ -34,11 +35,9 @@ namespace alpr getTimeMonotonic(&startTime); config = new Config(country, configFile, runtimeDir); - - plateDetector = ALPR_NULL_PTR; - stateDetector = ALPR_NULL_PTR; - ocr = ALPR_NULL_PTR; + prewarp = ALPR_NULL_PTR; + // Config file or runtime dir not found. Don't process any further. if (config->loaded == false) @@ -46,8 +45,24 @@ namespace alpr return; } - plateDetector = createDetector(config); - ocr = new OCR(config); + for (unsigned int i = 0; i < config->loaded_countries.size(); i++) + { + 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); setDetectRegion(DEFAULT_DETECT_REGION); @@ -62,18 +77,24 @@ namespace alpr cout << "OpenALPR Initialization Time: " << diffclock(startTime, endTime) << "ms." << endl; } + AlprImpl::~AlprImpl() { delete config; - if (plateDetector != ALPR_NULL_PTR) - delete plateDetector; + typedef std::map::iterator it_type; + for(it_type iterator = recognizers.begin(); iterator != recognizers.end(); iterator++) { - if (stateDetector != ALPR_NULL_PTR) - delete stateDetector; + if (iterator->second.plateDetector != ALPR_NULL_PTR) + 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) delete prewarp; @@ -126,174 +147,24 @@ namespace alpr // Warp the image if prewarp is provided grayImg = prewarp->warpImage(grayImg); warpedRegionsOfInterest = prewarp->projectRects(regionsOfInterest, grayImg.cols, grayImg.rows, false); - - vector warpedPlateRegions; - // Find all the candidate regions - if (config->skipDetection == false) + + // Iterate through each country provided (typically just one) + // and aggregate the results if necessary + ResultAggregator aggregator; + for (unsigned int i = 0; i < config->loaded_countries.size(); i++) { - warpedPlateRegions = 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); - } + if (config->debugGeneral) + cout << "Analyzing: " << config->loaded_countries[i] << endl; + + config->setCountry(config->loaded_countries[i]); + AlprFullDetails sub_results = analyzeSingleCountry(img, grayImg, warpedRegionsOfInterest); + + aggregator.addResults(sub_results); } + response = aggregator.getAggregateResults(); - queue 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 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 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 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 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); - if (config->debugTiming) { cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl; @@ -352,7 +223,183 @@ namespace alpr return response; } + AlprFullDetails AlprImpl::analyzeSingleCountry(cv::Mat colorImg, cv::Mat grayImg, std::vector warpedRegionsOfInterest) + { + AlprFullDetails response; + AlprRecognizers country_recognizers = recognizers[config->country]; + timespec startTime; + getTimeMonotonic(&startTime); + + vector 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 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 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 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 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 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 imageBytes) { @@ -596,18 +643,6 @@ namespace alpr { 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; - } } diff --git a/src/openalpr/alpr_impl.h b/src/openalpr/alpr_impl.h index 6941fd1..cc22fd4 100644 --- a/src/openalpr/alpr_impl.h +++ b/src/openalpr/alpr_impl.h @@ -66,6 +66,13 @@ namespace alpr AlprResults results; }; + struct AlprRecognizers + { + Detector* plateDetector; + StateDetector* stateDetector; + OCR* ocr; + }; + class AlprImpl { @@ -83,6 +90,8 @@ namespace alpr void applyRegionTemplate(AlprPlateResult* result, std::string region); + AlprFullDetails analyzeSingleCountry(cv::Mat colorImg, cv::Mat grayImg, std::vector regionsOfInterest); + void setDetectRegion(bool detectRegion); void setTopN(int topn); void setDefaultRegion(std::string region); @@ -99,9 +108,8 @@ namespace alpr private: - Detector* plateDetector; - StateDetector* stateDetector; - OCR* ocr; + std::map recognizers; + PreWarp* prewarp; int topN; diff --git a/src/openalpr/config.cpp b/src/openalpr/config.cpp index 3cc3049..8c93106 100644 --- a/src/openalpr/config.cpp +++ b/src/openalpr/config.cpp @@ -21,6 +21,7 @@ #include "support/filesystem.h" #include "support/platform.h" #include "simpleini/simpleini.h" +#include "utility.h" using namespace std; @@ -88,9 +89,6 @@ namespace alpr } - this->country = country; - - loadCommonValues(configFile); if (runtime_dir.compare("") != 0) @@ -115,21 +113,24 @@ namespace alpr return; } - 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; - } - - loadCountryValues(country_config_file, country); + this->loaded_countries = this->parse_country_string(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; } - + 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) { std::cout << debug_message << endl; @@ -309,7 +310,42 @@ namespace alpr } + std::vector Config::parse_country_string(std::string countries) + { + std::istringstream ss(countries); + std::string token; + std::vector 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) { diff --git a/src/openalpr/config.h b/src/openalpr/config.h index 3475f33..268ffde 100644 --- a/src/openalpr/config.h +++ b/src/openalpr/config.h @@ -26,6 +26,7 @@ #include #include +#include #include /* getenv */ #include @@ -130,11 +131,16 @@ namespace alpr std::string runtimeBaseDir; + std::vector loaded_countries; + + bool setCountry(std::string country); + private: float ocrImagePercent; float stateIdImagePercent; + std::vector parse_country_string(std::string countries); void loadCommonValues(std::string configFile); void loadCountryValues(std::string configFile, std::string country); diff --git a/src/openalpr/result_aggregator.cpp b/src/openalpr/result_aggregator.cpp new file mode 100644 index 0000000..38fe800 --- /dev/null +++ b/src/openalpr/result_aggregator.cpp @@ -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 . +*/ + +#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 > 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 > ResultAggregator::findClusters() + { + std::vector > 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 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 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 > 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; + } +} \ No newline at end of file diff --git a/src/openalpr/result_aggregator.h b/src/openalpr/result_aggregator.h new file mode 100644 index 0000000..837cd1f --- /dev/null +++ b/src/openalpr/result_aggregator.h @@ -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 . +*/ + +#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 all_results; + + PlateShapeInfo getShapeInfo(AlprPlateResult plate); + + std::vector > findClusters(); + int overlaps(AlprPlateResult plate, std::vector > clusters); + }; + +} + +#endif //OPENALPR_RESULTAGGREGATOR_H diff --git a/src/openalpr/utility.cpp b/src/openalpr/utility.cpp index bd1c1be..ac370cf 100644 --- a/src/openalpr/utility.cpp +++ b/src/openalpr/utility.cpp @@ -578,5 +578,22 @@ int levenshteinDistance (const std::string &s1, const std::string &s2, int max) ss << value; 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(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(std::isspace))).base(), s.end()); + return s; + } + +// trim from both ends + std::string &trim(std::string &s) { + return ltrim(rtrim(s)); + } + } \ No newline at end of file diff --git a/src/openalpr/utility.h b/src/openalpr/utility.h index 642fce2..b2743b7 100644 --- a/src/openalpr/utility.h +++ b/src/openalpr/utility.h @@ -109,6 +109,9 @@ namespace alpr std::string toString(float 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 diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 739553b..818df57 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -2,7 +2,8 @@ enable_testing() ADD_EXECUTABLE( unittests test_api.cpp - test_utility.cpp + test_utility.cpp + test_config.cpp test_regex.cpp ) diff --git a/src/tests/test_config.cpp b/src/tests/test_config.cpp new file mode 100644 index 0000000..c207796 --- /dev/null +++ b/src/tests/test_config.cpp @@ -0,0 +1,53 @@ +/* + * File: utility_tests.cpp + * Author: mhill + * + * Created on October 23, 2014, 10:16 PM + */ + +#include +#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"); +//} \ No newline at end of file