mirror of
https://github.com/kerberos-io/openalpr-base.git
synced 2025-10-06 06:46:53 +08:00
Merge branch 'multithread' into develop
Conflicts: src/misc_utilities/CMakeLists.txt src/openalpr/CMakeLists.txt src/openalpr/alpr_impl.cpp src/openalpr/alpr_impl.h src/openalpr/config.cpp src/openalpr/config.h
This commit is contained in:
@@ -14,6 +14,8 @@ max_plate_height_percent = 100
|
|||||||
detection_iteration_increase = 1.1
|
detection_iteration_increase = 1.1
|
||||||
|
|
||||||
opencl_enabled = 0
|
opencl_enabled = 0
|
||||||
|
multithreading_cores = 1
|
||||||
|
|
||||||
|
|
||||||
ocr_min_font_point = 6
|
ocr_min_font_point = 6
|
||||||
|
|
||||||
|
@@ -4,33 +4,35 @@ target_link_libraries(openalpr)
|
|||||||
|
|
||||||
|
|
||||||
ADD_EXECUTABLE( sortstate sortstate.cpp )
|
ADD_EXECUTABLE( sortstate sortstate.cpp )
|
||||||
TARGET_LINK_LIBRARIES(sortstate
|
TARGET_LINK_LIBRARIES(sortstate
|
||||||
openalpr
|
openalpr
|
||||||
support
|
support
|
||||||
${OpenCV_LIBS}
|
${OpenCV_LIBS}
|
||||||
tesseract
|
tesseract
|
||||||
)
|
)
|
||||||
|
|
||||||
ADD_EXECUTABLE( classifychars classifychars.cpp )
|
ADD_EXECUTABLE( classifychars classifychars.cpp )
|
||||||
TARGET_LINK_LIBRARIES(classifychars
|
TARGET_LINK_LIBRARIES(classifychars
|
||||||
openalpr
|
openalpr
|
||||||
support
|
support
|
||||||
${OpenCV_LIBS}
|
${OpenCV_LIBS}
|
||||||
tesseract
|
tesseract
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
ADD_EXECUTABLE( benchmark benchmark.cpp )
|
ADD_EXECUTABLE( benchmark benchmark.cpp )
|
||||||
TARGET_LINK_LIBRARIES(benchmark
|
TARGET_LINK_LIBRARIES(benchmark
|
||||||
openalpr
|
openalpr
|
||||||
support
|
support
|
||||||
${OpenCV_LIBS}
|
${OpenCV_LIBS}
|
||||||
tesseract
|
tesseract
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
ADD_EXECUTABLE( prepcharsfortraining prepcharsfortraining.cpp )
|
ADD_EXECUTABLE( prepcharsfortraining prepcharsfortraining.cpp )
|
||||||
TARGET_LINK_LIBRARIES(prepcharsfortraining
|
TARGET_LINK_LIBRARIES(prepcharsfortraining
|
||||||
support
|
support
|
||||||
${OpenCV_LIBS}
|
${OpenCV_LIBS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@@ -23,9 +23,10 @@ set(lpr_source_files
|
|||||||
verticalhistogram.cpp
|
verticalhistogram.cpp
|
||||||
trex.c
|
trex.c
|
||||||
cjson.c
|
cjson.c
|
||||||
|
tinythread/tinythread.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
add_subdirectory(simpleini)
|
add_subdirectory(simpleini)
|
||||||
add_subdirectory(support)
|
add_subdirectory(support)
|
||||||
@@ -35,4 +36,4 @@ add_library(openalpr ${lpr_source_files})
|
|||||||
|
|
||||||
|
|
||||||
# Add definition for default runtime dir
|
# 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/")
|
@@ -1,55 +1,59 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 New Designs Unlimited, LLC
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
*
|
*
|
||||||
* This file is part of OpenAlpr.
|
* This file is part of OpenAlpr.
|
||||||
*
|
*
|
||||||
* OpenAlpr is free software: you can redistribute it and/or modify
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License
|
* it under the terms of the GNU Affero General Public License
|
||||||
* version 3 as published by the Free Software Foundation
|
* version 3 as published by the Free Software Foundation
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "alpr_impl.h"
|
#include "alpr_impl.h"
|
||||||
|
|
||||||
|
void plateAnalysisThread(void* arg);
|
||||||
|
|
||||||
AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir)
|
AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir)
|
||||||
{
|
{
|
||||||
config = new Config(country, runtimeDir);
|
config = new Config(country, runtimeDir);
|
||||||
plateDetector = new RegionDetector(config);
|
plateDetector = new RegionDetector(config);
|
||||||
stateIdentifier = new StateIdentifier(config);
|
stateIdentifier = new StateIdentifier(config);
|
||||||
ocr = new OCR(config);
|
ocr = new OCR(config);
|
||||||
|
setNumThreads(0);
|
||||||
|
|
||||||
this->detectRegion = DEFAULT_DETECT_REGION;
|
this->detectRegion = DEFAULT_DETECT_REGION;
|
||||||
this->topN = DEFAULT_TOPN;
|
this->topN = DEFAULT_TOPN;
|
||||||
this->defaultRegion = "";
|
this->defaultRegion = "";
|
||||||
|
|
||||||
if (config->opencl_enabled)
|
if (config->opencl_enabled)
|
||||||
{
|
{
|
||||||
|
|
||||||
cv::ocl::PlatformsInfo platinfo;
|
cv::ocl::PlatformsInfo platinfo;
|
||||||
cv::ocl::getOpenCLPlatforms(platinfo);
|
cv::ocl::getOpenCLPlatforms(platinfo);
|
||||||
|
|
||||||
for (int i = 0; i < platinfo.size(); i++)
|
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::DevicesInfo devices;
|
||||||
cv::ocl::getOpenCLDevices(devices, cv::ocl::CVCL_DEVICE_TYPE_CPU);
|
cv::ocl::getOpenCLDevices(devices, cv::ocl::CVCL_DEVICE_TYPE_CPU);
|
||||||
|
|
||||||
for (int i = 0; i < devices.size(); i++)
|
for (int i = 0; i < devices.size(); i++)
|
||||||
std:: cout << devices[i]->deviceName << std::endl;
|
std:: cout << devices[i]->deviceName << std::endl;
|
||||||
|
|
||||||
if (devices.size() > 0)
|
if (devices.size() > 0)
|
||||||
{
|
{
|
||||||
cv::ocl::setDevice(devices[0]);
|
cv::ocl::setDevice(devices[0]);
|
||||||
|
|
||||||
cout << "Using OpenCL Device: " << devices[0]->deviceName << endl;
|
cout << "Using OpenCL Device: " << devices[0]->deviceName << endl;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -58,7 +62,6 @@ AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AlprImpl::~AlprImpl()
|
AlprImpl::~AlprImpl()
|
||||||
{
|
{
|
||||||
delete config;
|
delete config;
|
||||||
@@ -67,100 +70,43 @@ AlprImpl::~AlprImpl()
|
|||||||
delete ocr;
|
delete ocr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<AlprResult> AlprImpl::recognize(cv::Mat img)
|
std::vector<AlprResult> AlprImpl::recognize(cv::Mat img)
|
||||||
{
|
{
|
||||||
timespec startTime;
|
timespec startTime;
|
||||||
getTime(&startTime);
|
getTime(&startTime);
|
||||||
|
|
||||||
|
|
||||||
vector<AlprResult> response;
|
|
||||||
|
|
||||||
|
// Find all the candidate regions
|
||||||
vector<Rect> plateRegions = plateDetector->detect(img);
|
vector<Rect> 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<tthread::thread*> threads;
|
||||||
|
for (int i = 0; i < numThreads; i++)
|
||||||
{
|
{
|
||||||
timespec platestarttime;
|
tthread::thread * t = new tthread::thread(plateAnalysisThread, (void *) &dispatcher);
|
||||||
getTime(&platestarttime);
|
threads.push_back(t);
|
||||||
|
}
|
||||||
LicensePlateCandidate lp(img, plateRegions[i], config);
|
|
||||||
|
// Wait for all threads to finish
|
||||||
lp.recognize();
|
for(list<tthread::thread *>::iterator i = threads.begin(); i != threads.end(); ++ i)
|
||||||
|
{
|
||||||
if (lp.confidence > 10)
|
tthread::thread* t = *i;
|
||||||
{
|
t->join();
|
||||||
AlprResult plateResult;
|
delete t;
|
||||||
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<PPResult> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->debugTiming)
|
if (config->debugTiming)
|
||||||
@@ -169,7 +115,7 @@ std::vector<AlprResult> AlprImpl::recognize(cv::Mat img)
|
|||||||
getTime(&endTime);
|
getTime(&endTime);
|
||||||
cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl;
|
cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->debugGeneral && config->debugShowImages)
|
if (config->debugGeneral && config->debugShowImages)
|
||||||
{
|
{
|
||||||
displayImage(config, "Main Image", img);
|
displayImage(config, "Main Image", img);
|
||||||
@@ -177,47 +123,175 @@ std::vector<AlprResult> AlprImpl::recognize(cv::Mat img)
|
|||||||
while ((char) cv::waitKey(50) == -1)
|
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<PPResult> 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)
|
string AlprImpl::toJson(const vector< AlprResult > results)
|
||||||
{
|
{
|
||||||
cJSON *root = cJSON_CreateArray();
|
cJSON *root = cJSON_CreateArray();
|
||||||
|
|
||||||
for (int i = 0; i < results.size(); i++)
|
for (int i = 0; i < results.size(); i++)
|
||||||
{
|
{
|
||||||
cJSON *resultObj = createJsonObj( &results[i] );
|
cJSON *resultObj = createJsonObj( &results[i] );
|
||||||
cJSON_AddItemToArray(root, resultObj);
|
cJSON_AddItemToArray(root, resultObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the JSON object to a string and return
|
// Print the JSON object to a string and return
|
||||||
char *out;
|
char *out;
|
||||||
out=cJSON_PrintUnformatted(root);
|
out=cJSON_PrintUnformatted(root);
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
|
|
||||||
string response(out);
|
string response(out);
|
||||||
|
|
||||||
free(out);
|
free(out);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cJSON* AlprImpl::createJsonObj(const AlprResult* result)
|
cJSON* AlprImpl::createJsonObj(const AlprResult* result)
|
||||||
{
|
{
|
||||||
cJSON *root, *coords, *candidates;
|
cJSON *root, *coords, *candidates;
|
||||||
|
|
||||||
root=cJSON_CreateObject();
|
root=cJSON_CreateObject();
|
||||||
|
|
||||||
cJSON_AddStringToObject(root,"plate", result->bestPlate.characters.c_str());
|
cJSON_AddStringToObject(root,"plate", result->bestPlate.characters.c_str());
|
||||||
cJSON_AddNumberToObject(root,"confidence", result->bestPlate.overall_confidence);
|
cJSON_AddNumberToObject(root,"confidence", result->bestPlate.overall_confidence);
|
||||||
cJSON_AddNumberToObject(root,"matches_template", result->bestPlate.matches_template);
|
cJSON_AddNumberToObject(root,"matches_template", result->bestPlate.matches_template);
|
||||||
|
|
||||||
cJSON_AddStringToObject(root,"region", result->region.c_str());
|
cJSON_AddStringToObject(root,"region", result->region.c_str());
|
||||||
cJSON_AddNumberToObject(root,"region_confidence", result->regionConfidence);
|
cJSON_AddNumberToObject(root,"region_confidence", result->regionConfidence);
|
||||||
|
|
||||||
cJSON_AddNumberToObject(root,"processing_time_ms", result->processing_time_ms);
|
cJSON_AddNumberToObject(root,"processing_time_ms", result->processing_time_ms);
|
||||||
|
|
||||||
cJSON_AddItemToObject(root, "coordinates", coords=cJSON_CreateArray());
|
cJSON_AddItemToObject(root, "coordinates", coords=cJSON_CreateArray());
|
||||||
for (int i=0; i<4; i++)
|
for (int i=0;i<4;i++)
|
||||||
{
|
{
|
||||||
cJSON *coords_object;
|
cJSON *coords_object;
|
||||||
coords_object = cJSON_CreateObject();
|
coords_object = cJSON_CreateObject();
|
||||||
@@ -226,7 +300,8 @@ cJSON* AlprImpl::createJsonObj(const AlprResult* result)
|
|||||||
|
|
||||||
cJSON_AddItemToArray(coords, coords_object);
|
cJSON_AddItemToArray(coords, coords_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
cJSON_AddItemToObject(root, "candidates", candidates=cJSON_CreateArray());
|
cJSON_AddItemToObject(root, "candidates", candidates=cJSON_CreateArray());
|
||||||
for (int i = 0; i < result->topNPlates.size(); i++)
|
for (int i = 0; i < result->topNPlates.size(); i++)
|
||||||
{
|
{
|
||||||
@@ -238,21 +313,21 @@ cJSON* AlprImpl::createJsonObj(const AlprResult* result)
|
|||||||
|
|
||||||
cJSON_AddItemToArray(candidates, candidate_object);
|
cJSON_AddItemToArray(candidates, candidate_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AlprImpl::setDetectRegion(bool detectRegion)
|
void AlprImpl::setDetectRegion(bool detectRegion)
|
||||||
{
|
{
|
||||||
this->detectRegion = detectRegion;
|
this->detectRegion = detectRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlprImpl::setTopN(int topn)
|
void AlprImpl::setTopN(int topn)
|
||||||
{
|
{
|
||||||
this->topN = topn;
|
this->topN = topn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlprImpl::setDefaultRegion(string region)
|
void AlprImpl::setDefaultRegion(string region)
|
||||||
{
|
{
|
||||||
this->defaultRegion = region;
|
this->defaultRegion = region;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,25 +1,28 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 New Designs Unlimited, LLC
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
*
|
*
|
||||||
* This file is part of OpenAlpr.
|
* This file is part of OpenAlpr.
|
||||||
*
|
*
|
||||||
* OpenAlpr is free software: you can redistribute it and/or modify
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License
|
* it under the terms of the GNU Affero General Public License
|
||||||
* version 3 as published by the Free Software Foundation
|
* version 3 as published by the Free Software Foundation
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef ALPRIMPL_H
|
#ifndef ALPRIMPL_H
|
||||||
#define ALPRIMPL_H
|
#define ALPRIMPL_H
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
#include "alpr.h"
|
#include "alpr.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@@ -33,6 +36,9 @@
|
|||||||
|
|
||||||
#include <opencv2/core/core.hpp>
|
#include <opencv2/core/core.hpp>
|
||||||
#include "opencv2/ocl/ocl.hpp"
|
#include "opencv2/ocl/ocl.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#include "tinythread/tinythread.h"
|
||||||
|
|
||||||
#define DEFAULT_TOPN 25
|
#define DEFAULT_TOPN 25
|
||||||
#define DEFAULT_DETECT_REGION false
|
#define DEFAULT_DETECT_REGION false
|
||||||
@@ -45,28 +51,105 @@ class AlprImpl
|
|||||||
virtual ~AlprImpl();
|
virtual ~AlprImpl();
|
||||||
|
|
||||||
std::vector<AlprResult> recognize(cv::Mat img);
|
std::vector<AlprResult> recognize(cv::Mat img);
|
||||||
|
|
||||||
void applyRegionTemplate(AlprResult* result, std::string region);
|
void applyRegionTemplate(AlprResult* result, std::string region);
|
||||||
|
|
||||||
void setDetectRegion(bool detectRegion);
|
void setDetectRegion(bool detectRegion);
|
||||||
void setTopN(int topn);
|
void setTopN(int topn);
|
||||||
void setDefaultRegion(string region);
|
void setDefaultRegion(string region);
|
||||||
|
|
||||||
std::string toJson(const vector<AlprResult> results);
|
std::string toJson(const vector<AlprResult> results);
|
||||||
|
|
||||||
Config* config;
|
Config* config;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
RegionDetector* plateDetector;
|
RegionDetector* plateDetector;
|
||||||
StateIdentifier* stateIdentifier;
|
StateIdentifier* stateIdentifier;
|
||||||
OCR* ocr;
|
OCR* ocr;
|
||||||
|
|
||||||
int topN;
|
int topN;
|
||||||
bool detectRegion;
|
bool detectRegion;
|
||||||
std::string defaultRegion;
|
std::string defaultRegion;
|
||||||
|
|
||||||
cJSON* createJsonObj(const AlprResult* result);
|
cJSON* createJsonObj(const AlprResult* result);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ALPRIMPL_H
|
class PlateDispatcher
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PlateDispatcher(vector<Rect> 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<tthread::mutex> 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<tthread::mutex> guard(mMutex);
|
||||||
|
|
||||||
|
Rect plateRegion = plateRegions[plateRegions.size() - 1];
|
||||||
|
plateRegions.pop_back();
|
||||||
|
|
||||||
|
return plateRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addResult(AlprResult recognitionResult)
|
||||||
|
{
|
||||||
|
tthread::lock_guard<tthread::mutex> guard(mMutex);
|
||||||
|
recognitionResults.push_back(recognitionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<AlprResult> getRecognitionResults()
|
||||||
|
{
|
||||||
|
return recognitionResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StateIdentifier* stateIdentifier;
|
||||||
|
OCR* ocr;
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
int topN;
|
||||||
|
bool detectRegion;
|
||||||
|
std::string defaultRegion;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
tthread::mutex mMutex;
|
||||||
|
cv::Mat* frame;
|
||||||
|
vector<Rect> plateRegions;
|
||||||
|
vector<AlprResult> recognitionResults;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ALPRIMPL_H
|
@@ -1,35 +1,37 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 New Designs Unlimited, LLC
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
*
|
*
|
||||||
* This file is part of OpenAlpr.
|
* This file is part of OpenAlpr.
|
||||||
*
|
*
|
||||||
* OpenAlpr is free software: you can redistribute it and/or modify
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License
|
* it under the terms of the GNU Affero General Public License
|
||||||
* version 3 as published by the Free Software Foundation
|
* version 3 as published by the Free Software Foundation
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
Config::Config(const std::string country, const std::string runtimeBaseDir)
|
Config::Config(const std::string country, const std::string runtimeBaseDir)
|
||||||
{
|
{
|
||||||
this->runtimeBaseDir = runtimeBaseDir;
|
this->runtimeBaseDir = runtimeBaseDir;
|
||||||
|
|
||||||
ini = new CSimpleIniA();
|
ini = new CSimpleIniA();
|
||||||
|
|
||||||
char* envRuntimeDir;
|
char* envRuntimeDir;
|
||||||
envRuntimeDir = getenv (ENV_VARIABLE_RUNTIME_DIR);
|
envRuntimeDir = getenv (ENV_VARIABLE_RUNTIME_DIR);
|
||||||
if (runtimeBaseDir.compare("") != 0)
|
if (runtimeBaseDir.compare("") != 0)
|
||||||
{
|
{
|
||||||
// User has supplied a runtime directory. Use that.
|
// User has supplied a runtime directory. Use that.
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (envRuntimeDir!=NULL)
|
else if (envRuntimeDir!=NULL)
|
||||||
{
|
{
|
||||||
@@ -41,9 +43,9 @@ Config::Config(const std::string country, const std::string runtimeBaseDir)
|
|||||||
// Use the default
|
// Use the default
|
||||||
this->runtimeBaseDir = DEFAULT_RUNTIME_DIR;
|
this->runtimeBaseDir = DEFAULT_RUNTIME_DIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
string configFile = (this->runtimeBaseDir + CONFIG_FILE);
|
string configFile = (this->runtimeBaseDir + CONFIG_FILE);
|
||||||
|
|
||||||
if (DirectoryExists(this->runtimeBaseDir.c_str()) == false)
|
if (DirectoryExists(this->runtimeBaseDir.c_str()) == false)
|
||||||
{
|
{
|
||||||
std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not exist!" << endl;
|
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;
|
std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not contain a config file '" << CONFIG_FILE << "'!" << endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ini->LoadFile(configFile.c_str());
|
ini->LoadFile(configFile.c_str());
|
||||||
|
|
||||||
this->country = country;
|
this->country = country;
|
||||||
|
|
||||||
loadValues(country);
|
loadValues(country);
|
||||||
}
|
}
|
||||||
|
|
||||||
Config::~Config()
|
Config::~Config()
|
||||||
{
|
{
|
||||||
delete ini;
|
delete ini;
|
||||||
@@ -69,56 +70,59 @@ Config::~Config()
|
|||||||
|
|
||||||
void Config::loadValues(string country)
|
void Config::loadValues(string country)
|
||||||
{
|
{
|
||||||
|
|
||||||
opencl_enabled = getBoolean("common", "opencl_enabled", false);
|
opencl_enabled = getBoolean("common", "opencl_enabled", false);
|
||||||
|
multithreading_cores = getInt("common", "multithreading_cores", 1);
|
||||||
|
|
||||||
detection_iteration_increase = getFloat("common", "detection_iteration_increase", 1.1);
|
detection_iteration_increase = getFloat("common", "detection_iteration_increase", 1.1);
|
||||||
|
|
||||||
maxPlateWidthPercent = getFloat("common", "max_plate_width_percent", 100);
|
maxPlateWidthPercent = getFloat("common", "max_plate_width_percent", 100);
|
||||||
maxPlateHeightPercent = getFloat("common", "max_plate_height_percent", 100);
|
maxPlateHeightPercent = getFloat("common", "max_plate_height_percent", 100);
|
||||||
|
|
||||||
minPlateSizeWidthPx = getInt(country, "min_plate_size_width_px", 100);
|
minPlateSizeWidthPx = getInt(country, "min_plate_size_width_px", 100);
|
||||||
minPlateSizeHeightPx = getInt(country, "min_plate_size_height_px", 100);
|
minPlateSizeHeightPx = getInt(country, "min_plate_size_height_px", 100);
|
||||||
|
|
||||||
plateWidthMM = getFloat(country, "plate_width_mm", 100);
|
plateWidthMM = getFloat(country, "plate_width_mm", 100);
|
||||||
plateHeightMM = getFloat(country, "plate_height_mm", 100);
|
plateHeightMM = getFloat(country, "plate_height_mm", 100);
|
||||||
|
|
||||||
charHeightMM = getFloat(country, "char_height_mm", 100);
|
charHeightMM = getFloat(country, "char_height_mm", 100);
|
||||||
charWidthMM = getFloat(country, "char_width_mm", 100);
|
charWidthMM = getFloat(country, "char_width_mm", 100);
|
||||||
charWhitespaceTopMM = getFloat(country, "char_whitespace_top_mm", 100);
|
charWhitespaceTopMM = getFloat(country, "char_whitespace_top_mm", 100);
|
||||||
charWhitespaceBotMM = getFloat(country, "char_whitespace_bot_mm", 100);
|
charWhitespaceBotMM = getFloat(country, "char_whitespace_bot_mm", 100);
|
||||||
|
|
||||||
templateWidthPx = getInt(country, "template_max_width_px", 100);
|
templateWidthPx = getInt(country, "template_max_width_px", 100);
|
||||||
templateHeightPx = getInt(country, "template_max_height_px", 100);
|
templateHeightPx = getInt(country, "template_max_height_px", 100);
|
||||||
|
|
||||||
float ocrImagePercent = getFloat("common", "ocr_img_size_percent", 100);
|
float ocrImagePercent = getFloat("common", "ocr_img_size_percent", 100);
|
||||||
ocrImageWidthPx = round(((float) templateWidthPx) * ocrImagePercent);
|
ocrImageWidthPx = round(((float) templateWidthPx) * ocrImagePercent);
|
||||||
ocrImageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
|
ocrImageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
|
||||||
|
|
||||||
|
|
||||||
float stateIdImagePercent = getFloat("common", "state_id_img_size_percent", 100);
|
float stateIdImagePercent = getFloat("common", "state_id_img_size_percent", 100);
|
||||||
stateIdImageWidthPx = round(((float)templateWidthPx) * ocrImagePercent);
|
stateIdImageWidthPx = round(((float)templateWidthPx) * ocrImagePercent);
|
||||||
stateIdimageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
|
stateIdimageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
|
||||||
|
|
||||||
|
|
||||||
charAnalysisMinPercent = getFloat(country, "char_analysis_min_pct", 0);
|
charAnalysisMinPercent = getFloat(country, "char_analysis_min_pct", 0);
|
||||||
charAnalysisHeightRange = getFloat(country, "char_analysis_height_range", 0);
|
charAnalysisHeightRange = getFloat(country, "char_analysis_height_range", 0);
|
||||||
charAnalysisHeightStepSize = getFloat(country, "char_analysis_height_step_size", 0);
|
charAnalysisHeightStepSize = getFloat(country, "char_analysis_height_step_size", 0);
|
||||||
charAnalysisNumSteps = getInt(country, "char_analysis_height_num_steps", 0);
|
charAnalysisNumSteps = getInt(country, "char_analysis_height_num_steps", 0);
|
||||||
|
|
||||||
segmentationMinBoxWidthPx = getInt(country, "segmentation_min_box_width_px", 0);
|
segmentationMinBoxWidthPx = getInt(country, "segmentation_min_box_width_px", 0);
|
||||||
segmentationMinCharHeightPercent = getFloat(country, "segmentation_min_charheight_percent", 0);
|
segmentationMinCharHeightPercent = getFloat(country, "segmentation_min_charheight_percent", 0);
|
||||||
segmentationMaxCharWidthvsAverage = getFloat(country, "segmentation_max_segment_width_percent_vs_average", 0);
|
segmentationMaxCharWidthvsAverage = getFloat(country, "segmentation_max_segment_width_percent_vs_average", 0);
|
||||||
|
|
||||||
plateLinesSensitivityVertical = getFloat(country, "plateline_sensitivity_vertical", 0);
|
plateLinesSensitivityVertical = getFloat(country, "plateline_sensitivity_vertical", 0);
|
||||||
plateLinesSensitivityHorizontal = getFloat(country, "plateline_sensitivity_horizontal", 0);
|
plateLinesSensitivityHorizontal = getFloat(country, "plateline_sensitivity_horizontal", 0);
|
||||||
|
|
||||||
ocrLanguage = getString(country, "ocr_language", "none");
|
ocrLanguage = getString(country, "ocr_language", "none");
|
||||||
ocrMinFontSize = getInt("common", "ocr_min_font_point", 100);
|
ocrMinFontSize = getInt("common", "ocr_min_font_point", 100);
|
||||||
|
|
||||||
postProcessMinConfidence = getFloat("common", "postprocess_min_confidence", 100);
|
postProcessMinConfidence = getFloat("common", "postprocess_min_confidence", 100);
|
||||||
postProcessConfidenceSkipLevel = getFloat("common", "postprocess_confidence_skip_level", 100);
|
postProcessConfidenceSkipLevel = getFloat("common", "postprocess_confidence_skip_level", 100);
|
||||||
postProcessMaxSubstitutions = getInt("common", "postprocess_max_substitutions", 100);
|
postProcessMaxSubstitutions = getInt("common", "postprocess_max_substitutions", 100);
|
||||||
postProcessMinCharacters = getInt("common", "postprocess_min_characters", 100);
|
postProcessMinCharacters = getInt("common", "postprocess_min_characers", 100);
|
||||||
postProcessMaxCharacters = getInt("common", "postprocess_max_characters", 100);
|
postProcessMaxCharacters = getInt("common", "postprocess_max_characers", 100);
|
||||||
|
|
||||||
debugGeneral = getBoolean("debug", "general", false);
|
debugGeneral = getBoolean("debug", "general", false);
|
||||||
debugTiming = getBoolean("debug", "timing", false);
|
debugTiming = getBoolean("debug", "timing", false);
|
||||||
debugStateId = getBoolean("debug", "state_id", false);
|
debugStateId = getBoolean("debug", "state_id", false);
|
||||||
@@ -131,6 +135,7 @@ void Config::loadValues(string country)
|
|||||||
debugOcr = getBoolean("debug", "ocr", false);
|
debugOcr = getBoolean("debug", "ocr", false);
|
||||||
debugPostProcess = getBoolean("debug", "postprocess", false);
|
debugPostProcess = getBoolean("debug", "postprocess", false);
|
||||||
debugShowImages = getBoolean("debug", "show_images", false);
|
debugShowImages = getBoolean("debug", "show_images", false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::debugOff()
|
void Config::debugOff()
|
||||||
@@ -148,26 +153,27 @@ void Config::debugOff()
|
|||||||
debugPostProcess = false;
|
debugPostProcess = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string Config::getCascadeRuntimeDir()
|
string Config::getCascadeRuntimeDir()
|
||||||
{
|
{
|
||||||
return this->runtimeBaseDir + CASCADE_DIR;
|
return this->runtimeBaseDir + CASCADE_DIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Config::getKeypointsRuntimeDir()
|
string Config::getKeypointsRuntimeDir()
|
||||||
{
|
{
|
||||||
return this->runtimeBaseDir + KEYPOINTS_DIR;
|
return this->runtimeBaseDir + KEYPOINTS_DIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Config::getPostProcessRuntimeDir()
|
string Config::getPostProcessRuntimeDir()
|
||||||
{
|
{
|
||||||
return this->runtimeBaseDir + POSTPROCESS_DIR;
|
return this->runtimeBaseDir + POSTPROCESS_DIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Config::getTessdataPrefix()
|
string Config::getTessdataPrefix()
|
||||||
{
|
{
|
||||||
return "TESSDATA_PREFIX=" + this->runtimeBaseDir + "/ocr/";
|
return "TESSDATA_PREFIX=" + this->runtimeBaseDir + "/ocr/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
float Config::getFloat(string section, string key, float defaultValue)
|
float Config::getFloat(string section, string key, float defaultValue)
|
||||||
{
|
{
|
||||||
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
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;
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float val = atof(pszValue);
|
float val = atof(pszValue);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Config::getInt(string section, string key, int defaultValue)
|
int Config::getInt(string section, string key, int defaultValue)
|
||||||
{
|
{
|
||||||
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
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;
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int val = atoi(pszValue);
|
int val = atoi(pszValue);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Config::getBoolean(string section, string key, bool defaultValue)
|
bool Config::getBoolean(string section, string key, bool defaultValue)
|
||||||
{
|
{
|
||||||
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
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;
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int val = atoi(pszValue);
|
int val = atoi(pszValue);
|
||||||
return val != 0;
|
return val != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Config::getString(string section, string key, string defaultValue)
|
string Config::getString(string section, string key, string defaultValue)
|
||||||
{
|
{
|
||||||
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
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;
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string val = string(pszValue);
|
string val = string(pszValue);
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
@@ -1,25 +1,27 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013 New Designs Unlimited, LLC
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
*
|
*
|
||||||
* This file is part of OpenAlpr.
|
* This file is part of OpenAlpr.
|
||||||
*
|
*
|
||||||
* OpenAlpr is free software: you can redistribute it and/or modify
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License
|
* it under the terms of the GNU Affero General Public License
|
||||||
* version 3 as published by the Free Software Foundation
|
* version 3 as published by the Free Software Foundation
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Affero General Public License for more details.
|
* GNU Affero General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef CONFIG_H
|
#ifndef CONFIG_H
|
||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
|
|
||||||
#include "simpleini/simpleini.h"
|
#include "simpleini/simpleini.h"
|
||||||
#include "support/filesystem.h"
|
#include "support/filesystem.h"
|
||||||
|
|
||||||
@@ -40,55 +42,56 @@ class Config
|
|||||||
virtual ~Config();
|
virtual ~Config();
|
||||||
|
|
||||||
string country;
|
string country;
|
||||||
|
|
||||||
bool opencl_enabled;
|
|
||||||
|
|
||||||
float detection_iteration_increase;
|
|
||||||
|
|
||||||
|
bool opencl_enabled;
|
||||||
|
int multithreading_cores;
|
||||||
|
|
||||||
|
float detection_iteration_increase;
|
||||||
float maxPlateWidthPercent;
|
float maxPlateWidthPercent;
|
||||||
float maxPlateHeightPercent;
|
float maxPlateHeightPercent;
|
||||||
|
|
||||||
float minPlateSizeWidthPx;
|
float minPlateSizeWidthPx;
|
||||||
float minPlateSizeHeightPx;
|
float minPlateSizeHeightPx;
|
||||||
|
|
||||||
float plateWidthMM;
|
float plateWidthMM;
|
||||||
float plateHeightMM;
|
float plateHeightMM;
|
||||||
|
|
||||||
float charHeightMM;
|
float charHeightMM;
|
||||||
float charWidthMM;
|
float charWidthMM;
|
||||||
float charWhitespaceTopMM;
|
float charWhitespaceTopMM;
|
||||||
float charWhitespaceBotMM;
|
float charWhitespaceBotMM;
|
||||||
|
|
||||||
int templateWidthPx;
|
int templateWidthPx;
|
||||||
int templateHeightPx;
|
int templateHeightPx;
|
||||||
|
|
||||||
int ocrImageWidthPx;
|
int ocrImageWidthPx;
|
||||||
int ocrImageHeightPx;
|
int ocrImageHeightPx;
|
||||||
|
|
||||||
int stateIdImageWidthPx;
|
int stateIdImageWidthPx;
|
||||||
int stateIdimageHeightPx;
|
int stateIdimageHeightPx;
|
||||||
|
|
||||||
float charAnalysisMinPercent;
|
float charAnalysisMinPercent;
|
||||||
float charAnalysisHeightRange;
|
float charAnalysisHeightRange;
|
||||||
float charAnalysisHeightStepSize;
|
float charAnalysisHeightStepSize;
|
||||||
int charAnalysisNumSteps;
|
int charAnalysisNumSteps;
|
||||||
|
|
||||||
float plateLinesSensitivityVertical;
|
float plateLinesSensitivityVertical;
|
||||||
float plateLinesSensitivityHorizontal;
|
float plateLinesSensitivityHorizontal;
|
||||||
|
|
||||||
int segmentationMinBoxWidthPx;
|
int segmentationMinBoxWidthPx;
|
||||||
float segmentationMinCharHeightPercent;
|
float segmentationMinCharHeightPercent;
|
||||||
float segmentationMaxCharWidthvsAverage;
|
float segmentationMaxCharWidthvsAverage;
|
||||||
|
|
||||||
string ocrLanguage;
|
string ocrLanguage;
|
||||||
int ocrMinFontSize;
|
int ocrMinFontSize;
|
||||||
|
|
||||||
float postProcessMinConfidence;
|
float postProcessMinConfidence;
|
||||||
float postProcessConfidenceSkipLevel;
|
float postProcessConfidenceSkipLevel;
|
||||||
int postProcessMaxSubstitutions;
|
int postProcessMaxSubstitutions;
|
||||||
int postProcessMinCharacters;
|
int postProcessMinCharacters;
|
||||||
int postProcessMaxCharacters;
|
int postProcessMaxCharacters;
|
||||||
|
|
||||||
|
|
||||||
bool debugGeneral;
|
bool debugGeneral;
|
||||||
bool debugTiming;
|
bool debugTiming;
|
||||||
bool debugStateId;
|
bool debugStateId;
|
||||||
@@ -101,25 +104,26 @@ class Config
|
|||||||
bool debugOcr;
|
bool debugOcr;
|
||||||
bool debugPostProcess;
|
bool debugPostProcess;
|
||||||
bool debugShowImages;
|
bool debugShowImages;
|
||||||
|
|
||||||
void debugOff();
|
void debugOff();
|
||||||
|
|
||||||
string getKeypointsRuntimeDir();
|
string getKeypointsRuntimeDir();
|
||||||
string getCascadeRuntimeDir();
|
string getCascadeRuntimeDir();
|
||||||
string getPostProcessRuntimeDir();
|
string getPostProcessRuntimeDir();
|
||||||
string getTessdataPrefix();
|
string getTessdataPrefix();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CSimpleIniA* ini;
|
CSimpleIniA* ini;
|
||||||
|
|
||||||
string runtimeBaseDir;
|
string runtimeBaseDir;
|
||||||
|
|
||||||
void loadValues(string country);
|
void loadValues(string country);
|
||||||
|
|
||||||
int getInt(string section, string key, int defaultValue);
|
int getInt(string section, string key, int defaultValue);
|
||||||
float getFloat(string section, string key, float defaultValue);
|
float getFloat(string section, string key, float defaultValue);
|
||||||
string getString(string section, string key, string defaultValue);
|
string getString(string section, string key, string defaultValue);
|
||||||
bool getBoolean(string section, string key, bool defaultValue);
|
bool getBoolean(string section, string key, bool defaultValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFIG_H
|
|
||||||
|
#endif // CONFIG_H
|
248
src/openalpr/tinythread/fast_mutex.h
Normal file
248
src/openalpr/tinythread/fast_mutex.h
Normal file
@@ -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 <windows.h>
|
||||||
|
#ifdef __UNDEF_LEAN_AND_MEAN
|
||||||
|
#undef WIN32_LEAN_AND_MEAN
|
||||||
|
#undef __UNDEF_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef _FAST_MUTEX_ASM_
|
||||||
|
#include <sched.h>
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#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_
|
||||||
|
|
303
src/openalpr/tinythread/tinythread.cpp
Normal file
303
src/openalpr/tinythread/tinythread.cpp
Normal file
@@ -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 <exception>
|
||||||
|
#include "tinythread.h"
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_POSIX_)
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <map>
|
||||||
|
#elif defined(_TTHREAD_WIN32_)
|
||||||
|
#include <process.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
namespace tthread {
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// condition_variable
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// NOTE 1: The Win32 implementation of the condition_variable class is based on
|
||||||
|
// the corresponding implementation in GLFW, which in turn is based on a
|
||||||
|
// description by Douglas C. Schmidt and Irfan Pyarali:
|
||||||
|
// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
|
||||||
|
//
|
||||||
|
// NOTE 2: Windows Vista actually has native support for condition variables
|
||||||
|
// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to
|
||||||
|
// be portable with pre-Vista Windows versions, so TinyThread++ does not use
|
||||||
|
// Vista condition variables.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
#define _CONDITION_EVENT_ONE 0
|
||||||
|
#define _CONDITION_EVENT_ALL 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
condition_variable::condition_variable() : mWaitersCount(0)
|
||||||
|
{
|
||||||
|
mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||||
|
InitializeCriticalSection(&mWaitersCountLock);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
condition_variable::~condition_variable()
|
||||||
|
{
|
||||||
|
CloseHandle(mEvents[_CONDITION_EVENT_ONE]);
|
||||||
|
CloseHandle(mEvents[_CONDITION_EVENT_ALL]);
|
||||||
|
DeleteCriticalSection(&mWaitersCountLock);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
void condition_variable::_wait()
|
||||||
|
{
|
||||||
|
// Wait for either event to become signaled due to notify_one() or
|
||||||
|
// notify_all() being called
|
||||||
|
int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE);
|
||||||
|
|
||||||
|
// Check if we are the last waiter
|
||||||
|
EnterCriticalSection(&mWaitersCountLock);
|
||||||
|
-- mWaitersCount;
|
||||||
|
bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) &&
|
||||||
|
(mWaitersCount == 0);
|
||||||
|
LeaveCriticalSection(&mWaitersCountLock);
|
||||||
|
|
||||||
|
// If we are the last waiter to be notified to stop waiting, reset the event
|
||||||
|
if(lastWaiter)
|
||||||
|
ResetEvent(mEvents[_CONDITION_EVENT_ALL]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
void condition_variable::notify_one()
|
||||||
|
{
|
||||||
|
// Are there any waiters?
|
||||||
|
EnterCriticalSection(&mWaitersCountLock);
|
||||||
|
bool haveWaiters = (mWaitersCount > 0);
|
||||||
|
LeaveCriticalSection(&mWaitersCountLock);
|
||||||
|
|
||||||
|
// If we have any waiting threads, send them a signal
|
||||||
|
if(haveWaiters)
|
||||||
|
SetEvent(mEvents[_CONDITION_EVENT_ONE]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
void condition_variable::notify_all()
|
||||||
|
{
|
||||||
|
// Are there any waiters?
|
||||||
|
EnterCriticalSection(&mWaitersCountLock);
|
||||||
|
bool haveWaiters = (mWaitersCount > 0);
|
||||||
|
LeaveCriticalSection(&mWaitersCountLock);
|
||||||
|
|
||||||
|
// If we have any waiting threads, send them a signal
|
||||||
|
if(haveWaiters)
|
||||||
|
SetEvent(mEvents[_CONDITION_EVENT_ALL]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// POSIX pthread_t to unique thread::id mapping logic.
|
||||||
|
// Note: Here we use a global thread safe std::map to convert instances of
|
||||||
|
// pthread_t to small thread identifier numbers (unique within one process).
|
||||||
|
// This method should be portable across different POSIX implementations.
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#if defined(_TTHREAD_POSIX_)
|
||||||
|
static thread::id _pthread_t_to_ID(const pthread_t &aHandle)
|
||||||
|
{
|
||||||
|
static mutex idMapLock;
|
||||||
|
static std::map<pthread_t, unsigned long int> idMap;
|
||||||
|
static unsigned long int idCount(1);
|
||||||
|
|
||||||
|
lock_guard<mutex> guard(idMapLock);
|
||||||
|
if(idMap.find(aHandle) == idMap.end())
|
||||||
|
idMap[aHandle] = idCount ++;
|
||||||
|
return thread::id(idMap[aHandle]);
|
||||||
|
}
|
||||||
|
#endif // _TTHREAD_POSIX_
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// thread
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Information to pass to the new thread (what to run).
|
||||||
|
struct _thread_start_info {
|
||||||
|
void (*mFunction)(void *); ///< Pointer to the function to be executed.
|
||||||
|
void * mArg; ///< Function argument for the thread function.
|
||||||
|
thread * mThread; ///< Pointer to the thread object.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Thread wrapper function.
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
unsigned WINAPI thread::wrapper_function(void * aArg)
|
||||||
|
#elif defined(_TTHREAD_POSIX_)
|
||||||
|
void * thread::wrapper_function(void * aArg)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
// Get thread startup information
|
||||||
|
_thread_start_info * ti = (_thread_start_info *) aArg;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Call the actual client thread function
|
||||||
|
ti->mFunction(ti->mArg);
|
||||||
|
}
|
||||||
|
catch(...)
|
||||||
|
{
|
||||||
|
// Uncaught exceptions will terminate the application (default behavior
|
||||||
|
// according to C++11)
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The thread is no longer executing
|
||||||
|
lock_guard<mutex> guard(ti->mThread->mDataMutex);
|
||||||
|
ti->mThread->mNotAThread = true;
|
||||||
|
|
||||||
|
// The thread is responsible for freeing the startup information
|
||||||
|
delete ti;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::thread(void (*aFunction)(void *), void * aArg)
|
||||||
|
{
|
||||||
|
// Serialize access to this thread structure
|
||||||
|
lock_guard<mutex> guard(mDataMutex);
|
||||||
|
|
||||||
|
// Fill out the thread startup information (passed to the thread wrapper,
|
||||||
|
// which will eventually free it)
|
||||||
|
_thread_start_info * ti = new _thread_start_info;
|
||||||
|
ti->mFunction = aFunction;
|
||||||
|
ti->mArg = aArg;
|
||||||
|
ti->mThread = this;
|
||||||
|
|
||||||
|
// The thread is now alive
|
||||||
|
mNotAThread = false;
|
||||||
|
|
||||||
|
// Create the thread
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID);
|
||||||
|
#elif defined(_TTHREAD_POSIX_)
|
||||||
|
if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0)
|
||||||
|
mHandle = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Did we fail to create the thread?
|
||||||
|
if(!mHandle)
|
||||||
|
{
|
||||||
|
mNotAThread = true;
|
||||||
|
delete ti;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::~thread()
|
||||||
|
{
|
||||||
|
if(joinable())
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void thread::join()
|
||||||
|
{
|
||||||
|
if(joinable())
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
WaitForSingleObject(mHandle, INFINITE);
|
||||||
|
CloseHandle(mHandle);
|
||||||
|
#elif defined(_TTHREAD_POSIX_)
|
||||||
|
pthread_join(mHandle, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool thread::joinable() const
|
||||||
|
{
|
||||||
|
mDataMutex.lock();
|
||||||
|
bool result = !mNotAThread;
|
||||||
|
mDataMutex.unlock();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void thread::detach()
|
||||||
|
{
|
||||||
|
mDataMutex.lock();
|
||||||
|
if(!mNotAThread)
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
CloseHandle(mHandle);
|
||||||
|
#elif defined(_TTHREAD_POSIX_)
|
||||||
|
pthread_detach(mHandle);
|
||||||
|
#endif
|
||||||
|
mNotAThread = true;
|
||||||
|
}
|
||||||
|
mDataMutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::id thread::get_id() const
|
||||||
|
{
|
||||||
|
if(!joinable())
|
||||||
|
return id();
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
return id((unsigned long int) mWin32ThreadID);
|
||||||
|
#elif defined(_TTHREAD_POSIX_)
|
||||||
|
return _pthread_t_to_ID(mHandle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned thread::hardware_concurrency()
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
SYSTEM_INFO si;
|
||||||
|
GetSystemInfo(&si);
|
||||||
|
return (int) si.dwNumberOfProcessors;
|
||||||
|
#elif defined(_SC_NPROCESSORS_ONLN)
|
||||||
|
return (int) sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
|
#elif defined(_SC_NPROC_ONLN)
|
||||||
|
return (int) sysconf(_SC_NPROC_ONLN);
|
||||||
|
#else
|
||||||
|
// The standard requires this function to return zero if the number of
|
||||||
|
// hardware cores could not be determined.
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// this_thread
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
thread::id this_thread::get_id()
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
return thread::id((unsigned long int) GetCurrentThreadId());
|
||||||
|
#elif defined(_TTHREAD_POSIX_)
|
||||||
|
return _pthread_t_to_ID(pthread_self());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
714
src/openalpr/tinythread/tinythread.h
Normal file
714
src/openalpr/tinythread/tinythread.h
Normal file
@@ -0,0 +1,714 @@
|
|||||||
|
/* -*- 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 _TINYTHREAD_H_
|
||||||
|
#define _TINYTHREAD_H_
|
||||||
|
|
||||||
|
/// @file
|
||||||
|
/// @mainpage TinyThread++ API Reference
|
||||||
|
///
|
||||||
|
/// @section intro_sec Introduction
|
||||||
|
/// TinyThread++ is a minimal, portable implementation of basic threading
|
||||||
|
/// classes for C++.
|
||||||
|
///
|
||||||
|
/// They closely mimic the functionality and naming of the C++11 standard, and
|
||||||
|
/// should be easily replaceable with the corresponding std:: variants.
|
||||||
|
///
|
||||||
|
/// @section port_sec Portability
|
||||||
|
/// The Win32 variant uses the native Win32 API for implementing the thread
|
||||||
|
/// classes, while for other systems, the POSIX threads API (pthread) is used.
|
||||||
|
///
|
||||||
|
/// @section class_sec Classes
|
||||||
|
/// In order to mimic the threading API of the C++11 standard, subsets of
|
||||||
|
/// several classes are provided. The fundamental classes are:
|
||||||
|
/// @li tthread::thread
|
||||||
|
/// @li tthread::mutex
|
||||||
|
/// @li tthread::recursive_mutex
|
||||||
|
/// @li tthread::condition_variable
|
||||||
|
/// @li tthread::lock_guard
|
||||||
|
/// @li tthread::fast_mutex
|
||||||
|
///
|
||||||
|
/// @section misc_sec Miscellaneous
|
||||||
|
/// The following special keywords are available: #thread_local.
|
||||||
|
///
|
||||||
|
/// For more detailed information (including additional classes), browse the
|
||||||
|
/// different sections of this documentation. A good place to start is:
|
||||||
|
/// tinythread.h.
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Platform specific includes
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#define __UNDEF_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#ifdef __UNDEF_LEAN_AND_MEAN
|
||||||
|
#undef WIN32_LEAN_AND_MEAN
|
||||||
|
#undef __UNDEF_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Generic includes
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
/// TinyThread++ version (major number).
|
||||||
|
#define TINYTHREAD_VERSION_MAJOR 1
|
||||||
|
/// TinyThread++ version (minor number).
|
||||||
|
#define TINYTHREAD_VERSION_MINOR 1
|
||||||
|
/// TinyThread++ version (full version).
|
||||||
|
#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR)
|
||||||
|
|
||||||
|
// Do we have a fully featured C++11 compiler?
|
||||||
|
#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L))
|
||||||
|
#define _TTHREAD_CPP11_
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ...at least partial C++11?
|
||||||
|
#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__)
|
||||||
|
#define _TTHREAD_CPP11_PARTIAL_
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Macro for disabling assignments of objects.
|
||||||
|
#ifdef _TTHREAD_CPP11_PARTIAL_
|
||||||
|
#define _TTHREAD_DISABLE_ASSIGNMENT(name) \
|
||||||
|
name(const name&) = delete; \
|
||||||
|
name& operator=(const name&) = delete;
|
||||||
|
#else
|
||||||
|
#define _TTHREAD_DISABLE_ASSIGNMENT(name) \
|
||||||
|
name(const name&); \
|
||||||
|
name& operator=(const name&);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// @def thread_local
|
||||||
|
/// Thread local storage keyword.
|
||||||
|
/// A variable that is declared with the @c thread_local keyword makes the
|
||||||
|
/// value of the variable local to each thread (known as thread-local storage,
|
||||||
|
/// or TLS). Example usage:
|
||||||
|
/// @code
|
||||||
|
/// // This variable is local to each thread.
|
||||||
|
/// thread_local int variable;
|
||||||
|
/// @endcode
|
||||||
|
/// @note The @c thread_local keyword is a macro that maps to the corresponding
|
||||||
|
/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard
|
||||||
|
/// allows for non-trivial types (e.g. classes with constructors and
|
||||||
|
/// destructors) to be declared with the @c thread_local keyword, most pre-C++11
|
||||||
|
/// compilers only allow for trivial types (e.g. @c int). So, to guarantee
|
||||||
|
/// portable code, only use trivial types for thread local storage.
|
||||||
|
/// @note This directive is currently not supported on Mac OS X (it will give
|
||||||
|
/// a compiler error), since compile-time TLS is not supported in the Mac OS X
|
||||||
|
/// executable format. Also, some older versions of MinGW (before GCC 4.x) do
|
||||||
|
/// not support this directive.
|
||||||
|
/// @hideinitializer
|
||||||
|
|
||||||
|
#if !defined(_TTHREAD_CPP11_) && !defined(thread_local)
|
||||||
|
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__)
|
||||||
|
#define thread_local __thread
|
||||||
|
#else
|
||||||
|
#define thread_local __declspec(thread)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/// Main name space for TinyThread++.
|
||||||
|
/// This namespace is more or less equivalent to the @c std namespace for the
|
||||||
|
/// C++11 thread classes. For instance, the tthread::mutex class corresponds to
|
||||||
|
/// the std::mutex class.
|
||||||
|
namespace tthread {
|
||||||
|
|
||||||
|
/// Mutex class.
|
||||||
|
/// This is a mutual exclusion object for synchronizing access to shared
|
||||||
|
/// memory areas for several threads. The mutex is non-recursive (i.e. a
|
||||||
|
/// program may deadlock if the thread that owns a mutex object calls lock()
|
||||||
|
/// on that object).
|
||||||
|
/// @see recursive_mutex
|
||||||
|
class mutex {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
mutex()
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
: mAlreadyLocked(false)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
InitializeCriticalSection(&mHandle);
|
||||||
|
#else
|
||||||
|
pthread_mutex_init(&mHandle, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
~mutex()
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
DeleteCriticalSection(&mHandle);
|
||||||
|
#else
|
||||||
|
pthread_mutex_destroy(&mHandle);
|
||||||
|
#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(_TTHREAD_WIN32_)
|
||||||
|
EnterCriticalSection(&mHandle);
|
||||||
|
while(mAlreadyLocked) Sleep(1000); // Simulate deadlock...
|
||||||
|
mAlreadyLocked = true;
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&mHandle);
|
||||||
|
#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(_TTHREAD_WIN32_)
|
||||||
|
bool ret = (TryEnterCriticalSection(&mHandle) ? true : false);
|
||||||
|
if(ret && mAlreadyLocked)
|
||||||
|
{
|
||||||
|
LeaveCriticalSection(&mHandle);
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
#else
|
||||||
|
return (pthread_mutex_trylock(&mHandle) == 0) ? true : false;
|
||||||
|
#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(_TTHREAD_WIN32_)
|
||||||
|
mAlreadyLocked = false;
|
||||||
|
LeaveCriticalSection(&mHandle);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&mHandle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
_TTHREAD_DISABLE_ASSIGNMENT(mutex)
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
CRITICAL_SECTION mHandle;
|
||||||
|
bool mAlreadyLocked;
|
||||||
|
#else
|
||||||
|
pthread_mutex_t mHandle;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
friend class condition_variable;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Recursive mutex class.
|
||||||
|
/// This is a mutual exclusion object for synchronizing access to shared
|
||||||
|
/// memory areas for several threads. The mutex is recursive (i.e. a thread
|
||||||
|
/// may lock the mutex several times, as long as it unlocks the mutex the same
|
||||||
|
/// number of times).
|
||||||
|
/// @see mutex
|
||||||
|
class recursive_mutex {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
recursive_mutex()
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
InitializeCriticalSection(&mHandle);
|
||||||
|
#else
|
||||||
|
pthread_mutexattr_t attr;
|
||||||
|
pthread_mutexattr_init(&attr);
|
||||||
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||||
|
pthread_mutex_init(&mHandle, &attr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
~recursive_mutex()
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
DeleteCriticalSection(&mHandle);
|
||||||
|
#else
|
||||||
|
pthread_mutex_destroy(&mHandle);
|
||||||
|
#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(_TTHREAD_WIN32_)
|
||||||
|
EnterCriticalSection(&mHandle);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&mHandle);
|
||||||
|
#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(_TTHREAD_WIN32_)
|
||||||
|
return TryEnterCriticalSection(&mHandle) ? true : false;
|
||||||
|
#else
|
||||||
|
return (pthread_mutex_trylock(&mHandle) == 0) ? true : false;
|
||||||
|
#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(_TTHREAD_WIN32_)
|
||||||
|
LeaveCriticalSection(&mHandle);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&mHandle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
_TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex)
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
CRITICAL_SECTION mHandle;
|
||||||
|
#else
|
||||||
|
pthread_mutex_t mHandle;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
friend class condition_variable;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Lock guard class.
|
||||||
|
/// The constructor locks the mutex, and the destructor unlocks the mutex, so
|
||||||
|
/// the mutex will automatically be unlocked when the lock guard goes out of
|
||||||
|
/// scope. Example usage:
|
||||||
|
/// @code
|
||||||
|
/// mutex m;
|
||||||
|
/// int counter;
|
||||||
|
///
|
||||||
|
/// void increment()
|
||||||
|
/// {
|
||||||
|
/// lock_guard<mutex> guard(m);
|
||||||
|
/// ++ counter;
|
||||||
|
/// }
|
||||||
|
/// @endcode
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class lock_guard {
|
||||||
|
public:
|
||||||
|
typedef T mutex_type;
|
||||||
|
|
||||||
|
lock_guard() : mMutex(0) {}
|
||||||
|
|
||||||
|
/// The constructor locks the mutex.
|
||||||
|
explicit lock_guard(mutex_type &aMutex)
|
||||||
|
{
|
||||||
|
mMutex = &aMutex;
|
||||||
|
mMutex->lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The destructor unlocks the mutex.
|
||||||
|
~lock_guard()
|
||||||
|
{
|
||||||
|
if(mMutex)
|
||||||
|
mMutex->unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutex_type * mMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Condition variable class.
|
||||||
|
/// This is a signalling object for synchronizing the execution flow for
|
||||||
|
/// several threads. Example usage:
|
||||||
|
/// @code
|
||||||
|
/// // Shared data and associated mutex and condition variable objects
|
||||||
|
/// int count;
|
||||||
|
/// mutex m;
|
||||||
|
/// condition_variable cond;
|
||||||
|
///
|
||||||
|
/// // Wait for the counter to reach a certain number
|
||||||
|
/// void wait_counter(int targetCount)
|
||||||
|
/// {
|
||||||
|
/// lock_guard<mutex> guard(m);
|
||||||
|
/// while(count < targetCount)
|
||||||
|
/// cond.wait(m);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Increment the counter, and notify waiting threads
|
||||||
|
/// void increment()
|
||||||
|
/// {
|
||||||
|
/// lock_guard<mutex> guard(m);
|
||||||
|
/// ++ count;
|
||||||
|
/// cond.notify_all();
|
||||||
|
/// }
|
||||||
|
/// @endcode
|
||||||
|
class condition_variable {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
condition_variable();
|
||||||
|
#else
|
||||||
|
condition_variable()
|
||||||
|
{
|
||||||
|
pthread_cond_init(&mHandle, NULL);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
~condition_variable();
|
||||||
|
#else
|
||||||
|
~condition_variable()
|
||||||
|
{
|
||||||
|
pthread_cond_destroy(&mHandle);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Wait for the condition.
|
||||||
|
/// The function will block the calling thread until the condition variable
|
||||||
|
/// is woken by @c notify_one(), @c notify_all() or a spurious wake up.
|
||||||
|
/// @param[in] aMutex A mutex that will be unlocked when the wait operation
|
||||||
|
/// starts, an locked again as soon as the wait operation is finished.
|
||||||
|
template <class _mutexT>
|
||||||
|
inline void wait(_mutexT &aMutex)
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
// Increment number of waiters
|
||||||
|
EnterCriticalSection(&mWaitersCountLock);
|
||||||
|
++ mWaitersCount;
|
||||||
|
LeaveCriticalSection(&mWaitersCountLock);
|
||||||
|
|
||||||
|
// Release the mutex while waiting for the condition (will decrease
|
||||||
|
// the number of waiters when done)...
|
||||||
|
aMutex.unlock();
|
||||||
|
_wait();
|
||||||
|
aMutex.lock();
|
||||||
|
#else
|
||||||
|
pthread_cond_wait(&mHandle, &aMutex.mHandle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify one thread that is waiting for the condition.
|
||||||
|
/// If at least one thread is blocked waiting for this condition variable,
|
||||||
|
/// one will be woken up.
|
||||||
|
/// @note Only threads that started waiting prior to this call will be
|
||||||
|
/// woken up.
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
void notify_one();
|
||||||
|
#else
|
||||||
|
inline void notify_one()
|
||||||
|
{
|
||||||
|
pthread_cond_signal(&mHandle);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Notify all threads that are waiting for the condition.
|
||||||
|
/// All threads that are blocked waiting for this condition variable will
|
||||||
|
/// be woken up.
|
||||||
|
/// @note Only threads that started waiting prior to this call will be
|
||||||
|
/// woken up.
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
void notify_all();
|
||||||
|
#else
|
||||||
|
inline void notify_all()
|
||||||
|
{
|
||||||
|
pthread_cond_broadcast(&mHandle);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_TTHREAD_DISABLE_ASSIGNMENT(condition_variable)
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
void _wait();
|
||||||
|
HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs.
|
||||||
|
unsigned int mWaitersCount; ///< Count of the number of waiters.
|
||||||
|
CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount.
|
||||||
|
#else
|
||||||
|
pthread_cond_t mHandle;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Thread class.
|
||||||
|
class thread {
|
||||||
|
public:
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
typedef HANDLE native_handle_type;
|
||||||
|
#else
|
||||||
|
typedef pthread_t native_handle_type;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class id;
|
||||||
|
|
||||||
|
/// Default constructor.
|
||||||
|
/// Construct a @c thread object without an associated thread of execution
|
||||||
|
/// (i.e. non-joinable).
|
||||||
|
thread() : mHandle(0), mNotAThread(true)
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
, mWin32ThreadID(0)
|
||||||
|
#endif
|
||||||
|
{}
|
||||||
|
|
||||||
|
/// Thread starting constructor.
|
||||||
|
/// Construct a @c thread object with a new thread of execution.
|
||||||
|
/// @param[in] aFunction A function pointer to a function of type:
|
||||||
|
/// <tt>void fun(void * arg)</tt>
|
||||||
|
/// @param[in] aArg Argument to the thread function.
|
||||||
|
/// @note This constructor is not fully compatible with the standard C++
|
||||||
|
/// thread class. It is more similar to the pthread_create() (POSIX) and
|
||||||
|
/// CreateThread() (Windows) functions.
|
||||||
|
thread(void (*aFunction)(void *), void * aArg);
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
/// @note If the thread is joinable upon destruction, @c std::terminate()
|
||||||
|
/// will be called, which terminates the process. It is always wise to do
|
||||||
|
/// @c join() before deleting a thread object.
|
||||||
|
~thread();
|
||||||
|
|
||||||
|
/// Wait for the thread to finish (join execution flows).
|
||||||
|
/// After calling @c join(), the thread object is no longer associated with
|
||||||
|
/// a thread of execution (i.e. it is not joinable, and you may not join
|
||||||
|
/// with it nor detach from it).
|
||||||
|
void join();
|
||||||
|
|
||||||
|
/// Check if the thread is joinable.
|
||||||
|
/// A thread object is joinable if it has an associated thread of execution.
|
||||||
|
bool joinable() const;
|
||||||
|
|
||||||
|
/// Detach from the thread.
|
||||||
|
/// After calling @c detach(), the thread object is no longer assicated with
|
||||||
|
/// a thread of execution (i.e. it is not joinable). The thread continues
|
||||||
|
/// execution without the calling thread blocking, and when the thread
|
||||||
|
/// ends execution, any owned resources are released.
|
||||||
|
void detach();
|
||||||
|
|
||||||
|
/// Return the thread ID of a thread object.
|
||||||
|
id get_id() const;
|
||||||
|
|
||||||
|
/// Get the native handle for this thread.
|
||||||
|
/// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this
|
||||||
|
/// is a @c pthread_t.
|
||||||
|
inline native_handle_type native_handle()
|
||||||
|
{
|
||||||
|
return mHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the number of threads which can possibly execute concurrently.
|
||||||
|
/// This function is useful for determining the optimal number of threads to
|
||||||
|
/// use for a task.
|
||||||
|
/// @return The number of hardware thread contexts in the system.
|
||||||
|
/// @note If this value is not defined, the function returns zero (0).
|
||||||
|
static unsigned hardware_concurrency();
|
||||||
|
|
||||||
|
_TTHREAD_DISABLE_ASSIGNMENT(thread)
|
||||||
|
|
||||||
|
private:
|
||||||
|
native_handle_type mHandle; ///< Thread handle.
|
||||||
|
mutable mutex mDataMutex; ///< Serializer for access to the thread private data.
|
||||||
|
bool mNotAThread; ///< True if this object is not a thread of execution.
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex).
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This is the internal thread wrapper function.
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
static unsigned WINAPI wrapper_function(void * aArg);
|
||||||
|
#else
|
||||||
|
static void * wrapper_function(void * aArg);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Thread ID.
|
||||||
|
/// The thread ID is a unique identifier for each thread.
|
||||||
|
/// @see thread::get_id()
|
||||||
|
class thread::id {
|
||||||
|
public:
|
||||||
|
/// Default constructor.
|
||||||
|
/// The default constructed ID is that of thread without a thread of
|
||||||
|
/// execution.
|
||||||
|
id() : mId(0) {};
|
||||||
|
|
||||||
|
id(unsigned long int aId) : mId(aId) {};
|
||||||
|
|
||||||
|
id(const id& aId) : mId(aId.mId) {};
|
||||||
|
|
||||||
|
inline id & operator=(const id &aId)
|
||||||
|
{
|
||||||
|
mId = aId.mId;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline friend bool operator==(const id &aId1, const id &aId2)
|
||||||
|
{
|
||||||
|
return (aId1.mId == aId2.mId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline friend bool operator!=(const id &aId1, const id &aId2)
|
||||||
|
{
|
||||||
|
return (aId1.mId != aId2.mId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline friend bool operator<=(const id &aId1, const id &aId2)
|
||||||
|
{
|
||||||
|
return (aId1.mId <= aId2.mId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline friend bool operator<(const id &aId1, const id &aId2)
|
||||||
|
{
|
||||||
|
return (aId1.mId < aId2.mId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline friend bool operator>=(const id &aId1, const id &aId2)
|
||||||
|
{
|
||||||
|
return (aId1.mId >= aId2.mId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline friend bool operator>(const id &aId1, const id &aId2)
|
||||||
|
{
|
||||||
|
return (aId1.mId > aId2.mId);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline friend std::ostream& operator <<(std::ostream &os, const id &obj)
|
||||||
|
{
|
||||||
|
os << obj.mId;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned long int mId;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Related to <ratio> - minimal to be able to support chrono.
|
||||||
|
typedef long long __intmax_t;
|
||||||
|
|
||||||
|
/// Minimal implementation of the @c ratio class. This class provides enough
|
||||||
|
/// functionality to implement some basic @c chrono classes.
|
||||||
|
template <__intmax_t N, __intmax_t D = 1> class ratio {
|
||||||
|
public:
|
||||||
|
static double _as_double() { return double(N) / double(D); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Minimal implementation of the @c chrono namespace.
|
||||||
|
/// The @c chrono namespace provides types for specifying time intervals.
|
||||||
|
namespace chrono {
|
||||||
|
/// Duration template class. This class provides enough functionality to
|
||||||
|
/// implement @c this_thread::sleep_for().
|
||||||
|
template <class _Rep, class _Period = ratio<1> > class duration {
|
||||||
|
private:
|
||||||
|
_Rep rep_;
|
||||||
|
public:
|
||||||
|
typedef _Rep rep;
|
||||||
|
typedef _Period period;
|
||||||
|
|
||||||
|
/// Construct a duration object with the given duration.
|
||||||
|
template <class _Rep2>
|
||||||
|
explicit duration(const _Rep2& r) : rep_(r) {};
|
||||||
|
|
||||||
|
/// Return the value of the duration object.
|
||||||
|
rep count() const
|
||||||
|
{
|
||||||
|
return rep_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard duration types.
|
||||||
|
typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds.
|
||||||
|
typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds.
|
||||||
|
typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds.
|
||||||
|
typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds.
|
||||||
|
typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes.
|
||||||
|
typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The namespace @c this_thread provides methods for dealing with the
|
||||||
|
/// calling thread.
|
||||||
|
namespace this_thread {
|
||||||
|
/// Return the thread ID of the calling thread.
|
||||||
|
thread::id get_id();
|
||||||
|
|
||||||
|
/// Yield execution to another thread.
|
||||||
|
/// Offers the operating system the opportunity to schedule another thread
|
||||||
|
/// that is ready to run on the current processor.
|
||||||
|
inline void yield()
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
Sleep(0);
|
||||||
|
#else
|
||||||
|
sched_yield();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks the calling thread for a period of time.
|
||||||
|
/// @param[in] aTime Minimum time to put the thread to sleep.
|
||||||
|
/// Example usage:
|
||||||
|
/// @code
|
||||||
|
/// // Sleep for 100 milliseconds
|
||||||
|
/// this_thread::sleep_for(chrono::milliseconds(100));
|
||||||
|
/// @endcode
|
||||||
|
/// @note Supported duration types are: nanoseconds, microseconds,
|
||||||
|
/// milliseconds, seconds, minutes and hours.
|
||||||
|
template <class _Rep, class _Period> void sleep_for(const chrono::duration<_Rep, _Period>& aTime)
|
||||||
|
{
|
||||||
|
#if defined(_TTHREAD_WIN32_)
|
||||||
|
Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5));
|
||||||
|
#else
|
||||||
|
usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define/macro cleanup
|
||||||
|
#undef _TTHREAD_DISABLE_ASSIGNMENT
|
||||||
|
|
||||||
|
#endif // _TINYTHREAD_H_
|
Reference in New Issue
Block a user