diff --git a/runtime_data/openalpr.conf b/runtime_data/openalpr.conf
index 3a39856..e352b6d 100644
--- a/runtime_data/openalpr.conf
+++ b/runtime_data/openalpr.conf
@@ -14,6 +14,8 @@ max_plate_height_percent = 100
detection_iteration_increase = 1.1
opencl_enabled = 0
+multithreading_cores = 1
+
ocr_min_font_point = 6
diff --git a/src/misc_utilities/CMakeLists.txt b/src/misc_utilities/CMakeLists.txt
index 3151749..f2786b9 100644
--- a/src/misc_utilities/CMakeLists.txt
+++ b/src/misc_utilities/CMakeLists.txt
@@ -4,33 +4,35 @@ target_link_libraries(openalpr)
ADD_EXECUTABLE( sortstate sortstate.cpp )
-TARGET_LINK_LIBRARIES(sortstate
+TARGET_LINK_LIBRARIES(sortstate
openalpr
support
- ${OpenCV_LIBS}
+ ${OpenCV_LIBS}
tesseract
)
-
+
ADD_EXECUTABLE( classifychars classifychars.cpp )
-TARGET_LINK_LIBRARIES(classifychars
+TARGET_LINK_LIBRARIES(classifychars
openalpr
support
- ${OpenCV_LIBS}
+ ${OpenCV_LIBS}
tesseract
)
-
-
+
+
ADD_EXECUTABLE( benchmark benchmark.cpp )
-TARGET_LINK_LIBRARIES(benchmark
+TARGET_LINK_LIBRARIES(benchmark
openalpr
support
- ${OpenCV_LIBS}
+ ${OpenCV_LIBS}
tesseract
)
-
-
+
+
ADD_EXECUTABLE( prepcharsfortraining prepcharsfortraining.cpp )
-TARGET_LINK_LIBRARIES(prepcharsfortraining
+TARGET_LINK_LIBRARIES(prepcharsfortraining
support
- ${OpenCV_LIBS}
+ ${OpenCV_LIBS}
)
+
+
\ No newline at end of file
diff --git a/src/openalpr/CMakeLists.txt b/src/openalpr/CMakeLists.txt
index 9f01fab..2b5f3ea 100644
--- a/src/openalpr/CMakeLists.txt
+++ b/src/openalpr/CMakeLists.txt
@@ -23,9 +23,10 @@ set(lpr_source_files
verticalhistogram.cpp
trex.c
cjson.c
+ tinythread/tinythread.cpp
)
-
+
add_subdirectory(simpleini)
add_subdirectory(support)
@@ -35,4 +36,4 @@ add_library(openalpr ${lpr_source_files})
# Add definition for default runtime dir
-add_definitions(-DDEFAULT_RUNTIME_DIR="${CMAKE_SOURCE_DIR}/../runtime_data/")
+add_definitions(-DDEFAULT_RUNTIME_DIR="${CMAKE_SOURCE_DIR}/../runtime_data/")
\ No newline at end of file
diff --git a/src/openalpr/alpr_impl.cpp b/src/openalpr/alpr_impl.cpp
index ecc3e8c..62927ed 100644
--- a/src/openalpr/alpr_impl.cpp
+++ b/src/openalpr/alpr_impl.cpp
@@ -1,55 +1,59 @@
/*
* Copyright (c) 2013 New Designs Unlimited, LLC
* Opensource 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
- *
+ * 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 "alpr_impl.h"
+void plateAnalysisThread(void* arg);
+
AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir)
{
config = new Config(country, runtimeDir);
plateDetector = new RegionDetector(config);
stateIdentifier = new StateIdentifier(config);
ocr = new OCR(config);
-
+ setNumThreads(0);
+
this->detectRegion = DEFAULT_DETECT_REGION;
this->topN = DEFAULT_TOPN;
this->defaultRegion = "";
-
+
if (config->opencl_enabled)
{
+
cv::ocl::PlatformsInfo platinfo;
cv::ocl::getOpenCLPlatforms(platinfo);
-
+
for (int i = 0; i < platinfo.size(); i++)
{
- std::cout << platinfo[i]->platformName << std::endl;
+ std::cout << platinfo[i]->platformName << std::endl;
}
-
+
cv::ocl::DevicesInfo devices;
cv::ocl::getOpenCLDevices(devices, cv::ocl::CVCL_DEVICE_TYPE_CPU);
-
+
for (int i = 0; i < devices.size(); i++)
std:: cout << devices[i]->deviceName << std::endl;
-
+
if (devices.size() > 0)
{
cv::ocl::setDevice(devices[0]);
-
+
cout << "Using OpenCL Device: " << devices[0]->deviceName << endl;
}
else
@@ -58,7 +62,6 @@ AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir)
}
}
}
-
AlprImpl::~AlprImpl()
{
delete config;
@@ -67,100 +70,43 @@ AlprImpl::~AlprImpl()
delete ocr;
}
+
std::vector AlprImpl::recognize(cv::Mat img)
{
timespec startTime;
getTime(&startTime);
+
- vector response;
+ // Find all the candidate regions
vector plateRegions = plateDetector->detect(img);
- // Recognize.
+ // Get the number of threads specified and make sure the value is sane (cannot be greater than CPU cores or less than 1)
+ int numThreads = config->multithreading_cores;
+ if (numThreads > tthread::thread::hardware_concurrency())
+ numThreads = tthread::thread::hardware_concurrency();
+ if (numThreads <= 0)
+ numThreads = 1;
- for (int i = 0; i < plateRegions.size(); i++)
+
+ PlateDispatcher dispatcher(plateRegions, &img,
+ config, stateIdentifier, ocr,
+ topN, detectRegion, defaultRegion);
+
+ // Spawn n threads to process all of the candidate regions and recognize
+ list threads;
+ for (int i = 0; i < numThreads; i++)
{
- timespec platestarttime;
- getTime(&platestarttime);
-
- LicensePlateCandidate lp(img, plateRegions[i], config);
-
- lp.recognize();
-
- if (lp.confidence > 10)
- {
- AlprResult plateResult;
- plateResult.region = defaultRegion;
- plateResult.regionConfidence = 0;
-
- for (int pointidx = 0; pointidx < 4; pointidx++)
- {
- plateResult.plate_points[pointidx].x = (int) lp.plateCorners[pointidx].x;
- plateResult.plate_points[pointidx].y = (int) lp.plateCorners[pointidx].y;
- }
-
- if (detectRegion)
- {
- char statecode[4];
- plateResult.regionConfidence = stateIdentifier->recognize(img, plateRegions[i], statecode);
- if (plateResult.regionConfidence > 0)
- {
- plateResult.region = statecode;
- }
- }
-
- ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters);
-
- ocr->postProcessor->analyze(plateResult.region, topN);
-
- //plateResult.characters = ocr->postProcessor->bestChars;
- const vector ppResults = ocr->postProcessor->getResults();
-
- int bestPlateIndex = 0;
-
- for (int pp = 0; pp < ppResults.size(); pp++)
- {
- if (pp >= topN)
- break;
-
- // Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match
- if (bestPlateIndex == 0 && ppResults[pp].matchesTemplate)
- bestPlateIndex = pp;
-
- if (ppResults[pp].letters.size() >= config->postProcessMinCharacters &&
- ppResults[pp].letters.size() <= config->postProcessMaxCharacters)
- {
- AlprPlate aplate;
- aplate.characters = ppResults[pp].letters;
- aplate.overall_confidence = ppResults[pp].totalscore;
- aplate.matches_template = ppResults[pp].matchesTemplate;
- plateResult.topNPlates.push_back(aplate);
- }
- }
- plateResult.result_count = plateResult.topNPlates.size();
-
- if (plateResult.topNPlates.size() > 0)
- plateResult.bestPlate = plateResult.topNPlates[bestPlateIndex];
-
- timespec plateEndTime;
- getTime(&plateEndTime);
- plateResult.processing_time_ms = diffclock(platestarttime, plateEndTime);
-
- if (plateResult.result_count > 0)
- response.push_back(plateResult);
-
- if (config->debugGeneral)
- {
- rectangle(img, plateRegions[i], Scalar(0, 255, 0), 2);
- for (int z = 0; z < 4; z++)
- line(img, lp.plateCorners[z], lp.plateCorners[(z + 1) % 4], Scalar(255,0,255), 2);
- }
- }
- else
- {
- if (config->debugGeneral)
- rectangle(img, plateRegions[i], Scalar(0, 0, 255), 2);
- }
+ tthread::thread * t = new tthread::thread(plateAnalysisThread, (void *) &dispatcher);
+ threads.push_back(t);
+ }
+
+ // Wait for all threads to finish
+ for(list::iterator i = threads.begin(); i != threads.end(); ++ i)
+ {
+ tthread::thread* t = *i;
+ t->join();
+ delete t;
}
if (config->debugTiming)
@@ -169,7 +115,7 @@ std::vector AlprImpl::recognize(cv::Mat img)
getTime(&endTime);
cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl;
}
-
+
if (config->debugGeneral && config->debugShowImages)
{
displayImage(config, "Main Image", img);
@@ -177,47 +123,175 @@ std::vector AlprImpl::recognize(cv::Mat img)
while ((char) cv::waitKey(50) == -1)
{}
}
- return response;
+
+ // if (config->debugGeneral)
+// {
+// rectangle(img, plateRegion, Scalar(0, 255, 0), 2);
+// for (int z = 0; z < 4; z++)
+// line(img, lp.plateCorners[z], lp.plateCorners[(z + 1) % 4], Scalar(255,0,255), 2);
+// }
+
+// if (config->debugGeneral)
+// rectangle(img, plateRegion, Scalar(0, 0, 255), 2);
+
+ return dispatcher.getRecognitionResults();
+}
+
+void plateAnalysisThread(void* arg)
+{
+ PlateDispatcher* dispatcher = (PlateDispatcher*) arg;
+ if (dispatcher->config->debugGeneral)
+ cout << "Thread: " << tthread::this_thread::get_id() << " Initialized" << endl;
+
+ int loop_count = 0;
+ while (true)
+ {
+
+ if (dispatcher->hasPlate() == false)
+ break;
+
+ // Synchronized section
+ if (dispatcher->config->debugGeneral)
+ cout << "Thread: " << tthread::this_thread::get_id() << " loop " << ++loop_count << endl;
+
+ // Get a single plate region from the queue
+ Rect plateRegion = dispatcher->nextPlate();
+
+ Mat img = dispatcher->getImageCopy();
+
+
+ // Parallel section
+ timespec platestarttime;
+ getTime(&platestarttime);
+
+ LicensePlateCandidate lp(img, plateRegion, dispatcher->config);
+
+ lp.recognize();
+
+
+ if (lp.confidence > 10)
+ {
+ AlprResult plateResult;
+ plateResult.region = dispatcher->defaultRegion;
+ plateResult.regionConfidence = 0;
+
+ for (int pointidx = 0; pointidx < 4; pointidx++)
+ {
+ plateResult.plate_points[pointidx].x = (int) lp.plateCorners[pointidx].x;
+ plateResult.plate_points[pointidx].y = (int) lp.plateCorners[pointidx].y;
+ }
+
+ if (dispatcher->detectRegion)
+ {
+ char statecode[4];
+ plateResult.regionConfidence = dispatcher->stateIdentifier->recognize(img, plateRegion, statecode);
+ if (plateResult.regionConfidence > 0)
+ {
+ plateResult.region = statecode;
+ }
+ }
+
+
+ dispatcher->ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters);
+
+ dispatcher->ocr->postProcessor->analyze(plateResult.region, dispatcher->topN);
+
+ //plateResult.characters = ocr->postProcessor->bestChars;
+ const vector ppResults = dispatcher->ocr->postProcessor->getResults();
+
+ int bestPlateIndex = 0;
+
+ for (int pp = 0; pp < ppResults.size(); pp++)
+ {
+ if (pp >= dispatcher->topN)
+ break;
+
+ // Set our "best plate" match to either the first entry, or the first entry with a postprocessor template match
+ if (bestPlateIndex == 0 && ppResults[pp].matchesTemplate)
+ bestPlateIndex = pp;
+
+ if (ppResults[pp].letters.size() >= dispatcher->config->postProcessMinCharacters &&
+ ppResults[pp].letters.size() <= dispatcher->config->postProcessMaxCharacters)
+ {
+ AlprPlate aplate;
+ aplate.characters = ppResults[pp].letters;
+ aplate.overall_confidence = ppResults[pp].totalscore;
+ aplate.matches_template = ppResults[pp].matchesTemplate;
+ plateResult.topNPlates.push_back(aplate);
+ }
+ }
+ plateResult.result_count = plateResult.topNPlates.size();
+
+ if (plateResult.topNPlates.size() > 0)
+ plateResult.bestPlate = plateResult.topNPlates[bestPlateIndex];
+
+ timespec plateEndTime;
+ getTime(&plateEndTime);
+ plateResult.processing_time_ms = diffclock(platestarttime, plateEndTime);
+
+ if (plateResult.result_count > 0)
+ {
+ // Synchronized section
+ dispatcher->addResult(plateResult);
+
+ }
+
+
+ }
+
+ if (dispatcher->config->debugTiming)
+ {
+ timespec plateEndTime;
+ getTime(&plateEndTime);
+ cout << "Thread: " << tthread::this_thread::get_id() << " Finished loop " << loop_count << " in " << diffclock(platestarttime, plateEndTime) << "ms." << endl;
+ }
+
+ }
+
+ if (dispatcher->config->debugGeneral)
+ cout << "Thread: " << tthread::this_thread::get_id() << " Complete" << endl;
}
string AlprImpl::toJson(const vector< AlprResult > results)
{
- cJSON *root = cJSON_CreateArray();
-
+ cJSON *root = cJSON_CreateArray();
+
for (int i = 0; i < results.size(); i++)
{
cJSON *resultObj = createJsonObj( &results[i] );
cJSON_AddItemToArray(root, resultObj);
}
-
+
// Print the JSON object to a string and return
char *out;
out=cJSON_PrintUnformatted(root);
cJSON_Delete(root);
-
+
string response(out);
-
+
free(out);
return response;
}
+
+
cJSON* AlprImpl::createJsonObj(const AlprResult* result)
{
cJSON *root, *coords, *candidates;
-
- root=cJSON_CreateObject();
-
+
+ root=cJSON_CreateObject();
+
cJSON_AddStringToObject(root,"plate", result->bestPlate.characters.c_str());
cJSON_AddNumberToObject(root,"confidence", result->bestPlate.overall_confidence);
cJSON_AddNumberToObject(root,"matches_template", result->bestPlate.matches_template);
-
+
cJSON_AddStringToObject(root,"region", result->region.c_str());
cJSON_AddNumberToObject(root,"region_confidence", result->regionConfidence);
-
+
cJSON_AddNumberToObject(root,"processing_time_ms", result->processing_time_ms);
-
+
cJSON_AddItemToObject(root, "coordinates", coords=cJSON_CreateArray());
- for (int i=0; i<4; i++)
+ for (int i=0;i<4;i++)
{
cJSON *coords_object;
coords_object = cJSON_CreateObject();
@@ -226,7 +300,8 @@ cJSON* AlprImpl::createJsonObj(const AlprResult* result)
cJSON_AddItemToArray(coords, coords_object);
}
-
+
+
cJSON_AddItemToObject(root, "candidates", candidates=cJSON_CreateArray());
for (int i = 0; i < result->topNPlates.size(); i++)
{
@@ -238,21 +313,21 @@ cJSON* AlprImpl::createJsonObj(const AlprResult* result)
cJSON_AddItemToArray(candidates, candidate_object);
}
-
+
return root;
}
+
void AlprImpl::setDetectRegion(bool detectRegion)
{
this->detectRegion = detectRegion;
}
-
void AlprImpl::setTopN(int topn)
{
this->topN = topn;
}
-
void AlprImpl::setDefaultRegion(string region)
{
this->defaultRegion = region;
}
+
diff --git a/src/openalpr/alpr_impl.h b/src/openalpr/alpr_impl.h
index 41bc61c..1dadd25 100644
--- a/src/openalpr/alpr_impl.h
+++ b/src/openalpr/alpr_impl.h
@@ -1,25 +1,28 @@
/*
* Copyright (c) 2013 New Designs Unlimited, LLC
* Opensource 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
- *
+ * 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 ALPRIMPL_H
#define ALPRIMPL_H
+#include
+
#include "alpr.h"
#include "config.h"
@@ -33,6 +36,9 @@
#include
#include "opencv2/ocl/ocl.hpp"
+
+
+#include "tinythread/tinythread.h"
#define DEFAULT_TOPN 25
#define DEFAULT_DETECT_REGION false
@@ -45,28 +51,105 @@ class AlprImpl
virtual ~AlprImpl();
std::vector recognize(cv::Mat img);
-
+
void applyRegionTemplate(AlprResult* result, std::string region);
-
+
void setDetectRegion(bool detectRegion);
void setTopN(int topn);
void setDefaultRegion(string region);
-
+
std::string toJson(const vector results);
-
+
Config* config;
-
+
private:
-
+
RegionDetector* plateDetector;
StateIdentifier* stateIdentifier;
OCR* ocr;
-
+
int topN;
bool detectRegion;
std::string defaultRegion;
-
+
cJSON* createJsonObj(const AlprResult* result);
};
-#endif // ALPRIMPL_H
+class PlateDispatcher
+{
+ public:
+ PlateDispatcher(vector plateRegions, cv::Mat* image,
+ Config* config,
+ StateIdentifier* stateIdentifier,
+ OCR* ocr,
+ int topN, bool detectRegion, std::string defaultRegion)
+ {
+ this->plateRegions = plateRegions;
+ this->frame = image;
+
+ this->config = config;
+ this->stateIdentifier = stateIdentifier;
+ this->ocr = ocr;
+ this->topN = topN;
+ this->detectRegion = detectRegion;
+ this->defaultRegion = defaultRegion;
+ }
+
+ cv::Mat getImageCopy()
+ {
+ tthread::lock_guard guard(mMutex);
+
+ Mat img(this->frame->size(), this->frame->type());
+ this->frame->copyTo(img);
+
+ return img;
+ }
+
+ bool hasPlate()
+ {
+ bool plateAvailable;
+ mMutex.lock();
+ plateAvailable = plateRegions.size() > 0;
+ mMutex.unlock();
+ return plateAvailable;
+ }
+ Rect nextPlate()
+ {
+ tthread::lock_guard guard(mMutex);
+
+ Rect plateRegion = plateRegions[plateRegions.size() - 1];
+ plateRegions.pop_back();
+
+ return plateRegion;
+ }
+
+ void addResult(AlprResult recognitionResult)
+ {
+ tthread::lock_guard guard(mMutex);
+ recognitionResults.push_back(recognitionResult);
+ }
+
+ vector getRecognitionResults()
+ {
+ return recognitionResults;
+ }
+
+
+ StateIdentifier* stateIdentifier;
+ OCR* ocr;
+ Config* config;
+
+ int topN;
+ bool detectRegion;
+ std::string defaultRegion;
+
+ private:
+
+ tthread::mutex mMutex;
+ cv::Mat* frame;
+ vector plateRegions;
+ vector recognitionResults;
+
+};
+
+#endif // ALPRIMPL_H
\ No newline at end of file
diff --git a/src/openalpr/config.cpp b/src/openalpr/config.cpp
index cc9c24f..f65965b 100644
--- a/src/openalpr/config.cpp
+++ b/src/openalpr/config.cpp
@@ -1,35 +1,37 @@
/*
* Copyright (c) 2013 New Designs Unlimited, LLC
* Opensource 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
- *
+ * 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 "config.h"
+
Config::Config(const std::string country, const std::string runtimeBaseDir)
{
this->runtimeBaseDir = runtimeBaseDir;
-
+
ini = new CSimpleIniA();
-
+
char* envRuntimeDir;
envRuntimeDir = getenv (ENV_VARIABLE_RUNTIME_DIR);
if (runtimeBaseDir.compare("") != 0)
{
- // User has supplied a runtime directory. Use that.
+ // User has supplied a runtime directory. Use that.
+
}
else if (envRuntimeDir!=NULL)
{
@@ -41,9 +43,9 @@ Config::Config(const std::string country, const std::string runtimeBaseDir)
// Use the default
this->runtimeBaseDir = DEFAULT_RUNTIME_DIR;
}
-
+
string configFile = (this->runtimeBaseDir + CONFIG_FILE);
-
+
if (DirectoryExists(this->runtimeBaseDir.c_str()) == false)
{
std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not exist!" << endl;
@@ -54,14 +56,13 @@ Config::Config(const std::string country, const std::string runtimeBaseDir)
std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not contain a config file '" << CONFIG_FILE << "'!" << endl;
return;
}
-
+
ini->LoadFile(configFile.c_str());
-
+
this->country = country;
-
+
loadValues(country);
}
-
Config::~Config()
{
delete ini;
@@ -69,56 +70,59 @@ Config::~Config()
void Config::loadValues(string country)
{
+
opencl_enabled = getBoolean("common", "opencl_enabled", false);
-
+ multithreading_cores = getInt("common", "multithreading_cores", 1);
+
detection_iteration_increase = getFloat("common", "detection_iteration_increase", 1.1);
-
maxPlateWidthPercent = getFloat("common", "max_plate_width_percent", 100);
maxPlateHeightPercent = getFloat("common", "max_plate_height_percent", 100);
minPlateSizeWidthPx = getInt(country, "min_plate_size_width_px", 100);
minPlateSizeHeightPx = getInt(country, "min_plate_size_height_px", 100);
-
+
plateWidthMM = getFloat(country, "plate_width_mm", 100);
plateHeightMM = getFloat(country, "plate_height_mm", 100);
-
+
charHeightMM = getFloat(country, "char_height_mm", 100);
charWidthMM = getFloat(country, "char_width_mm", 100);
charWhitespaceTopMM = getFloat(country, "char_whitespace_top_mm", 100);
charWhitespaceBotMM = getFloat(country, "char_whitespace_bot_mm", 100);
-
+
templateWidthPx = getInt(country, "template_max_width_px", 100);
templateHeightPx = getInt(country, "template_max_height_px", 100);
-
+
float ocrImagePercent = getFloat("common", "ocr_img_size_percent", 100);
ocrImageWidthPx = round(((float) templateWidthPx) * ocrImagePercent);
ocrImageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
+
float stateIdImagePercent = getFloat("common", "state_id_img_size_percent", 100);
stateIdImageWidthPx = round(((float)templateWidthPx) * ocrImagePercent);
stateIdimageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
+
charAnalysisMinPercent = getFloat(country, "char_analysis_min_pct", 0);
charAnalysisHeightRange = getFloat(country, "char_analysis_height_range", 0);
charAnalysisHeightStepSize = getFloat(country, "char_analysis_height_step_size", 0);
charAnalysisNumSteps = getInt(country, "char_analysis_height_num_steps", 0);
-
+
segmentationMinBoxWidthPx = getInt(country, "segmentation_min_box_width_px", 0);
segmentationMinCharHeightPercent = getFloat(country, "segmentation_min_charheight_percent", 0);
segmentationMaxCharWidthvsAverage = getFloat(country, "segmentation_max_segment_width_percent_vs_average", 0);
-
+
plateLinesSensitivityVertical = getFloat(country, "plateline_sensitivity_vertical", 0);
plateLinesSensitivityHorizontal = getFloat(country, "plateline_sensitivity_horizontal", 0);
-
+
ocrLanguage = getString(country, "ocr_language", "none");
ocrMinFontSize = getInt("common", "ocr_min_font_point", 100);
-
+
postProcessMinConfidence = getFloat("common", "postprocess_min_confidence", 100);
postProcessConfidenceSkipLevel = getFloat("common", "postprocess_confidence_skip_level", 100);
postProcessMaxSubstitutions = getInt("common", "postprocess_max_substitutions", 100);
- postProcessMinCharacters = getInt("common", "postprocess_min_characters", 100);
- postProcessMaxCharacters = getInt("common", "postprocess_max_characters", 100);
-
+ postProcessMinCharacters = getInt("common", "postprocess_min_characers", 100);
+ postProcessMaxCharacters = getInt("common", "postprocess_max_characers", 100);
+
debugGeneral = getBoolean("debug", "general", false);
debugTiming = getBoolean("debug", "timing", false);
debugStateId = getBoolean("debug", "state_id", false);
@@ -131,6 +135,7 @@ void Config::loadValues(string country)
debugOcr = getBoolean("debug", "ocr", false);
debugPostProcess = getBoolean("debug", "postprocess", false);
debugShowImages = getBoolean("debug", "show_images", false);
+
}
void Config::debugOff()
@@ -148,26 +153,27 @@ void Config::debugOff()
debugPostProcess = false;
}
+
string Config::getCascadeRuntimeDir()
{
return this->runtimeBaseDir + CASCADE_DIR;
}
-
string Config::getKeypointsRuntimeDir()
{
return this->runtimeBaseDir + KEYPOINTS_DIR;
}
-
string Config::getPostProcessRuntimeDir()
{
return this->runtimeBaseDir + POSTPROCESS_DIR;
}
-
string Config::getTessdataPrefix()
{
return "TESSDATA_PREFIX=" + this->runtimeBaseDir + "/ocr/";
}
+
+
+
float Config::getFloat(string section, string key, float defaultValue)
{
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
@@ -176,11 +182,10 @@ float Config::getFloat(string section, string key, float defaultValue)
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
return defaultValue;
}
-
+
float val = atof(pszValue);
return val;
}
-
int Config::getInt(string section, string key, int defaultValue)
{
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
@@ -189,11 +194,10 @@ int Config::getInt(string section, string key, int defaultValue)
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
return defaultValue;
}
-
+
int val = atoi(pszValue);
return val;
}
-
bool Config::getBoolean(string section, string key, bool defaultValue)
{
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
@@ -202,11 +206,10 @@ bool Config::getBoolean(string section, string key, bool defaultValue)
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
return defaultValue;
}
-
+
int val = atoi(pszValue);
return val != 0;
}
-
string Config::getString(string section, string key, string defaultValue)
{
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
@@ -215,7 +218,7 @@ string Config::getString(string section, string key, string defaultValue)
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
return defaultValue;
}
-
+
string val = string(pszValue);
return val;
}
diff --git a/src/openalpr/config.h b/src/openalpr/config.h
index e2e9baf..27614a2 100644
--- a/src/openalpr/config.h
+++ b/src/openalpr/config.h
@@ -1,25 +1,27 @@
/*
* Copyright (c) 2013 New Designs Unlimited, LLC
* Opensource 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
- *
+ * 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 CONFIG_H
#define CONFIG_H
+
#include "simpleini/simpleini.h"
#include "support/filesystem.h"
@@ -40,55 +42,56 @@ class Config
virtual ~Config();
string country;
-
- bool opencl_enabled;
-
- float detection_iteration_increase;
+ bool opencl_enabled;
+ int multithreading_cores;
+
+ float detection_iteration_increase;
float maxPlateWidthPercent;
float maxPlateHeightPercent;
-
+
float minPlateSizeWidthPx;
float minPlateSizeHeightPx;
-
+
float plateWidthMM;
float plateHeightMM;
-
+
float charHeightMM;
float charWidthMM;
float charWhitespaceTopMM;
float charWhitespaceBotMM;
-
+
int templateWidthPx;
int templateHeightPx;
-
+
int ocrImageWidthPx;
int ocrImageHeightPx;
-
+
int stateIdImageWidthPx;
int stateIdimageHeightPx;
-
+
float charAnalysisMinPercent;
float charAnalysisHeightRange;
float charAnalysisHeightStepSize;
int charAnalysisNumSteps;
-
+
float plateLinesSensitivityVertical;
float plateLinesSensitivityHorizontal;
int segmentationMinBoxWidthPx;
float segmentationMinCharHeightPercent;
float segmentationMaxCharWidthvsAverage;
-
+
string ocrLanguage;
int ocrMinFontSize;
-
+
float postProcessMinConfidence;
float postProcessConfidenceSkipLevel;
int postProcessMaxSubstitutions;
int postProcessMinCharacters;
int postProcessMaxCharacters;
+
bool debugGeneral;
bool debugTiming;
bool debugStateId;
@@ -101,25 +104,26 @@ class Config
bool debugOcr;
bool debugPostProcess;
bool debugShowImages;
-
+
void debugOff();
-
+
string getKeypointsRuntimeDir();
string getCascadeRuntimeDir();
string getPostProcessRuntimeDir();
string getTessdataPrefix();
- private:
+private:
CSimpleIniA* ini;
string runtimeBaseDir;
-
+
void loadValues(string country);
-
+
int getInt(string section, string key, int defaultValue);
float getFloat(string section, string key, float defaultValue);
string getString(string section, string key, string defaultValue);
bool getBoolean(string section, string key, bool defaultValue);
};
-#endif // CONFIG_H
+
+#endif // CONFIG_H
\ No newline at end of file
diff --git a/src/openalpr/tinythread/fast_mutex.h b/src/openalpr/tinythread/fast_mutex.h
new file mode 100644
index 0000000..4d4b7cc
--- /dev/null
+++ b/src/openalpr/tinythread/fast_mutex.h
@@ -0,0 +1,248 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*-
+Copyright (c) 2010-2012 Marcus Geelnard
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+#ifndef _FAST_MUTEX_H_
+#define _FAST_MUTEX_H_
+
+/// @file
+
+// Which platform are we on?
+#if !defined(_TTHREAD_PLATFORM_DEFINED_)
+ #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
+ #define _TTHREAD_WIN32_
+ #else
+ #define _TTHREAD_POSIX_
+ #endif
+ #define _TTHREAD_PLATFORM_DEFINED_
+#endif
+
+// Check if we can support the assembly language level implementation (otherwise
+// revert to the system API)
+#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || \
+ (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || \
+ (defined(__GNUC__) && (defined(__ppc__)))
+ #define _FAST_MUTEX_ASM_
+#else
+ #define _FAST_MUTEX_SYS_
+#endif
+
+#if defined(_TTHREAD_WIN32_)
+ #ifndef WIN32_LEAN_AND_MEAN
+ #define WIN32_LEAN_AND_MEAN
+ #define __UNDEF_LEAN_AND_MEAN
+ #endif
+ #include
+ #ifdef __UNDEF_LEAN_AND_MEAN
+ #undef WIN32_LEAN_AND_MEAN
+ #undef __UNDEF_LEAN_AND_MEAN
+ #endif
+#else
+ #ifdef _FAST_MUTEX_ASM_
+ #include
+ #else
+ #include
+ #endif
+#endif
+
+namespace tthread {
+
+/// Fast mutex class.
+/// This is a mutual exclusion object for synchronizing access to shared
+/// memory areas for several threads. It is similar to the tthread::mutex class,
+/// but instead of using system level functions, it is implemented as an atomic
+/// spin lock with very low CPU overhead.
+///
+/// The \c fast_mutex class is NOT compatible with the \c condition_variable
+/// class (however, it IS compatible with the \c lock_guard class). It should
+/// also be noted that the \c fast_mutex class typically does not provide
+/// as accurate thread scheduling as a the standard \c mutex class does.
+///
+/// Because of the limitations of the class, it should only be used in
+/// situations where the mutex needs to be locked/unlocked very frequently.
+///
+/// @note The "fast" version of this class relies on inline assembler language,
+/// which is currently only supported for 32/64-bit Intel x86/AMD64 and
+/// PowerPC architectures on a limited number of compilers (GNU g++ and MS
+/// Visual C++).
+/// For other architectures/compilers, system functions are used instead.
+class fast_mutex {
+ public:
+ /// Constructor.
+#if defined(_FAST_MUTEX_ASM_)
+ fast_mutex() : mLock(0) {}
+#else
+ fast_mutex()
+ {
+ #if defined(_TTHREAD_WIN32_)
+ InitializeCriticalSection(&mHandle);
+ #elif defined(_TTHREAD_POSIX_)
+ pthread_mutex_init(&mHandle, NULL);
+ #endif
+ }
+#endif
+
+#if !defined(_FAST_MUTEX_ASM_)
+ /// Destructor.
+ ~fast_mutex()
+ {
+ #if defined(_TTHREAD_WIN32_)
+ DeleteCriticalSection(&mHandle);
+ #elif defined(_TTHREAD_POSIX_)
+ pthread_mutex_destroy(&mHandle);
+ #endif
+ }
+#endif
+
+ /// Lock the mutex.
+ /// The method will block the calling thread until a lock on the mutex can
+ /// be obtained. The mutex remains locked until \c unlock() is called.
+ /// @see lock_guard
+ inline void lock()
+ {
+#if defined(_FAST_MUTEX_ASM_)
+ bool gotLock;
+ do {
+ gotLock = try_lock();
+ if(!gotLock)
+ {
+ #if defined(_TTHREAD_WIN32_)
+ Sleep(0);
+ #elif defined(_TTHREAD_POSIX_)
+ sched_yield();
+ #endif
+ }
+ } while(!gotLock);
+#else
+ #if defined(_TTHREAD_WIN32_)
+ EnterCriticalSection(&mHandle);
+ #elif defined(_TTHREAD_POSIX_)
+ pthread_mutex_lock(&mHandle);
+ #endif
+#endif
+ }
+
+ /// Try to lock the mutex.
+ /// The method will try to lock the mutex. If it fails, the function will
+ /// return immediately (non-blocking).
+ /// @return \c true if the lock was acquired, or \c false if the lock could
+ /// not be acquired.
+ inline bool try_lock()
+ {
+#if defined(_FAST_MUTEX_ASM_)
+ int oldLock;
+ #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+ asm volatile (
+ "movl $1,%%eax\n\t"
+ "xchg %%eax,%0\n\t"
+ "movl %%eax,%1\n\t"
+ : "=m" (mLock), "=m" (oldLock)
+ :
+ : "%eax", "memory"
+ );
+ #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+ int *ptrLock = &mLock;
+ __asm {
+ mov eax,1
+ mov ecx,ptrLock
+ xchg eax,[ecx]
+ mov oldLock,eax
+ }
+ #elif defined(__GNUC__) && (defined(__ppc__))
+ int newLock = 1;
+ asm volatile (
+ "\n1:\n\t"
+ "lwarx %0,0,%1\n\t"
+ "cmpwi 0,%0,0\n\t"
+ "bne- 2f\n\t"
+ "stwcx. %2,0,%1\n\t"
+ "bne- 1b\n\t"
+ "isync\n"
+ "2:\n\t"
+ : "=&r" (oldLock)
+ : "r" (&mLock), "r" (newLock)
+ : "cr0", "memory"
+ );
+ #endif
+ return (oldLock == 0);
+#else
+ #if defined(_TTHREAD_WIN32_)
+ return TryEnterCriticalSection(&mHandle) ? true : false;
+ #elif defined(_TTHREAD_POSIX_)
+ return (pthread_mutex_trylock(&mHandle) == 0) ? true : false;
+ #endif
+#endif
+ }
+
+ /// Unlock the mutex.
+ /// If any threads are waiting for the lock on this mutex, one of them will
+ /// be unblocked.
+ inline void unlock()
+ {
+#if defined(_FAST_MUTEX_ASM_)
+ #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+ asm volatile (
+ "movl $0,%%eax\n\t"
+ "xchg %%eax,%0\n\t"
+ : "=m" (mLock)
+ :
+ : "%eax", "memory"
+ );
+ #elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+ int *ptrLock = &mLock;
+ __asm {
+ mov eax,0
+ mov ecx,ptrLock
+ xchg eax,[ecx]
+ }
+ #elif defined(__GNUC__) && (defined(__ppc__))
+ asm volatile (
+ "sync\n\t" // Replace with lwsync where possible?
+ : : : "memory"
+ );
+ mLock = 0;
+ #endif
+#else
+ #if defined(_TTHREAD_WIN32_)
+ LeaveCriticalSection(&mHandle);
+ #elif defined(_TTHREAD_POSIX_)
+ pthread_mutex_unlock(&mHandle);
+ #endif
+#endif
+ }
+
+ private:
+#if defined(_FAST_MUTEX_ASM_)
+ int mLock;
+#else
+ #if defined(_TTHREAD_WIN32_)
+ CRITICAL_SECTION mHandle;
+ #elif defined(_TTHREAD_POSIX_)
+ pthread_mutex_t mHandle;
+ #endif
+#endif
+};
+
+}
+
+#endif // _FAST_MUTEX_H_
+
diff --git a/src/openalpr/tinythread/tinythread.cpp b/src/openalpr/tinythread/tinythread.cpp
new file mode 100644
index 0000000..690ecee
--- /dev/null
+++ b/src/openalpr/tinythread/tinythread.cpp
@@ -0,0 +1,303 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*-
+Copyright (c) 2010-2012 Marcus Geelnard
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+#include
+#include "tinythread.h"
+
+#if defined(_TTHREAD_POSIX_)
+ #include
+ #include