mirror of
https://github.com/kerberos-io/openalpr-base.git
synced 2025-10-06 07:16:56 +08:00
Added OpenALPR library
This commit is contained in:
75
src/openalpr/TRexpp.h
Normal file
75
src/openalpr/TRexpp.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#ifndef _TREXPP_H_
|
||||||
|
#define _TREXPP_H_
|
||||||
|
/***************************************************************
|
||||||
|
T-Rex a tiny regular expression library
|
||||||
|
|
||||||
|
Copyright (C) 2003-2004 Alberto Demichelis
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "trex.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TRexParseException{TRexParseException(const TRexChar *c):desc(c){}const TRexChar *desc;};
|
||||||
|
|
||||||
|
class TRexpp {
|
||||||
|
public:
|
||||||
|
TRexpp() { _exp = (TRex *)0; }
|
||||||
|
~TRexpp() { CleanUp(); }
|
||||||
|
// compiles a regular expression
|
||||||
|
void Compile(const TRexChar *pattern) {
|
||||||
|
const TRexChar *error;
|
||||||
|
CleanUp();
|
||||||
|
if(!(_exp = trex_compile(pattern,&error)))
|
||||||
|
throw TRexParseException(error);
|
||||||
|
}
|
||||||
|
// return true if the given text match the expression
|
||||||
|
bool Match(const TRexChar* text) {
|
||||||
|
return _exp?(trex_match(_exp,text) != 0):false;
|
||||||
|
}
|
||||||
|
// Searches for the first match of the expression in a zero terminated string
|
||||||
|
bool Search(const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end) {
|
||||||
|
return _exp?(trex_search(_exp,text,out_begin,out_end) != 0):false;
|
||||||
|
}
|
||||||
|
// Searches for the first match of the expression in a string sarting at text_begin and ending at text_end
|
||||||
|
bool SearchRange(const TRexChar* text_begin,const TRexChar* text_end,const TRexChar** out_begin, const TRexChar** out_end) {
|
||||||
|
return _exp?(trex_searchrange(_exp,text_begin,text_end,out_begin,out_end) != 0):false;
|
||||||
|
}
|
||||||
|
bool GetSubExp(int n, const TRexChar** out_begin, int *out_len)
|
||||||
|
{
|
||||||
|
TRexMatch match;
|
||||||
|
TRexBool res = _exp?(trex_getsubexp(_exp,n,&match)):TRex_False;
|
||||||
|
if(res) {
|
||||||
|
*out_begin = match.begin;
|
||||||
|
*out_len = match.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int GetSubExpCount() { return _exp?trex_getsubexpcount(_exp):0; }
|
||||||
|
private:
|
||||||
|
void CleanUp() { if(_exp) trex_free(_exp); _exp = (TRex *)0; }
|
||||||
|
TRex *_exp;
|
||||||
|
};
|
||||||
|
#endif //_TREXPP_H_
|
89
src/openalpr/alpr.cpp
Normal file
89
src/openalpr/alpr.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "alpr.h"
|
||||||
|
#include "alpr_impl.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ALPR code
|
||||||
|
|
||||||
|
Alpr::Alpr(const std::string country, const std::string runtimeDir)
|
||||||
|
{
|
||||||
|
impl = new AlprImpl(country, runtimeDir);
|
||||||
|
}
|
||||||
|
Alpr::~Alpr()
|
||||||
|
{
|
||||||
|
delete impl;
|
||||||
|
}
|
||||||
|
std::vector<AlprResult> Alpr::recognize(std::string filepath)
|
||||||
|
{
|
||||||
|
cv::Mat img = cv::imread(filepath, CV_LOAD_IMAGE_COLOR);
|
||||||
|
return impl->recognize(img);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<AlprResult> Alpr::recognize(std::vector<unsigned char> imageBuffer)
|
||||||
|
{
|
||||||
|
// Not sure if this actually works
|
||||||
|
cv::Mat img = cv::imdecode(Mat(imageBuffer), 1);
|
||||||
|
|
||||||
|
return impl->recognize(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string Alpr::toJson(const vector< AlprResult > results)
|
||||||
|
{
|
||||||
|
return impl->toJson(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Alpr::setDetectRegion(bool detectRegion)
|
||||||
|
{
|
||||||
|
impl->setDetectRegion(detectRegion);
|
||||||
|
}
|
||||||
|
void Alpr::setTopN(int topN)
|
||||||
|
{
|
||||||
|
impl->setTopN(topN);
|
||||||
|
}
|
||||||
|
void Alpr::setDefaultRegion(std::string region)
|
||||||
|
{
|
||||||
|
impl->setDefaultRegion(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Alpr::isLoaded()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Results code
|
||||||
|
|
||||||
|
|
||||||
|
AlprResult::AlprResult()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
AlprResult::~AlprResult()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
88
src/openalpr/alpr.h
Normal file
88
src/openalpr/alpr.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ALPR_H
|
||||||
|
#define ALPR_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define OPENALPR_VERSION "0.3"
|
||||||
|
|
||||||
|
struct AlprPlate
|
||||||
|
{
|
||||||
|
std::string characters;
|
||||||
|
float overall_confidence;
|
||||||
|
|
||||||
|
bool matches_template;
|
||||||
|
//int char_confidence[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AlprCoordinate
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AlprResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AlprResult();
|
||||||
|
virtual ~AlprResult();
|
||||||
|
|
||||||
|
int requested_topn;
|
||||||
|
int result_count;
|
||||||
|
|
||||||
|
AlprPlate bestPlate;
|
||||||
|
std::vector<AlprPlate> topNPlates;
|
||||||
|
|
||||||
|
float processing_time_ms;
|
||||||
|
AlprCoordinate plate_points[4];
|
||||||
|
|
||||||
|
int regionConfidence;
|
||||||
|
std::string region;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AlprImpl;
|
||||||
|
class Alpr
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
Alpr(const std::string country, const std::string runtimeDir = "");
|
||||||
|
virtual ~Alpr();
|
||||||
|
|
||||||
|
void setDetectRegion(bool detectRegion);
|
||||||
|
void setTopN(int topN);
|
||||||
|
void setDefaultRegion(std::string region);
|
||||||
|
|
||||||
|
std::vector<AlprResult> recognize(std::string filepath);
|
||||||
|
std::vector<AlprResult> recognize(std::vector<unsigned char> imageBuffer);
|
||||||
|
|
||||||
|
std::string toJson(const std::vector<AlprResult> results);
|
||||||
|
|
||||||
|
bool isLoaded();
|
||||||
|
|
||||||
|
private:
|
||||||
|
AlprImpl* impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APLR_H
|
229
src/openalpr/alpr_impl.cpp
Normal file
229
src/openalpr/alpr_impl.cpp
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "alpr_impl.h"
|
||||||
|
|
||||||
|
|
||||||
|
AlprImpl::AlprImpl(const std::string country, const std::string runtimeDir)
|
||||||
|
{
|
||||||
|
config = new Config(country, runtimeDir);
|
||||||
|
plateDetector = new RegionDetector(config);
|
||||||
|
stateIdentifier = new StateIdentifier(config);
|
||||||
|
ocr = new OCR(config);
|
||||||
|
|
||||||
|
this->detectRegion = DEFAULT_DETECT_REGION;
|
||||||
|
this->topN = DEFAULT_TOPN;
|
||||||
|
this->defaultRegion = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
AlprImpl::~AlprImpl()
|
||||||
|
{
|
||||||
|
delete config;
|
||||||
|
delete plateDetector;
|
||||||
|
delete stateIdentifier;
|
||||||
|
delete ocr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<AlprResult> AlprImpl::recognize(cv::Mat img)
|
||||||
|
{
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
vector<AlprResult> response;
|
||||||
|
|
||||||
|
|
||||||
|
vector<Rect> plateRegions = plateDetector->detect(img);
|
||||||
|
|
||||||
|
|
||||||
|
// Recognize.
|
||||||
|
|
||||||
|
for (int i = 0; i < plateRegions.size(); i++)
|
||||||
|
{
|
||||||
|
timespec platestarttime;
|
||||||
|
getTime(&platestarttime);
|
||||||
|
|
||||||
|
LicensePlateCandidate lp(img, plateRegions[i], config);
|
||||||
|
|
||||||
|
lp.recognize();
|
||||||
|
|
||||||
|
|
||||||
|
if (lp.confidence > 10)
|
||||||
|
{
|
||||||
|
AlprResult plateResult;
|
||||||
|
plateResult.region = defaultRegion;
|
||||||
|
plateResult.regionConfidence = 0;
|
||||||
|
|
||||||
|
for (int pointidx = 0; pointidx < 4; pointidx++)
|
||||||
|
{
|
||||||
|
plateResult.plate_points[pointidx].x = (int) lp.plateCorners[pointidx].x;
|
||||||
|
plateResult.plate_points[pointidx].y = (int) lp.plateCorners[pointidx].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detectRegion)
|
||||||
|
{
|
||||||
|
char statecode[4];
|
||||||
|
plateResult.regionConfidence = stateIdentifier->recognize(img, plateRegions[i], statecode);
|
||||||
|
if (plateResult.regionConfidence > 0)
|
||||||
|
{
|
||||||
|
plateResult.region = statecode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ocr->performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters);
|
||||||
|
|
||||||
|
ocr->postProcessor->analyze(plateResult.region, topN);
|
||||||
|
|
||||||
|
//plateResult.characters = ocr->postProcessor->bestChars;
|
||||||
|
const vector<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)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->debugGeneral && config->debugShowImages)
|
||||||
|
{
|
||||||
|
displayImage(config, "Main Image", img);
|
||||||
|
// Pause indefinitely until they press a key
|
||||||
|
while ((char) cv::waitKey(50) == -1)
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
string AlprImpl::toJson(const vector< AlprResult > results)
|
||||||
|
{
|
||||||
|
cJSON *root = cJSON_CreateArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < results.size(); i++)
|
||||||
|
{
|
||||||
|
cJSON *resultObj = createJsonObj( &results[i] );
|
||||||
|
cJSON_AddItemToArray(root, resultObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the JSON object to a string and return
|
||||||
|
char *out;
|
||||||
|
out=cJSON_PrintUnformatted(root);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
string response(out);
|
||||||
|
|
||||||
|
free(out);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cJSON* AlprImpl::createJsonObj(const AlprResult* result)
|
||||||
|
{
|
||||||
|
cJSON *root, *coords;
|
||||||
|
|
||||||
|
root=cJSON_CreateObject();
|
||||||
|
|
||||||
|
cJSON_AddStringToObject(root,"plate", result->bestPlate.characters.c_str());
|
||||||
|
cJSON_AddNumberToObject(root,"confidence", result->bestPlate.overall_confidence);
|
||||||
|
cJSON_AddNumberToObject(root,"matches_template", result->bestPlate.matches_template);
|
||||||
|
|
||||||
|
cJSON_AddStringToObject(root,"region", result->region.c_str());
|
||||||
|
cJSON_AddNumberToObject(root,"region_confidence", result->regionConfidence);
|
||||||
|
|
||||||
|
cJSON_AddNumberToObject(root,"processing_time_ms", result->processing_time_ms);
|
||||||
|
|
||||||
|
cJSON_AddItemToObject(root, "coordinates", coords=cJSON_CreateArray());
|
||||||
|
for (int i=0;i<4;i++)
|
||||||
|
{
|
||||||
|
cJSON *coords_object;
|
||||||
|
coords_object = cJSON_CreateObject();
|
||||||
|
cJSON_AddNumberToObject(coords_object, "x", result->plate_points[i].x);
|
||||||
|
cJSON_AddNumberToObject(coords_object, "y", result->plate_points[i].y);
|
||||||
|
|
||||||
|
cJSON_AddItemToArray(coords, coords_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AlprImpl::setDetectRegion(bool detectRegion)
|
||||||
|
{
|
||||||
|
this->detectRegion = detectRegion;
|
||||||
|
}
|
||||||
|
void AlprImpl::setTopN(int topn)
|
||||||
|
{
|
||||||
|
this->topN = topn;
|
||||||
|
}
|
||||||
|
void AlprImpl::setDefaultRegion(string region)
|
||||||
|
{
|
||||||
|
this->defaultRegion = region;
|
||||||
|
}
|
||||||
|
|
73
src/openalpr/alpr_impl.h
Normal file
73
src/openalpr/alpr_impl.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ALPRIMPL_H
|
||||||
|
#define ALPRIMPL_H
|
||||||
|
|
||||||
|
#include "alpr.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "regiondetector.h"
|
||||||
|
#include "licenseplatecandidate.h"
|
||||||
|
#include "stateidentifier.h"
|
||||||
|
#include "charactersegmenter.h"
|
||||||
|
#include "ocr.h"
|
||||||
|
|
||||||
|
#include "cjson.h"
|
||||||
|
|
||||||
|
#include <opencv2/core/core.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
#define DEFAULT_TOPN 25
|
||||||
|
#define DEFAULT_DETECT_REGION false
|
||||||
|
|
||||||
|
class AlprImpl
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
AlprImpl(const std::string country, const std::string runtimeDir = "");
|
||||||
|
virtual ~AlprImpl();
|
||||||
|
|
||||||
|
std::vector<AlprResult> recognize(cv::Mat img);
|
||||||
|
|
||||||
|
void applyRegionTemplate(AlprResult* result, std::string region);
|
||||||
|
|
||||||
|
void setDetectRegion(bool detectRegion);
|
||||||
|
void setTopN(int topn);
|
||||||
|
void setDefaultRegion(string region);
|
||||||
|
|
||||||
|
std::string toJson(const vector<AlprResult> results);
|
||||||
|
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
RegionDetector* plateDetector;
|
||||||
|
StateIdentifier* stateIdentifier;
|
||||||
|
OCR* ocr;
|
||||||
|
|
||||||
|
int topN;
|
||||||
|
bool detectRegion;
|
||||||
|
std::string defaultRegion;
|
||||||
|
|
||||||
|
cJSON* createJsonObj(const AlprResult* result);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ALPRIMPL_H
|
372
src/openalpr/binarize_wolf.cpp
Normal file
372
src/openalpr/binarize_wolf.cpp
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**************************************************************
|
||||||
|
* Binarization with several methods
|
||||||
|
* (0) Niblacks method
|
||||||
|
* (1) Sauvola & Co.
|
||||||
|
* ICDAR 1997, pp 147-152
|
||||||
|
* (2) by myself - Christian Wolf
|
||||||
|
* Research notebook 19.4.2001, page 129
|
||||||
|
* (3) by myself - Christian Wolf
|
||||||
|
* 20.4.2007
|
||||||
|
*
|
||||||
|
* See also:
|
||||||
|
* Research notebook 24.4.2001, page 132 (Calculation of s)
|
||||||
|
**************************************************************/
|
||||||
|
|
||||||
|
#include "binarize_wolf.h"
|
||||||
|
|
||||||
|
|
||||||
|
// *************************************************************
|
||||||
|
// glide a window across the image and
|
||||||
|
// create two maps: mean and standard deviation.
|
||||||
|
// *************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
float calcLocalStats (Mat &im, Mat &map_m, Mat &map_s, int winx, int winy) {
|
||||||
|
|
||||||
|
float m,s,max_s, sum, sum_sq, foo;
|
||||||
|
int wxh = winx/2;
|
||||||
|
int wyh = winy/2;
|
||||||
|
int x_firstth= wxh;
|
||||||
|
int y_lastth = im.rows-wyh-1;
|
||||||
|
int y_firstth= wyh;
|
||||||
|
float winarea = winx*winy;
|
||||||
|
|
||||||
|
max_s = 0;
|
||||||
|
for (int j = y_firstth ; j<=y_lastth; j++)
|
||||||
|
{
|
||||||
|
// Calculate the initial window at the beginning of the line
|
||||||
|
sum = sum_sq = 0;
|
||||||
|
for (int wy=0 ; wy<winy; wy++)
|
||||||
|
for (int wx=0 ; wx<winx; wx++) {
|
||||||
|
foo = im.uget(wx,j-wyh+wy);
|
||||||
|
sum += foo;
|
||||||
|
sum_sq += foo*foo;
|
||||||
|
}
|
||||||
|
m = sum / winarea;
|
||||||
|
s = sqrt ((sum_sq - (sum*sum)/winarea)/winarea);
|
||||||
|
if (s > max_s)
|
||||||
|
max_s = s;
|
||||||
|
map_m.fset(x_firstth, j, m);
|
||||||
|
map_s.fset(x_firstth, j, s);
|
||||||
|
|
||||||
|
// Shift the window, add and remove new/old values to the histogram
|
||||||
|
for (int i=1 ; i <= im.cols-winx; i++) {
|
||||||
|
|
||||||
|
// Remove the left old column and add the right new column
|
||||||
|
for (int wy=0; wy<winy; ++wy) {
|
||||||
|
foo = im.uget(i-1,j-wyh+wy);
|
||||||
|
sum -= foo;
|
||||||
|
sum_sq -= foo*foo;
|
||||||
|
foo = im.uget(i+winx-1,j-wyh+wy);
|
||||||
|
sum += foo;
|
||||||
|
sum_sq += foo*foo;
|
||||||
|
}
|
||||||
|
m = sum / winarea;
|
||||||
|
s = sqrt ((sum_sq - (sum*sum)/winarea)/winarea);
|
||||||
|
if (s > max_s)
|
||||||
|
max_s = s;
|
||||||
|
map_m.fset(i+wxh, j, m);
|
||||||
|
map_s.fset(i+wxh, j, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* The binarization routine
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
void NiblackSauvolaWolfJolion (Mat im, Mat output, NiblackVersion version,
|
||||||
|
int winx, int winy, float k) {
|
||||||
|
|
||||||
|
float dR = BINARIZEWOLF_DEFAULTDR;
|
||||||
|
|
||||||
|
float m, s, max_s;
|
||||||
|
float th=0;
|
||||||
|
double min_I, max_I;
|
||||||
|
int wxh = winx/2;
|
||||||
|
int wyh = winy/2;
|
||||||
|
int x_firstth= wxh;
|
||||||
|
int x_lastth = im.cols-wxh-1;
|
||||||
|
int y_lastth = im.rows-wyh-1;
|
||||||
|
int y_firstth= wyh;
|
||||||
|
int mx, my;
|
||||||
|
|
||||||
|
// Create local statistics and store them in a float matrices
|
||||||
|
Mat map_m = Mat::zeros (im.rows, im.cols, CV_32F);
|
||||||
|
Mat map_s = Mat::zeros (im.rows, im.cols, CV_32F);
|
||||||
|
max_s = calcLocalStats (im, map_m, map_s, winx, winy);
|
||||||
|
|
||||||
|
minMaxLoc(im, &min_I, &max_I);
|
||||||
|
|
||||||
|
Mat thsurf (im.rows, im.cols, CV_32F);
|
||||||
|
|
||||||
|
// Create the threshold surface, including border processing
|
||||||
|
// ----------------------------------------------------
|
||||||
|
|
||||||
|
for (int j = y_firstth ; j<=y_lastth; j++) {
|
||||||
|
|
||||||
|
// NORMAL, NON-BORDER AREA IN THE MIDDLE OF THE WINDOW:
|
||||||
|
for (int i=0 ; i <= im.cols-winx; i++) {
|
||||||
|
|
||||||
|
m = map_m.fget(i+wxh, j);
|
||||||
|
s = map_s.fget(i+wxh, j);
|
||||||
|
|
||||||
|
// Calculate the threshold
|
||||||
|
switch (version) {
|
||||||
|
|
||||||
|
case NIBLACK:
|
||||||
|
th = m + k*s;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SAUVOLA:
|
||||||
|
th = m * (1 + k*(s/dR-1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WOLFJOLION:
|
||||||
|
th = m + k * (s/max_s-1) * (m-min_I);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
cerr << "Unknown threshold type in ImageThresholder::surfaceNiblackImproved()\n";
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
thsurf.fset(i+wxh,j,th);
|
||||||
|
|
||||||
|
if (i==0) {
|
||||||
|
// LEFT BORDER
|
||||||
|
for (int i=0; i<=x_firstth; ++i)
|
||||||
|
thsurf.fset(i,j,th);
|
||||||
|
|
||||||
|
// LEFT-UPPER CORNER
|
||||||
|
if (j==y_firstth)
|
||||||
|
for (int u=0; u<y_firstth; ++u)
|
||||||
|
for (int i=0; i<=x_firstth; ++i)
|
||||||
|
thsurf.fset(i,u,th);
|
||||||
|
|
||||||
|
// LEFT-LOWER CORNER
|
||||||
|
if (j==y_lastth)
|
||||||
|
for (int u=y_lastth+1; u<im.rows; ++u)
|
||||||
|
for (int i=0; i<=x_firstth; ++i)
|
||||||
|
thsurf.fset(i,u,th);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPPER BORDER
|
||||||
|
if (j==y_firstth)
|
||||||
|
for (int u=0; u<y_firstth; ++u)
|
||||||
|
thsurf.fset(i+wxh,u,th);
|
||||||
|
|
||||||
|
// LOWER BORDER
|
||||||
|
if (j==y_lastth)
|
||||||
|
for (int u=y_lastth+1; u<im.rows; ++u)
|
||||||
|
thsurf.fset(i+wxh,u,th);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RIGHT BORDER
|
||||||
|
for (int i=x_lastth; i<im.cols; ++i)
|
||||||
|
thsurf.fset(i,j,th);
|
||||||
|
|
||||||
|
// RIGHT-UPPER CORNER
|
||||||
|
if (j==y_firstth)
|
||||||
|
for (int u=0; u<y_firstth; ++u)
|
||||||
|
for (int i=x_lastth; i<im.cols; ++i)
|
||||||
|
thsurf.fset(i,u,th);
|
||||||
|
|
||||||
|
// RIGHT-LOWER CORNER
|
||||||
|
if (j==y_lastth)
|
||||||
|
for (int u=y_lastth+1; u<im.rows; ++u)
|
||||||
|
for (int i=x_lastth; i<im.cols; ++i)
|
||||||
|
thsurf.fset(i,u,th);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (int y=0; y<im.rows; ++y)
|
||||||
|
for (int x=0; x<im.cols; ++x)
|
||||||
|
{
|
||||||
|
if (im.uget(x,y) >= thsurf.fget(x,y))
|
||||||
|
{
|
||||||
|
output.uset(x,y,255);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output.uset(x,y,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* The main function
|
||||||
|
**********************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************
|
||||||
|
* Usage
|
||||||
|
**********************************************************/
|
||||||
|
/*
|
||||||
|
static void usage (char *com) {
|
||||||
|
cerr << "usage: " << com << " [ -x <winx> -y <winy> -k <parameter> ] [ version ] <inputimage> <outputimage>\n\n"
|
||||||
|
<< "version: n Niblack (1986) needs white text on black background\n"
|
||||||
|
<< " s Sauvola et al. (1997) needs black text on white background\n"
|
||||||
|
<< " w Wolf et al. (2001) needs black text on white background\n"
|
||||||
|
<< "\n"
|
||||||
|
<< "Default version: w (Wolf et al. 2001)\n"
|
||||||
|
<< "\n"
|
||||||
|
<< "example:\n"
|
||||||
|
<< " " << com << " w in.pgm out.pgm\n"
|
||||||
|
<< " " << com << " in.pgm out.pgm\n"
|
||||||
|
<< " " << com << " s -x 50 -y 50 -k 0.6 in.pgm out.pgm\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
char version;
|
||||||
|
int c;
|
||||||
|
int winx=0, winy=0;
|
||||||
|
float optK=0.5;
|
||||||
|
bool didSpecifyK=false;
|
||||||
|
NiblackVersion versionCode;
|
||||||
|
char *inputname, *outputname, *versionstring;
|
||||||
|
|
||||||
|
cerr << "===========================================================\n"
|
||||||
|
<< "Christian Wolf, LIRIS Laboratory, Lyon, France.\n"
|
||||||
|
<< "christian.wolf@liris.cnrs.fr\n"
|
||||||
|
<< "Version " << BINARIZEWOLF_VERSION << endl
|
||||||
|
<< "===========================================================\n";
|
||||||
|
|
||||||
|
// Argument processing
|
||||||
|
while ((c = getopt (argc, argv, "x:y:k:")) != EOF) {
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
|
||||||
|
case 'x':
|
||||||
|
winx = atof(optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'y':
|
||||||
|
winy = atof(optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'k':
|
||||||
|
optK = atof(optarg);
|
||||||
|
didSpecifyK = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
usage (*argv);
|
||||||
|
cerr << "\nProblem parsing the options!\n\n";
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(argc-optind)
|
||||||
|
{
|
||||||
|
case 3:
|
||||||
|
versionstring=argv[optind];
|
||||||
|
inputname=argv[optind+1];
|
||||||
|
outputname=argv[optind+2];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
versionstring=(char *) "w";
|
||||||
|
inputname=argv[optind];
|
||||||
|
outputname=argv[optind+1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
usage (*argv);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cerr << "Adaptive binarization\n"
|
||||||
|
<< "Threshold calculation: ";
|
||||||
|
|
||||||
|
// Determine the method
|
||||||
|
version = versionstring[0];
|
||||||
|
switch (version)
|
||||||
|
{
|
||||||
|
case 'n':
|
||||||
|
versionCode = NIBLACK;
|
||||||
|
cerr << "Niblack (1986)\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
versionCode = SAUVOLA;
|
||||||
|
cerr << "Sauvola et al. (1997)\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
versionCode = WOLFJOLION;
|
||||||
|
cerr << "Wolf and Jolion (2001)\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
usage (*argv);
|
||||||
|
cerr << "\nInvalid version: '" << version << "'!";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cerr << "parameter k=" << optK << endl;
|
||||||
|
|
||||||
|
if (!didSpecifyK)
|
||||||
|
cerr << "Setting k to default value " << optK << endl;
|
||||||
|
|
||||||
|
|
||||||
|
// Load the image in grayscale mode
|
||||||
|
Mat input = imread(inputname,CV_LOAD_IMAGE_GRAYSCALE);
|
||||||
|
|
||||||
|
|
||||||
|
if ((input.rows<=0) || (input.cols<=0)) {
|
||||||
|
cerr << "*** ERROR: Couldn't read input image " << inputname << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Treat the window size
|
||||||
|
if (winx==0||winy==0) {
|
||||||
|
cerr << "Input size: " << input.cols << "x" << input.rows << endl;
|
||||||
|
winy = (int) (2.0 * input.rows-1)/3;
|
||||||
|
winx = (int) input.cols-1 < winy ? input.cols-1 : winy;
|
||||||
|
// if the window is too big, than we asume that the image
|
||||||
|
// is not a single text box, but a document page: set
|
||||||
|
// the window size to a fixed constant.
|
||||||
|
if (winx > 100)
|
||||||
|
winx = winy = 40;
|
||||||
|
cerr << "Setting window size to [" << winx
|
||||||
|
<< "," << winy << "].\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Threshold
|
||||||
|
Mat output (input.rows, input.cols, CV_8U);
|
||||||
|
NiblackSauvolaWolfJolion (input, output, versionCode, winx, winy, optK);
|
||||||
|
|
||||||
|
// Write the tresholded file
|
||||||
|
cerr << "Writing binarized image to file '" << outputname << "'.\n";
|
||||||
|
imwrite (outputname, output);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*/
|
57
src/openalpr/binarize_wolf.h
Normal file
57
src/openalpr/binarize_wolf.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef BINARIZEWOLF_H
|
||||||
|
#define BINARIZEWOLF_H
|
||||||
|
|
||||||
|
#include "support/filesystem.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cv.h>
|
||||||
|
#include <highgui.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace cv;
|
||||||
|
|
||||||
|
enum NiblackVersion
|
||||||
|
{
|
||||||
|
NIBLACK=0,
|
||||||
|
SAUVOLA,
|
||||||
|
WOLFJOLION,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define BINARIZEWOLF_VERSION "2.3 (February 26th, 2013)"
|
||||||
|
#define BINARIZEWOLF_DEFAULTDR 128
|
||||||
|
|
||||||
|
|
||||||
|
#define uget(x,y) at<unsigned char>(y,x)
|
||||||
|
#define uset(x,y,v) at<unsigned char>(y,x)=v;
|
||||||
|
#define fget(x,y) at<float>(y,x)
|
||||||
|
#define fset(x,y,v) at<float>(y,x)=v;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NiblackSauvolaWolfJolion (Mat im, Mat output, NiblackVersion version,
|
||||||
|
int winx, int winy, float k);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // BINARIZEWOLF_H
|
1040
src/openalpr/characteranalysis.cpp
Normal file
1040
src/openalpr/characteranalysis.cpp
Normal file
File diff suppressed because it is too large
Load Diff
104
src/openalpr/characteranalysis.h
Normal file
104
src/openalpr/characteranalysis.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CHARACTERANALYSIS_H
|
||||||
|
#define CHARACTERANALYSIS_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
class CharacterAnalysis
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
CharacterAnalysis(Mat img, Config* config);
|
||||||
|
virtual ~CharacterAnalysis();
|
||||||
|
|
||||||
|
bool hasPlateMask;
|
||||||
|
Mat plateMask;
|
||||||
|
|
||||||
|
Mat bestThreshold;
|
||||||
|
vector<vector<Point> > bestContours;
|
||||||
|
vector<Vec4i> bestHierarchy;
|
||||||
|
vector<bool> bestCharSegments;
|
||||||
|
int bestCharSegmentsCount;
|
||||||
|
|
||||||
|
LineSegment topLine;
|
||||||
|
LineSegment bottomLine;
|
||||||
|
vector<Point> linePolygon;
|
||||||
|
vector<Point> charArea;
|
||||||
|
|
||||||
|
LineSegment charBoxTop;
|
||||||
|
LineSegment charBoxBottom;
|
||||||
|
LineSegment charBoxLeft;
|
||||||
|
LineSegment charBoxRight;
|
||||||
|
|
||||||
|
bool thresholdsInverted;
|
||||||
|
|
||||||
|
vector<Mat> thresholds;
|
||||||
|
vector<vector<vector<Point> > > allContours;
|
||||||
|
vector<vector<Vec4i> > allHierarchy;
|
||||||
|
vector<vector<bool> > charSegments;
|
||||||
|
|
||||||
|
void analyze();
|
||||||
|
|
||||||
|
Mat getCharacterMask();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
Mat img_gray;
|
||||||
|
|
||||||
|
Mat findOuterBoxMask( );
|
||||||
|
|
||||||
|
|
||||||
|
bool isPlateInverted();
|
||||||
|
vector<bool> filter(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy);
|
||||||
|
|
||||||
|
vector<bool> filterByBoxSize(vector<vector<Point> > contours, vector<bool> goodIndices, int minHeightPx, int maxHeightPx);
|
||||||
|
vector<bool> filterByParentContour( vector< vector< Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
vector<bool> filterContourHoles(vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
vector<bool> filterByOuterMask(vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
|
||||||
|
vector<Point> getCharArea();
|
||||||
|
vector<Point> getBestVotedLines(Mat img, vector<vector<Point> > contours, vector<bool> goodIndices);
|
||||||
|
//vector<Point> getCharSegmentsBetweenLines(Mat img, vector<vector<Point> > contours, vector<Point> outerPolygon);
|
||||||
|
vector<bool> filterBetweenLines(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<Point> outerPolygon, vector<bool> goodIndices);
|
||||||
|
|
||||||
|
bool verifySize(Mat r, float minHeightPx, float maxHeightPx);
|
||||||
|
|
||||||
|
int getGoodIndicesCount(vector<bool> goodIndices);
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CHARACTERANALYSIS_H
|
||||||
|
|
||||||
|
|
194
src/openalpr/characterregion.cpp
Normal file
194
src/openalpr/characterregion.cpp
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "characterregion.h"
|
||||||
|
//#include <apr-1.0/apr_poll.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
CharacterRegion::CharacterRegion(Mat img, Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
this->debug = config->debugCharRegions;
|
||||||
|
|
||||||
|
this->confidence = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
cout << "Starting CharacterRegion identification" << endl;
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
|
||||||
|
charAnalysis = new CharacterAnalysis(img, config);
|
||||||
|
charAnalysis->analyze();
|
||||||
|
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
{
|
||||||
|
vector<Mat> tempDash;
|
||||||
|
for (int z = 0; z < charAnalysis->thresholds.size(); z++)
|
||||||
|
{
|
||||||
|
Mat tmp(charAnalysis->thresholds[z].size(), charAnalysis->thresholds[z].type());
|
||||||
|
charAnalysis->thresholds[z].copyTo(tmp);
|
||||||
|
cvtColor(tmp, tmp, CV_GRAY2BGR);
|
||||||
|
|
||||||
|
tempDash.push_back(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Mat bestVal(charAnalysis->bestThreshold.size(), charAnalysis->bestThreshold.type());
|
||||||
|
charAnalysis->bestThreshold.copyTo(bestVal);
|
||||||
|
cvtColor(bestVal, bestVal, CV_GRAY2BGR);
|
||||||
|
|
||||||
|
for (int z = 0; z < charAnalysis->bestContours.size(); z++)
|
||||||
|
{
|
||||||
|
Scalar dcolor(255,0,0);
|
||||||
|
if (charAnalysis->bestCharSegments[z])
|
||||||
|
dcolor = Scalar(0,255,0);
|
||||||
|
drawContours(bestVal, charAnalysis->bestContours, z, dcolor, 1);
|
||||||
|
}
|
||||||
|
tempDash.push_back(bestVal);
|
||||||
|
displayImage(config, "Character Region Step 1 Thresholds", drawImageDashboard(tempDash, bestVal.type(), 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Mat img_contours(img_threshold.size(), CV_8U);
|
||||||
|
img_threshold.copyTo(img_contours);
|
||||||
|
cvtColor(img_contours, img_contours, CV_GRAY2RGB);
|
||||||
|
|
||||||
|
vector<vector<Point> > allowedContours;
|
||||||
|
for (int i = 0; i < contours.size(); i++)
|
||||||
|
{
|
||||||
|
if (charSegments[i])
|
||||||
|
allowedContours.push_back(contours[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawContours(img_contours, contours,
|
||||||
|
-1, // draw all contours
|
||||||
|
cv::Scalar(255,0,0), // in blue
|
||||||
|
1); // with a thickness of 1
|
||||||
|
|
||||||
|
drawContours(img_contours, allowedContours,
|
||||||
|
-1, // draw all contours
|
||||||
|
cv::Scalar(0,255,0), // in green
|
||||||
|
1); // with a thickness of 1
|
||||||
|
|
||||||
|
|
||||||
|
displayImage(config, "Matching Contours", img_contours);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//charsegments = this->getPossibleCharRegions(img_threshold, allContours, allHierarchy, STARTING_MIN_HEIGHT + (bestFitIndex * HEIGHT_STEP), STARTING_MAX_HEIGHT + (bestFitIndex * HEIGHT_STEP));
|
||||||
|
|
||||||
|
|
||||||
|
if (charAnalysis->linePolygon.size() > 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
int confidenceDrainers = 0;
|
||||||
|
int charSegmentCount = charAnalysis->bestCharSegmentsCount;
|
||||||
|
if (charSegmentCount == 1)
|
||||||
|
confidenceDrainers += 91;
|
||||||
|
else if (charSegmentCount < 5)
|
||||||
|
confidenceDrainers += (5 - charSegmentCount) * 10;
|
||||||
|
|
||||||
|
int absangle = abs(charAnalysis->topLine.angle);
|
||||||
|
if (absangle > 10)
|
||||||
|
confidenceDrainers += 91;
|
||||||
|
else if (absangle > 1)
|
||||||
|
confidenceDrainers += (10 - absangle) * 5;
|
||||||
|
|
||||||
|
|
||||||
|
if (confidenceDrainers >= 100)
|
||||||
|
this->confidence=1;
|
||||||
|
else
|
||||||
|
this->confidence = 100 - confidenceDrainers;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "Character Region Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CharacterRegion::~CharacterRegion()
|
||||||
|
{
|
||||||
|
delete(charAnalysis);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mat CharacterRegion::getPlateMask()
|
||||||
|
{
|
||||||
|
return charAnalysis->plateMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment CharacterRegion::getTopLine()
|
||||||
|
{
|
||||||
|
return charAnalysis->topLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment CharacterRegion::getBottomLine()
|
||||||
|
{
|
||||||
|
return charAnalysis->bottomLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<Point> CharacterRegion::getCharArea()
|
||||||
|
{
|
||||||
|
return charAnalysis->charArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment CharacterRegion::getCharBoxTop()
|
||||||
|
{
|
||||||
|
return charAnalysis->charBoxTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment CharacterRegion::getCharBoxBottom()
|
||||||
|
{
|
||||||
|
return charAnalysis->charBoxBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment CharacterRegion::getCharBoxLeft()
|
||||||
|
{
|
||||||
|
return charAnalysis->charBoxLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment CharacterRegion::getCharBoxRight()
|
||||||
|
{
|
||||||
|
return charAnalysis->charBoxRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharacterRegion::thresholdsInverted()
|
||||||
|
{
|
||||||
|
return charAnalysis->thresholdsInverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
87
src/openalpr/characterregion.h
Normal file
87
src/openalpr/characterregion.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CHARACTERREGION_H
|
||||||
|
#define CHARACTERREGION_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "characteranalysis.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
class CharacterRegion
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
CharacterRegion(Mat img, Config* config);
|
||||||
|
virtual ~CharacterRegion();
|
||||||
|
|
||||||
|
CharacterAnalysis *charAnalysis;
|
||||||
|
|
||||||
|
int confidence;
|
||||||
|
Mat getPlateMask();
|
||||||
|
|
||||||
|
LineSegment getTopLine();
|
||||||
|
LineSegment getBottomLine();
|
||||||
|
//vector<Point> getLinePolygon();
|
||||||
|
vector<Point> getCharArea();
|
||||||
|
|
||||||
|
LineSegment getCharBoxTop();
|
||||||
|
LineSegment getCharBoxBottom();
|
||||||
|
LineSegment getCharBoxLeft();
|
||||||
|
LineSegment getCharBoxRight();
|
||||||
|
|
||||||
|
bool thresholdsInverted();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Config* config;
|
||||||
|
bool debug;
|
||||||
|
|
||||||
|
Mat findOuterBoxMask(vector<Mat> thresholds, vector<vector<vector<Point> > > allContours, vector<vector<Vec4i> > allHierarchy);
|
||||||
|
|
||||||
|
vector<bool> filter(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy);
|
||||||
|
vector<bool> filterByBoxSize(Mat img, vector<vector<Point> > contours, vector<bool> goodIndices, float minHeightPx, float maxHeightPx);
|
||||||
|
vector<bool> filterByParentContour( vector< vector< Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
vector<bool> filterContourHoles(vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
|
||||||
|
vector<Point> getBestVotedLines(Mat img, vector<vector<Point> > contours, vector<bool> goodIndices);
|
||||||
|
//vector<Point> getCharSegmentsBetweenLines(Mat img, vector<vector<Point> > contours, vector<Point> outerPolygon);
|
||||||
|
vector<bool> filterBetweenLines(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<Point> outerPolygon, vector<bool> goodIndices);
|
||||||
|
Mat getCharacterMask(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
|
||||||
|
vector<Rect> wrapContours(vector<vector<Point> > contours);
|
||||||
|
bool verifySize(Mat r, float minHeightPx, float maxHeightPx);
|
||||||
|
|
||||||
|
int getGoodIndicesCount(vector<bool> goodIndices);
|
||||||
|
|
||||||
|
bool isPlateInverted(Mat threshold, vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CHARACTERREGION_H
|
||||||
|
|
||||||
|
|
1188
src/openalpr/charactersegmenter.cpp
Normal file
1188
src/openalpr/charactersegmenter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
106
src/openalpr/charactersegmenter.h
Normal file
106
src/openalpr/charactersegmenter.h
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CHARACTERSEGMENTER_H
|
||||||
|
#define CHARACTERSEGMENTER_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "binarize_wolf.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "characterregion.h"
|
||||||
|
#include "colorfilter.h"
|
||||||
|
#include "verticalhistogram.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
//const float MIN_BOX_WIDTH_PX = 4; // 4 pixels
|
||||||
|
|
||||||
|
const Scalar COLOR_DEBUG_EDGE(0,0,255); // Red
|
||||||
|
const Scalar COLOR_DEBUG_SPECKLES(0,0,255); // Red
|
||||||
|
const Scalar COLOR_DEBUG_MIN_HEIGHT(255,0,0); // Blue
|
||||||
|
const Scalar COLOR_DEBUG_MIN_AREA(255,0,0); // Blue
|
||||||
|
const Scalar COLOR_DEBUG_FULLBOX(255,255,0); // Blue-green
|
||||||
|
const Scalar COLOR_DEBUG_COLORFILTER(255,0,255); // Magenta
|
||||||
|
const Scalar COLOR_DEBUG_EMPTYFILTER(0,255,255); // Yellow
|
||||||
|
|
||||||
|
class CharacterSegmenter
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
CharacterSegmenter(Mat img, bool invertedColors, Config* config);
|
||||||
|
virtual ~CharacterSegmenter();
|
||||||
|
|
||||||
|
vector<Rect> characters;
|
||||||
|
int confidence;
|
||||||
|
|
||||||
|
vector<Mat> getThresholds();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
CharacterAnalysis* charAnalysis;
|
||||||
|
|
||||||
|
LineSegment top;
|
||||||
|
LineSegment bottom;
|
||||||
|
|
||||||
|
vector<Mat> imgDbgGeneral;
|
||||||
|
vector<Mat> imgDbgCleanStages;
|
||||||
|
|
||||||
|
vector<bool> filter(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy);
|
||||||
|
vector<bool> filterByBoxSize(vector< vector< Point> > contours, vector<bool> goodIndices, float minHeightPx, float maxHeightPx);
|
||||||
|
vector<bool> filterBetweenLines(Mat img, vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<Point> outerPolygon, vector<bool> goodIndices);
|
||||||
|
vector<bool> filterContourHoles(vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
|
||||||
|
vector<Point> getBestVotedLines(Mat img, vector<vector<Point> > contours, vector<bool> goodIndices);
|
||||||
|
int getGoodIndicesCount(vector<bool> goodIndices);
|
||||||
|
|
||||||
|
Mat getCharacterMask(Mat img_threshold, vector<vector<Point> > contours, vector<Vec4i> hierarchy, vector<bool> goodIndices);
|
||||||
|
Mat getCharBoxMask(Mat img_threshold, vector<Rect> charBoxes);
|
||||||
|
|
||||||
|
void removeSmallContours(vector<Mat> thresholds, vector<vector<vector<Point > > > allContours, float avgCharWidth, float avgCharHeight);
|
||||||
|
|
||||||
|
Mat getVerticalHistogram(Mat img, Mat mask);
|
||||||
|
vector<Rect> getHistogramBoxes(Mat histogram, float avgCharWidth, float avgCharHeight, float* score);
|
||||||
|
vector<Rect> getBestCharBoxes(Mat img, vector<Rect> charBoxes, float avgCharWidth);
|
||||||
|
vector<Rect> combineCloseBoxes( vector<Rect> charBoxes, float avgCharWidth);
|
||||||
|
|
||||||
|
vector<Rect> get1DHits(Mat img, int yOffset);
|
||||||
|
|
||||||
|
void cleanCharRegions(vector<Mat> thresholds, vector<Rect> charRegions);
|
||||||
|
void cleanBasedOnColor(vector<Mat> thresholds, Mat colorMask, vector<Rect> charRegions);
|
||||||
|
void cleanMostlyFullBoxes(vector<Mat> thresholds, const vector<Rect> charRegions);
|
||||||
|
vector<Rect> filterMostlyEmptyBoxes(vector<Mat> thresholds, const vector<Rect> charRegions);
|
||||||
|
void filterEdgeBoxes(vector<Mat> thresholds, const vector<Rect> charRegions, float avgCharWidth, float avgCharHeight);
|
||||||
|
|
||||||
|
int getLongestBlobLengthBetweenLines(Mat img, int col);
|
||||||
|
|
||||||
|
int isSkinnyLineInsideBox(Mat threshold, Rect box, vector<vector<Point> > contours, vector<Vec4i> hierarchy, float avgCharWidth, float avgCharHeight);
|
||||||
|
|
||||||
|
vector<Point> getEncapsulatingLines(Mat img, vector<vector<Point> > contours, vector<bool> goodIndices);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CHARACTERSEGMENTER_H
|
||||||
|
|
||||||
|
|
596
src/openalpr/cjson.c
Normal file
596
src/openalpr/cjson.c
Normal file
@@ -0,0 +1,596 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2009 Dave Gamble
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* cJSON */
|
||||||
|
/* JSON parser in C. */
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <float.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "cjson.h"
|
||||||
|
|
||||||
|
static const char *ep;
|
||||||
|
|
||||||
|
const char *cJSON_GetErrorPtr(void) {return ep;}
|
||||||
|
|
||||||
|
static int cJSON_strcasecmp(const char *s1,const char *s2)
|
||||||
|
{
|
||||||
|
if (!s1) return (s1==s2)?0:1;if (!s2) return 1;
|
||||||
|
for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0;
|
||||||
|
return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *(*cJSON_malloc)(size_t sz) = malloc;
|
||||||
|
static void (*cJSON_free)(void *ptr) = free;
|
||||||
|
|
||||||
|
static char* cJSON_strdup(const char* str)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
char* copy;
|
||||||
|
|
||||||
|
len = strlen(str) + 1;
|
||||||
|
if (!(copy = (char*)cJSON_malloc(len))) return 0;
|
||||||
|
memcpy(copy,str,len);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cJSON_InitHooks(cJSON_Hooks* hooks)
|
||||||
|
{
|
||||||
|
if (!hooks) { /* Reset hooks */
|
||||||
|
cJSON_malloc = malloc;
|
||||||
|
cJSON_free = free;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc;
|
||||||
|
cJSON_free = (hooks->free_fn)?hooks->free_fn:free;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Internal constructor. */
|
||||||
|
static cJSON *cJSON_New_Item(void)
|
||||||
|
{
|
||||||
|
cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));
|
||||||
|
if (node) memset(node,0,sizeof(cJSON));
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete a cJSON structure. */
|
||||||
|
void cJSON_Delete(cJSON *c)
|
||||||
|
{
|
||||||
|
cJSON *next;
|
||||||
|
while (c)
|
||||||
|
{
|
||||||
|
next=c->next;
|
||||||
|
if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child);
|
||||||
|
if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring);
|
||||||
|
if (c->string) cJSON_free(c->string);
|
||||||
|
cJSON_free(c);
|
||||||
|
c=next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the input text to generate a number, and populate the result into item. */
|
||||||
|
static const char *parse_number(cJSON *item,const char *num)
|
||||||
|
{
|
||||||
|
double n=0,sign=1,scale=0;int subscale=0,signsubscale=1;
|
||||||
|
|
||||||
|
if (*num=='-') sign=-1,num++; /* Has sign? */
|
||||||
|
if (*num=='0') num++; /* is zero */
|
||||||
|
if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */
|
||||||
|
if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */
|
||||||
|
if (*num=='e' || *num=='E') /* Exponent? */
|
||||||
|
{ num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */
|
||||||
|
while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */
|
||||||
|
}
|
||||||
|
|
||||||
|
n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */
|
||||||
|
|
||||||
|
item->valuedouble=n;
|
||||||
|
item->valueint=(int)n;
|
||||||
|
item->type=cJSON_Number;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Render the number nicely from the given item into a string. */
|
||||||
|
static char *print_number(cJSON *item)
|
||||||
|
{
|
||||||
|
char *str;
|
||||||
|
double d=item->valuedouble;
|
||||||
|
if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN)
|
||||||
|
{
|
||||||
|
str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */
|
||||||
|
if (str) sprintf(str,"%d",item->valueint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */
|
||||||
|
if (str)
|
||||||
|
{
|
||||||
|
if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d);
|
||||||
|
else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d);
|
||||||
|
else sprintf(str,"%f",d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned parse_hex4(const char *str)
|
||||||
|
{
|
||||||
|
unsigned h=0;
|
||||||
|
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
|
||||||
|
h=h<<4;str++;
|
||||||
|
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
|
||||||
|
h=h<<4;str++;
|
||||||
|
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
|
||||||
|
h=h<<4;str++;
|
||||||
|
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the input text into an unescaped cstring, and populate item. */
|
||||||
|
static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
|
||||||
|
static const char *parse_string(cJSON *item,const char *str)
|
||||||
|
{
|
||||||
|
const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2;
|
||||||
|
if (*str!='\"') {ep=str;return 0;} /* not a string! */
|
||||||
|
|
||||||
|
while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */
|
||||||
|
|
||||||
|
out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */
|
||||||
|
if (!out) return 0;
|
||||||
|
|
||||||
|
ptr=str+1;ptr2=out;
|
||||||
|
while (*ptr!='\"' && *ptr)
|
||||||
|
{
|
||||||
|
if (*ptr!='\\') *ptr2++=*ptr++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ptr++;
|
||||||
|
switch (*ptr)
|
||||||
|
{
|
||||||
|
case 'b': *ptr2++='\b'; break;
|
||||||
|
case 'f': *ptr2++='\f'; break;
|
||||||
|
case 'n': *ptr2++='\n'; break;
|
||||||
|
case 'r': *ptr2++='\r'; break;
|
||||||
|
case 't': *ptr2++='\t'; break;
|
||||||
|
case 'u': /* transcode utf16 to utf8. */
|
||||||
|
uc=parse_hex4(ptr+1);ptr+=4; /* get the unicode char. */
|
||||||
|
|
||||||
|
if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; /* check for invalid. */
|
||||||
|
|
||||||
|
if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs. */
|
||||||
|
{
|
||||||
|
if (ptr[1]!='\\' || ptr[2]!='u') break; /* missing second-half of surrogate. */
|
||||||
|
uc2=parse_hex4(ptr+3);ptr+=6;
|
||||||
|
if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */
|
||||||
|
uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF));
|
||||||
|
}
|
||||||
|
|
||||||
|
len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len;
|
||||||
|
|
||||||
|
switch (len) {
|
||||||
|
case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
|
||||||
|
case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
|
||||||
|
case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
|
||||||
|
case 1: *--ptr2 =(uc | firstByteMark[len]);
|
||||||
|
}
|
||||||
|
ptr2+=len;
|
||||||
|
break;
|
||||||
|
default: *ptr2++=*ptr; break;
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*ptr2=0;
|
||||||
|
if (*ptr=='\"') ptr++;
|
||||||
|
item->valuestring=out;
|
||||||
|
item->type=cJSON_String;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Render the cstring provided to an escaped version that can be printed. */
|
||||||
|
static char *print_string_ptr(const char *str)
|
||||||
|
{
|
||||||
|
const char *ptr;char *ptr2,*out;int len=0;unsigned char token;
|
||||||
|
|
||||||
|
if (!str) return cJSON_strdup("");
|
||||||
|
ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;}
|
||||||
|
|
||||||
|
out=(char*)cJSON_malloc(len+3);
|
||||||
|
if (!out) return 0;
|
||||||
|
|
||||||
|
ptr2=out;ptr=str;
|
||||||
|
*ptr2++='\"';
|
||||||
|
while (*ptr)
|
||||||
|
{
|
||||||
|
if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*ptr2++='\\';
|
||||||
|
switch (token=*ptr++)
|
||||||
|
{
|
||||||
|
case '\\': *ptr2++='\\'; break;
|
||||||
|
case '\"': *ptr2++='\"'; break;
|
||||||
|
case '\b': *ptr2++='b'; break;
|
||||||
|
case '\f': *ptr2++='f'; break;
|
||||||
|
case '\n': *ptr2++='n'; break;
|
||||||
|
case '\r': *ptr2++='r'; break;
|
||||||
|
case '\t': *ptr2++='t'; break;
|
||||||
|
default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*ptr2++='\"';*ptr2++=0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
/* Invote print_string_ptr (which is useful) on an item. */
|
||||||
|
static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);}
|
||||||
|
|
||||||
|
/* Predeclare these prototypes. */
|
||||||
|
static const char *parse_value(cJSON *item,const char *value);
|
||||||
|
static char *print_value(cJSON *item,int depth,int fmt);
|
||||||
|
static const char *parse_array(cJSON *item,const char *value);
|
||||||
|
static char *print_array(cJSON *item,int depth,int fmt);
|
||||||
|
static const char *parse_object(cJSON *item,const char *value);
|
||||||
|
static char *print_object(cJSON *item,int depth,int fmt);
|
||||||
|
|
||||||
|
/* Utility to jump whitespace and cr/lf */
|
||||||
|
static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;}
|
||||||
|
|
||||||
|
/* Parse an object - create a new root, and populate. */
|
||||||
|
cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated)
|
||||||
|
{
|
||||||
|
const char *end=0;
|
||||||
|
cJSON *c=cJSON_New_Item();
|
||||||
|
ep=0;
|
||||||
|
if (!c) return 0; /* memory fail */
|
||||||
|
|
||||||
|
end=parse_value(c,skip(value));
|
||||||
|
if (!end) {cJSON_Delete(c);return 0;} /* parse failure. ep is set. */
|
||||||
|
|
||||||
|
/* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
|
||||||
|
if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}
|
||||||
|
if (return_parse_end) *return_parse_end=end;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
/* Default options for cJSON_Parse */
|
||||||
|
cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}
|
||||||
|
|
||||||
|
/* Render a cJSON item/entity/structure to text. */
|
||||||
|
char *cJSON_Print(cJSON *item) {return print_value(item,0,1);}
|
||||||
|
char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);}
|
||||||
|
|
||||||
|
/* Parser core - when encountering text, process appropriately. */
|
||||||
|
static const char *parse_value(cJSON *item,const char *value)
|
||||||
|
{
|
||||||
|
if (!value) return 0; /* Fail on null. */
|
||||||
|
if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; }
|
||||||
|
if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; }
|
||||||
|
if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; }
|
||||||
|
if (*value=='\"') { return parse_string(item,value); }
|
||||||
|
if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); }
|
||||||
|
if (*value=='[') { return parse_array(item,value); }
|
||||||
|
if (*value=='{') { return parse_object(item,value); }
|
||||||
|
|
||||||
|
ep=value;return 0; /* failure. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Render a value to text. */
|
||||||
|
static char *print_value(cJSON *item,int depth,int fmt)
|
||||||
|
{
|
||||||
|
char *out=0;
|
||||||
|
if (!item) return 0;
|
||||||
|
switch ((item->type)&255)
|
||||||
|
{
|
||||||
|
case cJSON_NULL: out=cJSON_strdup("null"); break;
|
||||||
|
case cJSON_False: out=cJSON_strdup("false");break;
|
||||||
|
case cJSON_True: out=cJSON_strdup("true"); break;
|
||||||
|
case cJSON_Number: out=print_number(item);break;
|
||||||
|
case cJSON_String: out=print_string(item);break;
|
||||||
|
case cJSON_Array: out=print_array(item,depth,fmt);break;
|
||||||
|
case cJSON_Object: out=print_object(item,depth,fmt);break;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build an array from input text. */
|
||||||
|
static const char *parse_array(cJSON *item,const char *value)
|
||||||
|
{
|
||||||
|
cJSON *child;
|
||||||
|
if (*value!='[') {ep=value;return 0;} /* not an array! */
|
||||||
|
|
||||||
|
item->type=cJSON_Array;
|
||||||
|
value=skip(value+1);
|
||||||
|
if (*value==']') return value+1; /* empty array. */
|
||||||
|
|
||||||
|
item->child=child=cJSON_New_Item();
|
||||||
|
if (!item->child) return 0; /* memory fail */
|
||||||
|
value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */
|
||||||
|
if (!value) return 0;
|
||||||
|
|
||||||
|
while (*value==',')
|
||||||
|
{
|
||||||
|
cJSON *new_item;
|
||||||
|
if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */
|
||||||
|
child->next=new_item;new_item->prev=child;child=new_item;
|
||||||
|
value=skip(parse_value(child,skip(value+1)));
|
||||||
|
if (!value) return 0; /* memory fail */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*value==']') return value+1; /* end of array */
|
||||||
|
ep=value;return 0; /* malformed. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Render an array to text */
|
||||||
|
static char *print_array(cJSON *item,int depth,int fmt)
|
||||||
|
{
|
||||||
|
char **entries;
|
||||||
|
char *out=0,*ptr,*ret;int len=5;
|
||||||
|
cJSON *child=item->child;
|
||||||
|
int numentries=0,i=0,fail=0;
|
||||||
|
|
||||||
|
/* How many entries in the array? */
|
||||||
|
while (child) numentries++,child=child->next;
|
||||||
|
/* Explicitly handle numentries==0 */
|
||||||
|
if (!numentries)
|
||||||
|
{
|
||||||
|
out=(char*)cJSON_malloc(3);
|
||||||
|
if (out) strcpy(out,"[]");
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
/* Allocate an array to hold the values for each */
|
||||||
|
entries=(char**)cJSON_malloc(numentries*sizeof(char*));
|
||||||
|
if (!entries) return 0;
|
||||||
|
memset(entries,0,numentries*sizeof(char*));
|
||||||
|
/* Retrieve all the results: */
|
||||||
|
child=item->child;
|
||||||
|
while (child && !fail)
|
||||||
|
{
|
||||||
|
ret=print_value(child,depth+1,fmt);
|
||||||
|
entries[i++]=ret;
|
||||||
|
if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;
|
||||||
|
child=child->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we didn't fail, try to malloc the output string */
|
||||||
|
if (!fail) out=(char*)cJSON_malloc(len);
|
||||||
|
/* If that fails, we fail. */
|
||||||
|
if (!out) fail=1;
|
||||||
|
|
||||||
|
/* Handle failure. */
|
||||||
|
if (fail)
|
||||||
|
{
|
||||||
|
for (i=0;i<numentries;i++) if (entries[i]) cJSON_free(entries[i]);
|
||||||
|
cJSON_free(entries);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compose the output array. */
|
||||||
|
*out='[';
|
||||||
|
ptr=out+1;*ptr=0;
|
||||||
|
for (i=0;i<numentries;i++)
|
||||||
|
{
|
||||||
|
strcpy(ptr,entries[i]);ptr+=strlen(entries[i]);
|
||||||
|
if (i!=numentries-1) {*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;}
|
||||||
|
cJSON_free(entries[i]);
|
||||||
|
}
|
||||||
|
cJSON_free(entries);
|
||||||
|
*ptr++=']';*ptr++=0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build an object from the text. */
|
||||||
|
static const char *parse_object(cJSON *item,const char *value)
|
||||||
|
{
|
||||||
|
cJSON *child;
|
||||||
|
if (*value!='{') {ep=value;return 0;} /* not an object! */
|
||||||
|
|
||||||
|
item->type=cJSON_Object;
|
||||||
|
value=skip(value+1);
|
||||||
|
if (*value=='}') return value+1; /* empty array. */
|
||||||
|
|
||||||
|
item->child=child=cJSON_New_Item();
|
||||||
|
if (!item->child) return 0;
|
||||||
|
value=skip(parse_string(child,skip(value)));
|
||||||
|
if (!value) return 0;
|
||||||
|
child->string=child->valuestring;child->valuestring=0;
|
||||||
|
if (*value!=':') {ep=value;return 0;} /* fail! */
|
||||||
|
value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */
|
||||||
|
if (!value) return 0;
|
||||||
|
|
||||||
|
while (*value==',')
|
||||||
|
{
|
||||||
|
cJSON *new_item;
|
||||||
|
if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */
|
||||||
|
child->next=new_item;new_item->prev=child;child=new_item;
|
||||||
|
value=skip(parse_string(child,skip(value+1)));
|
||||||
|
if (!value) return 0;
|
||||||
|
child->string=child->valuestring;child->valuestring=0;
|
||||||
|
if (*value!=':') {ep=value;return 0;} /* fail! */
|
||||||
|
value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */
|
||||||
|
if (!value) return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*value=='}') return value+1; /* end of array */
|
||||||
|
ep=value;return 0; /* malformed. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Render an object to text. */
|
||||||
|
static char *print_object(cJSON *item,int depth,int fmt)
|
||||||
|
{
|
||||||
|
char **entries=0,**names=0;
|
||||||
|
char *out=0,*ptr,*ret,*str;int len=7,i=0,j;
|
||||||
|
cJSON *child=item->child;
|
||||||
|
int numentries=0,fail=0;
|
||||||
|
/* Count the number of entries. */
|
||||||
|
while (child) numentries++,child=child->next;
|
||||||
|
/* Explicitly handle empty object case */
|
||||||
|
if (!numentries)
|
||||||
|
{
|
||||||
|
out=(char*)cJSON_malloc(fmt?depth+4:3);
|
||||||
|
if (!out) return 0;
|
||||||
|
ptr=out;*ptr++='{';
|
||||||
|
if (fmt) {*ptr++='\n';for (i=0;i<depth-1;i++) *ptr++='\t';}
|
||||||
|
*ptr++='}';*ptr++=0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
/* Allocate space for the names and the objects */
|
||||||
|
entries=(char**)cJSON_malloc(numentries*sizeof(char*));
|
||||||
|
if (!entries) return 0;
|
||||||
|
names=(char**)cJSON_malloc(numentries*sizeof(char*));
|
||||||
|
if (!names) {cJSON_free(entries);return 0;}
|
||||||
|
memset(entries,0,sizeof(char*)*numentries);
|
||||||
|
memset(names,0,sizeof(char*)*numentries);
|
||||||
|
|
||||||
|
/* Collect all the results into our arrays: */
|
||||||
|
child=item->child;depth++;if (fmt) len+=depth;
|
||||||
|
while (child)
|
||||||
|
{
|
||||||
|
names[i]=str=print_string_ptr(child->string);
|
||||||
|
entries[i++]=ret=print_value(child,depth,fmt);
|
||||||
|
if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;
|
||||||
|
child=child->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to allocate the output string */
|
||||||
|
if (!fail) out=(char*)cJSON_malloc(len);
|
||||||
|
if (!out) fail=1;
|
||||||
|
|
||||||
|
/* Handle failure */
|
||||||
|
if (fail)
|
||||||
|
{
|
||||||
|
for (i=0;i<numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);}
|
||||||
|
cJSON_free(names);cJSON_free(entries);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compose the output: */
|
||||||
|
*out='{';ptr=out+1;if (fmt)*ptr++='\n';*ptr=0;
|
||||||
|
for (i=0;i<numentries;i++)
|
||||||
|
{
|
||||||
|
if (fmt) for (j=0;j<depth;j++) *ptr++='\t';
|
||||||
|
strcpy(ptr,names[i]);ptr+=strlen(names[i]);
|
||||||
|
*ptr++=':';if (fmt) *ptr++='\t';
|
||||||
|
strcpy(ptr,entries[i]);ptr+=strlen(entries[i]);
|
||||||
|
if (i!=numentries-1) *ptr++=',';
|
||||||
|
if (fmt) *ptr++='\n';*ptr=0;
|
||||||
|
cJSON_free(names[i]);cJSON_free(entries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_free(names);cJSON_free(entries);
|
||||||
|
if (fmt) for (i=0;i<depth-1;i++) *ptr++='\t';
|
||||||
|
*ptr++='}';*ptr++=0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get Array size/item / object item. */
|
||||||
|
int cJSON_GetArraySize(cJSON *array) {cJSON *c=array->child;int i=0;while(c)i++,c=c->next;return i;}
|
||||||
|
cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;}
|
||||||
|
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;}
|
||||||
|
|
||||||
|
/* Utility for array list handling. */
|
||||||
|
static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;}
|
||||||
|
/* Utility for handling references. */
|
||||||
|
static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;}
|
||||||
|
|
||||||
|
/* Add item to array/object. */
|
||||||
|
void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}}
|
||||||
|
void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);}
|
||||||
|
void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));}
|
||||||
|
void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));}
|
||||||
|
|
||||||
|
cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0;
|
||||||
|
if (c->prev) c->prev->next=c->next;if (c->next) c->next->prev=c->prev;if (c==array->child) array->child=c->next;c->prev=c->next=0;return c;}
|
||||||
|
void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));}
|
||||||
|
cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;}
|
||||||
|
void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));}
|
||||||
|
|
||||||
|
/* Replace array/object items with new ones. */
|
||||||
|
void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return;
|
||||||
|
newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem;
|
||||||
|
if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);}
|
||||||
|
void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}}
|
||||||
|
|
||||||
|
/* Create basic types: */
|
||||||
|
cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;}
|
||||||
|
cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;}
|
||||||
|
cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;}
|
||||||
|
cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;}
|
||||||
|
cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;}
|
||||||
|
cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;}
|
||||||
|
cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;}
|
||||||
|
cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;}
|
||||||
|
|
||||||
|
/* Create Arrays: */
|
||||||
|
cJSON *cJSON_CreateIntArray(const int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
|
||||||
|
cJSON *cJSON_CreateFloatArray(const float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
|
||||||
|
cJSON *cJSON_CreateDoubleArray(const double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
|
||||||
|
cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateString(strings[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
|
||||||
|
|
||||||
|
/* Duplication */
|
||||||
|
cJSON *cJSON_Duplicate(cJSON *item,int recurse)
|
||||||
|
{
|
||||||
|
cJSON *newitem,*cptr,*nptr=0,*newchild;
|
||||||
|
/* Bail on bad ptr */
|
||||||
|
if (!item) return 0;
|
||||||
|
/* Create new item */
|
||||||
|
newitem=cJSON_New_Item();
|
||||||
|
if (!newitem) return 0;
|
||||||
|
/* Copy over all vars */
|
||||||
|
newitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble;
|
||||||
|
if (item->valuestring) {newitem->valuestring=cJSON_strdup(item->valuestring); if (!newitem->valuestring) {cJSON_Delete(newitem);return 0;}}
|
||||||
|
if (item->string) {newitem->string=cJSON_strdup(item->string); if (!newitem->string) {cJSON_Delete(newitem);return 0;}}
|
||||||
|
/* If non-recursive, then we're done! */
|
||||||
|
if (!recurse) return newitem;
|
||||||
|
/* Walk the ->next chain for the child. */
|
||||||
|
cptr=item->child;
|
||||||
|
while (cptr)
|
||||||
|
{
|
||||||
|
newchild=cJSON_Duplicate(cptr,1); /* Duplicate (with recurse) each item in the ->next chain */
|
||||||
|
if (!newchild) {cJSON_Delete(newitem);return 0;}
|
||||||
|
if (nptr) {nptr->next=newchild,newchild->prev=nptr;nptr=newchild;} /* If newitem->child already set, then crosswire ->prev and ->next and move on */
|
||||||
|
else {newitem->child=newchild;nptr=newchild;} /* Set newitem->child and move to it */
|
||||||
|
cptr=cptr->next;
|
||||||
|
}
|
||||||
|
return newitem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cJSON_Minify(char *json)
|
||||||
|
{
|
||||||
|
char *into=json;
|
||||||
|
while (*json)
|
||||||
|
{
|
||||||
|
if (*json==' ') json++;
|
||||||
|
else if (*json=='\t') json++; // Whitespace characters.
|
||||||
|
else if (*json=='\r') json++;
|
||||||
|
else if (*json=='\n') json++;
|
||||||
|
else if (*json=='/' && json[1]=='/') while (*json && *json!='\n') json++; // double-slash comments, to end of line.
|
||||||
|
else if (*json=='/' && json[1]=='*') {while (*json && !(*json=='*' && json[1]=='/')) json++;json+=2;} // multiline comments.
|
||||||
|
else if (*json=='\"'){*into++=*json++;while (*json && *json!='\"'){if (*json=='\\') *into++=*json++;*into++=*json++;}*into++=*json++;} // string literals, which are \" sensitive.
|
||||||
|
else *into++=*json++; // All other characters.
|
||||||
|
}
|
||||||
|
*into=0; // and null-terminate.
|
||||||
|
}
|
143
src/openalpr/cjson.h
Normal file
143
src/openalpr/cjson.h
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2009 Dave Gamble
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef cJSON__h
|
||||||
|
#define cJSON__h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* cJSON Types: */
|
||||||
|
#define cJSON_False 0
|
||||||
|
#define cJSON_True 1
|
||||||
|
#define cJSON_NULL 2
|
||||||
|
#define cJSON_Number 3
|
||||||
|
#define cJSON_String 4
|
||||||
|
#define cJSON_Array 5
|
||||||
|
#define cJSON_Object 6
|
||||||
|
|
||||||
|
#define cJSON_IsReference 256
|
||||||
|
|
||||||
|
/* The cJSON structure: */
|
||||||
|
typedef struct cJSON {
|
||||||
|
struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
|
||||||
|
struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
|
||||||
|
|
||||||
|
int type; /* The type of the item, as above. */
|
||||||
|
|
||||||
|
char *valuestring; /* The item's string, if type==cJSON_String */
|
||||||
|
int valueint; /* The item's number, if type==cJSON_Number */
|
||||||
|
double valuedouble; /* The item's number, if type==cJSON_Number */
|
||||||
|
|
||||||
|
char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
|
||||||
|
} cJSON;
|
||||||
|
|
||||||
|
typedef struct cJSON_Hooks {
|
||||||
|
void *(*malloc_fn)(size_t sz);
|
||||||
|
void (*free_fn)(void *ptr);
|
||||||
|
} cJSON_Hooks;
|
||||||
|
|
||||||
|
/* Supply malloc, realloc and free functions to cJSON */
|
||||||
|
extern void cJSON_InitHooks(cJSON_Hooks* hooks);
|
||||||
|
|
||||||
|
|
||||||
|
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */
|
||||||
|
extern cJSON *cJSON_Parse(const char *value);
|
||||||
|
/* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */
|
||||||
|
extern char *cJSON_Print(cJSON *item);
|
||||||
|
/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */
|
||||||
|
extern char *cJSON_PrintUnformatted(cJSON *item);
|
||||||
|
/* Delete a cJSON entity and all subentities. */
|
||||||
|
extern void cJSON_Delete(cJSON *c);
|
||||||
|
|
||||||
|
/* Returns the number of items in an array (or object). */
|
||||||
|
extern int cJSON_GetArraySize(cJSON *array);
|
||||||
|
/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */
|
||||||
|
extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);
|
||||||
|
/* Get item "string" from object. Case insensitive. */
|
||||||
|
extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
|
||||||
|
|
||||||
|
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
|
||||||
|
extern const char *cJSON_GetErrorPtr(void);
|
||||||
|
|
||||||
|
/* These calls create a cJSON item of the appropriate type. */
|
||||||
|
extern cJSON *cJSON_CreateNull(void);
|
||||||
|
extern cJSON *cJSON_CreateTrue(void);
|
||||||
|
extern cJSON *cJSON_CreateFalse(void);
|
||||||
|
extern cJSON *cJSON_CreateBool(int b);
|
||||||
|
extern cJSON *cJSON_CreateNumber(double num);
|
||||||
|
extern cJSON *cJSON_CreateString(const char *string);
|
||||||
|
extern cJSON *cJSON_CreateArray(void);
|
||||||
|
extern cJSON *cJSON_CreateObject(void);
|
||||||
|
|
||||||
|
/* These utilities create an Array of count items. */
|
||||||
|
extern cJSON *cJSON_CreateIntArray(const int *numbers,int count);
|
||||||
|
extern cJSON *cJSON_CreateFloatArray(const float *numbers,int count);
|
||||||
|
extern cJSON *cJSON_CreateDoubleArray(const double *numbers,int count);
|
||||||
|
extern cJSON *cJSON_CreateStringArray(const char **strings,int count);
|
||||||
|
|
||||||
|
/* Append item to the specified array/object. */
|
||||||
|
extern void cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||||
|
extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item);
|
||||||
|
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
|
||||||
|
extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||||
|
extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item);
|
||||||
|
|
||||||
|
/* Remove/Detatch items from Arrays/Objects. */
|
||||||
|
extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which);
|
||||||
|
extern void cJSON_DeleteItemFromArray(cJSON *array,int which);
|
||||||
|
extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string);
|
||||||
|
extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string);
|
||||||
|
|
||||||
|
/* Update array items. */
|
||||||
|
extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem);
|
||||||
|
extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
|
||||||
|
|
||||||
|
/* Duplicate a cJSON item */
|
||||||
|
extern cJSON *cJSON_Duplicate(cJSON *item,int recurse);
|
||||||
|
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
|
||||||
|
need to be released. With recurse!=0, it will duplicate any children connected to the item.
|
||||||
|
The item->next and ->prev pointers are always zero on return from Duplicate. */
|
||||||
|
|
||||||
|
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
|
||||||
|
extern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated);
|
||||||
|
|
||||||
|
extern void cJSON_Minify(char *json);
|
||||||
|
|
||||||
|
/* Macros for creating things quickly. */
|
||||||
|
#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
|
||||||
|
#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
|
||||||
|
#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
|
||||||
|
#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
|
||||||
|
#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
|
||||||
|
#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
|
||||||
|
|
||||||
|
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
|
||||||
|
#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
421
src/openalpr/colorfilter.cpp
Normal file
421
src/openalpr/colorfilter.cpp
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "colorfilter.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ColorFilter::ColorFilter(Mat image, Mat characterMask, Config* config)
|
||||||
|
{
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
this->config = config;
|
||||||
|
|
||||||
|
this->debug = config->debugColorFiler;
|
||||||
|
|
||||||
|
|
||||||
|
this->grayscale = imageIsGrayscale(image);
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
cout << "ColorFilter: isGrayscale = " << grayscale << endl;
|
||||||
|
|
||||||
|
this->hsv = Mat(image.size(), image.type());
|
||||||
|
cvtColor( image, this->hsv, CV_BGR2HSV );
|
||||||
|
preprocessImage();
|
||||||
|
|
||||||
|
this->charMask = characterMask;
|
||||||
|
|
||||||
|
this->colorMask = Mat(image.size(), CV_8U);
|
||||||
|
|
||||||
|
findCharColors();
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << " -- ColorFilter Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorFilter::~ColorFilter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorFilter::imageIsGrayscale(Mat image)
|
||||||
|
{
|
||||||
|
// Check whether the original image is grayscale. If it is, we shouldn't attempt any color filter
|
||||||
|
for (int row = 0; row < image.rows; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < image.cols; col++)
|
||||||
|
{
|
||||||
|
int r = (int) image.at<Vec3b>(row, col)[0];
|
||||||
|
int g = (int) image.at<Vec3b>(row, col)[1];
|
||||||
|
int b = (int) image.at<Vec3b>(row, col)[2];
|
||||||
|
|
||||||
|
if (r == g == b)
|
||||||
|
{
|
||||||
|
// So far so good
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Image is color.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorFilter::preprocessImage()
|
||||||
|
{
|
||||||
|
// Equalize the brightness on the HSV channel "V"
|
||||||
|
vector<Mat> channels;
|
||||||
|
split(this->hsv,channels);
|
||||||
|
Mat img_equalized = equalizeBrightness(channels[2]);
|
||||||
|
merge(channels,this->hsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the hue/sat/val for areas that we believe are license plate characters
|
||||||
|
// Then uses that to filter the whole image and provide a mask.
|
||||||
|
void ColorFilter::findCharColors()
|
||||||
|
{
|
||||||
|
int MINIMUM_SATURATION = 45;
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
cout << "ColorFilter::findCharColors" << endl;
|
||||||
|
|
||||||
|
//charMask.copyTo(this->colorMask);
|
||||||
|
this->colorMask = Mat::zeros(charMask.size(), CV_8U);
|
||||||
|
bitwise_not(this->colorMask, this->colorMask);
|
||||||
|
|
||||||
|
Mat erodedCharMask(charMask.size(), CV_8U);
|
||||||
|
Mat element = getStructuringElement( 1,
|
||||||
|
Size( 2 + 1, 2+1 ),
|
||||||
|
Point( 1, 1 ) );
|
||||||
|
erode(charMask, erodedCharMask, element);
|
||||||
|
|
||||||
|
vector<vector<Point> > contours;
|
||||||
|
vector<Vec4i> hierarchy;
|
||||||
|
findContours(erodedCharMask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
vector<float> hMeans, sMeans, vMeans;
|
||||||
|
vector<float> hStdDevs, sStdDevs, vStdDevs;
|
||||||
|
|
||||||
|
for (int i = 0; i < contours.size(); i++)
|
||||||
|
{
|
||||||
|
if (hierarchy[i][3] != -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Mat singleCharMask = Mat::zeros(hsv.size(), CV_8U);
|
||||||
|
|
||||||
|
drawContours(singleCharMask, contours,
|
||||||
|
i, // draw this contour
|
||||||
|
cv::Scalar(255,255,255), // in
|
||||||
|
CV_FILLED,
|
||||||
|
8,
|
||||||
|
hierarchy
|
||||||
|
);
|
||||||
|
|
||||||
|
// get rid of the outline by drawing a 1 pixel width black line
|
||||||
|
drawContours(singleCharMask, contours,
|
||||||
|
i, // draw this contour
|
||||||
|
cv::Scalar(0,0,0), // in
|
||||||
|
1,
|
||||||
|
8,
|
||||||
|
hierarchy
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//drawAndWait(&singleCharMask);
|
||||||
|
|
||||||
|
Scalar mean;
|
||||||
|
Scalar stddev;
|
||||||
|
meanStdDev(hsv, mean, stddev, singleCharMask);
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
{
|
||||||
|
cout << "ColorFilter " << setw(3) << i << ". Mean: h: " << setw(7) << mean[0] << " s: " << setw(7) <<mean[1] << " v: " << setw(7) << mean[2]
|
||||||
|
<< " | Std: h: " << setw(7) <<stddev[0] << " s: " << setw(7) <<stddev[1] << " v: " << stddev[2] << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mean[0] == 0 && mean[1] == 0 && mean[2] == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
hMeans.push_back(mean[0]);
|
||||||
|
sMeans.push_back(mean[1]);
|
||||||
|
vMeans.push_back(mean[2]);
|
||||||
|
hStdDevs.push_back(stddev[0]);
|
||||||
|
sStdDevs.push_back(stddev[1]);
|
||||||
|
vStdDevs.push_back(stddev[2]);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hMeans.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int bestHueIndex = this->getMajorityOpinion(hMeans, .65, 30);
|
||||||
|
int bestSatIndex = this->getMajorityOpinion(sMeans, .65, 35);
|
||||||
|
int bestValIndex = this->getMajorityOpinion(vMeans, .65, 30);
|
||||||
|
|
||||||
|
|
||||||
|
if (sMeans[bestSatIndex] < MINIMUM_SATURATION)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
bool doHueFilter = false, doSatFilter = false, doValFilter = false;
|
||||||
|
float hueMin, hueMax;
|
||||||
|
float satMin, satMax;
|
||||||
|
float valMin, valMax;
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
cout << "ColorFilter Winning indices:" << endl;
|
||||||
|
if (bestHueIndex != -1)
|
||||||
|
{
|
||||||
|
doHueFilter = true;
|
||||||
|
hueMin = hMeans[bestHueIndex] - (2 * hStdDevs[bestHueIndex]);
|
||||||
|
hueMax = hMeans[bestHueIndex] + (2 * hStdDevs[bestHueIndex]);
|
||||||
|
|
||||||
|
if (abs(hueMin - hueMax) < 20)
|
||||||
|
{
|
||||||
|
hueMin = hMeans[bestHueIndex] - 20;
|
||||||
|
hueMax = hMeans[bestHueIndex] + 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hueMin < 0)
|
||||||
|
hueMin = 0;
|
||||||
|
if (hueMax > 180)
|
||||||
|
hueMax = 180;
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
cout << "ColorFilter Hue: " << bestHueIndex << " : " << setw(7) << hMeans[bestHueIndex] << " -- " << hueMin << "-" << hueMax << endl;
|
||||||
|
}
|
||||||
|
if (bestSatIndex != -1)
|
||||||
|
{
|
||||||
|
doSatFilter = true;
|
||||||
|
|
||||||
|
satMin = sMeans[bestSatIndex] - (2 * sStdDevs[bestSatIndex]);
|
||||||
|
satMax = sMeans[bestSatIndex] + (2 * sStdDevs[bestSatIndex]);
|
||||||
|
|
||||||
|
if (abs(satMin - satMax) < 20)
|
||||||
|
{
|
||||||
|
satMin = sMeans[bestSatIndex] - 20;
|
||||||
|
satMax = sMeans[bestSatIndex] + 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (satMin < 0)
|
||||||
|
satMin = 0;
|
||||||
|
if (satMax > 255)
|
||||||
|
satMax = 255;
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
cout << "ColorFilter Sat: " << bestSatIndex << " : " << setw(7) << sMeans[bestSatIndex] << " -- " << satMin << "-" << satMax << endl;
|
||||||
|
}
|
||||||
|
if (bestValIndex != -1)
|
||||||
|
{
|
||||||
|
doValFilter = true;
|
||||||
|
|
||||||
|
valMin = vMeans[bestValIndex] - (1.5 * vStdDevs[bestValIndex]);
|
||||||
|
valMax = vMeans[bestValIndex] + (1.5 * vStdDevs[bestValIndex]);
|
||||||
|
|
||||||
|
if (abs(valMin - valMax) < 20)
|
||||||
|
{
|
||||||
|
valMin = vMeans[bestValIndex] - 20;
|
||||||
|
valMax = vMeans[bestValIndex] + 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valMin < 0)
|
||||||
|
valMin = 0;
|
||||||
|
if (valMax > 255)
|
||||||
|
valMax = 255;
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
cout << "ColorFilter Val: " << bestValIndex << " : " << setw(7) << vMeans[bestValIndex] << " -- " << valMin << "-" << valMax << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mat imgDebugHueOnly = Mat::zeros(hsv.size(), hsv.type());
|
||||||
|
Mat imgDebug = Mat::zeros(hsv.size(), hsv.type());
|
||||||
|
Mat imgDistanceFromCenter = Mat::zeros(hsv.size(), CV_8U);
|
||||||
|
Mat debugMask = Mat::zeros(hsv.size(), CV_8U);
|
||||||
|
bitwise_not(debugMask, debugMask);
|
||||||
|
|
||||||
|
for (int row = 0; row < charMask.rows; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < charMask.cols; col++)
|
||||||
|
{
|
||||||
|
int h = (int) hsv.at<Vec3b>(row, col)[0];
|
||||||
|
int s = (int) hsv.at<Vec3b>(row, col)[1];
|
||||||
|
int v = (int) hsv.at<Vec3b>(row, col)[2];
|
||||||
|
|
||||||
|
bool hPasses = true;
|
||||||
|
bool sPasses = true;
|
||||||
|
bool vPasses = true;
|
||||||
|
|
||||||
|
int vDistance = abs(v - vMeans[bestValIndex]);
|
||||||
|
|
||||||
|
imgDebugHueOnly.at<Vec3b>(row, col)[0] = h;
|
||||||
|
imgDebugHueOnly.at<Vec3b>(row, col)[1] = 255;
|
||||||
|
imgDebugHueOnly.at<Vec3b>(row, col)[2] = 255;
|
||||||
|
|
||||||
|
imgDebug.at<Vec3b>(row, col)[0] = 255;
|
||||||
|
imgDebug.at<Vec3b>(row, col)[1] = 255;
|
||||||
|
imgDebug.at<Vec3b>(row, col)[2] = 255;
|
||||||
|
|
||||||
|
if (doHueFilter && (h < hueMin || h > hueMax))
|
||||||
|
{
|
||||||
|
hPasses = false;
|
||||||
|
imgDebug.at<Vec3b>(row, col)[0] = 0;
|
||||||
|
debugMask.at<uchar>(row, col) = 0;
|
||||||
|
}
|
||||||
|
if (doSatFilter && (s < satMin || s > satMax))
|
||||||
|
{
|
||||||
|
sPasses = false;
|
||||||
|
imgDebug.at<Vec3b>(row, col)[1] = 0;
|
||||||
|
}
|
||||||
|
if (doValFilter && (v < valMin || v > valMax))
|
||||||
|
{
|
||||||
|
vPasses = false;
|
||||||
|
imgDebug.at<Vec3b>(row, col)[2] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (pixelPasses)
|
||||||
|
// colorMask.at<uchar>(row, col) = 255;
|
||||||
|
//else
|
||||||
|
//imgDebug.at<Vec3b>(row, col)[0] = hPasses & 255;
|
||||||
|
//imgDebug.at<Vec3b>(row, col)[1] = sPasses & 255;
|
||||||
|
//imgDebug.at<Vec3b>(row, col)[2] = vPasses & 255;
|
||||||
|
|
||||||
|
if ((hPasses) || (hPasses && sPasses))//(hPasses && vPasses) || (sPasses && vPasses) ||
|
||||||
|
this->colorMask.at<uchar>(row, col) = 255;
|
||||||
|
else
|
||||||
|
this->colorMask.at<uchar>(row, col) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
if ((hPasses && sPasses) || (hPasses && vPasses) || (sPasses && vPasses))
|
||||||
|
{
|
||||||
|
vDistance = pow(vDistance, 0.9);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vDistance = pow(vDistance, 1.1);
|
||||||
|
}
|
||||||
|
if (vDistance > 255)
|
||||||
|
vDistance = 255;
|
||||||
|
imgDistanceFromCenter.at<uchar>(row, col) = vDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
vector<Mat> debugImagesSet;
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
{
|
||||||
|
debugImagesSet.push_back(addLabel(charMask, "Charecter mask"));
|
||||||
|
//debugImagesSet1.push_back(erodedCharMask);
|
||||||
|
Mat maskCopy(colorMask.size(), colorMask.type());
|
||||||
|
colorMask.copyTo(maskCopy);
|
||||||
|
debugImagesSet.push_back(addLabel(maskCopy, "color Mask Before"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Mat bigElement = getStructuringElement( 1,
|
||||||
|
Size( 3 + 1, 3+1 ),
|
||||||
|
Point( 1, 1 ) );
|
||||||
|
|
||||||
|
Mat smallElement = getStructuringElement( 1,
|
||||||
|
Size( 1 + 1, 1+1 ),
|
||||||
|
Point( 1, 1 ) );
|
||||||
|
|
||||||
|
morphologyEx(this->colorMask, this->colorMask, MORPH_CLOSE, bigElement);
|
||||||
|
//dilate(this->colorMask, this->colorMask, bigElement);
|
||||||
|
|
||||||
|
Mat combined(charMask.size(), charMask.type());
|
||||||
|
bitwise_and(charMask, colorMask, combined);
|
||||||
|
|
||||||
|
if (this->debug)
|
||||||
|
{
|
||||||
|
debugImagesSet.push_back(addLabel(colorMask, "Color Mask After"));
|
||||||
|
|
||||||
|
debugImagesSet.push_back(addLabel(combined, "Combined"));
|
||||||
|
|
||||||
|
//displayImage(config, "COLOR filter Mask", colorMask);
|
||||||
|
debugImagesSet.push_back(addLabel(imgDebug, "Color filter Debug"));
|
||||||
|
|
||||||
|
cvtColor(imgDebugHueOnly, imgDebugHueOnly, CV_HSV2BGR);
|
||||||
|
debugImagesSet.push_back(addLabel(imgDebugHueOnly, "Color Filter Hue"));
|
||||||
|
|
||||||
|
equalizeHist(imgDistanceFromCenter, imgDistanceFromCenter);
|
||||||
|
debugImagesSet.push_back(addLabel(imgDistanceFromCenter, "COLOR filter Distance"));
|
||||||
|
|
||||||
|
debugImagesSet.push_back(addLabel(debugMask, "COLOR Hues off"));
|
||||||
|
|
||||||
|
|
||||||
|
Mat dashboard = drawImageDashboard(debugImagesSet, imgDebugHueOnly.type(), 3);
|
||||||
|
displayImage(config, "Color Filter Images", dashboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Goes through an array of values, picks the winner based on the highest percentage of other values that are within the maxValDifference
|
||||||
|
// Return -1 if it fails.
|
||||||
|
int ColorFilter::getMajorityOpinion(vector<float> values, float minPercentAgreement, float maxValDifference)
|
||||||
|
{
|
||||||
|
float bestPercentAgreement = 0;
|
||||||
|
float lowestOverallDiff = 1000000000;
|
||||||
|
int bestPercentAgreementIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < values.size(); i++)
|
||||||
|
{
|
||||||
|
int valuesInRange = 0;
|
||||||
|
float overallDiff = 0;
|
||||||
|
for (int j = 0; j < values.size(); j++)
|
||||||
|
{
|
||||||
|
float diff = abs(values[i] - values[j]);
|
||||||
|
if (diff < maxValDifference)
|
||||||
|
valuesInRange++;
|
||||||
|
|
||||||
|
overallDiff += diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
float percentAgreement = ((float) valuesInRange) / ((float) values.size());
|
||||||
|
if (overallDiff < lowestOverallDiff && percentAgreement >= bestPercentAgreement && percentAgreement >= minPercentAgreement)
|
||||||
|
{
|
||||||
|
bestPercentAgreement = percentAgreement;
|
||||||
|
bestPercentAgreementIndex = i;
|
||||||
|
lowestOverallDiff = overallDiff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestPercentAgreementIndex;
|
||||||
|
}
|
65
src/openalpr/colorfilter.h
Normal file
65
src/openalpr/colorfilter.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef COLORFILTER_H
|
||||||
|
#define COLORFILTER_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
|
||||||
|
#include "constants.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ColorFilter
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
ColorFilter(Mat image, Mat characterMask, Config* config);
|
||||||
|
virtual ~ColorFilter();
|
||||||
|
|
||||||
|
Mat colorMask;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Config* config;
|
||||||
|
bool debug;
|
||||||
|
|
||||||
|
Mat hsv;
|
||||||
|
Mat charMask;
|
||||||
|
|
||||||
|
|
||||||
|
bool grayscale;
|
||||||
|
|
||||||
|
void preprocessImage();
|
||||||
|
void findCharColors();
|
||||||
|
|
||||||
|
bool imageIsGrayscale(Mat image);
|
||||||
|
int getMajorityOpinion(vector<float> values, float minPercentAgreement, float maxValDifference);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COLORFILTER_H
|
219
src/openalpr/config.cpp
Normal file
219
src/openalpr/config.cpp
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
Config::Config(const std::string country, const std::string runtimeBaseDir)
|
||||||
|
{
|
||||||
|
this->runtimeBaseDir = runtimeBaseDir;
|
||||||
|
|
||||||
|
ini = new CSimpleIniA();
|
||||||
|
|
||||||
|
char* envRuntimeDir;
|
||||||
|
envRuntimeDir = getenv (ENV_VARIABLE_RUNTIME_DIR);
|
||||||
|
if (runtimeBaseDir.compare("") != 0)
|
||||||
|
{
|
||||||
|
// User has supplied a runtime directory. Use that.
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (envRuntimeDir!=NULL)
|
||||||
|
{
|
||||||
|
// Environment variable is non-empty. Use that.
|
||||||
|
this->runtimeBaseDir = envRuntimeDir;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use the default
|
||||||
|
this->runtimeBaseDir = DEFAULT_RUNTIME_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
string configFile = (this->runtimeBaseDir + CONFIG_FILE);
|
||||||
|
|
||||||
|
if (DirectoryExists(this->runtimeBaseDir.c_str()) == false)
|
||||||
|
{
|
||||||
|
std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not exist!" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (fileExists(configFile.c_str()) == false)
|
||||||
|
{
|
||||||
|
std::cerr << "--(!)Runtime directory '" << this->runtimeBaseDir << "' does not contain a config file '" << CONFIG_FILE << "'!" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ini->LoadFile(configFile.c_str());
|
||||||
|
|
||||||
|
this->country = country;
|
||||||
|
|
||||||
|
loadValues(country);
|
||||||
|
}
|
||||||
|
Config::~Config()
|
||||||
|
{
|
||||||
|
delete ini;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::loadValues(string country)
|
||||||
|
{
|
||||||
|
maxPlateWidthPercent = getFloat("common", "max_plate_width_percent", 100);
|
||||||
|
maxPlateHeightPercent = getFloat("common", "max_plate_height_percent", 100);
|
||||||
|
|
||||||
|
minPlateSizeWidthPx = getInt(country, "min_plate_size_width_px", 100);
|
||||||
|
minPlateSizeHeightPx = getInt(country, "min_plate_size_height_px", 100);
|
||||||
|
|
||||||
|
plateWidthMM = getFloat(country, "plate_width_mm", 100);
|
||||||
|
plateHeightMM = getFloat(country, "plate_height_mm", 100);
|
||||||
|
|
||||||
|
charHeightMM = getFloat(country, "char_height_mm", 100);
|
||||||
|
charWidthMM = getFloat(country, "char_width_mm", 100);
|
||||||
|
charWhitespaceTopMM = getFloat(country, "char_whitespace_top_mm", 100);
|
||||||
|
charWhitespaceBotMM = getFloat(country, "char_whitespace_bot_mm", 100);
|
||||||
|
|
||||||
|
templateWidthPx = getInt(country, "template_max_width_px", 100);
|
||||||
|
templateHeightPx = getInt(country, "template_max_height_px", 100);
|
||||||
|
|
||||||
|
float ocrImagePercent = getFloat("common", "ocr_img_size_percent", 100);
|
||||||
|
ocrImageWidthPx = round(((float) templateWidthPx) * ocrImagePercent);
|
||||||
|
ocrImageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
|
||||||
|
|
||||||
|
|
||||||
|
float stateIdImagePercent = getFloat("common", "state_id_img_size_percent", 100);
|
||||||
|
stateIdImageWidthPx = round(((float)templateWidthPx) * ocrImagePercent);
|
||||||
|
stateIdimageHeightPx = round(((float)templateHeightPx) * ocrImagePercent);
|
||||||
|
|
||||||
|
|
||||||
|
charAnalysisMinPercent = getFloat(country, "char_analysis_min_pct", 0);
|
||||||
|
charAnalysisHeightRange = getFloat(country, "char_analysis_height_range", 0);
|
||||||
|
charAnalysisHeightStepSize = getFloat(country, "char_analysis_height_step_size", 0);
|
||||||
|
charAnalysisNumSteps = getInt(country, "char_analysis_height_num_steps", 0);
|
||||||
|
|
||||||
|
segmentationMinBoxWidthPx = getInt(country, "segmentation_min_box_width_px", 0);
|
||||||
|
segmentationMinCharHeightPercent = getFloat(country, "segmentation_min_charheight_percent", 0);
|
||||||
|
segmentationMaxCharWidthvsAverage = getFloat(country, "segmentation_max_segment_width_percent_vs_average", 0);
|
||||||
|
|
||||||
|
plateLinesSensitivityVertical = getFloat(country, "plateline_sensitivity_vertical", 0);
|
||||||
|
plateLinesSensitivityHorizontal = getFloat(country, "plateline_sensitivity_horizontal", 0);
|
||||||
|
|
||||||
|
ocrLanguage = getString(country, "ocr_language", "none");
|
||||||
|
ocrMinFontSize = getInt("common", "ocr_min_font_point", 100);
|
||||||
|
|
||||||
|
postProcessMinConfidence = getFloat("common", "postprocess_min_confidence", 100);
|
||||||
|
postProcessConfidenceSkipLevel = getFloat("common", "postprocess_confidence_skip_level", 100);
|
||||||
|
postProcessMaxSubstitutions = getInt("common", "postprocess_max_substitutions", 100);
|
||||||
|
postProcessMinCharacters = getInt("common", "postprocess_min_characers", 100);
|
||||||
|
postProcessMaxCharacters = getInt("common", "postprocess_max_characers", 100);
|
||||||
|
|
||||||
|
debugGeneral = getBoolean("debug", "general", false);
|
||||||
|
debugTiming = getBoolean("debug", "timing", false);
|
||||||
|
debugStateId = getBoolean("debug", "state_id", false);
|
||||||
|
debugPlateLines = getBoolean("debug", "plate_lines", false);
|
||||||
|
debugPlateCorners = getBoolean("debug", "plate_corners", false);
|
||||||
|
debugCharRegions = getBoolean("debug", "char_regions", false);
|
||||||
|
debugCharSegmenter = getBoolean("debug", "char_segment", false);
|
||||||
|
debugCharAnalysis = getBoolean("debug", "char_analysis", false);
|
||||||
|
debugColorFiler = getBoolean("debug", "color_filter", false);
|
||||||
|
debugOcr = getBoolean("debug", "ocr", false);
|
||||||
|
debugPostProcess = getBoolean("debug", "postprocess", false);
|
||||||
|
debugShowImages = getBoolean("debug", "show_images", false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::debugOff()
|
||||||
|
{
|
||||||
|
debugGeneral = false;
|
||||||
|
debugTiming = false;
|
||||||
|
debugStateId = false;
|
||||||
|
debugPlateLines = false;
|
||||||
|
debugPlateCorners = false;
|
||||||
|
debugCharRegions = false;
|
||||||
|
debugCharSegmenter = false;
|
||||||
|
debugCharAnalysis = false;
|
||||||
|
debugColorFiler = false;
|
||||||
|
debugOcr = false;
|
||||||
|
debugPostProcess = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
string Config::getCascadeRuntimeDir()
|
||||||
|
{
|
||||||
|
return this->runtimeBaseDir + CASCADE_DIR;
|
||||||
|
}
|
||||||
|
string Config::getKeypointsRuntimeDir()
|
||||||
|
{
|
||||||
|
return this->runtimeBaseDir + KEYPOINTS_DIR;
|
||||||
|
}
|
||||||
|
string Config::getPostProcessRuntimeDir()
|
||||||
|
{
|
||||||
|
return this->runtimeBaseDir + POSTPROCESS_DIR;
|
||||||
|
}
|
||||||
|
string Config::getTessdataPrefix()
|
||||||
|
{
|
||||||
|
return "TESSDATA_PREFIX=" + this->runtimeBaseDir + "/ocr/";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float Config::getFloat(string section, string key, float defaultValue)
|
||||||
|
{
|
||||||
|
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
||||||
|
if (pszValue == NULL)
|
||||||
|
{
|
||||||
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float val = atof(pszValue);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
int Config::getInt(string section, string key, int defaultValue)
|
||||||
|
{
|
||||||
|
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
||||||
|
if (pszValue == NULL)
|
||||||
|
{
|
||||||
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int val = atoi(pszValue);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
bool Config::getBoolean(string section, string key, bool defaultValue)
|
||||||
|
{
|
||||||
|
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
||||||
|
if (pszValue == NULL)
|
||||||
|
{
|
||||||
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int val = atoi(pszValue);
|
||||||
|
return val != 0;
|
||||||
|
}
|
||||||
|
string Config::getString(string section, string key, string defaultValue)
|
||||||
|
{
|
||||||
|
const char * pszValue = ini->GetValue(section.c_str(), key.c_str(), NULL /*default*/);
|
||||||
|
if (pszValue == NULL)
|
||||||
|
{
|
||||||
|
std::cout << "Error: missing configuration entry for: " << section << "->" << key << endl;
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string val = string(pszValue);
|
||||||
|
return val;
|
||||||
|
}
|
125
src/openalpr/config.h
Normal file
125
src/openalpr/config.h
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
|
||||||
|
#include "simpleini/simpleini.h"
|
||||||
|
#include "support/filesystem.h"
|
||||||
|
|
||||||
|
#include "linux_dev.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdlib.h> /* getenv */
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
Config(const std::string country, const std::string runtimeDir = "");
|
||||||
|
virtual ~Config();
|
||||||
|
|
||||||
|
string country;
|
||||||
|
|
||||||
|
float maxPlateWidthPercent;
|
||||||
|
float maxPlateHeightPercent;
|
||||||
|
|
||||||
|
float minPlateSizeWidthPx;
|
||||||
|
float minPlateSizeHeightPx;
|
||||||
|
|
||||||
|
float plateWidthMM;
|
||||||
|
float plateHeightMM;
|
||||||
|
|
||||||
|
float charHeightMM;
|
||||||
|
float charWidthMM;
|
||||||
|
float charWhitespaceTopMM;
|
||||||
|
float charWhitespaceBotMM;
|
||||||
|
|
||||||
|
int templateWidthPx;
|
||||||
|
int templateHeightPx;
|
||||||
|
|
||||||
|
int ocrImageWidthPx;
|
||||||
|
int ocrImageHeightPx;
|
||||||
|
|
||||||
|
int stateIdImageWidthPx;
|
||||||
|
int stateIdimageHeightPx;
|
||||||
|
|
||||||
|
float charAnalysisMinPercent;
|
||||||
|
float charAnalysisHeightRange;
|
||||||
|
float charAnalysisHeightStepSize;
|
||||||
|
int charAnalysisNumSteps;
|
||||||
|
|
||||||
|
float plateLinesSensitivityVertical;
|
||||||
|
float plateLinesSensitivityHorizontal;
|
||||||
|
|
||||||
|
int segmentationMinBoxWidthPx;
|
||||||
|
float segmentationMinCharHeightPercent;
|
||||||
|
float segmentationMaxCharWidthvsAverage;
|
||||||
|
|
||||||
|
string ocrLanguage;
|
||||||
|
int ocrMinFontSize;
|
||||||
|
|
||||||
|
float postProcessMinConfidence;
|
||||||
|
float postProcessConfidenceSkipLevel;
|
||||||
|
int postProcessMaxSubstitutions;
|
||||||
|
int postProcessMinCharacters;
|
||||||
|
int postProcessMaxCharacters;
|
||||||
|
|
||||||
|
|
||||||
|
bool debugGeneral;
|
||||||
|
bool debugTiming;
|
||||||
|
bool debugStateId;
|
||||||
|
bool debugPlateLines;
|
||||||
|
bool debugPlateCorners;
|
||||||
|
bool debugCharRegions;
|
||||||
|
bool debugCharSegmenter;
|
||||||
|
bool debugCharAnalysis;
|
||||||
|
bool debugColorFiler;
|
||||||
|
bool debugOcr;
|
||||||
|
bool debugPostProcess;
|
||||||
|
bool debugShowImages;
|
||||||
|
|
||||||
|
void debugOff();
|
||||||
|
|
||||||
|
string getKeypointsRuntimeDir();
|
||||||
|
string getCascadeRuntimeDir();
|
||||||
|
string getPostProcessRuntimeDir();
|
||||||
|
string getTessdataPrefix();
|
||||||
|
|
||||||
|
private:
|
||||||
|
CSimpleIniA* ini;
|
||||||
|
|
||||||
|
string runtimeBaseDir;
|
||||||
|
|
||||||
|
void loadValues(string country);
|
||||||
|
|
||||||
|
int getInt(string section, string key, int defaultValue);
|
||||||
|
float getFloat(string section, string key, float defaultValue);
|
||||||
|
string getString(string section, string key, string defaultValue);
|
||||||
|
bool getBoolean(string section, string key, bool defaultValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
21
src/openalpr/constants.h
Normal file
21
src/openalpr/constants.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "linux_dev.h"
|
||||||
|
|
432
src/openalpr/featurematcher.cpp
Normal file
432
src/openalpr/featurematcher.cpp
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "featurematcher.h"
|
||||||
|
|
||||||
|
|
||||||
|
//const int DEFAULT_QUERY_FEATURES = 305;
|
||||||
|
//const int DEFAULT_TRAINING_FEATURES = 305;
|
||||||
|
const float MAX_DISTANCE_TO_MATCH = 100.0f;
|
||||||
|
|
||||||
|
|
||||||
|
FeatureMatcher::FeatureMatcher(Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
|
||||||
|
//this->descriptorMatcher = DescriptorMatcher::create( "BruteForce-HammingLUT" );
|
||||||
|
this->descriptorMatcher = new BFMatcher(NORM_HAMMING, false);
|
||||||
|
|
||||||
|
//this->descriptorMatcher = DescriptorMatcher::create( "FlannBased" );
|
||||||
|
|
||||||
|
|
||||||
|
this->detector = new FastFeatureDetector(10, true);
|
||||||
|
this->extractor = new BRISK(10, 1, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
FeatureMatcher::~FeatureMatcher()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < trainingImgKeypoints.size(); i++)
|
||||||
|
trainingImgKeypoints[i].clear();
|
||||||
|
trainingImgKeypoints.clear();
|
||||||
|
|
||||||
|
descriptorMatcher.release();
|
||||||
|
detector.release();
|
||||||
|
extractor.release();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FeatureMatcher::isLoaded()
|
||||||
|
{
|
||||||
|
if( detector.empty() || extractor.empty() || descriptorMatcher.empty() )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FeatureMatcher::numTrainingElements()
|
||||||
|
{
|
||||||
|
return billMapping.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void FeatureMatcher::surfStyleMatching( const Mat& queryDescriptors, vector<KeyPoint> queryKeypoints,
|
||||||
|
vector<DMatch>& matches12 )
|
||||||
|
{
|
||||||
|
vector<vector<DMatch> > matchesKnn;
|
||||||
|
|
||||||
|
this->descriptorMatcher->radiusMatch(queryDescriptors, matchesKnn, MAX_DISTANCE_TO_MATCH);
|
||||||
|
|
||||||
|
|
||||||
|
vector<DMatch> tempMatches;
|
||||||
|
_surfStyleMatching(queryDescriptors, matchesKnn, tempMatches);
|
||||||
|
|
||||||
|
crisscrossFiltering(queryKeypoints, tempMatches, matches12);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void FeatureMatcher::_surfStyleMatching(const Mat& queryDescriptors, vector<vector<DMatch> > matchesKnn, vector<DMatch>& matches12)
|
||||||
|
{
|
||||||
|
|
||||||
|
//objectMatches.clear();
|
||||||
|
//objectMatches.resize(objectIds.size());
|
||||||
|
//cout << "starting matcher" << matchesKnn.size() << endl;
|
||||||
|
for (int descInd = 0; descInd < queryDescriptors.rows; descInd++)
|
||||||
|
{
|
||||||
|
const std::vector<DMatch> & matches = matchesKnn[descInd];
|
||||||
|
//cout << "two: " << descInd << ":" << matches.size() << endl;
|
||||||
|
|
||||||
|
// Check to make sure we have 2 matches. I think this is always the case, but it doesn't hurt to be sure
|
||||||
|
if (matchesKnn[descInd].size() > 1)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Next throw out matches with a crappy score
|
||||||
|
// Ignore... already handled by the radiusMatch
|
||||||
|
//if (matchesKnn[descInd][0].distance < MAX_DISTANCE_TO_MATCH)
|
||||||
|
//{
|
||||||
|
float ratioThreshold = 0.75;
|
||||||
|
|
||||||
|
// Check if both matches came from the same image. If they both came from the same image, score them slightly less harshly
|
||||||
|
if (matchesKnn[descInd][0].imgIdx == matchesKnn[descInd][1].imgIdx)
|
||||||
|
{
|
||||||
|
ratioThreshold = 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((matchesKnn[descInd][0].distance / matchesKnn[descInd][1].distance) < ratioThreshold)
|
||||||
|
{
|
||||||
|
bool already_exists = false;
|
||||||
|
// Quickly run through the matches we've already added and make sure it's not a duplicate...
|
||||||
|
for (int q = 0; q < matches12.size(); q++)
|
||||||
|
{
|
||||||
|
if (matchesKnn[descInd][0].queryIdx == matches12[q].queryIdx)
|
||||||
|
{
|
||||||
|
already_exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((matchesKnn[descInd][0].trainIdx == matches12[q].trainIdx) &&
|
||||||
|
(matchesKnn[descInd][0].imgIdx == matches12[q].imgIdx))
|
||||||
|
{
|
||||||
|
already_exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Good match.
|
||||||
|
if (already_exists == false)
|
||||||
|
matches12.push_back(matchesKnn[descInd][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
else if (matchesKnn[descInd].size() == 1)
|
||||||
|
{
|
||||||
|
// Only match? Does this ever happen?
|
||||||
|
matches12.push_back(matchesKnn[descInd][0]);
|
||||||
|
}
|
||||||
|
// In the ratio test, we will compare the quality of a match with the next match that is not from the same object:
|
||||||
|
// we can accept several matches with similar scores as long as they are for the same object. Those should not be
|
||||||
|
// part of the model anyway as they are not discriminative enough
|
||||||
|
|
||||||
|
//for (unsigned int first_index = 0; first_index < matches.size(); ++first_index)
|
||||||
|
//{
|
||||||
|
|
||||||
|
//matches12.push_back(match);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares the matches keypoints for parallel lines. Removes matches that are criss-crossing too much
|
||||||
|
// We assume that license plates won't be upside-down or backwards. So expect lines to be closely parallel
|
||||||
|
void FeatureMatcher::crisscrossFiltering(const vector<KeyPoint> queryKeypoints, const vector<DMatch> inputMatches, vector<DMatch> &outputMatches)
|
||||||
|
{
|
||||||
|
|
||||||
|
Rect crissCrossAreaVertical(0, 0, config->stateIdImageWidthPx, config->stateIdimageHeightPx * 2);
|
||||||
|
Rect crissCrossAreaHorizontal(0, 0, config->stateIdImageWidthPx * 2, config->stateIdimageHeightPx);
|
||||||
|
|
||||||
|
for (int i = 0; i < billMapping.size(); i++)
|
||||||
|
{
|
||||||
|
vector<DMatch> matchesForOnePlate;
|
||||||
|
for (int j = 0; j < inputMatches.size(); j++)
|
||||||
|
{
|
||||||
|
if (inputMatches[j].imgIdx == i)
|
||||||
|
matchesForOnePlate.push_back(inputMatches[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each plate, compare the lines for the keypoints (training image and query image)
|
||||||
|
// go through each line between keypoints and filter out matches that are criss-crossing
|
||||||
|
vector<LineSegment> vlines;
|
||||||
|
vector<LineSegment> hlines;
|
||||||
|
vector<int> matchIdx;
|
||||||
|
|
||||||
|
for (int j = 0; j < matchesForOnePlate.size(); j++)
|
||||||
|
{
|
||||||
|
KeyPoint tkp = trainingImgKeypoints[i][matchesForOnePlate[j].trainIdx];
|
||||||
|
KeyPoint qkp = queryKeypoints[matchesForOnePlate[j].queryIdx];
|
||||||
|
|
||||||
|
vlines.push_back(LineSegment(tkp.pt.x, tkp.pt.y + config->stateIdimageHeightPx, qkp.pt.x, qkp.pt.y));
|
||||||
|
hlines.push_back(LineSegment(tkp.pt.x, tkp.pt.y, qkp.pt.x + config->stateIdImageWidthPx, qkp.pt.y));
|
||||||
|
matchIdx.push_back(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Iterate through each line (n^2) removing the one with the most criss-crosses until there are none left.
|
||||||
|
int mostIntersections = 1;
|
||||||
|
while (mostIntersections > 0 && vlines.size() > 0)
|
||||||
|
{
|
||||||
|
int mostIntersectionsIndex = -1;
|
||||||
|
mostIntersections = 0;
|
||||||
|
|
||||||
|
for (int j = 0; j < vlines.size(); j++)
|
||||||
|
{
|
||||||
|
int intrCount = 0;
|
||||||
|
for (int q = 0; q < vlines.size(); q++)
|
||||||
|
{
|
||||||
|
Point vintr = vlines[j].intersection(vlines[q]);
|
||||||
|
Point hintr = hlines[j].intersection(hlines[q]);
|
||||||
|
float vangleDiff = abs(vlines[j].angle - vlines[q].angle);
|
||||||
|
float hangleDiff = abs(hlines[j].angle - hlines[q].angle);
|
||||||
|
if (vintr.inside(crissCrossAreaVertical) && vangleDiff > 10)
|
||||||
|
{
|
||||||
|
intrCount++;
|
||||||
|
}
|
||||||
|
else if (hintr.inside(crissCrossAreaHorizontal) && hangleDiff > 10)
|
||||||
|
{
|
||||||
|
intrCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intrCount > mostIntersections)
|
||||||
|
{
|
||||||
|
mostIntersections = intrCount;
|
||||||
|
mostIntersectionsIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mostIntersectionsIndex >= 0)
|
||||||
|
{
|
||||||
|
if (this->config->debugStateId)
|
||||||
|
cout << "Filtered intersection! " << billMapping[i] << endl;
|
||||||
|
vlines.erase(vlines.begin() + mostIntersectionsIndex);
|
||||||
|
hlines.erase(hlines.begin() + mostIntersectionsIndex);
|
||||||
|
matchIdx.erase(matchIdx.begin() + mostIntersectionsIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the non-crisscrosses back on the list
|
||||||
|
for (int j = 0; j < matchIdx.size(); j++)
|
||||||
|
{
|
||||||
|
outputMatches.push_back(matchesForOnePlate[matchIdx[j]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Returns true if successful, false otherwise
|
||||||
|
bool FeatureMatcher::loadRecognitionSet(string country)
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
out << config->getKeypointsRuntimeDir() << "/" << country << "/";
|
||||||
|
string country_dir = out.str();
|
||||||
|
|
||||||
|
|
||||||
|
if (DirectoryExists(country_dir.c_str()))
|
||||||
|
{
|
||||||
|
vector<Mat> trainImages;
|
||||||
|
vector<string> plateFiles = getFilesInDir(country_dir.c_str());
|
||||||
|
|
||||||
|
for (int i = 0; i < plateFiles.size(); i++)
|
||||||
|
{
|
||||||
|
if (hasEnding(plateFiles[i], ".jpg") == false)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string fullpath = country_dir + plateFiles[i];
|
||||||
|
Mat img = imread( fullpath );
|
||||||
|
|
||||||
|
// convert to gray and resize to the size of the templates
|
||||||
|
cvtColor(img, img, CV_BGR2GRAY);
|
||||||
|
resize(img, img, getSizeMaintainingAspect(img, config->stateIdImageWidthPx, config->stateIdimageHeightPx));
|
||||||
|
|
||||||
|
if( img.empty() )
|
||||||
|
{
|
||||||
|
cout << "Can not read images" << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Mat descriptors;
|
||||||
|
|
||||||
|
vector<KeyPoint> keypoints;
|
||||||
|
detector->detect( img, keypoints );
|
||||||
|
extractor->compute(img, keypoints, descriptors);
|
||||||
|
|
||||||
|
if (descriptors.cols > 0)
|
||||||
|
{
|
||||||
|
billMapping.push_back(plateFiles[i].substr(0, 2));
|
||||||
|
trainImages.push_back(descriptors);
|
||||||
|
trainingImgKeypoints.push_back(keypoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this->descriptorMatcher->add(trainImages);
|
||||||
|
this->descriptorMatcher->train();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RecognitionResult FeatureMatcher::recognize( const Mat& queryImg, bool drawOnImage, Mat* outputImage,
|
||||||
|
bool debug_on, vector<int> debug_matches_array
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RecognitionResult result;
|
||||||
|
|
||||||
|
result.haswinner = false;
|
||||||
|
|
||||||
|
Mat queryDescriptors;
|
||||||
|
vector<KeyPoint> queryKeypoints;
|
||||||
|
|
||||||
|
detector->detect( queryImg, queryKeypoints );
|
||||||
|
extractor->compute(queryImg, queryKeypoints, queryDescriptors);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (queryKeypoints.size() <= 5)
|
||||||
|
{
|
||||||
|
// Cut it loose if there's less than 5 keypoints... nothing would ever match anyway and it could crash the matcher.
|
||||||
|
if (drawOnImage)
|
||||||
|
{
|
||||||
|
drawKeypoints( queryImg, queryKeypoints, *outputImage, CV_RGB(0, 255, 0), DrawMatchesFlags::DEFAULT );
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
vector<DMatch> filteredMatches;
|
||||||
|
|
||||||
|
surfStyleMatching( queryDescriptors, queryKeypoints, filteredMatches );
|
||||||
|
|
||||||
|
|
||||||
|
// Create and initialize the counts to 0
|
||||||
|
std::vector<int> bill_match_counts( billMapping.size() );
|
||||||
|
|
||||||
|
for (int i = 0; i < billMapping.size(); i++) { bill_match_counts[i] = 0; }
|
||||||
|
|
||||||
|
for (int i = 0; i < filteredMatches.size(); i++)
|
||||||
|
{
|
||||||
|
bill_match_counts[filteredMatches[i].imgIdx]++;
|
||||||
|
//if (filteredMatches[i].imgIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float max_count = 0; // represented as a percent (0 to 100)
|
||||||
|
int secondmost_count = 0;
|
||||||
|
int maxcount_index = -1;
|
||||||
|
for (int i = 0; i < billMapping.size(); i++)
|
||||||
|
{
|
||||||
|
if (bill_match_counts[i] > max_count && bill_match_counts[i] >= 4)
|
||||||
|
{
|
||||||
|
secondmost_count = max_count;
|
||||||
|
if (secondmost_count <= 2) // A value of 1 or 2 is effectively 0
|
||||||
|
secondmost_count = 0;
|
||||||
|
|
||||||
|
max_count = bill_match_counts[i];
|
||||||
|
maxcount_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float score = ((max_count - secondmost_count - 3) / 10) * 100;
|
||||||
|
if (score < 0)
|
||||||
|
score = 0;
|
||||||
|
else if (score > 100)
|
||||||
|
score = 100;
|
||||||
|
|
||||||
|
|
||||||
|
if (score > 0)
|
||||||
|
{
|
||||||
|
result.haswinner = true;
|
||||||
|
result.winner = billMapping[maxcount_index];
|
||||||
|
result.confidence = score;
|
||||||
|
|
||||||
|
if (drawOnImage)
|
||||||
|
{
|
||||||
|
vector<KeyPoint> positiveMatches;
|
||||||
|
for (int i = 0; i < filteredMatches.size(); i++)
|
||||||
|
{
|
||||||
|
if (filteredMatches[i].imgIdx == maxcount_index)
|
||||||
|
{
|
||||||
|
positiveMatches.push_back( queryKeypoints[filteredMatches[i].queryIdx] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat tmpImg;
|
||||||
|
drawKeypoints( queryImg, queryKeypoints, tmpImg, CV_RGB(185, 0, 0), DrawMatchesFlags::DEFAULT );
|
||||||
|
drawKeypoints( tmpImg, positiveMatches, *outputImage, CV_RGB(0, 255, 0), DrawMatchesFlags::DEFAULT );
|
||||||
|
|
||||||
|
if (result.haswinner == true)
|
||||||
|
{
|
||||||
|
|
||||||
|
std::ostringstream out;
|
||||||
|
out << result.winner << " (" << result.confidence << "%)";
|
||||||
|
|
||||||
|
// we detected a bill, let the people know!
|
||||||
|
//putText(*outputImage, out.str(), Point(15, 27), FONT_HERSHEY_DUPLEX, 1.1, CV_RGB(0, 0, 0), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->config->debugStateId)
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = 0; i < billMapping.size(); i++)
|
||||||
|
{
|
||||||
|
cout << billMapping[i] << " : " << bill_match_counts[i] << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
93
src/openalpr/featurematcher.h
Normal file
93
src/openalpr/featurematcher.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef FEATUREMATCHER_H
|
||||||
|
#define FEATUREMATCHER_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/calib3d/calib3d.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "opencv2/features2d/features2d.hpp"
|
||||||
|
#include "opencv2/video/tracking.hpp"
|
||||||
|
|
||||||
|
#include "support/filesystem.h"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct RecognitionResult {
|
||||||
|
bool haswinner;
|
||||||
|
string winner;
|
||||||
|
int confidence;
|
||||||
|
} ;
|
||||||
|
|
||||||
|
class FeatureMatcher
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
FeatureMatcher(Config* config);
|
||||||
|
virtual ~FeatureMatcher();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RecognitionResult recognize( const Mat& queryImg, bool drawOnImage, Mat* outputImage,
|
||||||
|
bool debug_on, vector<int> debug_matches_array );
|
||||||
|
|
||||||
|
|
||||||
|
bool loadRecognitionSet(string country);
|
||||||
|
|
||||||
|
bool isLoaded();
|
||||||
|
|
||||||
|
int numTrainingElements();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
Ptr<DescriptorMatcher> descriptorMatcher;
|
||||||
|
Ptr<FastFeatureDetector> detector;
|
||||||
|
Ptr<BRISK> extractor;
|
||||||
|
|
||||||
|
|
||||||
|
vector<vector<KeyPoint> > trainingImgKeypoints;
|
||||||
|
|
||||||
|
|
||||||
|
void _surfStyleMatching(const Mat& queryDescriptors, vector<vector<DMatch> > matchesKnn, vector<DMatch>& matches12);
|
||||||
|
|
||||||
|
void crisscrossFiltering(const vector<KeyPoint> queryKeypoints, const vector<DMatch> inputMatches, vector<DMatch> &outputMatches);
|
||||||
|
|
||||||
|
vector<string> billMapping;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void surfStyleMatching( const Mat& queryDescriptors, vector<KeyPoint> queryKeypoints,
|
||||||
|
vector<DMatch>& matches12 );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FEATUREMATCHER_H
|
||||||
|
|
||||||
|
|
202
src/openalpr/licenseplatecandidate.cpp
Normal file
202
src/openalpr/licenseplatecandidate.cpp
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "licenseplatecandidate.h"
|
||||||
|
|
||||||
|
|
||||||
|
LicensePlateCandidate::LicensePlateCandidate(Mat frame, Rect regionOfInterest, Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
|
||||||
|
this->frame = frame;
|
||||||
|
this->plateRegion = regionOfInterest;
|
||||||
|
}
|
||||||
|
|
||||||
|
LicensePlateCandidate::~LicensePlateCandidate()
|
||||||
|
{
|
||||||
|
delete charSegmenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must delete this pointer in parent class
|
||||||
|
void LicensePlateCandidate::recognize()
|
||||||
|
{
|
||||||
|
charSegmenter = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
this->confidence = 0;
|
||||||
|
|
||||||
|
int expandX = round(this->plateRegion.width * 0.15);
|
||||||
|
int expandY = round(this->plateRegion.height * 0.10);
|
||||||
|
// expand box by 15% in all directions
|
||||||
|
Rect expandedRegion = expandRect( this->plateRegion, expandX, expandY, frame.cols, frame.rows) ;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mat plate_bgr = Mat(frame, expandedRegion);
|
||||||
|
resize(plate_bgr, plate_bgr, Size(config->templateWidthPx, config->templateHeightPx));
|
||||||
|
|
||||||
|
Mat plate_bgr_cleaned = Mat(plate_bgr.size(), plate_bgr.type());
|
||||||
|
this->cleanupColors(plate_bgr, plate_bgr_cleaned);
|
||||||
|
|
||||||
|
|
||||||
|
CharacterRegion charRegion(plate_bgr, config);
|
||||||
|
|
||||||
|
|
||||||
|
if (charRegion.confidence > 10)
|
||||||
|
{
|
||||||
|
|
||||||
|
PlateLines plateLines(config);
|
||||||
|
//Mat boogedy = charRegion.getPlateMask();
|
||||||
|
|
||||||
|
plateLines.processImage(charRegion.getPlateMask(), 1.15);
|
||||||
|
plateLines.processImage(plate_bgr_cleaned, 0.9);
|
||||||
|
|
||||||
|
PlateCorners cornerFinder(plate_bgr, &plateLines, &charRegion, config);
|
||||||
|
vector<Point> smallPlateCorners = cornerFinder.findPlateCorners();
|
||||||
|
|
||||||
|
if (cornerFinder.confidence > 0)
|
||||||
|
{
|
||||||
|
this->plateCorners = transformPointsToOriginalImage(frame, plate_bgr, expandedRegion, smallPlateCorners);
|
||||||
|
|
||||||
|
|
||||||
|
this->deskewed = deSkewPlate(frame, this->plateCorners);
|
||||||
|
|
||||||
|
|
||||||
|
charSegmenter = new CharacterSegmenter(deskewed, charRegion.thresholdsInverted(), config);
|
||||||
|
|
||||||
|
|
||||||
|
//this->recognizedText = ocr->recognizedText;
|
||||||
|
//strcpy(this->recognizedText, ocr.recognizedText);
|
||||||
|
|
||||||
|
this->confidence = 100;
|
||||||
|
|
||||||
|
}
|
||||||
|
charRegion.confidence = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Re-maps the coordinates from the smallImage to the coordinate space of the bigImage.
|
||||||
|
vector<Point2f> LicensePlateCandidate::transformPointsToOriginalImage(Mat bigImage, Mat smallImage, Rect region, vector<Point> corners)
|
||||||
|
{
|
||||||
|
vector<Point2f> cornerPoints;
|
||||||
|
for (int i = 0; i < corners.size(); i++)
|
||||||
|
{
|
||||||
|
float bigX = (corners[i].x * ((float) region.width / smallImage.cols));
|
||||||
|
float bigY = (corners[i].y * ((float) region.height / smallImage.rows));
|
||||||
|
|
||||||
|
bigX = bigX + region.x;
|
||||||
|
bigY = bigY + region.y;
|
||||||
|
|
||||||
|
cornerPoints.push_back(Point2f(bigX, bigY));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cornerPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Mat LicensePlateCandidate::deSkewPlate(Mat inputImage, vector<Point2f> corners)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Figure out the appoximate width/height of the license plate region, so we can maintain the aspect ratio.
|
||||||
|
LineSegment leftEdge(round(corners[3].x), round(corners[3].y), round(corners[0].x), round(corners[0].y));
|
||||||
|
LineSegment rightEdge(round(corners[2].x), round(corners[2].y), round(corners[1].x), round(corners[1].y));
|
||||||
|
LineSegment topEdge(round(corners[0].x), round(corners[0].y), round(corners[1].x), round(corners[1].y));
|
||||||
|
LineSegment bottomEdge(round(corners[3].x), round(corners[3].y), round(corners[2].x), round(corners[2].y));
|
||||||
|
|
||||||
|
float w = distanceBetweenPoints(leftEdge.midpoint(), rightEdge.midpoint());
|
||||||
|
float h = distanceBetweenPoints(bottomEdge.midpoint(), topEdge.midpoint());
|
||||||
|
float aspect = w/h;
|
||||||
|
|
||||||
|
int width = config->ocrImageWidthPx;
|
||||||
|
int height = round(((float) width) / aspect);
|
||||||
|
if (height > config->ocrImageHeightPx)
|
||||||
|
{
|
||||||
|
height = config->ocrImageHeightPx;
|
||||||
|
width = round(((float) height) * aspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat deskewed(height, width, frame.type());
|
||||||
|
|
||||||
|
// Corners of the destination image
|
||||||
|
vector<Point2f> quad_pts;
|
||||||
|
quad_pts.push_back(Point2f(0, 0));
|
||||||
|
quad_pts.push_back(Point2f(deskewed.cols, 0));
|
||||||
|
quad_pts.push_back(Point2f(deskewed.cols, deskewed.rows));
|
||||||
|
quad_pts.push_back(Point2f(0, deskewed.rows));
|
||||||
|
|
||||||
|
// Get transformation matrix
|
||||||
|
Mat transmtx = getPerspectiveTransform(corners, quad_pts);
|
||||||
|
|
||||||
|
// Apply perspective transformation
|
||||||
|
warpPerspective(inputImage, deskewed, transmtx, deskewed.size());
|
||||||
|
|
||||||
|
if (this->config->debugGeneral)
|
||||||
|
displayImage(config, "quadrilateral", deskewed);
|
||||||
|
|
||||||
|
return deskewed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LicensePlateCandidate::cleanupColors(Mat inputImage, Mat outputImage)
|
||||||
|
{
|
||||||
|
if (this->config->debugGeneral)
|
||||||
|
cout << "LicensePlate::cleanupColors" << endl;
|
||||||
|
|
||||||
|
//Mat normalized(inputImage.size(), inputImage.type());
|
||||||
|
|
||||||
|
Mat intermediate(inputImage.size(), inputImage.type());
|
||||||
|
|
||||||
|
normalize(inputImage, intermediate, 0, 255, CV_MINMAX );
|
||||||
|
|
||||||
|
// Equalize intensity:
|
||||||
|
if(intermediate.channels() >= 3)
|
||||||
|
{
|
||||||
|
Mat ycrcb;
|
||||||
|
|
||||||
|
cvtColor(intermediate,ycrcb,CV_BGR2YCrCb);
|
||||||
|
|
||||||
|
vector<Mat> channels;
|
||||||
|
split(ycrcb,channels);
|
||||||
|
|
||||||
|
equalizeHist(channels[0], channels[0]);
|
||||||
|
|
||||||
|
merge(channels,ycrcb);
|
||||||
|
|
||||||
|
cvtColor(ycrcb,intermediate,CV_YCrCb2BGR);
|
||||||
|
|
||||||
|
//ycrcb.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bilateralFilter(intermediate, outputImage, 3, 25, 35);
|
||||||
|
|
||||||
|
|
||||||
|
if (this->config->debugGeneral)
|
||||||
|
{
|
||||||
|
displayImage(config, "After cleanup", outputImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
83
src/openalpr/licenseplatecandidate.h
Normal file
83
src/openalpr/licenseplatecandidate.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef STAGE2_H
|
||||||
|
#define STAGE2_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <vector>
|
||||||
|
//#include <apr-1.0/apr_poll.h>
|
||||||
|
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "opencv2/core/core.hpp"
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
|
||||||
|
#include "utility.h"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "platelines.h"
|
||||||
|
#include "characterregion.h"
|
||||||
|
#include "charactersegmenter.h"
|
||||||
|
#include "platecorners.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace cv;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//vector<Rect> getCharacterRegions(Mat frame, vector<Rect> regionsOfInterest);
|
||||||
|
//vector<RotatedRect> getCharSegmentsBetweenLines(Mat img, vector<vector<Point> > contours, LineSegment top, LineSegment bottom);
|
||||||
|
|
||||||
|
class LicensePlateCandidate
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
LicensePlateCandidate(Mat frame, Rect regionOfInterest, Config* config);
|
||||||
|
virtual ~LicensePlateCandidate();
|
||||||
|
|
||||||
|
float confidence; // 0-100
|
||||||
|
//vector<Point> points; // top-left, top-right, bottom-right, bottom-left
|
||||||
|
vector<Point2f> plateCorners;
|
||||||
|
|
||||||
|
void recognize();
|
||||||
|
|
||||||
|
Mat deskewed;
|
||||||
|
CharacterSegmenter* charSegmenter;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
|
||||||
|
Mat frame;
|
||||||
|
Rect plateRegion;
|
||||||
|
|
||||||
|
void cleanupColors(Mat inputImage, Mat outputImage);
|
||||||
|
Mat filterByCharacterHue(vector<vector<Point> > charRegionContours);
|
||||||
|
vector<Point> findPlateCorners(Mat inputImage, PlateLines plateLines, CharacterRegion charRegion); // top-left, top-right, bottom-right, bottom-left
|
||||||
|
|
||||||
|
vector<Point2f> transformPointsToOriginalImage(Mat bigImage, Mat smallImage, Rect region, vector<Point> corners);
|
||||||
|
Mat deSkewPlate(Mat inputImage, vector<Point2f> corners);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // STAGE2_H
|
28
src/openalpr/linux_dev.h
Normal file
28
src/openalpr/linux_dev.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CONFIG_FILE "/openalpr.conf"
|
||||||
|
#define KEYPOINTS_DIR "/keypoints"
|
||||||
|
#define CASCADE_DIR "/region/"
|
||||||
|
#define POSTPROCESS_DIR "/postprocess"
|
||||||
|
|
||||||
|
#define DEFAULT_RUNTIME_DIR "/home/mhill/projects/alpr/runtime_data"
|
||||||
|
#define ENV_VARIABLE_RUNTIME_DIR "OPENALPR_RUNTIME_DIR"
|
||||||
|
|
||||||
|
|
145
src/openalpr/ocr.cpp
Normal file
145
src/openalpr/ocr.cpp
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "ocr.h"
|
||||||
|
|
||||||
|
OCR::OCR(Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
|
||||||
|
this->postProcessor = new PostProcess(config);
|
||||||
|
|
||||||
|
tesseract=new TessBaseAPI();
|
||||||
|
|
||||||
|
// Tesseract requires the prefix directory to be set as an env variable
|
||||||
|
vector<char> tessdataPrefix(config->getTessdataPrefix().size());
|
||||||
|
|
||||||
|
strcpy(tessdataPrefix.data(), config->getTessdataPrefix().c_str());
|
||||||
|
putenv(tessdataPrefix.data());
|
||||||
|
|
||||||
|
|
||||||
|
tesseract->Init("", config->ocrLanguage.c_str() );
|
||||||
|
tesseract->SetVariable("save_blob_choices", "T");
|
||||||
|
//tesseract->SetVariable("tessedit_char_whitelist", "ABCDEFGHIJKLMNPQRSTUVWXYZ1234567890");
|
||||||
|
tesseract->SetPageSegMode(PSM_SINGLE_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
OCR::~OCR()
|
||||||
|
{
|
||||||
|
tesseract->Clear();
|
||||||
|
delete postProcessor;
|
||||||
|
delete tesseract;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void OCR::performOCR(vector<Mat> thresholds, vector<Rect> charRegions)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
|
||||||
|
postProcessor->clear();
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < thresholds.size(); i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Make it black text on white background
|
||||||
|
bitwise_not(thresholds[i], thresholds[i]);
|
||||||
|
tesseract->SetImage((uchar*) thresholds[i].data, thresholds[i].size().width, thresholds[i].size().height, thresholds[i].channels(), thresholds[i].step1());
|
||||||
|
|
||||||
|
|
||||||
|
for (int j = 0; j < charRegions.size(); j++)
|
||||||
|
{
|
||||||
|
Rect expandedRegion = expandRect( charRegions[j], 2, 2, thresholds[i].cols, thresholds[i].rows) ;
|
||||||
|
|
||||||
|
tesseract->SetRectangle(expandedRegion.x, expandedRegion.y, expandedRegion.width, expandedRegion.height);
|
||||||
|
tesseract->Recognize(NULL);
|
||||||
|
|
||||||
|
tesseract::ResultIterator* ri = tesseract->GetIterator();
|
||||||
|
tesseract::PageIteratorLevel level = tesseract::RIL_SYMBOL;
|
||||||
|
do {
|
||||||
|
const char* symbol = ri->GetUTF8Text(level);
|
||||||
|
float conf = ri->Confidence(level);
|
||||||
|
|
||||||
|
bool dontcare;
|
||||||
|
int fontindex = 0;
|
||||||
|
int pointsize = 0;
|
||||||
|
const char* fontName = ri->WordFontAttributes(&dontcare, &dontcare, &dontcare, &dontcare, &dontcare, &dontcare, &pointsize, &fontindex);
|
||||||
|
|
||||||
|
if(symbol != 0 && pointsize >= config->ocrMinFontSize) {
|
||||||
|
postProcessor->addLetter(*symbol, j, conf);
|
||||||
|
|
||||||
|
|
||||||
|
if (this->config->debugOcr)
|
||||||
|
printf("charpos%d: threshold %d: symbol %s, conf: %f font: %s (index %d) size %dpx", j, i, symbol, conf, fontName, fontindex, pointsize);
|
||||||
|
|
||||||
|
bool indent = false;
|
||||||
|
tesseract::ChoiceIterator ci(*ri);
|
||||||
|
do {
|
||||||
|
const char* choice = ci.GetUTF8Text();
|
||||||
|
|
||||||
|
postProcessor->addLetter(*choice, j, ci.Confidence());
|
||||||
|
|
||||||
|
|
||||||
|
//letterScores.addScore(*choice, j, ci.Confidence() - MIN_CONFIDENCE);
|
||||||
|
if (this->config->debugOcr)
|
||||||
|
{
|
||||||
|
if (indent) printf("\t\t ");
|
||||||
|
printf("\t- ");
|
||||||
|
printf("%s conf: %f\n", choice, ci.Confidence());
|
||||||
|
}
|
||||||
|
|
||||||
|
indent = true;
|
||||||
|
} while(ci.Next());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->config->debugOcr)
|
||||||
|
printf("---------------------------------------------\n");
|
||||||
|
|
||||||
|
delete[] symbol;
|
||||||
|
} while((ri->Next(level)));
|
||||||
|
|
||||||
|
delete ri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "OCR Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
69
src/openalpr/ocr.h
Normal file
69
src/openalpr/ocr.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef OCR_H
|
||||||
|
#define OCR_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "utility.h"
|
||||||
|
#include "postprocess.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "constants.h"
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
|
||||||
|
#include "baseapi.h"
|
||||||
|
using namespace tesseract;
|
||||||
|
using namespace std;
|
||||||
|
using namespace cv;
|
||||||
|
|
||||||
|
|
||||||
|
class OCR
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
OCR(Config* config);
|
||||||
|
virtual ~OCR();
|
||||||
|
|
||||||
|
void performOCR(vector<Mat> thresholds, vector<Rect> charRegions);
|
||||||
|
|
||||||
|
PostProcess* postProcessor;
|
||||||
|
//string recognizedText;
|
||||||
|
//float confidence;
|
||||||
|
//float overallConfidence;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
TessBaseAPI *tesseract;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // OCR_H
|
447
src/openalpr/platecorners.cpp
Normal file
447
src/openalpr/platecorners.cpp
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "platecorners.h"
|
||||||
|
|
||||||
|
PlateCorners::PlateCorners(Mat inputImage, PlateLines* plateLines, CharacterRegion* charRegion, Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
|
||||||
|
if (this->config->debugPlateCorners)
|
||||||
|
cout << "PlateCorners constructor" << endl;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this->inputImage = inputImage;
|
||||||
|
this->plateLines = plateLines;
|
||||||
|
this->charRegion = charRegion;
|
||||||
|
|
||||||
|
this->bestHorizontalScore = 9999999999999;
|
||||||
|
this->bestVerticalScore = 9999999999999;
|
||||||
|
|
||||||
|
|
||||||
|
Point topPoint = charRegion->getTopLine().midpoint();
|
||||||
|
Point bottomPoint = charRegion->getBottomLine().closestPointOnSegmentTo(topPoint);
|
||||||
|
this->charHeight = distanceBetweenPoints(topPoint, bottomPoint);
|
||||||
|
|
||||||
|
//this->charHeight = distanceBetweenPoints(charRegion->getCharArea()[0], charRegion->getCharArea()[3]);
|
||||||
|
//this->charHeight = this->charHeight - 2; // Adjust since this height is a box around our char.
|
||||||
|
// Adjust the char height for the difference in size...
|
||||||
|
//this->charHeight = ((float) inputImage.size().height / (float) TEMPLATE_PLATE_HEIGHT) * this->charHeight;
|
||||||
|
|
||||||
|
this->charAngle = angleBetweenPoints(charRegion->getCharArea()[0], charRegion->getCharArea()[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlateCorners::~PlateCorners()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<Point> PlateCorners::findPlateCorners()
|
||||||
|
{
|
||||||
|
if (this->config->debugPlateCorners)
|
||||||
|
cout << "PlateCorners::findPlateCorners" << endl;
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
int horizontalLines = this->plateLines->horizontalLines.size();
|
||||||
|
int verticalLines = this->plateLines->verticalLines.size();
|
||||||
|
|
||||||
|
|
||||||
|
// layout horizontal lines
|
||||||
|
for (int h1 = NO_LINE; h1 < horizontalLines; h1++)
|
||||||
|
{
|
||||||
|
for (int h2 = NO_LINE; h2 < horizontalLines; h2++)
|
||||||
|
{
|
||||||
|
if (h1 == h2 && h1 != NO_LINE) continue;
|
||||||
|
|
||||||
|
this->scoreHorizontals(h1, h2);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// layout vertical lines
|
||||||
|
for (int v1 = NO_LINE; v1 < verticalLines; v1++)
|
||||||
|
{
|
||||||
|
for (int v2 = NO_LINE; v2 < verticalLines; v2++)
|
||||||
|
{
|
||||||
|
if (v1 == v2 && v1 != NO_LINE) continue;
|
||||||
|
|
||||||
|
this->scoreVerticals(v1, v2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this->config->debugPlateCorners)
|
||||||
|
{
|
||||||
|
|
||||||
|
cout << "Drawing debug stuff..." << endl;
|
||||||
|
|
||||||
|
Mat imgCorners = Mat(inputImage.size(), inputImage.type());
|
||||||
|
inputImage.copyTo(imgCorners);
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
circle(imgCorners, charRegion->getCharArea()[i], 2, Scalar(0, 0, 0));
|
||||||
|
|
||||||
|
|
||||||
|
line(imgCorners, this->bestTop.p1, this->bestTop.p2, Scalar(255, 0, 0), 1, CV_AA);
|
||||||
|
line(imgCorners, this->bestRight.p1, this->bestRight.p2, Scalar(0, 0, 255), 1, CV_AA);
|
||||||
|
line(imgCorners, this->bestBottom.p1, this->bestBottom.p2, Scalar(0, 0, 255), 1, CV_AA);
|
||||||
|
line(imgCorners, this->bestLeft.p1, this->bestLeft.p2, Scalar(255, 0, 0), 1, CV_AA);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
displayImage(config, "Winning top/bottom Boundaries", imgCorners);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a left/right edge has been established.
|
||||||
|
if (bestLeft.p1.x == 0 && bestLeft.p1.y == 0 && bestLeft.p2.x == 0 && bestLeft.p2.y == 0)
|
||||||
|
confidence = 0;
|
||||||
|
else if (bestTop.p1.x == 0 && bestTop.p1.y == 0 && bestTop.p2.x == 0 && bestTop.p2.y == 0)
|
||||||
|
confidence = 0;
|
||||||
|
else
|
||||||
|
confidence = 100;
|
||||||
|
|
||||||
|
vector<Point> corners;
|
||||||
|
corners.push_back(bestTop.intersection(bestLeft));
|
||||||
|
corners.push_back(bestTop.intersection(bestRight));
|
||||||
|
corners.push_back(bestBottom.intersection(bestRight));
|
||||||
|
corners.push_back(bestBottom.intersection(bestLeft));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "Plate Corners Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return corners;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PlateCorners::scoreVerticals(int v1, int v2)
|
||||||
|
{
|
||||||
|
|
||||||
|
float score = 0; // Lower is better
|
||||||
|
|
||||||
|
LineSegment left;
|
||||||
|
LineSegment right;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float charHeightToPlateWidthRatio = config->plateWidthMM / config->charHeightMM;
|
||||||
|
float idealPixelWidth = this->charHeight * (charHeightToPlateWidthRatio * 1.05); // Add 10% so we don't clip any characters
|
||||||
|
|
||||||
|
if (v1 == NO_LINE && v2 == NO_LINE)
|
||||||
|
{
|
||||||
|
//return;
|
||||||
|
Point centerTop = charRegion->getCharBoxTop().midpoint();
|
||||||
|
Point centerBottom = charRegion->getCharBoxBottom().midpoint();
|
||||||
|
LineSegment centerLine = LineSegment(centerBottom.x, centerBottom.y, centerTop.x, centerTop.y);
|
||||||
|
|
||||||
|
left = centerLine.getParallelLine(idealPixelWidth / 2);
|
||||||
|
right = centerLine.getParallelLine(-1 * idealPixelWidth / 2 );
|
||||||
|
|
||||||
|
score += SCORING_MISSING_SEGMENT_PENALTY_VERTICAL * 2;
|
||||||
|
}
|
||||||
|
else if (v1 != NO_LINE && v2 != NO_LINE)
|
||||||
|
{
|
||||||
|
left = this->plateLines->verticalLines[v1];
|
||||||
|
right = this->plateLines->verticalLines[v2];
|
||||||
|
}
|
||||||
|
else if (v1 == NO_LINE && v2 != NO_LINE)
|
||||||
|
{
|
||||||
|
right = this->plateLines->verticalLines[v2];
|
||||||
|
left = right.getParallelLine(idealPixelWidth);
|
||||||
|
score += SCORING_MISSING_SEGMENT_PENALTY_VERTICAL;
|
||||||
|
}
|
||||||
|
else if (v1 != NO_LINE && v2 == NO_LINE)
|
||||||
|
{
|
||||||
|
left = this->plateLines->verticalLines[v1];
|
||||||
|
right = left.getParallelLine(-1 * idealPixelWidth);
|
||||||
|
score += SCORING_MISSING_SEGMENT_PENALTY_VERTICAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Make sure this line is to the left of our license plate letters
|
||||||
|
if (left.isPointBelowLine(charRegion->getCharBoxLeft().midpoint()) == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Make sure this line is to the right of our license plate letters
|
||||||
|
if (right.isPointBelowLine(charRegion->getCharBoxRight().midpoint()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Score "Distance from the edge...
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
float leftDistanceFromEdge = abs((float) (left.p1.x + left.p2.x) / 2);
|
||||||
|
float rightDistanceFromEdge = abs(this->inputImage.cols - ((float) (right.p1.x + right.p2.x) / 2));
|
||||||
|
|
||||||
|
float distanceFromEdge = leftDistanceFromEdge + rightDistanceFromEdge;
|
||||||
|
score += distanceFromEdge * SCORING_VERTICALDISTANCE_FROMEDGE_WEIGHT;
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Score "Boxiness" of the 4 lines. How close is it to a parallelogram?
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
float verticalAngleDiff = abs(left.angle - right.angle);
|
||||||
|
|
||||||
|
score += (verticalAngleDiff) * SCORING_BOXINESS_WEIGHT;
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// SCORE the shape wrt character position and height relative to position
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
Point leftMidLinePoint = left.closestPointOnSegmentTo(charRegion->getCharBoxLeft().midpoint());
|
||||||
|
Point rightMidLinePoint = right.closestPointOnSegmentTo(charRegion->getCharBoxRight().midpoint());
|
||||||
|
|
||||||
|
float plateDistance = abs(idealPixelWidth - distanceBetweenPoints(leftMidLinePoint, rightMidLinePoint));
|
||||||
|
|
||||||
|
score += plateDistance * SCORING_VERTICALDISTANCE_WEIGHT;
|
||||||
|
|
||||||
|
if (score < this->bestVerticalScore)
|
||||||
|
{
|
||||||
|
float scorecomponent;
|
||||||
|
|
||||||
|
|
||||||
|
if (this->config->debugPlateCorners)
|
||||||
|
{
|
||||||
|
cout << "xx xx Score: charHeight " << this->charHeight << endl;
|
||||||
|
cout << "xx xx Score: idealwidth " << idealPixelWidth << endl;
|
||||||
|
cout << "xx xx Score: v1,v2= " << v1 << "," << v2 << endl;
|
||||||
|
cout << "xx xx Score: Left= " << left.str() << endl;
|
||||||
|
cout << "xx xx Score: Right= " << right.str() << endl;
|
||||||
|
|
||||||
|
cout << "Vertical breakdown Score:" << endl;
|
||||||
|
cout << " -- Boxiness Score: " << verticalAngleDiff << " -- Weight (" << SCORING_BOXINESS_WEIGHT << ")" << endl;
|
||||||
|
scorecomponent = verticalAngleDiff * SCORING_BOXINESS_WEIGHT;
|
||||||
|
cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl;
|
||||||
|
|
||||||
|
cout << " -- Distance From Edge Score: " << distanceFromEdge << " -- Weight (" << SCORING_VERTICALDISTANCE_FROMEDGE_WEIGHT << ")" << endl;
|
||||||
|
scorecomponent = distanceFromEdge * SCORING_VERTICALDISTANCE_FROMEDGE_WEIGHT;
|
||||||
|
cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl;
|
||||||
|
|
||||||
|
cout << " -- Distance Score: " << plateDistance << " -- Weight (" << SCORING_VERTICALDISTANCE_WEIGHT << ")" << endl;
|
||||||
|
scorecomponent = plateDistance * SCORING_VERTICALDISTANCE_WEIGHT;
|
||||||
|
cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl;
|
||||||
|
|
||||||
|
cout << " -- Score: " << score << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->bestVerticalScore = score;
|
||||||
|
bestLeft = LineSegment(left.p1.x, left.p1.y, left.p2.x, left.p2.y);
|
||||||
|
bestRight = LineSegment(right.p1.x, right.p1.y, right.p2.x, right.p2.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// Score a collection of lines as a possible license plate region.
|
||||||
|
// If any segments are missing, extrapolate the missing pieces
|
||||||
|
void PlateCorners::scoreHorizontals(int h1, int h2)
|
||||||
|
{
|
||||||
|
|
||||||
|
//if (this->debug)
|
||||||
|
// cout << "PlateCorners::scorePlate" << endl;
|
||||||
|
|
||||||
|
float score = 0; // Lower is better
|
||||||
|
|
||||||
|
LineSegment top;
|
||||||
|
LineSegment bottom;
|
||||||
|
|
||||||
|
float charHeightToPlateHeightRatio = config->plateHeightMM / config->charHeightMM;
|
||||||
|
float idealPixelHeight = this->charHeight * charHeightToPlateHeightRatio;
|
||||||
|
|
||||||
|
|
||||||
|
if (h1 == NO_LINE && h2 == NO_LINE)
|
||||||
|
{
|
||||||
|
// return;
|
||||||
|
Point centerLeft = charRegion->getCharBoxLeft().midpoint();
|
||||||
|
Point centerRight = charRegion->getCharBoxRight().midpoint();
|
||||||
|
LineSegment centerLine = LineSegment(centerLeft.x, centerLeft.y, centerRight.x, centerRight.y);
|
||||||
|
|
||||||
|
top = centerLine.getParallelLine(idealPixelHeight / 2);
|
||||||
|
bottom = centerLine.getParallelLine(-1 * idealPixelHeight / 2 );
|
||||||
|
|
||||||
|
score += SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL * 2;
|
||||||
|
}
|
||||||
|
else if (h1 != NO_LINE && h2 != NO_LINE)
|
||||||
|
{
|
||||||
|
top = this->plateLines->horizontalLines[h1];
|
||||||
|
bottom = this->plateLines->horizontalLines[h2];
|
||||||
|
}
|
||||||
|
else if (h1 == NO_LINE && h2 != NO_LINE)
|
||||||
|
{
|
||||||
|
bottom = this->plateLines->horizontalLines[h2];
|
||||||
|
top = bottom.getParallelLine(idealPixelHeight);
|
||||||
|
score += SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL;
|
||||||
|
}
|
||||||
|
else if (h1 != NO_LINE && h2 == NO_LINE)
|
||||||
|
{
|
||||||
|
top = this->plateLines->horizontalLines[h1];
|
||||||
|
bottom = top.getParallelLine(-1 * idealPixelHeight);
|
||||||
|
score += SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Make sure this line is above our license plate letters
|
||||||
|
if (top.isPointBelowLine(charRegion->getCharBoxTop().midpoint()) == false)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
// Make sure this line is below our license plate letters
|
||||||
|
if (bottom.isPointBelowLine(charRegion->getCharBoxBottom().midpoint()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// We now have 4 possible lines. Let's put them to the test and score them...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// Score "Boxiness" of the 4 lines. How close is it to a parallelogram?
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
float horizontalAngleDiff = abs(top.angle - bottom.angle);
|
||||||
|
|
||||||
|
|
||||||
|
score += (horizontalAngleDiff) * SCORING_BOXINESS_WEIGHT;
|
||||||
|
// if (this->debug)
|
||||||
|
// cout << "PlateCorners boxiness score: " << (horizontalAngleDiff + verticalAngleDiff) * SCORING_BOXINESS_WEIGHT << endl;
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// SCORE the shape wrt character position and height relative to position
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Point topPoint = top.midpoint();
|
||||||
|
Point botPoint = bottom.closestPointOnSegmentTo(topPoint);
|
||||||
|
float plateHeightPx = distanceBetweenPoints(topPoint, botPoint);
|
||||||
|
|
||||||
|
// Get the height difference
|
||||||
|
|
||||||
|
|
||||||
|
float heightRatio = charHeight / plateHeightPx;
|
||||||
|
float idealHeightRatio = (config->charHeightMM / config->plateHeightMM);
|
||||||
|
//if (leftRatio < MIN_CHAR_HEIGHT_RATIO || leftRatio > MAX_CHAR_HEIGHT_RATIO || rightRatio < MIN_CHAR_HEIGHT_RATIO || rightRatio > MAX_CHAR_HEIGHT_RATIO)
|
||||||
|
float heightRatioDiff = abs(heightRatio - idealHeightRatio);
|
||||||
|
// Ideal ratio == ~.45
|
||||||
|
|
||||||
|
// Get the distance from the top and the distance from the bottom
|
||||||
|
// Take the average distances from the corners of the character region to the top/bottom lines
|
||||||
|
// float topDistance = distanceBetweenPoints(topMidLinePoint, charRegion->getCharBoxTop().midpoint());
|
||||||
|
// float bottomDistance = distanceBetweenPoints(bottomMidLinePoint, charRegion->getCharBoxBottom().midpoint());
|
||||||
|
|
||||||
|
// float idealTopDistance = charHeight * (TOP_WHITESPACE_HEIGHT_MM / CHARACTER_HEIGHT_MM);
|
||||||
|
// float idealBottomDistance = charHeight * (BOTTOM_WHITESPACE_HEIGHT_MM / CHARACTER_HEIGHT_MM);
|
||||||
|
// float distScore = abs(topDistance - idealTopDistance) + abs(bottomDistance - idealBottomDistance);
|
||||||
|
|
||||||
|
|
||||||
|
score += heightRatioDiff * SCORING_PLATEHEIGHT_WEIGHT;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// SCORE the middliness of the stuff. We want our top and bottom line to have the characters right towards the middle
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Point charAreaMidPoint = charRegion->getCharBoxLeft().midpoint();
|
||||||
|
Point topLineSpot = top.closestPointOnSegmentTo(charAreaMidPoint);
|
||||||
|
Point botLineSpot = bottom.closestPointOnSegmentTo(charAreaMidPoint);
|
||||||
|
|
||||||
|
float topDistanceFromMiddle = distanceBetweenPoints(topLineSpot, charAreaMidPoint);
|
||||||
|
float bottomDistanceFromMiddle = distanceBetweenPoints(topLineSpot, charAreaMidPoint);
|
||||||
|
|
||||||
|
float idealDistanceFromMiddle = idealPixelHeight / 2;
|
||||||
|
|
||||||
|
float middleScore = abs(topDistanceFromMiddle - idealDistanceFromMiddle) + abs(bottomDistanceFromMiddle - idealDistanceFromMiddle);
|
||||||
|
|
||||||
|
score += middleScore * SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT;
|
||||||
|
|
||||||
|
// if (this->debug)
|
||||||
|
// {
|
||||||
|
// cout << "PlateCorners boxiness score: " << avgRatio * SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT << endl;
|
||||||
|
// cout << "PlateCorners boxiness score: " << distScore * SCORING_PLATEHEIGHT_WEIGHT << endl;
|
||||||
|
// }
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
// SCORE: the shape for angles matching the character region
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
float charanglediff = abs(charAngle - top.angle) + abs(charAngle - bottom.angle);
|
||||||
|
|
||||||
|
|
||||||
|
score += charanglediff * SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT;
|
||||||
|
|
||||||
|
// if (this->debug)
|
||||||
|
// cout << "PlateCorners boxiness score: " << charanglediff * SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT << endl;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (score < this->bestHorizontalScore)
|
||||||
|
{
|
||||||
|
float scorecomponent;
|
||||||
|
|
||||||
|
if (this->config->debugPlateCorners)
|
||||||
|
{
|
||||||
|
cout << "xx xx Score: charHeight " << this->charHeight << endl;
|
||||||
|
cout << "xx xx Score: idealHeight " << idealPixelHeight << endl;
|
||||||
|
cout << "xx xx Score: h1,h2= " << h1 << "," << h2 << endl;
|
||||||
|
cout << "xx xx Score: Top= " << top.str() << endl;
|
||||||
|
cout << "xx xx Score: Bottom= " << bottom.str() << endl;
|
||||||
|
|
||||||
|
cout << "Horizontal breakdown Score:" << endl;
|
||||||
|
cout << " -- Boxiness Score: " << horizontalAngleDiff << " -- Weight (" << SCORING_BOXINESS_WEIGHT << ")" << endl;
|
||||||
|
scorecomponent = horizontalAngleDiff * SCORING_BOXINESS_WEIGHT;
|
||||||
|
cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl;
|
||||||
|
|
||||||
|
cout << " -- Height Ratio Diff Score: " << heightRatioDiff << " -- Weight (" << SCORING_PLATEHEIGHT_WEIGHT << ")" << endl;
|
||||||
|
scorecomponent = heightRatioDiff * SCORING_PLATEHEIGHT_WEIGHT;
|
||||||
|
cout << " -- -- " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl;
|
||||||
|
|
||||||
|
cout << " -- Distance Score: " << middleScore << " -- Weight (" << SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT << ")" << endl;
|
||||||
|
scorecomponent = middleScore * SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT;
|
||||||
|
cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl;
|
||||||
|
|
||||||
|
cout << " -- Char angle Score: " << charanglediff << " -- Weight (" << SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT << ")" << endl;
|
||||||
|
scorecomponent = charanglediff * SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT;
|
||||||
|
cout << " -- -- Score: " << scorecomponent << " = " << scorecomponent / score * 100 << "% of score" << endl;
|
||||||
|
|
||||||
|
cout << " -- Score: " << score << endl;
|
||||||
|
}
|
||||||
|
this->bestHorizontalScore = score;
|
||||||
|
bestTop = LineSegment(top.p1.x, top.p1.y, top.p2.x, top.p2.y);
|
||||||
|
bestBottom = LineSegment(bottom.p1.x, bottom.p1.y, bottom.p2.x, bottom.p2.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
83
src/openalpr/platecorners.h
Normal file
83
src/openalpr/platecorners.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef PLATECORNERS_H
|
||||||
|
#define PLATECORNERS_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "characterregion.h"
|
||||||
|
#include "platelines.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#define NO_LINE -1
|
||||||
|
|
||||||
|
|
||||||
|
#define SCORING_MISSING_SEGMENT_PENALTY_VERTICAL 10
|
||||||
|
#define SCORING_MISSING_SEGMENT_PENALTY_HORIZONTAL 15
|
||||||
|
|
||||||
|
#define SCORING_BOXINESS_WEIGHT 0.8
|
||||||
|
#define SCORING_PLATEHEIGHT_WEIGHT 2.2
|
||||||
|
#define SCORING_TOP_BOTTOM_SPACE_VS_CHARHEIGHT_WEIGHT 0.05
|
||||||
|
#define SCORING_ANGLE_MATCHES_LPCHARS_WEIGHT 1.1
|
||||||
|
#define SCORING_VERTICALDISTANCE_WEIGHT 0.1
|
||||||
|
|
||||||
|
#define SCORING_VERTICALDISTANCE_FROMEDGE_WEIGHT 0.05
|
||||||
|
|
||||||
|
class PlateCorners
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlateCorners(Mat inputImage, PlateLines* plateLines, CharacterRegion* charRegion, Config* config);
|
||||||
|
virtual ~PlateCorners();
|
||||||
|
|
||||||
|
vector<Point> findPlateCorners();
|
||||||
|
|
||||||
|
float confidence;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Config* config;
|
||||||
|
Mat inputImage;
|
||||||
|
float charHeight;
|
||||||
|
float charAngle;
|
||||||
|
|
||||||
|
float bestHorizontalScore;
|
||||||
|
float bestVerticalScore;
|
||||||
|
LineSegment bestTop;
|
||||||
|
LineSegment bestBottom;
|
||||||
|
LineSegment bestLeft;
|
||||||
|
LineSegment bestRight;
|
||||||
|
|
||||||
|
PlateLines* plateLines;
|
||||||
|
CharacterRegion* charRegion;
|
||||||
|
|
||||||
|
void scoreHorizontals( int h1, int h2 );
|
||||||
|
void scoreVerticals( int v1, int v2 );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PLATELINES_H
|
||||||
|
|
||||||
|
|
366
src/openalpr/platelines.cpp
Normal file
366
src/openalpr/platelines.cpp
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "platelines.h"
|
||||||
|
|
||||||
|
|
||||||
|
PlateLines::PlateLines(Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
this->debug = config->debugPlateLines;
|
||||||
|
|
||||||
|
if (debug)
|
||||||
|
cout << "PlateLines constructor" << endl;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PlateLines::~PlateLines()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void PlateLines::processImage(Mat inputImage, float sensitivity)
|
||||||
|
{
|
||||||
|
if (this->debug)
|
||||||
|
cout << "PlateLines findLines" << endl;
|
||||||
|
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
|
||||||
|
Mat smoothed(inputImage.size(), inputImage.type());
|
||||||
|
inputImage.copyTo(smoothed);
|
||||||
|
int morph_elem = 2;
|
||||||
|
int morph_size = 2;
|
||||||
|
Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||||
|
|
||||||
|
morphologyEx( smoothed, smoothed, MORPH_CLOSE, element );
|
||||||
|
|
||||||
|
morph_size = 1;
|
||||||
|
element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||||
|
|
||||||
|
//morphologyEx( thresholded, thresholded, MORPH_GRADIENT, element );
|
||||||
|
|
||||||
|
morph_size = 1;
|
||||||
|
element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||||
|
morphologyEx( smoothed, smoothed, MORPH_OPEN, element );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mat edges(inputImage.size(), inputImage.type());
|
||||||
|
Canny(smoothed, edges, 66, 133);
|
||||||
|
|
||||||
|
|
||||||
|
vector<LineSegment> hlines = this->getLines(edges, sensitivity, false);
|
||||||
|
vector<LineSegment> vlines = this->getLines(edges, sensitivity, true);
|
||||||
|
for (int i = 0; i < hlines.size(); i++)
|
||||||
|
this->horizontalLines.push_back(hlines[i]);
|
||||||
|
for (int i = 0; i < vlines.size(); i++)
|
||||||
|
this->verticalLines.push_back(vlines[i]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// if debug is enabled, draw the image
|
||||||
|
if (this->debug)
|
||||||
|
{
|
||||||
|
Mat debugImgHoriz(edges.size(), edges.type());
|
||||||
|
Mat debugImgVert(edges.size(), edges.type());
|
||||||
|
edges.copyTo(debugImgHoriz);
|
||||||
|
edges.copyTo(debugImgVert);
|
||||||
|
cvtColor(debugImgHoriz,debugImgHoriz,CV_GRAY2BGR);
|
||||||
|
cvtColor(debugImgVert,debugImgVert,CV_GRAY2BGR);
|
||||||
|
|
||||||
|
for( size_t i = 0; i < this->horizontalLines.size(); i++ )
|
||||||
|
{
|
||||||
|
line( debugImgHoriz, this->horizontalLines[i].p1, this->horizontalLines[i].p2, Scalar(0,0,255), 1, CV_AA);
|
||||||
|
}
|
||||||
|
|
||||||
|
for( size_t i = 0; i < this->verticalLines.size(); i++ )
|
||||||
|
{
|
||||||
|
line( debugImgVert, this->verticalLines[i].p1, this->verticalLines[i].p2, Scalar(0,0,255), 1, CV_AA);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<Mat> images;
|
||||||
|
images.push_back(debugImgHoriz);
|
||||||
|
images.push_back(debugImgVert);
|
||||||
|
|
||||||
|
Mat dashboard = drawImageDashboard(images, debugImgVert.type(), 1);
|
||||||
|
displayImage(config, "Hough Lines", dashboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "Plate Lines Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
//smoothed.release();
|
||||||
|
|
||||||
|
|
||||||
|
//////////////// METHOD2!!!!!!!////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
Mat imgBlur;
|
||||||
|
Mat imgCanny;
|
||||||
|
GaussianBlur(inputImage, imgBlur, Size(9, 9), 1, 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Canny(imgBlur, imgCanny, 10, 30, 3);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//int morph_elem = 2;
|
||||||
|
//int morph_size = 1;
|
||||||
|
//Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
|
||||||
|
morphologyEx( imgCanny, imgCanny, MORPH_CLOSE, element );
|
||||||
|
|
||||||
|
|
||||||
|
Mat imgShaped;
|
||||||
|
imgCanny.copyTo(imgShaped);
|
||||||
|
//Find contours of possibles characters
|
||||||
|
vector< vector< Point> > biggestShapes;
|
||||||
|
findContours(imgShaped,
|
||||||
|
biggestShapes, // a vector of contours
|
||||||
|
CV_RETR_EXTERNAL, // retrieve the external contours
|
||||||
|
CV_CHAIN_APPROX_SIMPLE ); // all pixels of each contours
|
||||||
|
|
||||||
|
// Draw blue contours on a white image
|
||||||
|
//cvtColor(imgShaped, imgShaped, CV_GRAY2RGB);
|
||||||
|
cv::drawContours(imgShaped,biggestShapes,
|
||||||
|
-1, // draw all contours
|
||||||
|
cv::Scalar(255,255,255), // in blue
|
||||||
|
1); // with a thickness of 1
|
||||||
|
|
||||||
|
displayImage(config, "Blurred", imgCanny);
|
||||||
|
displayImage(config, "Blurred Contours", imgShaped);
|
||||||
|
|
||||||
|
vector<Rect> shapeRects( biggestShapes.size() );
|
||||||
|
|
||||||
|
vector<vector<Point> >hull( biggestShapes.size() );
|
||||||
|
for( int i = 0; i < biggestShapes.size(); i++ )
|
||||||
|
{
|
||||||
|
//approxPolyDP( Mat(biggestShapes[i]), shapeRects[i], 3, true );
|
||||||
|
convexHull( biggestShapes[i], hull[i], false );
|
||||||
|
//approxPolyDP( biggestShapes[i], hull[i], 10, true );
|
||||||
|
|
||||||
|
//minEnclosingCircle( (Mat)contours_poly[i], center[i], radius[i] );
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
vector<LineSegment> PlateLines::getLines(Mat edges, bool vertical)
|
||||||
|
{
|
||||||
|
|
||||||
|
vector<LineSegment> filteredLines;
|
||||||
|
|
||||||
|
int sensitivity;
|
||||||
|
|
||||||
|
LSWMS lswms(Size(edges.cols, edges.rows), 3, 155, false);
|
||||||
|
|
||||||
|
vector<LSEG> lsegs;
|
||||||
|
vector<double> errors;
|
||||||
|
lswms.run(edges, lsegs, errors);
|
||||||
|
|
||||||
|
|
||||||
|
for( size_t i = 0; i < lsegs.size(); i++ )
|
||||||
|
{
|
||||||
|
|
||||||
|
if (vertical)
|
||||||
|
{
|
||||||
|
LineSegment candidate;
|
||||||
|
if (lsegs[i][0].y <= lsegs[i][1].y)
|
||||||
|
candidate = LineSegment(lsegs[i][0].x, lsegs[i][0].y, lsegs[i][1].x, lsegs[i][1].y);
|
||||||
|
else
|
||||||
|
candidate = LineSegment(lsegs[i][1].x, lsegs[i][1].y, lsegs[i][0].x, lsegs[i][0].y);
|
||||||
|
|
||||||
|
cout << "VERT Angle: " << candidate.angle << endl;
|
||||||
|
//if ((candidate.angle > 70 && candidate.angle < 110) || (candidate.angle > 250 && candidate.angle < 290))
|
||||||
|
//{
|
||||||
|
// good vertical
|
||||||
|
filteredLines.push_back(candidate);
|
||||||
|
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LineSegment candidate;
|
||||||
|
if (lsegs[i][0].x <= lsegs[i][1].x)
|
||||||
|
candidate = LineSegment(lsegs[i][0].x, lsegs[i][0].y, lsegs[i][1].x, lsegs[i][1].y);
|
||||||
|
else
|
||||||
|
candidate = LineSegment(lsegs[i][1].x, lsegs[i][1].y, lsegs[i][0].x, lsegs[i][0].y);
|
||||||
|
cout << "HORIZAngle: " << candidate.angle << endl;
|
||||||
|
|
||||||
|
//if ( (candidate.angle > -20 && candidate.angle < 20) || (candidate.angle > 160 && candidate.angle < 200))
|
||||||
|
//{
|
||||||
|
// good horizontal
|
||||||
|
filteredLines.push_back(candidate);
|
||||||
|
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if debug is enabled, draw the image
|
||||||
|
if (this->debug)
|
||||||
|
{
|
||||||
|
Mat debugImg(edges.size(), edges.type());
|
||||||
|
edges.copyTo(debugImg);
|
||||||
|
cvtColor(debugImg,debugImg,CV_GRAY2BGR);
|
||||||
|
|
||||||
|
for( size_t i = 0; i < filteredLines.size(); i++ )
|
||||||
|
{
|
||||||
|
|
||||||
|
line( debugImg, filteredLines[i].p1, filteredLines[i].p2, Scalar(0,0,255), 1, CV_AA);
|
||||||
|
}
|
||||||
|
if (vertical)
|
||||||
|
displayImage(config, "Lines Vertical", debugImg);
|
||||||
|
else
|
||||||
|
displayImage(config, "Lines Horizontal", debugImg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredLines;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
vector<LineSegment> PlateLines::getLines(Mat edges, float sensitivityMultiplier, bool vertical)
|
||||||
|
{
|
||||||
|
if (this->debug)
|
||||||
|
cout << "PlateLines::getLines" << endl;
|
||||||
|
|
||||||
|
static int HORIZONTAL_SENSITIVITY = config->plateLinesSensitivityHorizontal;
|
||||||
|
static int VERTICAL_SENSITIVITY = config->plateLinesSensitivityVertical;
|
||||||
|
|
||||||
|
vector<Vec2f> allLines;
|
||||||
|
vector<LineSegment> filteredLines;
|
||||||
|
|
||||||
|
int sensitivity;
|
||||||
|
if (vertical)
|
||||||
|
sensitivity = VERTICAL_SENSITIVITY * (1.0 / sensitivityMultiplier);
|
||||||
|
else
|
||||||
|
sensitivity = HORIZONTAL_SENSITIVITY * (1.0 / sensitivityMultiplier);
|
||||||
|
|
||||||
|
HoughLines( edges, allLines, 1, CV_PI/180, sensitivity, 0, 0 );
|
||||||
|
|
||||||
|
|
||||||
|
for( size_t i = 0; i < allLines.size(); i++ )
|
||||||
|
{
|
||||||
|
float rho = allLines[i][0], theta = allLines[i][1];
|
||||||
|
Point pt1, pt2;
|
||||||
|
double a = cos(theta), b = sin(theta);
|
||||||
|
double x0 = a*rho, y0 = b*rho;
|
||||||
|
|
||||||
|
double angle = theta * (180 / CV_PI);
|
||||||
|
pt1.x = cvRound(x0 + 1000*(-b));
|
||||||
|
pt1.y = cvRound(y0 + 1000*(a));
|
||||||
|
pt2.x = cvRound(x0 - 1000*(-b));
|
||||||
|
pt2.y = cvRound(y0 - 1000*(a));
|
||||||
|
|
||||||
|
if (vertical)
|
||||||
|
{
|
||||||
|
if (angle < 20 || angle > 340 || (angle > 160 && angle < 210))
|
||||||
|
{
|
||||||
|
// good vertical
|
||||||
|
|
||||||
|
LineSegment line;
|
||||||
|
if (pt1.y <= pt2.y)
|
||||||
|
line = LineSegment(pt2.x, pt2.y, pt1.x, pt1.y);
|
||||||
|
else
|
||||||
|
line = LineSegment(pt1.x, pt1.y, pt2.x, pt2.y);
|
||||||
|
|
||||||
|
// Get rid of the -1000, 1000 stuff. Terminate at the edges of the image
|
||||||
|
// Helps with debugging/rounding issues later
|
||||||
|
LineSegment top(0, 0, edges.cols, 0);
|
||||||
|
LineSegment bottom(0, edges.rows, edges.cols, edges.rows);
|
||||||
|
Point p1 = line.intersection(bottom);
|
||||||
|
Point p2 = line.intersection(top);
|
||||||
|
filteredLines.push_back(LineSegment(p1.x, p1.y, p2.x, p2.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
if ( (angle > 70 && angle < 110) || (angle > 250 && angle < 290))
|
||||||
|
{
|
||||||
|
// good horizontal
|
||||||
|
|
||||||
|
LineSegment line;
|
||||||
|
if (pt1.x <= pt2.x)
|
||||||
|
line = LineSegment(pt1.x, pt1.y, pt2.x, pt2.y);
|
||||||
|
else
|
||||||
|
line =LineSegment(pt2.x, pt2.y, pt1.x, pt1.y);
|
||||||
|
|
||||||
|
// Get rid of the -1000, 1000 stuff. Terminate at the edges of the image
|
||||||
|
// Helps with debugging/ rounding issues later
|
||||||
|
int newY1 = line.getPointAt(0);
|
||||||
|
int newY2 = line.getPointAt(edges.cols);
|
||||||
|
|
||||||
|
filteredLines.push_back(LineSegment(0, newY1, edges.cols, newY2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return filteredLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mat PlateLines::customGrayscaleConversion(Mat src)
|
||||||
|
{
|
||||||
|
Mat img_hsv;
|
||||||
|
cvtColor(src,img_hsv,CV_BGR2HSV);
|
||||||
|
|
||||||
|
|
||||||
|
Mat grayscale = Mat(img_hsv.size(), CV_8U );
|
||||||
|
Mat hue(img_hsv.size(), CV_8U );
|
||||||
|
|
||||||
|
for (int row = 0; row < img_hsv.rows; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < img_hsv.cols; col++)
|
||||||
|
{
|
||||||
|
int h = (int) img_hsv.at<Vec3b>(row, col)[0];
|
||||||
|
int s = (int) img_hsv.at<Vec3b>(row, col)[1];
|
||||||
|
int v = (int) img_hsv.at<Vec3b>(row, col)[2];
|
||||||
|
|
||||||
|
int pixval = pow(v, 1.05);
|
||||||
|
|
||||||
|
|
||||||
|
if (pixval > 255)
|
||||||
|
pixval = 255;
|
||||||
|
grayscale.at<uchar>(row, col) = pixval;
|
||||||
|
|
||||||
|
hue.at<uchar>(row, col) = h * (255.0 / 180.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//displayImage(config, "Hue", hue);
|
||||||
|
return grayscale;
|
||||||
|
}
|
62
src/openalpr/platelines.h
Normal file
62
src/openalpr/platelines.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef PLATELINES_H
|
||||||
|
#define PLATELINES_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "binarize_wolf.h"
|
||||||
|
//#include "lswms.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
class PlateLines
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlateLines(Config* config);
|
||||||
|
virtual ~PlateLines();
|
||||||
|
|
||||||
|
void processImage(Mat img, float sensitivity=1.0);
|
||||||
|
|
||||||
|
vector<LineSegment> horizontalLines;
|
||||||
|
vector<LineSegment> verticalLines;
|
||||||
|
|
||||||
|
vector<Point> winningCorners;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config;
|
||||||
|
bool debug;
|
||||||
|
|
||||||
|
|
||||||
|
Mat customGrayscaleConversion(Mat src);
|
||||||
|
void findLines(Mat inputImage);
|
||||||
|
vector<LineSegment> getLines(Mat edges, float sensitivityMultiplier, bool vertical);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PLATELINES_H
|
||||||
|
|
||||||
|
|
478
src/openalpr/postprocess.cpp
Normal file
478
src/openalpr/postprocess.cpp
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postprocess.h"
|
||||||
|
|
||||||
|
|
||||||
|
PostProcess::PostProcess(Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
|
||||||
|
stringstream filename;
|
||||||
|
filename << config->getPostProcessRuntimeDir() << "/" << config->country << ".patterns";
|
||||||
|
|
||||||
|
std::ifstream infile(filename.str().c_str());
|
||||||
|
|
||||||
|
|
||||||
|
string region, pattern;
|
||||||
|
while (infile >> region >> pattern)
|
||||||
|
{
|
||||||
|
RegexRule* rule = new RegexRule(region, pattern);
|
||||||
|
//cout << "REGION: " << region << " PATTERN: " << pattern << endl;
|
||||||
|
|
||||||
|
if (rules.find(region) == rules.end())
|
||||||
|
{
|
||||||
|
vector<RegexRule*> newRule;
|
||||||
|
newRule.push_back(rule);
|
||||||
|
rules[region] = newRule;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vector<RegexRule*> oldRule = rules[region];
|
||||||
|
oldRule.push_back(rule);
|
||||||
|
rules[region] = oldRule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//vector<RegexRule> test = rules["base"];
|
||||||
|
//for (int i = 0; i < test.size(); i++)
|
||||||
|
// cout << "Rule: " << test[i].regex << endl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PostProcess::~PostProcess()
|
||||||
|
{
|
||||||
|
// TODO: Delete all entries in rules vector
|
||||||
|
map<string, vector<RegexRule*> >::iterator iter;
|
||||||
|
|
||||||
|
for (iter = rules.begin(); iter != rules.end(); ++iter) {
|
||||||
|
for (int i = 0; i < iter->second.size(); i++)
|
||||||
|
{
|
||||||
|
delete iter->second[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PostProcess::addLetter(char letter, int charposition, float score)
|
||||||
|
{
|
||||||
|
if (score < config->postProcessMinConfidence)
|
||||||
|
return;
|
||||||
|
|
||||||
|
insertLetter(letter, charposition, score);
|
||||||
|
|
||||||
|
if (score < config->postProcessConfidenceSkipLevel)
|
||||||
|
{
|
||||||
|
float adjustedScore = abs(config->postProcessConfidenceSkipLevel - score) + config->postProcessMinConfidence;
|
||||||
|
insertLetter(SKIP_CHAR, charposition, adjustedScore );
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (letter == '0')
|
||||||
|
//{
|
||||||
|
// insertLetter('O', charposition, score - 0.5);
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcess::insertLetter(char letter, int charposition, float score)
|
||||||
|
{
|
||||||
|
|
||||||
|
score = score - config->postProcessMinConfidence;
|
||||||
|
|
||||||
|
|
||||||
|
int existingIndex = -1;
|
||||||
|
if (letters.size() < charposition + 1)
|
||||||
|
{
|
||||||
|
for (int i = letters.size(); i < charposition + 1; i++)
|
||||||
|
{
|
||||||
|
vector<Letter> tmp;
|
||||||
|
letters.push_back(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < letters[charposition].size(); i++)
|
||||||
|
{
|
||||||
|
if (letters[charposition][i].letter == letter &&
|
||||||
|
letters[charposition][i].charposition == charposition)
|
||||||
|
{
|
||||||
|
existingIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingIndex == -1)
|
||||||
|
{
|
||||||
|
Letter newLetter;
|
||||||
|
newLetter.charposition = charposition;
|
||||||
|
newLetter.letter = letter;
|
||||||
|
newLetter.occurences = 1;
|
||||||
|
newLetter.totalscore = score;
|
||||||
|
letters[charposition].push_back(newLetter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
letters[charposition][existingIndex].occurences = letters[charposition][existingIndex].occurences + 1;
|
||||||
|
letters[charposition][existingIndex].totalscore = letters[charposition][existingIndex].totalscore + score;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PostProcess::clear()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < letters.size(); i++)
|
||||||
|
{
|
||||||
|
letters[i].clear();
|
||||||
|
}
|
||||||
|
letters.resize(0);
|
||||||
|
|
||||||
|
unknownCharPositions.clear();
|
||||||
|
unknownCharPositions.resize(0);
|
||||||
|
allPossibilities.clear();
|
||||||
|
//allPossibilities.resize(0);
|
||||||
|
|
||||||
|
bestChars = "";
|
||||||
|
matchesTemplate = false;
|
||||||
|
}
|
||||||
|
void PostProcess::analyze(string templateregion, int topn)
|
||||||
|
{
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Get a list of missing positions
|
||||||
|
for (int i = letters.size() -1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (letters[i].size() == 0)
|
||||||
|
{
|
||||||
|
unknownCharPositions.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (letters.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
// Sort the letters as they are
|
||||||
|
for (int i = 0; i < letters.size(); i++)
|
||||||
|
{
|
||||||
|
if (letters[i].size() > 0)
|
||||||
|
sort(letters[i].begin(), letters[i].end(), letterCompare);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//getTopN();
|
||||||
|
vector<Letter> tmp;
|
||||||
|
findAllPermutations(tmp, 0, config->postProcessMaxSubstitutions);
|
||||||
|
|
||||||
|
|
||||||
|
timespec sortStartTime;
|
||||||
|
getTime(&sortStartTime);
|
||||||
|
|
||||||
|
int numelements = topn;
|
||||||
|
if (allPossibilities.size() < topn)
|
||||||
|
numelements = allPossibilities.size() - 1;
|
||||||
|
|
||||||
|
partial_sort( allPossibilities.begin(), allPossibilities.begin() + numelements, allPossibilities.end() - 1, wordCompare );
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec sortEndTime;
|
||||||
|
getTime(&sortEndTime);
|
||||||
|
cout << " -- PostProcess Sort Time: " << diffclock(sortStartTime, sortEndTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
matchesTemplate = false;
|
||||||
|
|
||||||
|
|
||||||
|
if (templateregion != "")
|
||||||
|
{
|
||||||
|
vector<RegexRule*> regionRules = rules[templateregion];
|
||||||
|
|
||||||
|
for (int i = 0; i < allPossibilities.size(); i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < regionRules.size(); j++)
|
||||||
|
{
|
||||||
|
allPossibilities[i].matchesTemplate = regionRules[j]->match(allPossibilities[i].letters);
|
||||||
|
if (allPossibilities[i].matchesTemplate)
|
||||||
|
{
|
||||||
|
allPossibilities[i].letters = regionRules[j]->filterSkips(allPossibilities[i].letters);
|
||||||
|
//bestChars = regionRules[j]->filterSkips(allPossibilities[i].letters);
|
||||||
|
matchesTemplate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (i >= topn - 1)
|
||||||
|
break;
|
||||||
|
//if (matchesTemplate || i >= TOP_N - 1)
|
||||||
|
//break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchesTemplate)
|
||||||
|
{
|
||||||
|
for (int z = 0; z < allPossibilities.size(); z++)
|
||||||
|
{
|
||||||
|
if (allPossibilities[z].matchesTemplate)
|
||||||
|
{
|
||||||
|
bestChars = allPossibilities[z].letters;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bestChars = allPossibilities[0].letters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now adjust the confidence scores to a percentage value
|
||||||
|
if (allPossibilities.size() > 0)
|
||||||
|
{
|
||||||
|
float maxPercentScore = calculateMaxConfidenceScore();
|
||||||
|
float highestRelativeScore = (float) allPossibilities[0].totalscore;
|
||||||
|
|
||||||
|
for (int i = 0; i < allPossibilities.size(); i++)
|
||||||
|
{
|
||||||
|
allPossibilities[i].totalscore = maxPercentScore * (allPossibilities[i].totalscore / highestRelativeScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this->config->debugPostProcess)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Print all letters
|
||||||
|
for (int i = 0; i < letters.size(); i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < letters[i].size(); j++)
|
||||||
|
cout << "PostProcess Letter: " << letters[i][j].charposition << " " << letters[i][j].letter << " -- score: " << letters[i][j].totalscore << " -- occurences: " << letters[i][j].occurences << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print top words
|
||||||
|
for (int i = 0; i < allPossibilities.size(); i++)
|
||||||
|
{
|
||||||
|
cout << "Top " << topn << " Possibilities: " << allPossibilities[i].letters << " :\t" << allPossibilities[i].totalscore;
|
||||||
|
if (allPossibilities[i].letters == bestChars)
|
||||||
|
cout << " <--- ";
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
if (i >= topn - 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cout << allPossibilities.size() << " total permutations" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "PostProcess Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->config->debugPostProcess)
|
||||||
|
cout << "PostProcess Analysis Complete: " << bestChars << " -- MATCH: " << matchesTemplate << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
float PostProcess::calculateMaxConfidenceScore()
|
||||||
|
{
|
||||||
|
// Take the best score for each char position and average it.
|
||||||
|
|
||||||
|
float totalScore = 0;
|
||||||
|
int numScores = 0;
|
||||||
|
// Get a list of missing positions
|
||||||
|
for (int i = 0; i < letters.size(); i++)
|
||||||
|
{
|
||||||
|
if (letters[i].size() > 0)
|
||||||
|
{
|
||||||
|
totalScore += (letters[i][0].totalscore / letters[i][0].occurences) + config->postProcessMinConfidence;
|
||||||
|
numScores++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numScores == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return totalScore / ((float) numScores);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const vector<PPResult> PostProcess::getResults()
|
||||||
|
{
|
||||||
|
return this->allPossibilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcess::findAllPermutations(vector<Letter> prevletters, int charPos, int substitutionsLeft)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (substitutionsLeft < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add my letter to the chain and recurse
|
||||||
|
for (int i = 0; i < letters[charPos].size(); i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (charPos == letters.size() - 1)
|
||||||
|
{
|
||||||
|
// Last letter, add the word
|
||||||
|
PPResult possibility;
|
||||||
|
possibility.letters = "";
|
||||||
|
possibility.totalscore = 0;
|
||||||
|
possibility.matchesTemplate = false;
|
||||||
|
for (int z = 0; z < prevletters.size(); z++)
|
||||||
|
{
|
||||||
|
if (prevletters[z].letter != SKIP_CHAR)
|
||||||
|
possibility.letters = possibility.letters + prevletters[z].letter;
|
||||||
|
possibility.totalscore = possibility.totalscore + prevletters[z].totalscore;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (letters[charPos][i].letter != SKIP_CHAR)
|
||||||
|
possibility.letters = possibility.letters + letters[charPos][i].letter;
|
||||||
|
possibility.totalscore = possibility.totalscore +letters[charPos][i].totalscore;
|
||||||
|
|
||||||
|
allPossibilities.push_back(possibility);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prevletters.push_back(letters[charPos][i]);
|
||||||
|
|
||||||
|
float scorePercentDiff = abs( letters[charPos][0].totalscore - letters[charPos][i].totalscore ) / letters[charPos][0].totalscore;
|
||||||
|
if (i != 0 && letters[charPos][i].letter != SKIP_CHAR && scorePercentDiff > 0.10f )
|
||||||
|
findAllPermutations(prevletters, charPos + 1, substitutionsLeft - 1);
|
||||||
|
else
|
||||||
|
findAllPermutations(prevletters, charPos + 1, substitutionsLeft);
|
||||||
|
|
||||||
|
prevletters.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (letters[charPos].size() == 0)
|
||||||
|
{
|
||||||
|
// No letters for this char position...
|
||||||
|
// Just pass it along
|
||||||
|
findAllPermutations(prevletters, charPos + 1, substitutionsLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool wordCompare( const PPResult &left, const PPResult &right ){
|
||||||
|
if (left.totalscore < right.totalscore)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool letterCompare( const Letter &left, const Letter &right )
|
||||||
|
{
|
||||||
|
if (left.totalscore < right.totalscore)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RegexRule::RegexRule(string region, string pattern)
|
||||||
|
{
|
||||||
|
this->original = pattern;
|
||||||
|
this->region = region;
|
||||||
|
|
||||||
|
numchars = 0;
|
||||||
|
for (int i = 0; i < pattern.size(); i++)
|
||||||
|
{
|
||||||
|
if (pattern.at(i) == '[')
|
||||||
|
{
|
||||||
|
while (pattern.at(i) != ']' )
|
||||||
|
{
|
||||||
|
this->regex = this->regex + pattern.at(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
this->regex = this->regex + ']';
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (pattern.at(i) == '?')
|
||||||
|
{
|
||||||
|
this->regex = this->regex + '.';
|
||||||
|
this->skipPositions.push_back(numchars);
|
||||||
|
}
|
||||||
|
else if (pattern.at(i) == '@')
|
||||||
|
{
|
||||||
|
this->regex = this->regex + "\\a";
|
||||||
|
}
|
||||||
|
else if (pattern.at(i) == '#')
|
||||||
|
{
|
||||||
|
this->regex = this->regex + "\\d";
|
||||||
|
}
|
||||||
|
|
||||||
|
numchars++;
|
||||||
|
}
|
||||||
|
|
||||||
|
trexp.Compile(this->regex.c_str());
|
||||||
|
|
||||||
|
//cout << "AA " << this->region << ": " << original << " regex: " << regex << endl;
|
||||||
|
//for (int z = 0; z < this->skipPositions.size(); z++)
|
||||||
|
// cout << "AA Skip position: " << skipPositions[z] << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool RegexRule::match(string text)
|
||||||
|
{
|
||||||
|
if (text.length() != numchars)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return trexp.Match(text.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string RegexRule::filterSkips(string text)
|
||||||
|
{
|
||||||
|
string response = "";
|
||||||
|
for (int i = 0; i < text.size(); i++)
|
||||||
|
{
|
||||||
|
bool skip = false;
|
||||||
|
for (int j = 0; j < skipPositions.size(); j++)
|
||||||
|
{
|
||||||
|
if (skipPositions[j] == i)
|
||||||
|
{
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip == false)
|
||||||
|
response = response + text[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
130
src/openalpr/postprocess.h
Normal file
130
src/openalpr/postprocess.h
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef POSTPROCESS_H
|
||||||
|
#define POSTPROCESS_H
|
||||||
|
|
||||||
|
#include "TRexpp.h"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
#define SKIP_CHAR '~'
|
||||||
|
|
||||||
|
struct Letter
|
||||||
|
{
|
||||||
|
char letter;
|
||||||
|
int charposition;
|
||||||
|
float totalscore;
|
||||||
|
int occurences;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PPResult
|
||||||
|
{
|
||||||
|
string letters;
|
||||||
|
float totalscore;
|
||||||
|
bool matchesTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool wordCompare( const PPResult &left, const PPResult &right );
|
||||||
|
bool letterCompare( const Letter &left, const Letter &right );
|
||||||
|
|
||||||
|
|
||||||
|
class RegexRule
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RegexRule(string region, string pattern);
|
||||||
|
|
||||||
|
|
||||||
|
bool match(string text);
|
||||||
|
string filterSkips(string text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int numchars;
|
||||||
|
TRexpp trexp;
|
||||||
|
string original;
|
||||||
|
string regex;
|
||||||
|
string region;
|
||||||
|
vector<int> skipPositions;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class PostProcess
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PostProcess(Config* config);
|
||||||
|
~PostProcess();
|
||||||
|
|
||||||
|
void addLetter(char letter, int charposition, float score);
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void analyze(string templateregion, int topn);
|
||||||
|
|
||||||
|
string bestChars;
|
||||||
|
bool matchesTemplate;
|
||||||
|
|
||||||
|
const vector<PPResult> getResults();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config;
|
||||||
|
//void getTopN();
|
||||||
|
void findAllPermutations(vector<Letter> prevletters, int charPos, int substitutionsLeft);
|
||||||
|
|
||||||
|
void insertLetter(char letter, int charPosition, float score);
|
||||||
|
|
||||||
|
map<string, vector<RegexRule*> > rules;
|
||||||
|
|
||||||
|
float calculateMaxConfidenceScore();
|
||||||
|
|
||||||
|
vector<vector<Letter> > letters;
|
||||||
|
vector<int> unknownCharPositions;
|
||||||
|
|
||||||
|
|
||||||
|
vector<PPResult> allPossibilities;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
class LetterScores
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LetterScores(int numCharPositions);
|
||||||
|
|
||||||
|
void addScore(char letter, int charposition, float score);
|
||||||
|
|
||||||
|
vector<char> getBestScore();
|
||||||
|
float getConfidence();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int numCharPositions;
|
||||||
|
|
||||||
|
vector<char> letters;
|
||||||
|
vector<int> charpositions;
|
||||||
|
vector<float> scores;
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
#endif // POSTPROCESS_H
|
115
src/openalpr/regiondetector.cpp
Normal file
115
src/openalpr/regiondetector.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "regiondetector.h"
|
||||||
|
|
||||||
|
|
||||||
|
RegionDetector::RegionDetector(Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
// Don't scale. Can change this in the future (i.e., maximum resolution preference, or some such).
|
||||||
|
this->scale_factor = 1.0f;
|
||||||
|
|
||||||
|
|
||||||
|
// Load the cascade classifier
|
||||||
|
if( this->plate_cascade.load( config->getCascadeRuntimeDir() + config->country + ".xml" ) )
|
||||||
|
{
|
||||||
|
this->loaded = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("--(!)Error loading classifier\n");
|
||||||
|
this->loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionDetector::~RegionDetector()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool RegionDetector::isLoaded()
|
||||||
|
{
|
||||||
|
return this->loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vector<Rect> RegionDetector::detect(Mat frame)
|
||||||
|
{
|
||||||
|
|
||||||
|
Mat frame_gray;
|
||||||
|
cvtColor( frame, frame_gray, CV_BGR2GRAY );
|
||||||
|
|
||||||
|
vector<Rect> regionsOfInterest = doCascade(frame_gray);
|
||||||
|
|
||||||
|
return regionsOfInterest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @function detectAndDisplay */
|
||||||
|
vector<Rect> RegionDetector::doCascade(Mat frame)
|
||||||
|
{
|
||||||
|
//float scale_factor = 1;
|
||||||
|
int w = frame.size().width;
|
||||||
|
int h = frame.size().height;
|
||||||
|
|
||||||
|
vector<Rect> plates;
|
||||||
|
|
||||||
|
equalizeHist( frame, frame );
|
||||||
|
resize(frame, frame, Size(w * this->scale_factor, h * this->scale_factor));
|
||||||
|
|
||||||
|
//-- Detect plates
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
Size minSize(config->minPlateSizeWidthPx * this->scale_factor, config->minPlateSizeHeightPx * this->scale_factor);
|
||||||
|
Size maxSize(w * config->maxPlateWidthPercent * this->scale_factor, h * config->maxPlateHeightPercent * this->scale_factor);
|
||||||
|
|
||||||
|
|
||||||
|
plate_cascade.detectMultiScale( frame, plates, 1.1, 3,
|
||||||
|
0,
|
||||||
|
//0|CV_HAAR_SCALE_IMAGE,
|
||||||
|
minSize, maxSize );
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "LBP Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for( int i = 0; i < plates.size(); i++ )
|
||||||
|
{
|
||||||
|
plates[i].x = plates[i].x / scale_factor;
|
||||||
|
plates[i].y = plates[i].y / scale_factor;
|
||||||
|
plates[i].width = plates[i].width / scale_factor;
|
||||||
|
plates[i].height = plates[i].height / scale_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return plates;
|
||||||
|
|
||||||
|
}
|
68
src/openalpr/regiondetector.h
Normal file
68
src/openalpr/regiondetector.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef REGIONDETECTOR_H
|
||||||
|
#define REGIONDETECTOR_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "opencv2/objdetect/objdetect.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "opencv2/core/core.hpp"
|
||||||
|
#include "opencv2/ml/ml.hpp"
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
|
||||||
|
#include "utility.h"
|
||||||
|
#include "support/timing.h"
|
||||||
|
#include "constants.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class RegionDetector
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
RegionDetector(Config* config);
|
||||||
|
virtual ~RegionDetector();
|
||||||
|
|
||||||
|
bool isLoaded();
|
||||||
|
vector<Rect> detect(Mat frame);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
float scale_factor;
|
||||||
|
CascadeClassifier plate_cascade;
|
||||||
|
CvSVM* svmClassifier;
|
||||||
|
bool loaded;
|
||||||
|
|
||||||
|
vector<Rect> doCascade(Mat frame);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // REGIONDETECTOR_H
|
98
src/openalpr/stateidentifier.cpp
Normal file
98
src/openalpr/stateidentifier.cpp
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "stateidentifier.h"
|
||||||
|
|
||||||
|
|
||||||
|
StateIdentifier::StateIdentifier(Config* config)
|
||||||
|
{
|
||||||
|
this->config = config;
|
||||||
|
|
||||||
|
featureMatcher = new FeatureMatcher(config);
|
||||||
|
|
||||||
|
if (featureMatcher->isLoaded() == false)
|
||||||
|
{
|
||||||
|
cout << "Can not create detector or descriptor extractor or descriptor matcher of given types" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
featureMatcher->loadRecognitionSet(config->country);
|
||||||
|
}
|
||||||
|
|
||||||
|
StateIdentifier::~StateIdentifier()
|
||||||
|
{
|
||||||
|
delete featureMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StateIdentifier::recognize(Mat img, Rect frame, char* stateCode)
|
||||||
|
{
|
||||||
|
Mat croppedImage = Mat(img, frame);
|
||||||
|
|
||||||
|
return this->recognize(croppedImage, stateCode);
|
||||||
|
}
|
||||||
|
// Attempts to recognize the plate. Returns a confidence level. Updates teh "stateCode" variable
|
||||||
|
// with the value of the country/state
|
||||||
|
int StateIdentifier::recognize(Mat img, char* stateCode)
|
||||||
|
{
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
cvtColor(img, img, CV_BGR2GRAY);
|
||||||
|
|
||||||
|
resize(img, img, getSizeMaintainingAspect(img, config->stateIdImageWidthPx, config->stateIdimageHeightPx));
|
||||||
|
|
||||||
|
Mat plateImg(img.size(), img.type());
|
||||||
|
//plateImg = equalizeBrightness(img);
|
||||||
|
img.copyTo(plateImg);
|
||||||
|
|
||||||
|
Mat debugImg(plateImg.size(), plateImg.type());
|
||||||
|
plateImg.copyTo(debugImg);
|
||||||
|
vector<int> matchesArray(featureMatcher->numTrainingElements());
|
||||||
|
|
||||||
|
|
||||||
|
RecognitionResult result = featureMatcher->recognize(plateImg, true, &debugImg, true, matchesArray );
|
||||||
|
|
||||||
|
if (this->config->debugStateId)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
displayImage(config, "State Identifier1", plateImg);
|
||||||
|
displayImage(config, "State Identifier", debugImg);
|
||||||
|
cout << result.haswinner << " : " << result.confidence << " : " << result.winner << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << "State Identification Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (result.haswinner == false)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
strcpy(stateCode, result.winner.c_str());
|
||||||
|
|
||||||
|
|
||||||
|
return result.confidence;
|
||||||
|
}
|
||||||
|
|
59
src/openalpr/stateidentifier.h
Normal file
59
src/openalpr/stateidentifier.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef STATEIDENTIFIER_H
|
||||||
|
#define STATEIDENTIFIER_H
|
||||||
|
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "featurematcher.h"
|
||||||
|
#include "utility.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class StateIdentifier
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
StateIdentifier(Config* config);
|
||||||
|
virtual ~StateIdentifier();
|
||||||
|
|
||||||
|
int recognize(Mat img, Rect frame, char* stateCode);
|
||||||
|
int recognize(Mat img, char* stateCode);
|
||||||
|
|
||||||
|
//int confidence;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Config* config;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
|
||||||
|
FeatureMatcher* featureMatcher;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STATEIDENTIFIER_H
|
||||||
|
|
||||||
|
|
643
src/openalpr/trex.c
Normal file
643
src/openalpr/trex.c
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
/* see copyright notice in trex.h */
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include "trex.h"
|
||||||
|
|
||||||
|
#ifdef _UINCODE
|
||||||
|
#define scisprint iswprint
|
||||||
|
#define scstrlen wcslen
|
||||||
|
#define scprintf wprintf
|
||||||
|
#define _SC(x) L(x)
|
||||||
|
#else
|
||||||
|
#define scisprint isprint
|
||||||
|
#define scstrlen strlen
|
||||||
|
#define scprintf printf
|
||||||
|
#define _SC(x) (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const TRexChar *g_nnames[] =
|
||||||
|
{
|
||||||
|
_SC("NONE"),_SC("OP_GREEDY"), _SC("OP_OR"),
|
||||||
|
_SC("OP_EXPR"),_SC("OP_NOCAPEXPR"),_SC("OP_DOT"), _SC("OP_CLASS"),
|
||||||
|
_SC("OP_CCLASS"),_SC("OP_NCLASS"),_SC("OP_RANGE"),_SC("OP_CHAR"),
|
||||||
|
_SC("OP_EOL"),_SC("OP_BOL"),_SC("OP_WB")
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#define OP_GREEDY (MAX_CHAR+1) // * + ? {n}
|
||||||
|
#define OP_OR (MAX_CHAR+2)
|
||||||
|
#define OP_EXPR (MAX_CHAR+3) //parentesis ()
|
||||||
|
#define OP_NOCAPEXPR (MAX_CHAR+4) //parentesis (?:)
|
||||||
|
#define OP_DOT (MAX_CHAR+5)
|
||||||
|
#define OP_CLASS (MAX_CHAR+6)
|
||||||
|
#define OP_CCLASS (MAX_CHAR+7)
|
||||||
|
#define OP_NCLASS (MAX_CHAR+8) //negates class the [^
|
||||||
|
#define OP_RANGE (MAX_CHAR+9)
|
||||||
|
#define OP_CHAR (MAX_CHAR+10)
|
||||||
|
#define OP_EOL (MAX_CHAR+11)
|
||||||
|
#define OP_BOL (MAX_CHAR+12)
|
||||||
|
#define OP_WB (MAX_CHAR+13)
|
||||||
|
|
||||||
|
#define TREX_SYMBOL_ANY_CHAR ('.')
|
||||||
|
#define TREX_SYMBOL_GREEDY_ONE_OR_MORE ('+')
|
||||||
|
#define TREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*')
|
||||||
|
#define TREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?')
|
||||||
|
#define TREX_SYMBOL_BRANCH ('|')
|
||||||
|
#define TREX_SYMBOL_END_OF_STRING ('$')
|
||||||
|
#define TREX_SYMBOL_BEGINNING_OF_STRING ('^')
|
||||||
|
#define TREX_SYMBOL_ESCAPE_CHAR ('\\')
|
||||||
|
|
||||||
|
|
||||||
|
typedef int TRexNodeType;
|
||||||
|
|
||||||
|
typedef struct tagTRexNode{
|
||||||
|
TRexNodeType type;
|
||||||
|
int left;
|
||||||
|
int right;
|
||||||
|
int next;
|
||||||
|
}TRexNode;
|
||||||
|
|
||||||
|
struct TRex{
|
||||||
|
const TRexChar *_eol;
|
||||||
|
const TRexChar *_bol;
|
||||||
|
const TRexChar *_p;
|
||||||
|
int _first;
|
||||||
|
int _op;
|
||||||
|
TRexNode *_nodes;
|
||||||
|
int _nallocated;
|
||||||
|
int _nsize;
|
||||||
|
int _nsubexpr;
|
||||||
|
TRexMatch *_matches;
|
||||||
|
int _currsubexp;
|
||||||
|
void *_jmpbuf;
|
||||||
|
const TRexChar **_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int trex_list(TRex *exp);
|
||||||
|
|
||||||
|
static int trex_newnode(TRex *exp, TRexNodeType type)
|
||||||
|
{
|
||||||
|
TRexNode n;
|
||||||
|
int newid;
|
||||||
|
n.type = type;
|
||||||
|
n.next = n.right = n.left = -1;
|
||||||
|
if(type == OP_EXPR)
|
||||||
|
n.right = exp->_nsubexpr++;
|
||||||
|
if(exp->_nallocated < (exp->_nsize + 1)) {
|
||||||
|
int oldsize = exp->_nallocated;
|
||||||
|
exp->_nallocated *= 2;
|
||||||
|
exp->_nodes = (TRexNode *)realloc(exp->_nodes, exp->_nallocated * sizeof(TRexNode));
|
||||||
|
}
|
||||||
|
exp->_nodes[exp->_nsize++] = n;
|
||||||
|
newid = exp->_nsize - 1;
|
||||||
|
return (int)newid;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trex_error(TRex *exp,const TRexChar *error)
|
||||||
|
{
|
||||||
|
if(exp->_error) *exp->_error = error;
|
||||||
|
longjmp(*((jmp_buf*)exp->_jmpbuf),-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trex_expect(TRex *exp, int n){
|
||||||
|
if((*exp->_p) != n)
|
||||||
|
trex_error(exp, _SC("expected paren"));
|
||||||
|
exp->_p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TRexChar trex_escapechar(TRex *exp)
|
||||||
|
{
|
||||||
|
if(*exp->_p == TREX_SYMBOL_ESCAPE_CHAR){
|
||||||
|
exp->_p++;
|
||||||
|
switch(*exp->_p) {
|
||||||
|
case 'v': exp->_p++; return '\v';
|
||||||
|
case 'n': exp->_p++; return '\n';
|
||||||
|
case 't': exp->_p++; return '\t';
|
||||||
|
case 'r': exp->_p++; return '\r';
|
||||||
|
case 'f': exp->_p++; return '\f';
|
||||||
|
default: return (*exp->_p++);
|
||||||
|
}
|
||||||
|
} else if(!scisprint(*exp->_p)) trex_error(exp,_SC("letter expected"));
|
||||||
|
return (*exp->_p++);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int trex_charclass(TRex *exp,int classid)
|
||||||
|
{
|
||||||
|
int n = trex_newnode(exp,OP_CCLASS);
|
||||||
|
exp->_nodes[n].left = classid;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int trex_charnode(TRex *exp,TRexBool isclass)
|
||||||
|
{
|
||||||
|
TRexChar t;
|
||||||
|
if(*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) {
|
||||||
|
exp->_p++;
|
||||||
|
switch(*exp->_p) {
|
||||||
|
case 'n': exp->_p++; return trex_newnode(exp,'\n');
|
||||||
|
case 't': exp->_p++; return trex_newnode(exp,'\t');
|
||||||
|
case 'r': exp->_p++; return trex_newnode(exp,'\r');
|
||||||
|
case 'f': exp->_p++; return trex_newnode(exp,'\f');
|
||||||
|
case 'v': exp->_p++; return trex_newnode(exp,'\v');
|
||||||
|
case 'a': case 'A': case 'w': case 'W': case 's': case 'S':
|
||||||
|
case 'd': case 'D': case 'x': case 'X': case 'c': case 'C':
|
||||||
|
case 'p': case 'P': case 'l': case 'u':
|
||||||
|
{
|
||||||
|
t = *exp->_p; exp->_p++;
|
||||||
|
return trex_charclass(exp,t);
|
||||||
|
}
|
||||||
|
case 'b':
|
||||||
|
case 'B':
|
||||||
|
if(!isclass) {
|
||||||
|
int node = trex_newnode(exp,OP_WB);
|
||||||
|
exp->_nodes[node].left = *exp->_p;
|
||||||
|
exp->_p++;
|
||||||
|
return node;
|
||||||
|
} //else default
|
||||||
|
default:
|
||||||
|
t = *exp->_p; exp->_p++;
|
||||||
|
return trex_newnode(exp,t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(!scisprint(*exp->_p)) {
|
||||||
|
|
||||||
|
trex_error(exp,_SC("letter expected"));
|
||||||
|
}
|
||||||
|
t = *exp->_p; exp->_p++;
|
||||||
|
return trex_newnode(exp,t);
|
||||||
|
}
|
||||||
|
static int trex_class(TRex *exp)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
int first = -1,chain;
|
||||||
|
if(*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING){
|
||||||
|
ret = trex_newnode(exp,OP_NCLASS);
|
||||||
|
exp->_p++;
|
||||||
|
}else ret = trex_newnode(exp,OP_CLASS);
|
||||||
|
|
||||||
|
if(*exp->_p == ']') trex_error(exp,_SC("empty class"));
|
||||||
|
chain = ret;
|
||||||
|
while(*exp->_p != ']' && exp->_p != exp->_eol) {
|
||||||
|
if(*exp->_p == '-' && first != -1){
|
||||||
|
int r,t;
|
||||||
|
if(*exp->_p++ == ']') trex_error(exp,_SC("unfinished range"));
|
||||||
|
r = trex_newnode(exp,OP_RANGE);
|
||||||
|
if(first>*exp->_p) trex_error(exp,_SC("invalid range"));
|
||||||
|
if(exp->_nodes[first].type == OP_CCLASS) trex_error(exp,_SC("cannot use character classes in ranges"));
|
||||||
|
exp->_nodes[r].left = exp->_nodes[first].type;
|
||||||
|
t = trex_escapechar(exp);
|
||||||
|
exp->_nodes[r].right = t;
|
||||||
|
exp->_nodes[chain].next = r;
|
||||||
|
chain = r;
|
||||||
|
first = -1;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if(first!=-1){
|
||||||
|
int c = first;
|
||||||
|
exp->_nodes[chain].next = c;
|
||||||
|
chain = c;
|
||||||
|
first = trex_charnode(exp,TRex_True);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
first = trex_charnode(exp,TRex_True);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(first!=-1){
|
||||||
|
int c = first;
|
||||||
|
exp->_nodes[chain].next = c;
|
||||||
|
chain = c;
|
||||||
|
first = -1;
|
||||||
|
}
|
||||||
|
/* hack? */
|
||||||
|
exp->_nodes[ret].left = exp->_nodes[ret].next;
|
||||||
|
exp->_nodes[ret].next = -1;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int trex_parsenumber(TRex *exp)
|
||||||
|
{
|
||||||
|
int ret = *exp->_p-'0';
|
||||||
|
int positions = 10;
|
||||||
|
exp->_p++;
|
||||||
|
while(isdigit(*exp->_p)) {
|
||||||
|
ret = ret*10+(*exp->_p++-'0');
|
||||||
|
if(positions==1000000000) trex_error(exp,_SC("overflow in numeric constant"));
|
||||||
|
positions *= 10;
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int trex_element(TRex *exp)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
switch(*exp->_p)
|
||||||
|
{
|
||||||
|
case '(': {
|
||||||
|
int expr,newn;
|
||||||
|
exp->_p++;
|
||||||
|
|
||||||
|
|
||||||
|
if(*exp->_p =='?') {
|
||||||
|
exp->_p++;
|
||||||
|
trex_expect(exp,':');
|
||||||
|
expr = trex_newnode(exp,OP_NOCAPEXPR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
expr = trex_newnode(exp,OP_EXPR);
|
||||||
|
newn = trex_list(exp);
|
||||||
|
exp->_nodes[expr].left = newn;
|
||||||
|
ret = expr;
|
||||||
|
trex_expect(exp,')');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
exp->_p++;
|
||||||
|
ret = trex_class(exp);
|
||||||
|
trex_expect(exp,']');
|
||||||
|
break;
|
||||||
|
case TREX_SYMBOL_END_OF_STRING: exp->_p++; ret = trex_newnode(exp,OP_EOL);break;
|
||||||
|
case TREX_SYMBOL_ANY_CHAR: exp->_p++; ret = trex_newnode(exp,OP_DOT);break;
|
||||||
|
default:
|
||||||
|
ret = trex_charnode(exp,TRex_False);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int op;
|
||||||
|
TRexBool isgreedy = TRex_False;
|
||||||
|
unsigned short p0 = 0, p1 = 0;
|
||||||
|
switch(*exp->_p){
|
||||||
|
case TREX_SYMBOL_GREEDY_ZERO_OR_MORE: p0 = 0; p1 = 0xFFFF; exp->_p++; isgreedy = TRex_True; break;
|
||||||
|
case TREX_SYMBOL_GREEDY_ONE_OR_MORE: p0 = 1; p1 = 0xFFFF; exp->_p++; isgreedy = TRex_True; break;
|
||||||
|
case TREX_SYMBOL_GREEDY_ZERO_OR_ONE: p0 = 0; p1 = 1; exp->_p++; isgreedy = TRex_True; break;
|
||||||
|
case '{':
|
||||||
|
exp->_p++;
|
||||||
|
if(!isdigit(*exp->_p)) trex_error(exp,_SC("number expected"));
|
||||||
|
p0 = (unsigned short)trex_parsenumber(exp);
|
||||||
|
/*******************************/
|
||||||
|
switch(*exp->_p) {
|
||||||
|
case '}':
|
||||||
|
p1 = p0; exp->_p++;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
exp->_p++;
|
||||||
|
p1 = 0xFFFF;
|
||||||
|
if(isdigit(*exp->_p)){
|
||||||
|
p1 = (unsigned short)trex_parsenumber(exp);
|
||||||
|
}
|
||||||
|
trex_expect(exp,'}');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
trex_error(exp,_SC(", or } expected"));
|
||||||
|
}
|
||||||
|
/*******************************/
|
||||||
|
isgreedy = TRex_True;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
if(isgreedy) {
|
||||||
|
int nnode = trex_newnode(exp,OP_GREEDY);
|
||||||
|
op = OP_GREEDY;
|
||||||
|
exp->_nodes[nnode].left = ret;
|
||||||
|
exp->_nodes[nnode].right = ((p0)<<16)|p1;
|
||||||
|
ret = nnode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if((*exp->_p != TREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != TREX_SYMBOL_GREEDY_ZERO_OR_MORE) && (*exp->_p != TREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) {
|
||||||
|
int nnode = trex_element(exp);
|
||||||
|
exp->_nodes[ret].next = nnode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int trex_list(TRex *exp)
|
||||||
|
{
|
||||||
|
int ret=-1,e;
|
||||||
|
if(*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) {
|
||||||
|
exp->_p++;
|
||||||
|
ret = trex_newnode(exp,OP_BOL);
|
||||||
|
}
|
||||||
|
e = trex_element(exp);
|
||||||
|
if(ret != -1) {
|
||||||
|
exp->_nodes[ret].next = e;
|
||||||
|
}
|
||||||
|
else ret = e;
|
||||||
|
|
||||||
|
if(*exp->_p == TREX_SYMBOL_BRANCH) {
|
||||||
|
int temp,tright;
|
||||||
|
exp->_p++;
|
||||||
|
temp = trex_newnode(exp,OP_OR);
|
||||||
|
exp->_nodes[temp].left = ret;
|
||||||
|
tright = trex_list(exp);
|
||||||
|
exp->_nodes[temp].right = tright;
|
||||||
|
ret = temp;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TRexBool trex_matchcclass(int cclass,TRexChar c)
|
||||||
|
{
|
||||||
|
switch(cclass) {
|
||||||
|
case 'a': return isalpha(c)?TRex_True:TRex_False;
|
||||||
|
case 'A': return !isalpha(c)?TRex_True:TRex_False;
|
||||||
|
case 'w': return (isalnum(c) || c == '_')?TRex_True:TRex_False;
|
||||||
|
case 'W': return (!isalnum(c) && c != '_')?TRex_True:TRex_False;
|
||||||
|
case 's': return isspace(c)?TRex_True:TRex_False;
|
||||||
|
case 'S': return !isspace(c)?TRex_True:TRex_False;
|
||||||
|
case 'd': return isdigit(c)?TRex_True:TRex_False;
|
||||||
|
case 'D': return !isdigit(c)?TRex_True:TRex_False;
|
||||||
|
case 'x': return isxdigit(c)?TRex_True:TRex_False;
|
||||||
|
case 'X': return !isxdigit(c)?TRex_True:TRex_False;
|
||||||
|
case 'c': return iscntrl(c)?TRex_True:TRex_False;
|
||||||
|
case 'C': return !iscntrl(c)?TRex_True:TRex_False;
|
||||||
|
case 'p': return ispunct(c)?TRex_True:TRex_False;
|
||||||
|
case 'P': return !ispunct(c)?TRex_True:TRex_False;
|
||||||
|
case 'l': return islower(c)?TRex_True:TRex_False;
|
||||||
|
case 'u': return isupper(c)?TRex_True:TRex_False;
|
||||||
|
}
|
||||||
|
return TRex_False; /*cannot happen*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static TRexBool trex_matchclass(TRex* exp,TRexNode *node,TRexChar c)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
switch(node->type) {
|
||||||
|
case OP_RANGE:
|
||||||
|
if(c >= node->left && c <= node->right) return TRex_True;
|
||||||
|
break;
|
||||||
|
case OP_CCLASS:
|
||||||
|
if(trex_matchcclass(node->left,c)) return TRex_True;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(c == node->type)return TRex_True;
|
||||||
|
}
|
||||||
|
} while((node->next != -1) && (node = &exp->_nodes[node->next]));
|
||||||
|
return TRex_False;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TRexChar *trex_matchnode(TRex* exp,TRexNode *node,const TRexChar *str,TRexNode *next)
|
||||||
|
{
|
||||||
|
|
||||||
|
TRexNodeType type = node->type;
|
||||||
|
switch(type) {
|
||||||
|
case OP_GREEDY: {
|
||||||
|
//TRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : NULL;
|
||||||
|
TRexNode *greedystop = NULL;
|
||||||
|
int p0 = (node->right >> 16)&0x0000FFFF, p1 = node->right&0x0000FFFF, nmaches = 0;
|
||||||
|
const TRexChar *s=str, *good = str;
|
||||||
|
|
||||||
|
if(node->next != -1) {
|
||||||
|
greedystop = &exp->_nodes[node->next];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
greedystop = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
while((nmaches == 0xFFFF || nmaches < p1)) {
|
||||||
|
|
||||||
|
const TRexChar *stop;
|
||||||
|
if(!(s = trex_matchnode(exp,&exp->_nodes[node->left],s,greedystop)))
|
||||||
|
break;
|
||||||
|
nmaches++;
|
||||||
|
good=s;
|
||||||
|
if(greedystop) {
|
||||||
|
//checks that 0 matches satisfy the expression(if so skips)
|
||||||
|
//if not would always stop(for instance if is a '?')
|
||||||
|
if(greedystop->type != OP_GREEDY ||
|
||||||
|
(greedystop->type == OP_GREEDY && ((greedystop->right >> 16)&0x0000FFFF) != 0))
|
||||||
|
{
|
||||||
|
TRexNode *gnext = NULL;
|
||||||
|
if(greedystop->next != -1) {
|
||||||
|
gnext = &exp->_nodes[greedystop->next];
|
||||||
|
}else if(next && next->next != -1){
|
||||||
|
gnext = &exp->_nodes[next->next];
|
||||||
|
}
|
||||||
|
stop = trex_matchnode(exp,greedystop,s,gnext);
|
||||||
|
if(stop) {
|
||||||
|
//if satisfied stop it
|
||||||
|
if(p0 == p1 && p0 == nmaches) break;
|
||||||
|
else if(nmaches >= p0 && p1 == 0xFFFF) break;
|
||||||
|
else if(nmaches >= p0 && nmaches <= p1) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s >= exp->_eol)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(p0 == p1 && p0 == nmaches) return good;
|
||||||
|
else if(nmaches >= p0 && p1 == 0xFFFF) return good;
|
||||||
|
else if(nmaches >= p0 && nmaches <= p1) return good;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
case OP_OR: {
|
||||||
|
const TRexChar *asd = str;
|
||||||
|
TRexNode *temp=&exp->_nodes[node->left];
|
||||||
|
while( (asd = trex_matchnode(exp,temp,asd,NULL)) ) {
|
||||||
|
if(temp->next != -1)
|
||||||
|
temp = &exp->_nodes[temp->next];
|
||||||
|
else
|
||||||
|
return asd;
|
||||||
|
}
|
||||||
|
asd = str;
|
||||||
|
temp = &exp->_nodes[node->right];
|
||||||
|
while( (asd = trex_matchnode(exp,temp,asd,NULL)) ) {
|
||||||
|
if(temp->next != -1)
|
||||||
|
temp = &exp->_nodes[temp->next];
|
||||||
|
else
|
||||||
|
return asd;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_EXPR:
|
||||||
|
case OP_NOCAPEXPR:{
|
||||||
|
TRexNode *n = &exp->_nodes[node->left];
|
||||||
|
const TRexChar *cur = str;
|
||||||
|
int capture = -1;
|
||||||
|
if(node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) {
|
||||||
|
capture = exp->_currsubexp;
|
||||||
|
exp->_matches[capture].begin = cur;
|
||||||
|
exp->_currsubexp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
TRexNode *subnext = NULL;
|
||||||
|
if(n->next != -1) {
|
||||||
|
subnext = &exp->_nodes[n->next];
|
||||||
|
}else {
|
||||||
|
subnext = next;
|
||||||
|
}
|
||||||
|
if(!(cur = trex_matchnode(exp,n,cur,subnext))) {
|
||||||
|
if(capture != -1){
|
||||||
|
exp->_matches[capture].begin = 0;
|
||||||
|
exp->_matches[capture].len = 0;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} while((n->next != -1) && (n = &exp->_nodes[n->next]));
|
||||||
|
|
||||||
|
if(capture != -1)
|
||||||
|
exp->_matches[capture].len = cur - exp->_matches[capture].begin;
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
case OP_WB:
|
||||||
|
if(str == exp->_bol && !isspace(*str)
|
||||||
|
|| (str == exp->_eol && !isspace(*(str-1)))
|
||||||
|
|| (!isspace(*str) && isspace(*(str+1)))
|
||||||
|
|| (isspace(*str) && !isspace(*(str+1))) ) {
|
||||||
|
return (node->left == 'b')?str:NULL;
|
||||||
|
}
|
||||||
|
return (node->left == 'b')?NULL:str;
|
||||||
|
case OP_BOL:
|
||||||
|
if(str == exp->_bol) return str;
|
||||||
|
return NULL;
|
||||||
|
case OP_EOL:
|
||||||
|
if(str == exp->_eol) return str;
|
||||||
|
return NULL;
|
||||||
|
case OP_DOT:{
|
||||||
|
*str++;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
case OP_NCLASS:
|
||||||
|
case OP_CLASS:
|
||||||
|
if(trex_matchclass(exp,&exp->_nodes[node->left],*str)?(type == OP_CLASS?TRex_True:TRex_False):(type == OP_NCLASS?TRex_True:TRex_False)) {
|
||||||
|
*str++;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
case OP_CCLASS:
|
||||||
|
if(trex_matchcclass(node->left,*str)) {
|
||||||
|
*str++;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
default: /* char */
|
||||||
|
if(*str != node->type) return NULL;
|
||||||
|
*str++;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public api */
|
||||||
|
TRex *trex_compile(const TRexChar *pattern,const TRexChar **error)
|
||||||
|
{
|
||||||
|
TRex *exp = (TRex *)malloc(sizeof(TRex));
|
||||||
|
exp->_eol = exp->_bol = NULL;
|
||||||
|
exp->_p = pattern;
|
||||||
|
exp->_nallocated = (int)scstrlen(pattern) * sizeof(TRexChar);
|
||||||
|
exp->_nodes = (TRexNode *)malloc(exp->_nallocated * sizeof(TRexNode));
|
||||||
|
exp->_nsize = 0;
|
||||||
|
exp->_matches = 0;
|
||||||
|
exp->_nsubexpr = 0;
|
||||||
|
exp->_first = trex_newnode(exp,OP_EXPR);
|
||||||
|
exp->_error = error;
|
||||||
|
exp->_jmpbuf = malloc(sizeof(jmp_buf));
|
||||||
|
if(setjmp(*((jmp_buf*)exp->_jmpbuf)) == 0) {
|
||||||
|
int res = trex_list(exp);
|
||||||
|
exp->_nodes[exp->_first].left = res;
|
||||||
|
if(*exp->_p!='\0')
|
||||||
|
trex_error(exp,_SC("unexpected character"));
|
||||||
|
#ifdef _DEBUG
|
||||||
|
{
|
||||||
|
int nsize,i;
|
||||||
|
TRexNode *t;
|
||||||
|
nsize = exp->_nsize;
|
||||||
|
t = &exp->_nodes[0];
|
||||||
|
scprintf(_SC("\n"));
|
||||||
|
for(i = 0;i < nsize; i++) {
|
||||||
|
if(exp->_nodes[i].type>MAX_CHAR)
|
||||||
|
scprintf(_SC("[%02d] %10s "),i,g_nnames[exp->_nodes[i].type-MAX_CHAR]);
|
||||||
|
else
|
||||||
|
scprintf(_SC("[%02d] %10c "),i,exp->_nodes[i].type);
|
||||||
|
scprintf(_SC("left %02d right %02d next %02d\n"),exp->_nodes[i].left,exp->_nodes[i].right,exp->_nodes[i].next);
|
||||||
|
}
|
||||||
|
scprintf(_SC("\n"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
exp->_matches = (TRexMatch *) malloc(exp->_nsubexpr * sizeof(TRexMatch));
|
||||||
|
memset(exp->_matches,0,exp->_nsubexpr * sizeof(TRexMatch));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
trex_free(exp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void trex_free(TRex *exp)
|
||||||
|
{
|
||||||
|
if(exp) {
|
||||||
|
if(exp->_nodes) free(exp->_nodes);
|
||||||
|
if(exp->_jmpbuf) free(exp->_jmpbuf);
|
||||||
|
if(exp->_matches) free(exp->_matches);
|
||||||
|
free(exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TRexBool trex_match(TRex* exp,const TRexChar* text)
|
||||||
|
{
|
||||||
|
const TRexChar* res = NULL;
|
||||||
|
exp->_bol = text;
|
||||||
|
exp->_eol = text + scstrlen(text);
|
||||||
|
exp->_currsubexp = 0;
|
||||||
|
res = trex_matchnode(exp,exp->_nodes,text,NULL);
|
||||||
|
if(res == NULL || res != exp->_eol)
|
||||||
|
return TRex_False;
|
||||||
|
return TRex_True;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRexBool trex_searchrange(TRex* exp,const TRexChar* text_begin,const TRexChar* text_end,const TRexChar** out_begin, const TRexChar** out_end)
|
||||||
|
{
|
||||||
|
const TRexChar *cur = NULL;
|
||||||
|
int node = exp->_first;
|
||||||
|
if(text_begin >= text_end) return TRex_False;
|
||||||
|
exp->_bol = text_begin;
|
||||||
|
exp->_eol = text_end;
|
||||||
|
do {
|
||||||
|
cur = text_begin;
|
||||||
|
while(node != -1) {
|
||||||
|
exp->_currsubexp = 0;
|
||||||
|
cur = trex_matchnode(exp,&exp->_nodes[node],cur,NULL);
|
||||||
|
if(!cur)
|
||||||
|
break;
|
||||||
|
node = exp->_nodes[node].next;
|
||||||
|
}
|
||||||
|
*text_begin++;
|
||||||
|
} while(cur == NULL && text_begin != text_end);
|
||||||
|
|
||||||
|
if(cur == NULL)
|
||||||
|
return TRex_False;
|
||||||
|
|
||||||
|
--text_begin;
|
||||||
|
|
||||||
|
if(out_begin) *out_begin = text_begin;
|
||||||
|
if(out_end) *out_end = cur;
|
||||||
|
return TRex_True;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRexBool trex_search(TRex* exp,const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end)
|
||||||
|
{
|
||||||
|
return trex_searchrange(exp,text,text + scstrlen(text),out_begin,out_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
int trex_getsubexpcount(TRex* exp)
|
||||||
|
{
|
||||||
|
return exp->_nsubexpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch *subexp)
|
||||||
|
{
|
||||||
|
if( n<0 || n >= exp->_nsubexpr) return TRex_False;
|
||||||
|
*subexp = exp->_matches[n];
|
||||||
|
return TRex_True;
|
||||||
|
}
|
||||||
|
|
67
src/openalpr/trex.h
Normal file
67
src/openalpr/trex.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef _TREX_H_
|
||||||
|
#define _TREX_H_
|
||||||
|
/***************************************************************
|
||||||
|
T-Rex a tiny regular expression library
|
||||||
|
|
||||||
|
Copyright (C) 2003-2006 Alberto Demichelis
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
#define TRexChar unsigned short
|
||||||
|
#define MAX_CHAR 0xFFFF
|
||||||
|
#define _TREXC(c) L##c
|
||||||
|
#define trex_strlen wcslen
|
||||||
|
#define trex_printf wprintf
|
||||||
|
#else
|
||||||
|
#define TRexChar char
|
||||||
|
#define MAX_CHAR 0xFF
|
||||||
|
#define _TREXC(c) (c)
|
||||||
|
#define trex_strlen strlen
|
||||||
|
#define trex_printf printf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TREX_API
|
||||||
|
#define TREX_API extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TRex_True 1
|
||||||
|
#define TRex_False 0
|
||||||
|
|
||||||
|
typedef unsigned int TRexBool;
|
||||||
|
typedef struct TRex TRex;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const TRexChar *begin;
|
||||||
|
int len;
|
||||||
|
} TRexMatch;
|
||||||
|
|
||||||
|
TREX_API TRex *trex_compile(const TRexChar *pattern,const TRexChar **error);
|
||||||
|
TREX_API void trex_free(TRex *exp);
|
||||||
|
TREX_API TRexBool trex_match(TRex* exp,const TRexChar* text);
|
||||||
|
TREX_API TRexBool trex_search(TRex* exp,const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end);
|
||||||
|
TREX_API TRexBool trex_searchrange(TRex* exp,const TRexChar* text_begin,const TRexChar* text_end,const TRexChar** out_begin, const TRexChar** out_end);
|
||||||
|
TREX_API int trex_getsubexpcount(TRex* exp);
|
||||||
|
TREX_API TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch *subexp);
|
||||||
|
|
||||||
|
#endif
|
400
src/openalpr/utility.cpp
Normal file
400
src/openalpr/utility.cpp
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "utility.h"
|
||||||
|
|
||||||
|
#include <omp.h>
|
||||||
|
|
||||||
|
Rect expandRect(Rect original, int expandXPixels, int expandYPixels, int maxX, int maxY)
|
||||||
|
{
|
||||||
|
Rect expandedRegion = Rect(original);
|
||||||
|
|
||||||
|
float halfX = round((float) expandXPixels / 2.0);
|
||||||
|
float halfY = round((float) expandYPixels / 2.0);
|
||||||
|
expandedRegion.x = expandedRegion.x - halfX;
|
||||||
|
expandedRegion.width = expandedRegion.width + expandXPixels;
|
||||||
|
expandedRegion.y = expandedRegion.y - halfY;
|
||||||
|
expandedRegion.height = expandedRegion.height + expandYPixels;
|
||||||
|
|
||||||
|
if (expandedRegion.x < 0)
|
||||||
|
expandedRegion.x = 0;
|
||||||
|
if (expandedRegion.y < 0)
|
||||||
|
expandedRegion.y = 0;
|
||||||
|
if (expandedRegion.x + expandedRegion.width > maxX)
|
||||||
|
expandedRegion.width = maxX - expandedRegion.x;
|
||||||
|
if (expandedRegion.y + expandedRegion.height > maxY)
|
||||||
|
expandedRegion.height = maxY - expandedRegion.y;
|
||||||
|
|
||||||
|
return expandedRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat drawImageDashboard(vector<Mat> images, int imageType, int numColumns)
|
||||||
|
{
|
||||||
|
int numRows = ceil((float) images.size() / (float) numColumns);
|
||||||
|
|
||||||
|
Mat dashboard(Size(images[0].cols * numColumns, images[0].rows * numRows), imageType);
|
||||||
|
|
||||||
|
for (int i = 0; i < numColumns * numRows; i++)
|
||||||
|
{
|
||||||
|
if (i < images.size())
|
||||||
|
images[i].copyTo(dashboard(Rect((i%numColumns) * images[i].cols, floor((float) i/numColumns) * images[i].rows, images[i].cols, images[i].rows)));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Mat black = Mat::zeros(images[0].size(), imageType);
|
||||||
|
black.copyTo(dashboard(Rect((i%numColumns) * images[0].cols, floor((float) i/numColumns) * images[0].rows, images[0].cols, images[0].rows)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat addLabel(Mat input, string label)
|
||||||
|
{
|
||||||
|
const int border_size = 1;
|
||||||
|
const Scalar border_color(0,0,255);
|
||||||
|
const int extraHeight = 20;
|
||||||
|
const Scalar bg(222,222,222);
|
||||||
|
const Scalar fg(0,0,0);
|
||||||
|
|
||||||
|
Rect destinationRect(border_size, extraHeight, input.cols, input.rows);
|
||||||
|
Mat newImage(Size(input.cols + (border_size), input.rows + extraHeight + (border_size )), input.type());
|
||||||
|
input.copyTo(newImage(destinationRect));
|
||||||
|
|
||||||
|
cout << " Adding label " << label << endl;
|
||||||
|
if (input.type() == CV_8U)
|
||||||
|
cvtColor(newImage, newImage, CV_GRAY2BGR);
|
||||||
|
|
||||||
|
rectangle(newImage, Point(0,0), Point(input.cols, extraHeight), bg, CV_FILLED);
|
||||||
|
putText(newImage, label, Point(5, extraHeight - 5), CV_FONT_HERSHEY_PLAIN , 0.7, fg);
|
||||||
|
|
||||||
|
rectangle(newImage, Point(0,0), Point(newImage.cols - 1, newImage.rows -1), border_color, border_size);
|
||||||
|
|
||||||
|
return newImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void drawAndWait(cv::Mat* frame)
|
||||||
|
{
|
||||||
|
cv::imshow("Temp Window", *frame);
|
||||||
|
|
||||||
|
while (cv::waitKey(50) == -1)
|
||||||
|
{
|
||||||
|
// loop
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::destroyWindow("Temp Window");
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayImage(Config* config, string windowName, cv::Mat frame)
|
||||||
|
{
|
||||||
|
if (config->debugShowImages)
|
||||||
|
imshow(windowName, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<Mat> produceThresholds(const Mat img_gray, Config* config)
|
||||||
|
{
|
||||||
|
const int THRESHOLD_COUNT = 10;
|
||||||
|
//Mat img_equalized = equalizeBrightness(img_gray);
|
||||||
|
|
||||||
|
timespec startTime;
|
||||||
|
getTime(&startTime);
|
||||||
|
|
||||||
|
vector<Mat> thresholds;
|
||||||
|
|
||||||
|
//#pragma omp parallel for
|
||||||
|
for (int i = 0; i < THRESHOLD_COUNT; i++)
|
||||||
|
thresholds.push_back(Mat(img_gray.size(), CV_8U));
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < THRESHOLD_COUNT; i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
if (i <= 2) //0-2
|
||||||
|
{
|
||||||
|
int k = ((i%3) * 5) + 7; // 7, 12, 17
|
||||||
|
if (k==12) k = 13; // change 12 to 13
|
||||||
|
//#pragma omp ordered
|
||||||
|
adaptiveThreshold(img_gray, thresholds[i], 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV , k, 3);
|
||||||
|
}
|
||||||
|
else if (i <= 6) //3-6
|
||||||
|
{
|
||||||
|
int k = i%2; // 0 or 1
|
||||||
|
int win = 18 + (k * 4); // 18 or 22
|
||||||
|
//#pragma omp ordered
|
||||||
|
NiblackSauvolaWolfJolion (img_gray, thresholds[i], WOLFJOLION, win, win, 0.05 + (k * 0.35));
|
||||||
|
bitwise_not(thresholds[i], thresholds[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (i <= 9) //7-9
|
||||||
|
{
|
||||||
|
int k = (i%3) + 1; // 1,2,3
|
||||||
|
//#pragma omp ordered
|
||||||
|
NiblackSauvolaWolfJolion (img_gray, thresholds[i], SAUVOLA, 12, 12, 0.18 * k);
|
||||||
|
bitwise_not(thresholds[i], thresholds[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (config->debugTiming)
|
||||||
|
{
|
||||||
|
timespec endTime;
|
||||||
|
getTime(&endTime);
|
||||||
|
cout << " -- Produce Threshold Time: " << diffclock(startTime, endTime) << "ms." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return thresholds;
|
||||||
|
//threshold(img_equalized, img_threshold, 100, 255, THRESH_BINARY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mat equalizeBrightness(Mat img)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Divide the image by its morphologically closed counterpart
|
||||||
|
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(19,19));
|
||||||
|
Mat closed;
|
||||||
|
morphologyEx(img, closed, MORPH_CLOSE, kernel);
|
||||||
|
|
||||||
|
img.convertTo(img, CV_32FC1); // divide requires floating-point
|
||||||
|
divide(img, closed, img, 1, CV_32FC1);
|
||||||
|
normalize(img, img, 0, 255, NORM_MINMAX);
|
||||||
|
img.convertTo(img, CV_8U); // convert back to unsigned int
|
||||||
|
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawRotatedRect(Mat* img, RotatedRect rect, Scalar color, int thickness)
|
||||||
|
{
|
||||||
|
|
||||||
|
Point2f rect_points[4];
|
||||||
|
rect.points( rect_points );
|
||||||
|
for( int j = 0; j < 4; j++ )
|
||||||
|
line( *img, rect_points[j], rect_points[(j+1)%4], color, thickness, 8 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillMask(Mat img, const Mat mask, Scalar color)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < img.rows; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < img.cols; col++)
|
||||||
|
{
|
||||||
|
int m = (int) mask.at<uchar>(row, col);
|
||||||
|
|
||||||
|
if (m)
|
||||||
|
{
|
||||||
|
for (int z = 0; z < 3; z++)
|
||||||
|
{
|
||||||
|
int prevVal = img.at<Vec3b>(row, col)[z];
|
||||||
|
img.at<Vec3b>(row, col)[z] = ((int) color[z]) | prevVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void drawX(Mat img, Rect rect, Scalar color, int thickness)
|
||||||
|
{
|
||||||
|
Point tl(rect.x, rect.y);
|
||||||
|
Point tr(rect.x + rect.width, rect.y);
|
||||||
|
Point bl(rect.x, rect.y + rect.height);
|
||||||
|
Point br(rect.x + rect.width, rect.y + rect.height);
|
||||||
|
|
||||||
|
line(img, tl, br, color, thickness);
|
||||||
|
line(img, bl, tr, color, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
double distanceBetweenPoints(Point p1, Point p2)
|
||||||
|
{
|
||||||
|
float asquared = (p2.x - p1.x)*(p2.x - p1.x);
|
||||||
|
float bsquared = (p2.y - p1.y)*(p2.y - p1.y);
|
||||||
|
|
||||||
|
return sqrt(asquared + bsquared);
|
||||||
|
}
|
||||||
|
|
||||||
|
float angleBetweenPoints(Point p1, Point p2)
|
||||||
|
{
|
||||||
|
int deltaY = p2.y - p1.y;
|
||||||
|
int deltaX = p2.x - p1.x;
|
||||||
|
|
||||||
|
return atan2((float) deltaY, (float) deltaX) * (180 / CV_PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Size getSizeMaintainingAspect(Mat inputImg, int maxWidth, int maxHeight)
|
||||||
|
{
|
||||||
|
float aspect = ((float) inputImg.cols) / ((float) inputImg.rows);
|
||||||
|
|
||||||
|
if (maxWidth / aspect > maxHeight)
|
||||||
|
{
|
||||||
|
return Size(maxHeight * aspect, maxHeight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Size(maxWidth, maxWidth / aspect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LineSegment::LineSegment()
|
||||||
|
{
|
||||||
|
init(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment::LineSegment(Point p1, Point p2)
|
||||||
|
{
|
||||||
|
init(p1.x, p1.y, p2.x, p2.y);
|
||||||
|
}
|
||||||
|
LineSegment::LineSegment(int x1, int y1, int x2, int y2)
|
||||||
|
{
|
||||||
|
init(x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineSegment::init(int x1, int y1, int x2, int y2)
|
||||||
|
{
|
||||||
|
this->p1 = Point(x1, y1);
|
||||||
|
this->p2 = Point(x2, y2);
|
||||||
|
|
||||||
|
if (p2.x - p1.x == 0)
|
||||||
|
this->slope = 0.00000000001;
|
||||||
|
else
|
||||||
|
this->slope = (float) (p2.y - p1.y) / (float) (p2.x - p1.x);
|
||||||
|
|
||||||
|
this->length = distanceBetweenPoints(p1, p2);
|
||||||
|
|
||||||
|
this->angle = angleBetweenPoints(p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LineSegment::isPointBelowLine( Point tp ){
|
||||||
|
return ((p2.x - p1.x)*(tp.y - p1.y) - (p2.y - p1.y)*(tp.x - p1.x)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float LineSegment::getPointAt(float x)
|
||||||
|
{
|
||||||
|
return slope * (x - p2.x) + p2.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point LineSegment::closestPointOnSegmentTo(Point p)
|
||||||
|
{
|
||||||
|
float top = (p.x - p1.x) * (p2.x - p1.x) + (p.y - p1.y)*(p2.y - p1.y);
|
||||||
|
|
||||||
|
float bottom = distanceBetweenPoints(p2, p1);
|
||||||
|
bottom = bottom * bottom;
|
||||||
|
|
||||||
|
float u = top / bottom;
|
||||||
|
|
||||||
|
float x = p1.x + u * (p2.x - p1.x);
|
||||||
|
float y = p1.y + u * (p2.y - p1.y);
|
||||||
|
|
||||||
|
return Point(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point LineSegment::intersection(LineSegment line)
|
||||||
|
{
|
||||||
|
float c1, c2;
|
||||||
|
float intersection_X = -1, intersection_Y= -1;
|
||||||
|
|
||||||
|
|
||||||
|
c1 = p1.y - slope * p1.x; // which is same as y2 - slope * x2
|
||||||
|
|
||||||
|
c2 = line.p2.y - line.slope * line.p2.x; // which is same as y2 - slope * x2
|
||||||
|
|
||||||
|
if( (slope - line.slope) == 0)
|
||||||
|
{
|
||||||
|
//std::cout << "No Intersection between the lines" << endl;
|
||||||
|
}
|
||||||
|
else if (p1.x == p2.x)
|
||||||
|
{
|
||||||
|
// Line1 is vertical
|
||||||
|
return Point(p1.x, line.getPointAt(p1.x));
|
||||||
|
}
|
||||||
|
else if (line.p1.x == line.p2.x)
|
||||||
|
{
|
||||||
|
// Line2 is vertical
|
||||||
|
return Point(line.p1.x, getPointAt(line.p1.x));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
intersection_X = (c2 - c1) / (slope - line.slope);
|
||||||
|
intersection_Y = slope * intersection_X + c1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return Point(intersection_X, intersection_Y);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Point LineSegment::midpoint()
|
||||||
|
{
|
||||||
|
// Handle the case where the line is vertical
|
||||||
|
if (p1.x == p2.x)
|
||||||
|
{
|
||||||
|
float ydiff = p2.y-p1.y;
|
||||||
|
float y = p1.y + (ydiff/2);
|
||||||
|
return Point(p1.x, y);
|
||||||
|
}
|
||||||
|
float diff = p2.x - p1.x;
|
||||||
|
float midX = ((float) p1.x) + (diff / 2);
|
||||||
|
int midY = getPointAt(midX);
|
||||||
|
|
||||||
|
return Point(midX, midY);
|
||||||
|
}
|
||||||
|
|
||||||
|
LineSegment LineSegment::getParallelLine(float distance)
|
||||||
|
{
|
||||||
|
float diff_x = p2.x - p1.x;
|
||||||
|
float diff_y = p2.y - p1.y;
|
||||||
|
float angle = atan2( diff_x, diff_y);
|
||||||
|
float dist_x = distance * cos(angle);
|
||||||
|
float dist_y = -distance * sin(angle);
|
||||||
|
|
||||||
|
int offsetX = (int)round(dist_x);
|
||||||
|
int offsetY = (int)round(dist_y);
|
||||||
|
|
||||||
|
LineSegment result(p1.x + offsetX, p1.y + offsetY,
|
||||||
|
p2.x + offsetX, p2.y + offsetY);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
119
src/openalpr/utility.h
Normal file
119
src/openalpr/utility.h
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef UTILITY_H
|
||||||
|
#define UTILITY_H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "constants.h"
|
||||||
|
#include "support/timing.h"
|
||||||
|
#include "opencv2/highgui/highgui.hpp"
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
#include "opencv2/core/core.hpp"
|
||||||
|
#include "binarize_wolf.h"
|
||||||
|
#include <vector>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
struct LineSegment
|
||||||
|
{
|
||||||
|
float x1;
|
||||||
|
float y1;
|
||||||
|
float x2;
|
||||||
|
float y2;
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class LineSegment
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
Point p1, p2;
|
||||||
|
float slope;
|
||||||
|
float length;
|
||||||
|
float angle;
|
||||||
|
|
||||||
|
// LineSegment(Point point1, Point point2);
|
||||||
|
LineSegment();
|
||||||
|
LineSegment(int x1, int y1, int x2, int y2);
|
||||||
|
LineSegment(Point p1, Point p2);
|
||||||
|
|
||||||
|
void init(int x1, int y1, int x2, int y2);
|
||||||
|
|
||||||
|
bool isPointBelowLine(Point tp);
|
||||||
|
|
||||||
|
float getPointAt(float x);
|
||||||
|
|
||||||
|
Point closestPointOnSegmentTo(Point p);
|
||||||
|
|
||||||
|
Point intersection(LineSegment line);
|
||||||
|
|
||||||
|
LineSegment getParallelLine(float distance);
|
||||||
|
|
||||||
|
Point midpoint();
|
||||||
|
|
||||||
|
inline std::string str()
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "(" << p1.x << ", " << p1.y << ") : (" << p2.x << ", " << p2.y << ")";
|
||||||
|
return ss.str() ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
vector<Mat> produceThresholds(const Mat img_gray, Config* config);
|
||||||
|
|
||||||
|
Mat drawImageDashboard(vector<Mat> images, int imageType, int numColumns);
|
||||||
|
|
||||||
|
void displayImage(Config* config, string windowName, cv::Mat frame);
|
||||||
|
void drawAndWait(cv::Mat* frame);
|
||||||
|
|
||||||
|
double distanceBetweenPoints(Point p1, Point p2);
|
||||||
|
|
||||||
|
void drawRotatedRect(Mat* img, RotatedRect rect, Scalar color, int thickness);
|
||||||
|
|
||||||
|
void drawX(Mat img, Rect rect, Scalar color, int thickness);
|
||||||
|
void fillMask(Mat img, const Mat mask, Scalar color);
|
||||||
|
|
||||||
|
float angleBetweenPoints(Point p1, Point p2);
|
||||||
|
|
||||||
|
Size getSizeMaintainingAspect(Mat inputImg, int maxWidth, int maxHeight);
|
||||||
|
|
||||||
|
Mat equalizeBrightness(Mat img);
|
||||||
|
|
||||||
|
Rect expandRect(Rect original, int expandXPixels, int expandYPixels, int maxX, int maxY);
|
||||||
|
|
||||||
|
Mat addLabel(Mat input, string label);
|
||||||
|
|
||||||
|
#endif // UTILITY_H
|
155
src/openalpr/verticalhistogram.cpp
Normal file
155
src/openalpr/verticalhistogram.cpp
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "verticalhistogram.h"
|
||||||
|
|
||||||
|
VerticalHistogram::VerticalHistogram(Mat inputImage, Mat mask)
|
||||||
|
{
|
||||||
|
analyzeImage(inputImage, mask);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalHistogram::~VerticalHistogram()
|
||||||
|
{
|
||||||
|
debugImg.release();
|
||||||
|
colHeights.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VerticalHistogram::analyzeImage(Mat inputImage, Mat mask)
|
||||||
|
{
|
||||||
|
highestPeak = 0;
|
||||||
|
lowestValley = inputImage.rows;
|
||||||
|
|
||||||
|
|
||||||
|
debugImg = Mat::zeros(inputImage.size(), CV_8U);
|
||||||
|
|
||||||
|
int columnCount;
|
||||||
|
|
||||||
|
for (int col = 0; col < inputImage.cols; col++)
|
||||||
|
{
|
||||||
|
columnCount = 0;
|
||||||
|
|
||||||
|
for (int row = 0; row < inputImage.rows; row++)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (inputImage.at<uchar>(row, col) > 0 && mask.at<uchar>(row, col) > 0)
|
||||||
|
columnCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (; columnCount > 0; columnCount--)
|
||||||
|
debugImg.at<uchar>(inputImage.rows - columnCount, col) = 255;
|
||||||
|
|
||||||
|
this->colHeights.push_back(columnCount);
|
||||||
|
|
||||||
|
if (columnCount < lowestValley)
|
||||||
|
lowestValley = columnCount;
|
||||||
|
if (columnCount > highestPeak)
|
||||||
|
highestPeak = columnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalHistogram::findValleys()
|
||||||
|
{
|
||||||
|
int MINIMUM_PEAK_HEIGHT = (int) (((float) highestPeak) * 0.75);
|
||||||
|
|
||||||
|
int totalWidth = colHeights.size();
|
||||||
|
|
||||||
|
int midpoint = ((highestPeak - lowestValley) / 2) + lowestValley;
|
||||||
|
|
||||||
|
HistogramDirection prevDirection = FALLING;
|
||||||
|
|
||||||
|
int relativePeakHeight = 0;
|
||||||
|
int valleyStart = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < totalWidth; i++)
|
||||||
|
{
|
||||||
|
bool aboveMidpoint = (colHeights[i] >= midpoint);
|
||||||
|
|
||||||
|
if (aboveMidpoint)
|
||||||
|
{
|
||||||
|
if (colHeights[i] > relativePeakHeight)
|
||||||
|
relativePeakHeight = colHeights[i];
|
||||||
|
|
||||||
|
|
||||||
|
prevDirection = FLAT;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
relativePeakHeight = 0;
|
||||||
|
|
||||||
|
HistogramDirection direction = getHistogramDirection(i);
|
||||||
|
|
||||||
|
if ((prevDirection == FALLING || prevDirection == FLAT) && direction == RISING)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
else if ((prevDirection == FALLING || prevDirection == FLAT) && direction == RISING)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HistogramDirection VerticalHistogram::getHistogramDirection(int index)
|
||||||
|
{
|
||||||
|
int EXTRA_WIDTH_TO_AVERAGE = 2;
|
||||||
|
|
||||||
|
float trailingAverage = 0;
|
||||||
|
float forwardAverage = 0;
|
||||||
|
|
||||||
|
int trailStartIndex = index - EXTRA_WIDTH_TO_AVERAGE;
|
||||||
|
if (trailStartIndex < 0)
|
||||||
|
trailStartIndex = 0;
|
||||||
|
int forwardEndIndex = index + EXTRA_WIDTH_TO_AVERAGE;
|
||||||
|
if (forwardEndIndex >= colHeights.size())
|
||||||
|
forwardEndIndex = colHeights.size() - 1;
|
||||||
|
|
||||||
|
for (int i = index; i >= trailStartIndex; i--)
|
||||||
|
{
|
||||||
|
trailingAverage += colHeights[i];
|
||||||
|
}
|
||||||
|
trailingAverage = trailingAverage / ((float) (1 + index - trailStartIndex));
|
||||||
|
|
||||||
|
for (int i = index; i <= forwardEndIndex; i++)
|
||||||
|
{
|
||||||
|
forwardAverage += colHeights[i];
|
||||||
|
}
|
||||||
|
forwardAverage = forwardAverage / ((float) (1 + forwardEndIndex - index));
|
||||||
|
|
||||||
|
|
||||||
|
float diff = forwardAverage - trailingAverage;
|
||||||
|
float minDiff = ((float) (highestPeak - lowestValley)) * 0.10;
|
||||||
|
|
||||||
|
if (diff > minDiff)
|
||||||
|
return RISING;
|
||||||
|
else if (diff < minDiff)
|
||||||
|
return FALLING;
|
||||||
|
else
|
||||||
|
return FLAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
62
src/openalpr/verticalhistogram.h
Normal file
62
src/openalpr/verticalhistogram.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013 New Designs Unlimited, LLC
|
||||||
|
* Opensource Automated License Plate Recognition [http://www.openalpr.com]
|
||||||
|
*
|
||||||
|
* This file is part of OpenAlpr.
|
||||||
|
*
|
||||||
|
* OpenAlpr is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License
|
||||||
|
* version 3 as published by the Free Software Foundation
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef VERTICALHISTOGRAM_H
|
||||||
|
#define VERTICALHISTOGRAM_H
|
||||||
|
|
||||||
|
#include "opencv2/imgproc/imgproc.hpp"
|
||||||
|
|
||||||
|
using namespace cv;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
struct Valley
|
||||||
|
{
|
||||||
|
int startIndex;
|
||||||
|
int endIndex;
|
||||||
|
int width;
|
||||||
|
int pixelsWithin;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum HistogramDirection { RISING, FALLING, FLAT };
|
||||||
|
|
||||||
|
class VerticalHistogram
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
VerticalHistogram(Mat inputImage, Mat mask);
|
||||||
|
virtual ~VerticalHistogram();
|
||||||
|
|
||||||
|
Mat debugImg;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
vector<int> colHeights;
|
||||||
|
int highestPeak;
|
||||||
|
int lowestValley;
|
||||||
|
vector<Valley> valleys;
|
||||||
|
|
||||||
|
void analyzeImage(Mat inputImage, Mat mask);
|
||||||
|
void findValleys();
|
||||||
|
|
||||||
|
HistogramDirection getHistogramDirection(int index);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VERTICALHISTOGRAM_H
|
Reference in New Issue
Block a user