Merge branch 'develop'

This commit is contained in:
Matt Hill
2014-01-08 22:48:56 -06:00
160 changed files with 26557 additions and 1 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*~
build/
scratch/
/libraries/
CMakeFiles/
CMakeCache.txt
.o
benchmarks/

123
README.md
View File

@@ -1,4 +1,125 @@
openalpr
========
Automated License Plate Recognition library
OpenALPR is an open source *Automated License Plate Recognition* library written in C++. The library analyzes images and identifies license plates. The output is the text representation of any license plate characters found in the image.
Check out a live online demo here: http://www.openalpr.com/demo.html
User Guide
-----------
OpenALPR includes a command line utility. Simply typing "alpr [image file path]" is enough to get started recognizing license plate images.
```
user@linux:~/openalpr$ alpr --help
USAGE:
alpr [-t <region code>] [-r <runtime_dir>] [-n <topN>]
[--seek <integer_ms>] [-c <country_code>]
[--clock] [-d] [-j] [--] [--version] [-h]
<image_file_path>
Where:
-t <region code>, --template_region <region code>
Attempt to match the plate number against a region template (e.g., md
for Maryland, ca for California)
-r <runtime_dir>, --runtime_dir <runtime_dir>
Path to the OpenAlpr runtime data directory
-n <topN>, --topn <topN>
Max number of possible plate numbers to return. Default=10
--seek <integer_ms>
Seek to the specied millisecond in a video file. Default=0
-c <country_code>, --country <country_code>
Country code to identify (either us for USA or eu for Europe).
Default=us
--clock
Measure/print the total time to process image and all plates.
Default=off
-d, --detect_region
Attempt to detect the region of the plate image. Default=off
-j, --json
Output recognition results in JSON format. Default=off
--, --ignore_rest
Ignores the rest of the labeled arguments following this flag.
--version
Displays version information and exits.
-h, --help
Displays usage information and exits.
<image_file_path>
(required) Image containing license plates
OpenAlpr Command Line Utility
```
For example, the following output is created by analyzing the plate image at this URL: http://www.openalpr.com/images/demoscreenshots/plate3.png
The library is told that this is a Missouri (MO) license plate which validates the plate letters against a regional template.
```
user@linux:~/openalpr$ alpr ./samplecar.png -t mo -r ~/openalpr/runtime_dir/
plate0: top 10 results -- Processing Time = 58.1879ms.
- PE3R2X confidence: 88.9371 template_match: 1
- PE32X confidence: 78.1385 template_match: 0
- PE3R2 confidence: 77.5444 template_match: 0
- PE3R2Y confidence: 76.1448 template_match: 1
- P63R2X confidence: 72.9016 template_match: 0
- FE3R2X confidence: 72.1147 template_match: 1
- PE32 confidence: 66.7458 template_match: 0
- PE32Y confidence: 65.3462 template_match: 0
- P632X confidence: 62.1031 template_match: 0
- P63R2 confidence: 61.5089 template_match: 0
```
Compiling
-----------
OpenALPR has been compiled successfully on Linux, however other operating systems should also work.
OpenALPR requires the following additional libraries:
- Tesseract OCR v3.x (https://code.google.com/p/tesseract-ocr/)
- OpenCV v2.4.x (http://opencv.org/)
After cloning this GitHub repository, you should download and extract Tesseract and OpenCV source code into their own directories. Compile both libraries.
Update the src/CMakeLists.txt file in the OpenALPR project. Update the following lines to match the directories of your libraries on your system:
* SET(OpenCV_DIR "../libraries/opencv/")
* SET(Tesseract_DIR "/home/mhill/projects/alpr/libraries/tesseract-ocr")
Finally, in the src directory, execute the following commands:
* cmake ./
* make
If all went well, there should be an executable named *alpr* along with *libopenalpr.a* that can be linked into your project.
Questions
---------
Please post questions or comments to the Google group list: https://groups.google.com/forum/#!forum/openalpr
License
-------
Affero GPLv3
http://www.gnu.org/licenses/agpl-3.0.html

23
cla.txt Normal file
View File

@@ -0,0 +1,23 @@
In order to clarify the intellectual property license granted with Contributions from any person or entity, New Designs Unlimited, LLC. ("NDU") must have a Contributor License Agreement ("CLA") on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of NDU; it does not change your rights to use your own Contributions for any other purpose.
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to NDU. Except for the license granted herein to NDU and recipients of software distributed by NDU, You reserve all right, title, and interest in and to Your Contributions.
Definitions.
"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with NDU. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to NDU for inclusion in, or documentation of, any of the products owned or managed by NDU (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to NDU or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, NDU for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to NDU and to recipients of software distributed by NDU a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to NDU and to recipients of software distributed by NDU a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to NDU, or that your employer has executed a separate Corporate CLA with NDU.
You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
Should You wish to submit work that is not Your original creation, You may submit it to NDU separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [[]named here]".
You agree to notify NDU of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

115
runtime_data/openalpr.conf Normal file
View File

@@ -0,0 +1,115 @@
[common]
ocr_img_size_percent = 1.33333333
state_id_img_size_percent = 2.0
max_plate_width_percent = 100
max_plate_height_percent = 100
ocr_min_font_point = 6
; Minimum OCR confidence percent to consider.
postprocess_min_confidence = 60
; Any OCR character lower than this will also add an equally likely
; chance that the character is incorrect and will be skipped. Value is a confidence percent
postprocess_confidence_skip_level = 75
; Reduces the total permutations to consider for scoring.
postprocess_max_substitutions = 2
; Results with fewer characters will be discarded
postprocess_min_characers = 4
postprocess_max_characers = 8
[debug]
general = 0
timing = 0
state_id = 0
plate_lines = 0
plate_corners = 0
char_regions = 0
char_segment = 0
char_analysis = 0
color_filter = 0
ocr = 0
postprocess = 0
show_images = 0
;;; Country Specific variables ;;;;
[us]
; 30-50, 40-60, 50-70, 60-80, 70-90
char_analysis_min_pct = 0.30
char_analysis_height_range = 0.20
char_analysis_height_step_size = 0.10
char_analysis_height_num_steps = 5
segmentation_min_box_width_px = 4
segmentation_min_charheight_percent = 0.3;
segmentation_max_segment_width_percent_vs_average = 1.35;
plate_width_mm = 304.8
plate_height_mm = 152.4
char_height_mm = 70
char_width_mm = 35
char_whitespace_top_mm = 38
char_whitespace_bot_mm = 38
template_max_width_px = 120
template_max_height_px = 60
; Higher sensitivity means less lines
plateline_sensitivity_vertical = 25
plateline_sensitivity_horizontal = 45
; Regions smaller than this will be disqualified
min_plate_size_width_px = 70
min_plate_size_height_px = 35
ocr_language = lus
[eu]
; 55-70, 65-80, 75-90, 85-100
char_analysis_min_pct = 0.45
char_analysis_height_range = 0.15
char_analysis_height_step_size = 0.10
char_analysis_height_num_steps = 5
segmentation_min_box_width_px = 8
segmentation_min_charheight_percent = 0.5;
segmentation_max_segment_width_percent_vs_average = 2.0;
plate_width_mm = 520
plate_height_mm = 110
char_height_mm = 80
char_width_mm = 53
char_whitespace_top_mm = 10
char_whitespace_bot_mm = 10
template_max_width_px = 184
template_max_height_px = 46
; Higher sensitivity means less lines
plateline_sensitivity_vertical = 18
plateline_sensitivity_horizontal = 55
; Regions smaller than this will be disqualified
min_plate_size_width_px = 100
min_plate_size_height_px = 20
ocr_language = leu

View File

View File

@@ -0,0 +1,7 @@
Each line is a possible lp pattern organized by region/state and then likelihood.
The parser goes through each line and tries to match
@ = any letter
# = any number
? = a skip position (can be anything, but remove it if encountered)
[A-FGZ] is just a single char position with specific letter requirements. In this example, the regex defines characters ABCDEFGZ

View File

@@ -0,0 +1,212 @@
base @@@####
base @@@###
base ###@@@
al #@##@##
al ##@##@#
al @@#####
al #####@@
al #@####@
al ##@###@
al ##@#@#@
ak @@@###
as ####
az @@@####
az ?@@@####
az ###@@@
ar ###@@@
ar @@@###
ca #@@@###
ca #@@@###
ca #@#####
ca #####@#
ca ###@@@
co ###@@@
co @@####
co @@@###
co @@@####
ct #@@@@#
ct ###@@@
ct #####
ct ######
ct @###
ct @@###
ct @@####
de ######
de #####
de ####
de ###
dc @@####
dc ######
fl @@@@##
fl ####[GH]@
fl ###[H-Y]@@
fl @###@@
fl @##@@@
fl @###@@
fl @@@?@##
fl ###?#[GH]@
fl ###?[H-Y]@@
fl @##?#@@
fl @##?@@@
fl @##?#@@
ga @@@####
ga ####@@@
ga ####@@
ga #####[Q]@
ga ###@@@
gu @@####
gu @@@####
gu @@@###@
hi [A-HJKNMPR-Z]@@###
id @######
id #@#####
id #@@####
id #@@@###
id [A]#####[T]
id [A]@####[T]
id #[A]####[T]
id #[A]@###[T]
id [BU]####
id ####[BEFGHIJKLMNPRSTUXYZ]
id ##[S][A][S]
id #@@#[S]
id [J]@###
id #####[BCS]
id ###@[E]
id ##@@[E]
id #####
il @######
il #####
il ######
il @@####
il @@@###
il @#####
il #######
il ####@
il #####@
in ###@
in ###@@
in ###@@@
in ####
ia @@@###
ia ###@@@
ia ####@@
ks ###@@@
ks @@@###
ky ###@@@
la @@@###
me ####@@
me ###@@
me ##@@
me ###@@@
ms @@@@@#
ms @@@###
ms @@@?###
ms ##[W]##
md #@@@##
md #[AB]####
md @@@###
md ###[AB][A-N][A-MY]
md [AB][A-E][A-Y]##@
md #####[ABCY]
md @#####
ma ###@@#
ma #@@###
ma #@@@##
ma ######
ma ###@@@
ma ####@@
ma ##@@##
mi @@@####
mi #@@@##
mn ###@@@
mn @@@###
mo @@#@#@
mo ###@@@
mo #@@##@
mt ######[A]
mt #####@
mt ####@
ne #@####
ne #@@###
ne ##@###
ne ##@@##
nv ###@@@
nv @#####
nv @@####
nv @@@###
nh ######
nh #######
nh @@@###
nj @##@@@
nj @@@##@
nj @@###@
nj @@@####
nm @@@###
nm ###@@@
nm @@@###
nm @@###
nm @###
ny @@@####
ny @@@###
ny #@@###
ny @#@###
ny @###@@
ny @@###@
nc @@@####
nd @@@###
mp @@@###
oh @@@####
oh @@##@@
ok ###@@@
or ###@@@
or @@@###
or ###?@@@
or @@@?###
pa @@@####
pr @@@###
ri @@###
ri ######
ri #####
sc @@@###
sc ####@@
sd #@@###
sd ##@###
sd ##@@##
sd ##@@@#
tn ###@@@
tx @@@####
tx @##@@@
tx ###@@@
tx @@@###
tx @@#@###
ut @###@@
ut @###@@
ut ###@#
ut ###@@@
ut @@@###
ut @##?#@@
ut @##?#@@
ut ###?@#
ut ###?@@@
ut @@@?###
vt @@@###
vt ###@###
vt ##[B]##
vi @@@###
va @@@####
va [J-Z]@@####
va @@@###
va @@####
va ####@@
va #####[JY]
wa ###@@@
wa @@@####
wv [1-9DON]@@###
wv [1-9DON]@####
wy ######
wy #######
wy #####
wy ####@
wy ###@@
wy ##@@@

794
runtime_data/region/eu.xml Normal file
View File

@@ -0,0 +1,794 @@
<?xml version="1.0"?>
<opencv_storage>
<cascade>
<stageType>BOOST</stageType>
<featureType>LBP</featureType>
<height>13</height>
<width>52</width>
<stageParams>
<boostType>GAB</boostType>
<minHitRate>9.9500000476837158e-01</minHitRate>
<maxFalseAlarm>5.0000000000000000e-01</maxFalseAlarm>
<weightTrimRate>9.4999999999999996e-01</weightTrimRate>
<maxDepth>1</maxDepth>
<maxWeakCount>100</maxWeakCount></stageParams>
<featureParams>
<maxCatCount>256</maxCatCount>
<featSize>1</featSize></featureParams>
<stageNum>13</stageNum>
<stages>
<!-- stage 0 -->
<_>
<maxWeakCount>5</maxWeakCount>
<stageThreshold>-1.0891276597976685e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 28 352329180 -175631 -15754753 -173569 -1164334081
-5526021 -1649426433 -1073862520</internalNodes>
<leafValues>
-8.2864367961883545e-01 4.8592057824134827e-01</leafValues></_>
<_>
<internalNodes>
0 -1 4 -286263569 -2102402321 -534598418 -353374577 -2098177
2011675391 -1473 -150996993</internalNodes>
<leafValues>
-6.9584220647811890e-01 5.5770379304885864e-01</leafValues></_>
<_>
<internalNodes>
0 -1 21 -1658523684 -3753748 -1638885633 -1073860924
-148705297 -4219205 -1078266113 -1356330260</internalNodes>
<leafValues>
-6.7338258028030396e-01 5.8551847934722900e-01</leafValues></_>
<_>
<internalNodes>
0 -1 43 823137617 -8441479 -41 -1228801 -1077740829
-69494529 -1348542529 -1111619332</internalNodes>
<leafValues>
-6.1313426494598389e-01 5.7877427339553833e-01</leafValues></_>
<_>
<internalNodes>
0 -1 57 -1050130 181039615 1040326952 61194906 1532956580
640426412 1996563789 2145386463</internalNodes>
<leafValues>
-7.2357940673828125e-01 5.3356456756591797e-01</leafValues></_></weakClassifiers></_>
<!-- stage 1 -->
<_>
<maxWeakCount>5</maxWeakCount>
<stageThreshold>-9.8854637145996094e-01</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 74 -253 -359664761 -1022376704 -942685554 -77862893
1391706287 -203955712 -134217729</internalNodes>
<leafValues>
-7.9230999946594238e-01 5.3734940290451050e-01</leafValues></_>
<_>
<internalNodes>
0 -1 18 -2103041794 -2420585 -292465441 -8957641 -1147163905
-71444327 -1123811329 -1950445889</internalNodes>
<leafValues>
-6.7109984159469604e-01 5.2774083614349365e-01</leafValues></_>
<_>
<internalNodes>
0 -1 26 -958403858 537524426 -1073003830 -1563432246
275475263 169722019 -739266557 -204472333</internalNodes>
<leafValues>
-6.2689256668090820e-01 5.7461816072463989e-01</leafValues></_>
<_>
<internalNodes>
0 -1 48 -286267614 -1567719296 -1045512160 37978761
-72244469 575078921 38732035 -469763093</internalNodes>
<leafValues>
-5.4586493968963623e-01 5.7905143499374390e-01</leafValues></_>
<_>
<internalNodes>
0 -1 52 -1627890276 -539224611 424991221 -35959907 -41645316
-1154983528 1058863805 -1097069384</internalNodes>
<leafValues>
-6.5484923124313354e-01 5.2270460128784180e-01</leafValues></_></weakClassifiers></_>
<!-- stage 2 -->
<_>
<maxWeakCount>4</maxWeakCount>
<stageThreshold>-1.8370269536972046e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 37 1431346684 -537013775 -1123001891 -2162347
-1702327825 -4347461 -1953956866 -1089984500</internalNodes>
<leafValues>
-8.2538139820098877e-01 4.0558964014053345e-01</leafValues></_>
<_>
<internalNodes>
0 -1 14 -1832854276 -544078180 -1811917825 -141162300
-1954358273 -41221669 -1426021649 -1163646968</internalNodes>
<leafValues>
-7.3796498775482178e-01 4.7394013404846191e-01</leafValues></_>
<_>
<internalNodes>
0 -1 1 -2102754562 -67680845 -112394849 -141565556
-1592870465 -4200277 -540738561 -1351728664</internalNodes>
<leafValues>
-6.2538594007492065e-01 5.4038310050964355e-01</leafValues></_>
<_>
<internalNodes>
0 -1 51 268443088 2144609117 -1754192003 1904871869
312118475 -72677336 -1987240737 780406832</internalNodes>
<leafValues>
-8.2318174839019775e-01 3.5170540213584900e-01</leafValues></_></weakClassifiers></_>
<!-- stage 3 -->
<_>
<maxWeakCount>5</maxWeakCount>
<stageThreshold>-1.3525897264480591e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 13 -1508977665 -1057447 -34121761 -540319749 -569640001
-1339237 -342098945 -1364206081</internalNodes>
<leafValues>
-7.5512629747390747e-01 5.0126796960830688e-01</leafValues></_>
<_>
<internalNodes>
0 -1 33 520099280 1597862321 898108883 -18035 1485454798
-273143652 -14050881 -1613809444</internalNodes>
<leafValues>
-6.4212208986282349e-01 4.7490003705024719e-01</leafValues></_>
<_>
<internalNodes>
0 -1 56 547364912 -2395823 -114049065 1964708223 -1171563521
-5256977 -1115117137 -1678169866</internalNodes>
<leafValues>
-6.5468692779541016e-01 4.2123568058013916e-01</leafValues></_>
<_>
<internalNodes>
0 -1 63 -21056708 1040462754 926941452 390376382 1332548548
492073200 1597633341 2138045439</internalNodes>
<leafValues>
-7.5756329298019409e-01 3.3693149685859680e-01</leafValues></_>
<_>
<internalNodes>
0 -1 6 -286610690 7889566 -923630852 -281375265 -643215128
397426828 1607180287 -554238081</internalNodes>
<leafValues>
-6.8040394783020020e-01 3.8098624348640442e-01</leafValues></_></weakClassifiers></_>
<!-- stage 4 -->
<_>
<maxWeakCount>5</maxWeakCount>
<stageThreshold>-1.1867469549179077e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 0 -823459841 -8924257 -619054113 -4194819 -1466958849
-2367267 -75760641 -823207425</internalNodes>
<leafValues>
-7.2308593988418579e-01 4.5390692353248596e-01</leafValues></_>
<_>
<internalNodes>
0 -1 72 -608178429 -874522369 -62262784 -486606033 -20972793
293317947 -234882267 -134221857</internalNodes>
<leafValues>
-6.1693340539932251e-01 4.7333467006683350e-01</leafValues></_>
<_>
<internalNodes>
0 -1 24 -282542852 -14125448 -607988225 985927440
-1076299265 -71640899 2145434619 -1968559064</internalNodes>
<leafValues>
-6.7338615655899048e-01 4.0413773059844971e-01</leafValues></_>
<_>
<internalNodes>
0 -1 55 -1071390686 -54536730 -2119197217 -171443501
-307238675 -334529970 1733814651 1110426695</internalNodes>
<leafValues>
-5.9707373380661011e-01 4.2942461371421814e-01</leafValues></_>
<_>
<internalNodes>
0 -1 35 1900082676 -552307788 622089147 2073940261
-1113802728 -2520758 933810168 283651232</internalNodes>
<leafValues>
-6.8028992414474487e-01 3.9772990345954895e-01</leafValues></_></weakClassifiers></_>
<!-- stage 5 -->
<_>
<maxWeakCount>6</maxWeakCount>
<stageThreshold>-1.5750128030776978e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 16 -33556993 -52634121 -545 -33554497 -1572865
-35925285 -1 -1</internalNodes>
<leafValues>
-6.4789474010467529e-01 6.8716579675674438e-01</leafValues></_>
<_>
<internalNodes>
0 -1 45 276824529 -1073785419 -44370465 -12895747
-1159143426 -1078200165 -561136897 -1080355410</internalNodes>
<leafValues>
-5.9955614805221558e-01 4.4649451971054077e-01</leafValues></_>
<_>
<internalNodes>
0 -1 2 -353383921 1651439343 -875639586 -892410273
1191004023 1369560507 -1749073306 -176171433</internalNodes>
<leafValues>
-5.0599515438079834e-01 4.9024388194084167e-01</leafValues></_>
<_>
<internalNodes>
0 -1 47 530060796 -1653009667 448302035 521999321 2016688604
-1074226724 -1618575621 -1073853763</internalNodes>
<leafValues>
-5.9446120262145996e-01 4.2087084054946899e-01</leafValues></_>
<_>
<internalNodes>
0 -1 38 -1071136254 1223685519 1471152063 -134250673
1994376079 -345059705 -744244737 1109902082</internalNodes>
<leafValues>
-6.2733697891235352e-01 3.7706780433654785e-01</leafValues></_>
<_>
<internalNodes>
0 -1 66 -289474716 492493535 1633723308 663684984 -706944683
469863266 1912419244 1862268927</internalNodes>
<leafValues>
-5.9589976072311401e-01 3.8489931821823120e-01</leafValues></_></weakClassifiers></_>
<!-- stage 6 -->
<_>
<maxWeakCount>7</maxWeakCount>
<stageThreshold>-1.4239969253540039e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 70 -2097401 2147480859 -1686906241 -537929249
-208950013 -70520649 -2360434 -67109889</internalNodes>
<leafValues>
-6.9891244173049927e-01 4.6869069337844849e-01</leafValues></_>
<_>
<internalNodes>
0 -1 19 -293603474 181316838 -609486917 -28311826 -111673345
-31469125 -5506049 -19922945</internalNodes>
<leafValues>
-5.4466742277145386e-01 4.3645247817039490e-01</leafValues></_>
<_>
<internalNodes>
0 -1 32 2108988376 -1073981975 923187955 -29909155 -72553316
-1683061819 -1683427877 529006664</internalNodes>
<leafValues>
-6.8817603588104248e-01 3.2912179827690125e-01</leafValues></_>
<_>
<internalNodes>
0 -1 49 -270538870 -389823518 55248416 -1031340886
-643591296 4219461 -405545198 1395127295</internalNodes>
<leafValues>
-6.8365395069122314e-01 3.0457839369773865e-01</leafValues></_>
<_>
<internalNodes>
0 -1 27 -1998585712 -3221643 -2121639969 -809041 -346030083
-23343465 -1078461833 -1415313396</internalNodes>
<leafValues>
-6.5273976325988770e-01 3.6138573288917542e-01</leafValues></_>
<_>
<internalNodes>
0 -1 64 -2113681918 -1294213469 -132286017 -712527217
-916456545 -119827030 -913585586 1075835683</internalNodes>
<leafValues>
-5.8287250995635986e-01 3.8705006241798401e-01</leafValues></_>
<_>
<internalNodes>
0 -1 30 -276836593 -1965109297 -765473682 -453055127
-628622583 406486027 -683059631 -245368065</internalNodes>
<leafValues>
-5.3347849845886230e-01 4.2466723918914795e-01</leafValues></_></weakClassifiers></_>
<!-- stage 7 -->
<_>
<maxWeakCount>6</maxWeakCount>
<stageThreshold>-1.9118229150772095e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 77 -209 -273681601 -1215703776 -69208333 -67109083
398458087 -71304188 -167773185</internalNodes>
<leafValues>
-7.7420461177825928e-01 1.9384615123271942e-01</leafValues></_>
<_>
<internalNodes>
0 -1 11 -16844833 -1610869765 -1139346433 531641518
-1698705665 -72641841 -270861569 -18153985</internalNodes>
<leafValues>
-5.1794987916946411e-01 5.1850950717926025e-01</leafValues></_>
<_>
<internalNodes>
0 -1 8 -890320154 1616805846 -569442322 -51972884 1983217523
1882690923 -149225483 -134743041</internalNodes>
<leafValues>
-6.4354902505874634e-01 3.4861961007118225e-01</leafValues></_>
<_>
<internalNodes>
0 -1 53 -545997892 2146712560 1027094448 1072698174
987312028 1072971401 2141460220 1073741823</internalNodes>
<leafValues>
-7.3423033952713013e-01 2.7811017632484436e-01</leafValues></_>
<_>
<internalNodes>
0 -1 61 -839913558 1383071418 -1031696629 177087465
-2124516560 406474775 -750558174 57146367</internalNodes>
<leafValues>
-6.6036331653594971e-01 3.1190189719200134e-01</leafValues></_>
<_>
<internalNodes>
0 -1 62 -1084269292 -15240231 -655132905 -8584425
-1433421644 -1610766851 501946608 -1665981136</internalNodes>
<leafValues>
-5.6968742609024048e-01 4.0613368153572083e-01</leafValues></_></weakClassifiers></_>
<!-- stage 8 -->
<_>
<maxWeakCount>7</maxWeakCount>
<stageThreshold>-1.9141877889633179e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 69 -249 -671092433 -207620097 -1058825 -204472321
-268437547 -3407873 -1048593</internalNodes>
<leafValues>
-6.5279299020767212e-01 4.8660713434219360e-01</leafValues></_>
<_>
<internalNodes>
0 -1 23 -65537 -1073840649 -1090600999 -1112359043
-268509457 -196609 -1090666497 -1075118081</internalNodes>
<leafValues>
-4.4178628921508789e-01 5.2866697311401367e-01</leafValues></_>
<_>
<internalNodes>
0 -1 54 -1610382333 -536871001 -4097 -134221825 -268437505
-1 -272900165 -2110004729</internalNodes>
<leafValues>
-4.3439030647277832e-01 4.9104723334312439e-01</leafValues></_>
<_>
<internalNodes>
0 -1 29 -645621284 -74591240 1734207223 -241348156
-1428227122 -73901090 -1222624529 339740672</internalNodes>
<leafValues>
-6.9722545146942139e-01 3.1018492579460144e-01</leafValues></_>
<_>
<internalNodes>
0 -1 7 -957363474 1145487462 -840710418 -20520813 1933828693
1729360047 -1162608945 -705762305</internalNodes>
<leafValues>
-6.5354657173156738e-01 3.5359036922454834e-01</leafValues></_>
<_>
<internalNodes>
0 -1 59 -1118220 1072477084 -716423692 900504924 407658456
713846217 1067723772 -537921537</internalNodes>
<leafValues>
-5.8904892206192017e-01 3.6263024806976318e-01</leafValues></_>
<_>
<internalNodes>
0 -1 73 -16781553 -504110849 -137626336 2020002273
-1549809821 1108234023 -176708078 -439034884</internalNodes>
<leafValues>
-5.1023495197296143e-01 4.1906473040580750e-01</leafValues></_></weakClassifiers></_>
<!-- stage 9 -->
<_>
<maxWeakCount>7</maxWeakCount>
<stageThreshold>-1.3252449035644531e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 17 -1364197385 -8365 -8388609 -36725793 -308288513
-2169185 -1323009 -1364198401</internalNodes>
<leafValues>
-6.9964027404785156e-01 3.1848186254501343e-01</leafValues></_>
<_>
<internalNodes>
0 -1 10 235841278 -11560137 -1781388161 -137386726
1574787071 -1615148845 -1610863873 -9460806</internalNodes>
<leafValues>
-5.4633712768554688e-01 3.4596458077430725e-01</leafValues></_>
<_>
<internalNodes>
0 -1 68 -1066417145 -2049 -536870913 -33033 -142608385 -17
-67125761 -1036794873</internalNodes>
<leafValues>
-3.7736514210700989e-01 5.1756191253662109e-01</leafValues></_>
<_>
<internalNodes>
0 -1 46 -8736836 925633468 -1649269292 330746758 1061227216
1604410751 958224828 2113929211</internalNodes>
<leafValues>
-6.0834312438964844e-01 3.2764193415641785e-01</leafValues></_>
<_>
<internalNodes>
0 -1 58 134217761 -145820769 -918561281 -32565 -342885637
-1104166501 -276054129 1059268039</internalNodes>
<leafValues>
-6.9328433275222778e-01 2.9834014177322388e-01</leafValues></_>
<_>
<internalNodes>
0 -1 25 -491526289 13550039 -471015962 -520687409 1891889001
1848636827 -195366252 -67117217</internalNodes>
<leafValues>
-5.0798887014389038e-01 4.2228734493255615e-01</leafValues></_>
<_>
<internalNodes>
0 -1 67 -63500032 2073776991 -648816647 -681346729
-1731736049 -895557187 -661139524 403862288</internalNodes>
<leafValues>
-6.5347433090209961e-01 3.0001255869865417e-01</leafValues></_></weakClassifiers></_>
<!-- stage 10 -->
<_>
<maxWeakCount>7</maxWeakCount>
<stageThreshold>-1.4286619424819946e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 15 -1537 341630967 -644612737 -41945765 -67904561
-67114549 -71570945 -33554433</internalNodes>
<leafValues>
-6.7523366212844849e-01 3.1159418821334839e-01</leafValues></_>
<_>
<internalNodes>
0 -1 50 -81924 1073219548 -1656450052 531635190 797730296
529286869 453778140 -16465</internalNodes>
<leafValues>
-6.2270468473434448e-01 2.5747936964035034e-01</leafValues></_>
<_>
<internalNodes>
0 -1 3 -6095106 -540174978 -880374563 -147402106 -1426218753
-72770389 -102766085 -1431346578</internalNodes>
<leafValues>
-4.5853179693222046e-01 4.0388166904449463e-01</leafValues></_>
<_>
<internalNodes>
0 -1 65 458754 -1277427969 -115352577 -2124241 -72647681
-607148621 -38831637 1073971266</internalNodes>
<leafValues>
-7.1188706159591675e-01 2.6471686363220215e-01</leafValues></_>
<_>
<internalNodes>
0 -1 75 -274727637 -1962155238 -172493568 -292301350
-74516140 -689320765 -257446400 -134423309</internalNodes>
<leafValues>
-4.9807366728782654e-01 3.6395463347434998e-01</leafValues></_>
<_>
<internalNodes>
0 -1 60 -84740 468980664 454041488 534654744 464272364
176965560 1983490748 1073428479</internalNodes>
<leafValues>
-6.9375485181808472e-01 2.5259119272232056e-01</leafValues></_>
<_>
<internalNodes>
0 -1 39 -451948633 -878710325 -89990998 1928771826 -70590373
106065995 1971450036 -185073665</internalNodes>
<leafValues>
-4.4236001372337341e-01 4.2276427149772644e-01</leafValues></_></weakClassifiers></_>
<!-- stage 11 -->
<_>
<maxWeakCount>7</maxWeakCount>
<stageThreshold>-1.3514715433120728e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 22 -1 830472175 -4194305 -100665153 -275461 -67110917
-100663297 -67109889</internalNodes>
<leafValues>
-6.0122072696685791e-01 6.6442954540252686e-01</leafValues></_>
<_>
<internalNodes>
0 -1 9 -536871186 -319887182 -572858202 -387529530
-263783649 1936881186 -208273929 -145498425</internalNodes>
<leafValues>
-5.4182785749435425e-01 3.4675773978233337e-01</leafValues></_>
<_>
<internalNodes>
0 -1 34 429658036 366774229 -1757103107 -1074039435
756829181 1055017305 -70350401 -1074360147</internalNodes>
<leafValues>
-5.7372617721557617e-01 3.2286378741264343e-01</leafValues></_>
<_>
<internalNodes>
0 -1 40 -33636948 991968120 -103904560 1002483790
-1179373060 255358432 1071392492 2113848255</internalNodes>
<leafValues>
-6.6383731365203857e-01 2.5130525231361389e-01</leafValues></_>
<_>
<internalNodes>
0 -1 44 -2147318784 -2925 -784346277 -536882289 -747914266
-352392513 -337117441 -2146721146</internalNodes>
<leafValues>
-6.1697775125503540e-01 2.7669304609298706e-01</leafValues></_>
<_>
<internalNodes>
0 -1 12 -815355970 2035791128 -1130456197 1048879784
-1685378358 -880031607 635238911 -1982913778</internalNodes>
<leafValues>
-4.7567644715309143e-01 3.8744848966598511e-01</leafValues></_>
<_>
<internalNodes>
0 -1 71 -100668145 185059827 -197985022 -1561928745
1593365762 827494795 -940055742 -140640293</internalNodes>
<leafValues>
-5.3159093856811523e-01 3.6455613374710083e-01</leafValues></_></weakClassifiers></_>
<!-- stage 12 -->
<_>
<maxWeakCount>8</maxWeakCount>
<stageThreshold>-1.7042466402053833e+00</stageThreshold>
<weakClassifiers>
<_>
<internalNodes>
0 -1 0 -286590977 -1059105 -67956225 -143142401 -306186497
-67394918 -1074790401 -810549761</internalNodes>
<leafValues>
-6.9610232114791870e-01 2.5562700629234314e-01</leafValues></_>
<_>
<internalNodes>
0 -1 20 -52441857 -723714835 -2110241 -637534465 -34181129
-729097 -16779299 -71368705</internalNodes>
<leafValues>
-4.0139526128768921e-01 5.0971853733062744e-01</leafValues></_>
<_>
<internalNodes>
0 -1 5 -268439697 -1407736901 1323822782 -388012338
1206856575 2103180175 1979148022 -416350275</internalNodes>
<leafValues>
-4.3721720576286316e-01 3.8813734054565430e-01</leafValues></_>
<_>
<internalNodes>
0 -1 36 -1757391624 -1619958841 1569675345 1026588057
-1709392434 -9961349 -616780808 182717056</internalNodes>
<leafValues>
-6.2012439966201782e-01 2.8761184215545654e-01</leafValues></_>
<_>
<internalNodes>
0 -1 41 201327621 -11320879 -950796801 -59561 -1416692737
-124038232 -1409552433 579429813</internalNodes>
<leafValues>
-7.2984564304351807e-01 2.4998624622821808e-01</leafValues></_>
<_>
<internalNodes>
0 -1 42 -870064096 285238103 1820599346 -1621180257
285589700 1678906683 838856958 -270533921</internalNodes>
<leafValues>
-5.2406799793243408e-01 3.4712812304496765e-01</leafValues></_>
<_>
<internalNodes>
0 -1 76 1329575926 244245492 -951086856 -1126403084
-877660755 25837750 -119410448 -717432460</internalNodes>
<leafValues>
-5.7642680406570435e-01 2.9808703064918518e-01</leafValues></_>
<_>
<internalNodes>
0 -1 31 -411331404 899449501 471203871 1061499199 421532152
-1951856520 238168271 268370447</internalNodes>
<leafValues>
-4.8114898800849915e-01 3.8295701146125793e-01</leafValues></_></weakClassifiers></_></stages>
<features>
<_>
<rect>
0 0 4 1</rect></_>
<_>
<rect>
0 0 5 1</rect></_>
<_>
<rect>
0 1 1 3</rect></_>
<_>
<rect>
0 1 12 1</rect></_>
<_>
<rect>
0 2 3 2</rect></_>
<_>
<rect>
0 3 1 2</rect></_>
<_>
<rect>
0 4 4 3</rect></_>
<_>
<rect>
0 5 3 2</rect></_>
<_>
<rect>
0 7 2 1</rect></_>
<_>
<rect>
0 8 1 1</rect></_>
<_>
<rect>
0 9 15 1</rect></_>
<_>
<rect>
0 10 4 1</rect></_>
<_>
<rect>
0 10 17 1</rect></_>
<_>
<rect>
1 0 3 1</rect></_>
<_>
<rect>
1 1 14 1</rect></_>
<_>
<rect>
1 4 2 3</rect></_>
<_>
<rect>
1 6 2 2</rect></_>
<_>
<rect>
2 0 2 1</rect></_>
<_>
<rect>
2 0 10 1</rect></_>
<_>
<rect>
2 1 2 2</rect></_>
<_>
<rect>
2 6 2 2</rect></_>
<_>
<rect>
2 10 12 1</rect></_>
<_>
<rect>
3 1 1 3</rect></_>
<_>
<rect>
3 10 2 1</rect></_>
<_>
<rect>
3 10 7 1</rect></_>
<_>
<rect>
4 3 1 1</rect></_>
<_>
<rect>
4 3 1 2</rect></_>
<_>
<rect>
5 0 4 1</rect></_>
<_>
<rect>
5 0 14 1</rect></_>
<_>
<rect>
6 1 12 1</rect></_>
<_>
<rect>
6 5 1 1</rect></_>
<_>
<rect>
7 2 13 1</rect></_>
<_>
<rect>
7 10 12 1</rect></_>
<_>
<rect>
8 9 14 1</rect></_>
<_>
<rect>
9 9 4 1</rect></_>
<_>
<rect>
10 1 12 1</rect></_>
<_>
<rect>
10 1 14 1</rect></_>
<_>
<rect>
11 10 13 1</rect></_>
<_>
<rect>
12 0 2 3</rect></_>
<_>
<rect>
13 4 1 1</rect></_>
<_>
<rect>
13 4 1 3</rect></_>
<_>
<rect>
14 0 1 1</rect></_>
<_>
<rect>
14 6 9 1</rect></_>
<_>
<rect>
18 0 4 1</rect></_>
<_>
<rect>
20 0 1 2</rect></_>
<_>
<rect>
20 0 5 1</rect></_>
<_>
<rect>
21 4 1 3</rect></_>
<_>
<rect>
21 9 7 1</rect></_>
<_>
<rect>
22 4 1 2</rect></_>
<_>
<rect>
22 4 2 2</rect></_>
<_>
<rect>
22 7 1 2</rect></_>
<_>
<rect>
23 0 7 1</rect></_>
<_>
<rect>
23 10 4 1</rect></_>
<_>
<rect>
25 7 2 2</rect></_>
<_>
<rect>
27 0 1 2</rect></_>
<_>
<rect>
28 0 1 3</rect></_>
<_>
<rect>
28 0 7 1</rect></_>
<_>
<rect>
28 4 2 3</rect></_>
<_>
<rect>
29 0 1 1</rect></_>
<_>
<rect>
32 7 1 2</rect></_>
<_>
<rect>
32 7 2 2</rect></_>
<_>
<rect>
33 2 2 3</rect></_>
<_>
<rect>
34 10 6 1</rect></_>
<_>
<rect>
35 4 2 3</rect></_>
<_>
<rect>
36 0 1 3</rect></_>
<_>
<rect>
36 0 2 2</rect></_>
<_>
<rect>
39 1 1 4</rect></_>
<_>
<rect>
40 0 4 1</rect></_>
<_>
<rect>
41 0 1 2</rect></_>
<_>
<rect>
49 0 1 2</rect></_>
<_>
<rect>
49 1 1 1</rect></_>
<_>
<rect>
49 2 1 1</rect></_>
<_>
<rect>
49 3 1 1</rect></_>
<_>
<rect>
49 3 1 2</rect></_>
<_>
<rect>
49 4 1 2</rect></_>
<_>
<rect>
49 7 1 1</rect></_>
<_>
<rect>
49 7 1 2</rect></_>
<_>
<rect>
49 8 1 1</rect></_></features></cascade>
</opencv_storage>

1256
runtime_data/region/us.xml Normal file

File diff suppressed because it is too large Load Diff

56
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,56 @@
project(src)
cmake_minimum_required (VERSION 2.6)
IF (WIN32)
add_definitions( -DWINDOWS)
add_definitions( -DNOMINMAX)
SET(OpenCV_DIR "C:\\projects\\alpr\\libraries\\opencv")
SET(Tesseract_DIR "C:\\projects\\alpr\\libraries\\tesseract-3.02")
include_directories(
${Tesseract_DIR}/include/tesseract
)
link_directories( ${Tesseract_DIR}/lib/ )
ELSE()
SET(OpenCV_DIR "../libraries/opencv/")
SET(Tesseract_DIR "/home/mhill/projects/alpr/libraries/tesseract-ocr")
include_directories(
${Tesseract_DIR}/api
${Tesseract_DIR}/ccutil/
${Tesseract_DIR}/ccstruct/
${Tesseract_DIR}/ccmain/
)
link_directories( ${Tesseract_DIR}/api/.libs/ )
ENDIF()
# Opencv Package
FIND_PACKAGE( OpenCV REQUIRED )
IF (${OpenCV_VERSION} VERSION_LESS 2.3.0)
MESSAGE(FATAL_ERROR "OpenCV version is not compatible : ${OpenCV_VERSION}")
ENDIF()
include_directories(./openalpr )
set(CMAKE_CSS_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall ")
ADD_EXECUTABLE( alpr main.cpp )
TARGET_LINK_LIBRARIES(alpr
openalpr
support
${OpenCV_LIBS}
tesseract
)
add_subdirectory(openalpr)
add_subdirectory(misc_utilities)

255
src/main.cpp Normal file
View File

@@ -0,0 +1,255 @@
/*
* 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 <iostream>
#include <stdio.h>
#include <sys/stat.h>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "tclap/CmdLine.h"
#include "support/filesystem.h"
#include "support/timing.h"
#include "alpr.h"
const std::string MAIN_WINDOW_NAME = "ALPR main window";
const bool SAVE_LAST_VIDEO_STILL = false;
const std::string LAST_VIDEO_STILL_LOCATION = "/tmp/laststill.jpg";
/** Function Headers */
bool detectandshow(Alpr* alpr, cv::Mat frame, std::string region, bool writeJson);
bool measureProcessingTime = false;
int main( int argc, const char** argv )
{
std::string filename;
std::string runtimePath = "";
bool outputJson = false;
int seektoms = 0;
bool detectRegion = false;
std::string templateRegion;
std::string country;
int topn;
try {
TCLAP::CmdLine cmd("OpenAlpr Command Line Utility", ' ', OPENALPR_VERSION);
TCLAP::UnlabeledValueArg<std::string> fileArg( "image_file", "Image containing license plates", true, "", "image_file_path" );
TCLAP::ValueArg<std::string> countryCodeArg("c","country","Country code to identify (either us for USA or eu for Europe). Default=us",false, "us" ,"country_code");
TCLAP::ValueArg<int> seekToMsArg("","seek","Seek to the specied millisecond in a video file. Default=0",false, 0 ,"integer_ms");
TCLAP::ValueArg<std::string> runtimeDirArg("r","runtime_dir","Path to the OpenAlpr runtime data directory",false, "" ,"runtime_dir");
TCLAP::ValueArg<std::string> templateRegionArg("t","template_region","Attempt to match the plate number against a region template (e.g., md for Maryland, ca for California)",false, "" ,"region code");
TCLAP::ValueArg<int> topNArg("n","topn","Max number of possible plate numbers to return. Default=10",false, 10 ,"topN");
TCLAP::SwitchArg jsonSwitch("j","json","Output recognition results in JSON format. Default=off", cmd, false);
TCLAP::SwitchArg detectRegionSwitch("d","detect_region","Attempt to detect the region of the plate image. Default=off", cmd, false);
TCLAP::SwitchArg clockSwitch("","clock","Measure/print the total time to process image and all plates. Default=off", cmd, false);
cmd.add( fileArg );
cmd.add( countryCodeArg );
cmd.add( seekToMsArg );
cmd.add( topNArg );
cmd.add( runtimeDirArg );
cmd.add( templateRegionArg );
cmd.parse( argc, argv );
filename = fileArg.getValue();
country = countryCodeArg.getValue();
seektoms = seekToMsArg.getValue();
outputJson = jsonSwitch.getValue();
runtimePath = runtimeDirArg.getValue();
detectRegion = detectRegionSwitch.getValue();
templateRegion = templateRegionArg.getValue();
topn = topNArg.getValue();
measureProcessingTime = clockSwitch.getValue();
} catch (TCLAP::ArgException &e) // catch any exceptions
{
std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl;
return 1;
}
cv::Mat frame;
Alpr alpr(country, runtimePath);
alpr.setTopN(topn);
if (detectRegion)
alpr.setDetectRegion(detectRegion);
if (strcmp(templateRegion.c_str(), "") != 0)
{
alpr.setDefaultRegion(templateRegion);
}
if (alpr.isLoaded() == false)
{
std::cerr << "Error loading OpenAlpr" << std::endl;
return 1;
}
if (strcmp(filename.c_str(), "webcam") == 0)
{
int framenum = 0;
cv::VideoCapture cap(0);
if (!cap.isOpened())
{
std::cout << "Error opening webcam" << std::endl;
return 1;
}
while (cap.read(frame) == true)
{
detectandshow(&alpr, frame, "", outputJson);
cv::waitKey(1);
framenum++;
}
}
else if (hasEnding(filename, ".avi") || hasEnding(filename, ".mp4") || hasEnding(filename, ".webm") || hasEnding(filename, ".flv"))
{
int framenum = 0;
cv::VideoCapture cap=cv::VideoCapture();
cap.open(filename);
cap.set(CV_CAP_PROP_POS_MSEC, seektoms);
while (cap.read(frame) == true)
{
if (SAVE_LAST_VIDEO_STILL == true)
{
cv::imwrite(LAST_VIDEO_STILL_LOCATION, frame);
}
std::cout << "Frame: " << framenum << std::endl;
detectandshow( &alpr, frame, "", outputJson);
//create a 1ms delay
cv::waitKey(1);
framenum++;
}
}
else if (hasEnding(filename, ".png") || hasEnding(filename, ".jpg") || hasEnding(filename, ".gif"))
{
frame = cv::imread( filename );
detectandshow( &alpr, frame, "", outputJson);
}
else if (DirectoryExists(filename.c_str()))
{
std::vector<std::string> files = getFilesInDir(filename.c_str());
std::sort( files.begin(), files.end(), stringCompare );
for (int i = 0; i< files.size(); i++)
{
if (hasEnding(files[i], ".jpg") || hasEnding(files[i], ".png"))
{
std::string fullpath = filename + "/" + files[i];
std::cout << fullpath << std::endl;
frame = cv::imread( fullpath.c_str() );
if (detectandshow( &alpr, frame, "", outputJson))
{
//while ((char) cv::waitKey(50) != 'c') { }
}
else
{
//cv::waitKey(50);
}
}
}
}
else
{
std::cerr << "Unknown file type" << std::endl;
return 1;
}
return 0;
}
bool detectandshow( Alpr* alpr, cv::Mat frame, std::string region, bool writeJson)
{
std::vector<uchar> buffer;
cv::imencode(".bmp", frame, buffer );
timespec startTime;
getTime(&startTime);
std::vector<AlprResult> results = alpr->recognize(buffer);
if (writeJson)
{
std::cout << alpr->toJson(results) << std::endl;
}
else
{
for (int i = 0; i < results.size(); i++)
{
std::cout << "plate" << i << ": " << results[i].result_count << " results -- Processing Time = " << results[i].processing_time_ms << "ms." << std::endl;
for (int k = 0; k < results[i].topNPlates.size(); k++)
{
std::cout << " - " << results[i].topNPlates[k].characters << "\t confidence: " << results[i].topNPlates[k].overall_confidence << "\t template_match: " << results[i].topNPlates[k].matches_template << std::endl;
}
}
}
timespec endTime;
getTime(&endTime);
if (measureProcessingTime)
std::cout << "Total Time to process image: " << diffclock(startTime, endTime) << "ms." << std::endl;
if (results.size() > 0)
return true;
return false;
}

View File

@@ -0,0 +1,38 @@
target_link_libraries(openalpr)
ADD_EXECUTABLE( sortstate sortstate.cpp )
TARGET_LINK_LIBRARIES(sortstate
openalpr
support
${OpenCV_LIBS}
tesseract
)
ADD_EXECUTABLE( classifychars classifychars.cpp )
TARGET_LINK_LIBRARIES(classifychars
openalpr
support
${OpenCV_LIBS}
tesseract
)
ADD_EXECUTABLE( benchmark benchmark.cpp )
TARGET_LINK_LIBRARIES(benchmark
openalpr
support
${OpenCV_LIBS}
tesseract
)
ADD_EXECUTABLE( prepcharsfortraining prepcharsfortraining.cpp )
TARGET_LINK_LIBRARIES(prepcharsfortraining
support
${OpenCV_LIBS}
)

View File

@@ -0,0 +1,361 @@
/*
* 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 "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <sys/stat.h>
#include <numeric> // std::accumulate
#include "alpr_impl.h"
//#include "stage1.h"
//#include "stage2.h"
//#include "stateidentifier.h"
//#include "utility.h"
#include "support/filesystem.h"
using namespace std;
using namespace cv;
// Given a directory full of lp images (named [statecode]#.png) crop out the alphanumeric characters.
// These will be used to train the OCR
void outputStats(vector<double> datapoints);
int main( int argc, const char** argv )
{
string country;
string benchmarkName;
string inDir;
string outDir;
Mat frame;
//Check if user specify image to process
if(argc == 5)
{
country = argv[1];
benchmarkName = argv[2];
inDir = argv[3];
outDir = argv[4];
}else{
printf("Use:\n\t%s [country] [benchmark name] [img input dir] [results output dir]\n",argv[0]);
printf("\tex: %s us speed ./speed/usimages ./speed\n",argv[0]);
printf("\n");
printf("\ttest names are: speed, segocr, detection\n\n" );
return 0;
}
if (DirectoryExists(inDir.c_str()) == false)
{
printf("Input dir does not exist\n");
return 0;
}
if (DirectoryExists(outDir.c_str()) == false)
{
printf("Output dir does not exist\n");
return 0;
}
vector<string> files = getFilesInDir(inDir.c_str());
sort( files.begin(), files.end(), stringCompare );
if (benchmarkName.compare("segocr") == 0)
{
Config* config = new Config(country);
config->debugOff();
OCR* ocr = new OCR(config);
for (int i = 0; i< files.size(); i++)
{
if (hasEnding(files[i], ".png"))
{
string fullpath = inDir + "/" + files[i];
frame = imread( fullpath.c_str() );
resize(frame, frame, Size(config->ocrImageWidthPx, config->ocrImageHeightPx));
Rect plateCoords;
plateCoords.x = 0;
plateCoords.y = 0;
plateCoords.width = frame.cols;
plateCoords.height = frame.rows;
char statecode[3];
statecode[0] = files[i][0];
statecode[1] = files[i][1];
statecode[2] = '\0';
string statecodestr(statecode);
CharacterRegion charRegion(frame, config);
if (abs(charRegion.getTopLine().angle) > 4)
{
// Rotate image:
Mat rotated(frame.size(), frame.type());
Mat rot_mat( 2, 3, CV_32FC1 );
Point center = Point( frame.cols/2, frame.rows/2 );
rot_mat = getRotationMatrix2D( center, charRegion.getTopLine().angle, 1.0 );
warpAffine( frame, rotated, rot_mat, frame.size() );
rotated.copyTo(frame);
}
CharacterSegmenter charSegmenter(frame, charRegion.thresholdsInverted(), config);
ocr->performOCR(charSegmenter.getThresholds(), charSegmenter.characters);
ocr->postProcessor->analyze(statecode, 25);
cout << files[i] << "," << statecode << "," << ocr->postProcessor->bestChars << endl;
imshow("Current LP", frame);
waitKey(5);
}
}
delete config;
delete ocr;
}
else if (benchmarkName.compare("detection") == 0)
{
Config config(country);
RegionDetector plateDetector(&config);
for (int i = 0; i< files.size(); i++)
{
if (hasEnding(files[i], ".png"))
{
string fullpath = inDir + "/" + files[i];
frame = imread( fullpath.c_str() );
vector<Rect> regions = plateDetector.detect(frame);
imshow("Current LP", frame);
waitKey(5);
}
}
}
else if (benchmarkName.compare("speed") == 0)
{
// Benchmarks speed of region detection, plate analysis, and OCR
timespec startTime;
timespec endTime;
Config config(country);
config.debugOff();
AlprImpl alpr(country);
alpr.config->debugOff();
alpr.setDetectRegion(true);
RegionDetector plateDetector(&config);
StateIdentifier stateIdentifier(&config);
OCR ocr(&config);
vector<double> endToEndTimes;
vector<double> regionDetectionTimes;
vector<double> stateIdTimes;
vector<double> lpAnalysisPositiveTimes;
vector<double> lpAnalysisNegativeTimes;
vector<double> ocrTimes;
vector<double> postProcessTimes;
for (int i = 0; i< files.size(); i++)
{
if (hasEnding(files[i], ".png"))
{
cout << "Image: " << files[i] << endl;
string fullpath = inDir + "/" + files[i];
frame = imread( fullpath.c_str() );
getTime(&startTime);
alpr.recognize(frame);
getTime(&endTime);
double endToEndTime = diffclock(startTime, endTime);
cout << " -- End to End recognition time: " << endToEndTime << "ms." << endl;
endToEndTimes.push_back(endToEndTime);
getTime(&startTime);
vector<Rect> regions = plateDetector.detect(frame);
getTime(&endTime);
double regionDetectionTime = diffclock(startTime, endTime);
cout << " -- Region detection time: " << regionDetectionTime << "ms." << endl;
regionDetectionTimes.push_back(regionDetectionTime);
for (int z = 0; z < regions.size(); z++)
{
getTime(&startTime);
char temp[5];
stateIdentifier.recognize(frame, regions[z], temp);
getTime(&endTime);
double stateidTime = diffclock(startTime, endTime);
cout << "\tRegion " << z << ": State ID time: " << stateidTime << "ms." << endl;
stateIdTimes.push_back(stateidTime);
getTime(&startTime);
LicensePlateCandidate lp(frame, regions[z], &config);
lp.recognize();
getTime(&endTime);
double analysisTime = diffclock(startTime, endTime);
cout << "\tRegion " << z << ": Analysis time: " << analysisTime << "ms." << endl;
if (lp.confidence > 10)
{
lpAnalysisPositiveTimes.push_back(analysisTime);
getTime(&startTime);
ocr.performOCR(lp.charSegmenter->getThresholds(), lp.charSegmenter->characters);
getTime(&endTime);
double ocrTime = diffclock(startTime, endTime);
cout << "\tRegion " << z << ": OCR time: " << ocrTime << "ms." << endl;
ocrTimes.push_back(ocrTime);
getTime(&startTime);
ocr.postProcessor->analyze("", 25);
getTime(&endTime);
double postProcessTime = diffclock(startTime, endTime);
cout << "\tRegion " << z << ": PostProcess time: " << postProcessTime << "ms." << endl;
postProcessTimes.push_back(postProcessTime);
}
else
{
lpAnalysisNegativeTimes.push_back(analysisTime);
}
}
waitKey(5);
}
}
cout << endl << "---------------------" << endl;
cout << "End to End Time Statistics:" << endl;
outputStats(endToEndTimes);
cout << endl;
cout << "Region Detection Time Statistics:" << endl;
outputStats(regionDetectionTimes);
cout << endl;
cout << "State ID Time Statistics:" << endl;
outputStats(stateIdTimes);
cout << endl;
cout << "Positive Region Analysis Time Statistics:" << endl;
outputStats(lpAnalysisPositiveTimes);
cout << endl;
cout << "Negative Region Analysis Time Statistics:" << endl;
outputStats(lpAnalysisNegativeTimes);
cout << endl;
cout << "OCR Time Statistics:" << endl;
outputStats(ocrTimes);
cout << endl;
cout << "Post Processing Time Statistics:" << endl;
outputStats(postProcessTimes);
cout << endl;
}
else if (benchmarkName.compare("endtoend") == 0)
{
Alpr alpr(country);
alpr.setDetectRegion(true);
ofstream outputdatafile;
outputdatafile.open("results.txt");
for (int i = 0; i< files.size(); i++)
{
if (hasEnding(files[i], ".png"))
{
string fullpath = inDir + "/" + files[i];
frame = imread( fullpath.c_str() );
vector<uchar> buffer;
imencode(".bmp", frame, buffer );
vector<AlprResult> results = alpr.recognize(buffer);
outputdatafile << files[i] << ": ";
for (int z = 0; z < results.size(); z++)
{
outputdatafile << results[z].bestPlate.characters << ", ";
}
outputdatafile << endl;
imshow("Current LP", frame);
waitKey(5);
}
}
outputdatafile.close();
}
}
void outputStats(vector<double> datapoints)
{
double sum = std::accumulate(datapoints.begin(), datapoints.end(), 0.0);
double mean = sum / datapoints.size();
std::vector<double> diff(datapoints.size());
std::transform(datapoints.begin(), datapoints.end(), diff.begin(),
std::bind2nd(std::minus<double>(), mean));
double sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
double stdev = std::sqrt(sq_sum / datapoints.size());
cout << "\t" << datapoints.size() << " samples, avg: " << mean << "ms, stdev: " << stdev << endl;
}

View File

@@ -0,0 +1,355 @@
/*
* 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 "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <sys/stat.h>
#include "regiondetector.h"
#include "licenseplatecandidate.h"
#include "stateidentifier.h"
#include "utility.h"
#include "support/filesystem.h"
#include "ocr.h"
using namespace std;
using namespace cv;
// Given a directory full of lp images (named [statecode]#.png) crop out the alphanumeric characters.
// These will be used to train the OCR
const int LEFT_ARROW_KEY = 81;
const int RIGHT_ARROW_KEY = 83;
const int SPACE_KEY = 32;
const int ENTER_KEY = 10;
const int ESCAPE_KEY = 27;
const int DOWN_ARROW_KEY = 84;
const int UP_ARROW_KEY= 82;
const int DASHBOARD_COLUMNS = 3;
void showDashboard(vector<Mat> images, vector<bool> selectedImages, int selectedIndex);
vector<char> showCharSelection(Mat image, vector<Rect> charRegions, string state);
int main( int argc, const char** argv )
{
string inDir;
string outDir;
Mat frame;
//Check if user specify image to process
if(argc == 3)
{
inDir = argv[1];
outDir = argv[2];
}else{
printf("Use:\n\t%s indirectory outdirectory\n",argv[0]);
printf("Ex: \n\t%s ./pics/ ./out \n",argv[0]);
return 0;
}
if (DirectoryExists(outDir.c_str()) == false)
{
printf("Output dir does not exist\n");
return 0;
}
cout << "Usage: " << endl;
cout << "\tn -- Next plate" << endl;
cout << "\tp -- Previous plate" << endl;
cout << "\ts -- Save characters" << endl;
cout << "\t<- and -> -- Cycle between images" << endl;
cout << "\tEnt/space -- Select plate" << endl;
cout << endl;
cout << "Within a plate" << endl;
cout << "\t<- and -> -- Cycle between characters" << endl;
cout << "\t[0-9A-Z] -- Identify a character (saves the image)" << endl;
cout << "\tESC/Ent/Space -- Back to plate selection" << endl;
Config* config = new Config("eu");
OCR ocr(config);
if (DirectoryExists(inDir.c_str()))
{
vector<string> files = getFilesInDir(inDir.c_str());
sort( files.begin(), files.end(), stringCompare );
for (int i = 0; i< files.size(); i++)
{
if (hasEnding(files[i], ".png") || hasEnding(files[i], ".jpg"))
{
string fullpath = inDir + "/" + files[i];
cout << fullpath << endl;
frame = imread( fullpath.c_str() );
resize(frame, frame, Size(config->ocrImageWidthPx, config->ocrImageHeightPx));
imshow ("Original", frame);
char statecode[3];
statecode[0] = files[i][0];
statecode[1] = files[i][1];
statecode[2] = '\0';
string statecodestr(statecode);
CharacterRegion regionizer(frame, config);
if (abs(regionizer.getTopLine().angle) > 4)
{
// Rotate image:
Mat rotated(frame.size(), frame.type());
Mat rot_mat( 2, 3, CV_32FC1 );
Point center = Point( frame.cols/2, frame.rows/2 );
rot_mat = getRotationMatrix2D( center, regionizer.getTopLine().angle, 1.0 );
warpAffine( frame, rotated, rot_mat, frame.size() );
rotated.copyTo(frame);
}
CharacterSegmenter charSegmenter(frame, regionizer.thresholdsInverted(), config);
//ocr.cleanCharRegions(charSegmenter.thresholds, charSegmenter.characters);
ocr.performOCR(charSegmenter.getThresholds(), charSegmenter.characters);
ocr.postProcessor->analyze(statecodestr, 25);
cout << "OCR results: " << ocr.postProcessor->bestChars << endl;
vector<bool> selectedBoxes(charSegmenter.getThresholds().size());
for (int z = 0; z < charSegmenter.getThresholds().size(); z++)
selectedBoxes[z] = false;
int curDashboardSelection = 0;
vector<char> humanInputs(charSegmenter.characters.size());
for (int z = 0; z < charSegmenter.characters.size(); z++)
humanInputs[z] = ' ';
showDashboard(charSegmenter.getThresholds(), selectedBoxes, 0);
char waitkey = (char) waitKey(50);
while (waitkey != 'n' && waitkey != 'p') // Next image
{
if (waitkey == LEFT_ARROW_KEY) // left arrow key
{
if (curDashboardSelection > 0)
curDashboardSelection--;
showDashboard(charSegmenter.getThresholds(), selectedBoxes, curDashboardSelection);
}
else if (waitkey == RIGHT_ARROW_KEY) // right arrow key
{
if (curDashboardSelection < charSegmenter.getThresholds().size() - 1)
curDashboardSelection++;
showDashboard(charSegmenter.getThresholds(), selectedBoxes, curDashboardSelection);
}
else if (waitkey == DOWN_ARROW_KEY)
{
if (curDashboardSelection + DASHBOARD_COLUMNS <= charSegmenter.getThresholds().size() - 1)
curDashboardSelection += DASHBOARD_COLUMNS;
showDashboard(charSegmenter.getThresholds(), selectedBoxes, curDashboardSelection);
}
else if (waitkey == UP_ARROW_KEY)
{
if (curDashboardSelection - DASHBOARD_COLUMNS >= 0)
curDashboardSelection -= DASHBOARD_COLUMNS;
showDashboard(charSegmenter.getThresholds(), selectedBoxes, curDashboardSelection);
}
else if (waitkey == ENTER_KEY)
{
vector<char> tempdata = showCharSelection(charSegmenter.getThresholds()[curDashboardSelection], charSegmenter.characters, statecodestr);
for (int c = 0; c < charSegmenter.characters.size(); c++)
humanInputs[c] = tempdata[c];
}
else if (waitkey == SPACE_KEY)
{
selectedBoxes[curDashboardSelection] = !selectedBoxes[curDashboardSelection];
showDashboard(charSegmenter.getThresholds(), selectedBoxes, curDashboardSelection);
}
else if (waitkey == 's' || waitkey == 'S')
{
bool somethingSelected = false;
bool chardataTagged = false;
for (int c = 0; c < charSegmenter.getThresholds().size(); c++)
{
if (selectedBoxes[c])
{
somethingSelected = true;
break;
}
}
for (int c = 0; c < charSegmenter.characters.size(); c++)
{
if (humanInputs[c] != ' ')
{
chardataTagged = true;
break;
}
}
// Save
if (somethingSelected && chardataTagged)
{
for (int c = 0; c < charSegmenter.characters.size(); c++)
{
if (humanInputs[c] == ' ')
continue;
for (int t = 0; t < charSegmenter.getThresholds().size(); t++)
{
if (selectedBoxes[t] == false)
continue;
stringstream filename;
Mat cropped = charSegmenter.getThresholds()[t](charSegmenter.characters[c]);
filename << outDir << "/" << humanInputs[c] << "-" << t << "-" << files[i];
imwrite(filename.str(), cropped);
cout << "Writing char image: " << filename.str() << endl;
}
}
}
else if (somethingSelected == false)
cout << "Did not select any boxes" << endl;
else if (chardataTagged == false)
cout << "You have not tagged any characters" << endl;
}
waitkey = (char) waitKey(50);
}
if (waitkey == 'p')
i = i - 2;
if (i < -1)
i = -1;
}
}
}
}
void showDashboard(vector<Mat> images, vector<bool> selectedImages, int selectedIndex)
{
vector<Mat> vecCopy;
if (selectedIndex < 0)
selectedIndex = 0;
if (selectedIndex >= images.size())
selectedIndex = images.size() -1;
for (int i = 0; i < images.size(); i++)
{
Mat imgCopy(images[i].size(), images[i].type());
images[i].copyTo(imgCopy);
cvtColor(imgCopy, imgCopy, CV_GRAY2BGR);
if (i == selectedIndex)
{
rectangle(imgCopy, Point(1,1), Point(imgCopy.size().width - 1, imgCopy.size().height -1), Scalar(0, 255, 0), 1);
}
if (selectedImages[i] == true)
{
rectangle(imgCopy, Point(2,2), Point(imgCopy.size().width - 2, imgCopy.size().height -2), Scalar(255, 0, 0), 1);
}
vecCopy.push_back(imgCopy);
}
Mat dashboard = drawImageDashboard(vecCopy, vecCopy[0].type(), DASHBOARD_COLUMNS);
imshow("Selection dashboard", dashboard);
}
vector<char> showCharSelection(Mat image, vector<Rect> charRegions, string state)
{
int curCharIdx = 0;
vector<char> humanInputs(charRegions.size());
for (int i = 0; i < charRegions.size(); i++)
humanInputs[i] = (char) SPACE_KEY;
char waitkey = (char) waitKey(50);
while (waitkey != ENTER_KEY && waitkey != ESCAPE_KEY)
{
Mat imgCopy(image.size(), image.type());
image.copyTo(imgCopy);
cvtColor(imgCopy, imgCopy, CV_GRAY2BGR);
rectangle(imgCopy, charRegions[curCharIdx], Scalar(0, 255, 0), 1);
imshow("Character selector", imgCopy);
if (waitkey == LEFT_ARROW_KEY)
curCharIdx--;
else if (waitkey == RIGHT_ARROW_KEY )
curCharIdx++;
else if ((waitkey >= '0' && waitkey <= '9') || (waitkey >= 'a' && waitkey <= 'z') || waitkey == SPACE_KEY)
{
// Save the character to disk
humanInputs[curCharIdx] = toupper((char) waitkey);
curCharIdx++;
if (curCharIdx >= charRegions.size())
{
waitkey = (char) ENTER_KEY;
break;
}
}
if (curCharIdx < 0)
curCharIdx = 0;
if (curCharIdx >= charRegions.size())
curCharIdx = charRegions.size() -1;
waitkey = (char) waitKey(50);
}
if (waitkey == ENTER_KEY)
{
// Save all the inputs
for (int i = 0; i < charRegions.size(); i++)
{
if (humanInputs[i] != (char) SPACE_KEY)
cout << "Tagged " << state << " char code: '" << humanInputs[i] << "' at char position: " << i << endl;
}
}
destroyWindow("Character selector");
return humanInputs;
}

View File

@@ -0,0 +1,173 @@
/*
* 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 "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <fstream>
#include <sys/stat.h>
#include "support/filesystem.h"
using namespace std;
using namespace cv;
// Takes a directory full of single char images, and plops them on a big tif files
// Also creates a box file so Tesseract can recognize it
int main( int argc, const char** argv )
{
string inDir;
//Check if user specify image to process
if(argc == 2)
{
inDir = argv[1];
}else{
printf("Use:\n\t%s input dir \n",argv[0]);
return 0;
}
if (DirectoryExists(inDir.c_str()) == false)
{
printf("Output dir does not exist\n");
return 0;
}
cout << "Usage: " << endl;
cout << "\tinputdir -- input dir for benchmark data" << endl;
if (DirectoryExists(inDir.c_str()))
{
const int X_OFFSET = 10;
const int Y_OFFSET = 10;
const int PAGE_MARGIN_X = 70;
const int PAGE_MARGIN_Y = 70;
const int HORIZONTAL_RESOLUTION = 3500;
const int TILE_WIDTH = 55;
const int CHAR_HORIZ_OFFSET = 40;
const int TILE_HEIGHT = 70;
const int CHAR_VERT_OFFSET = 48;
vector<string> files = getFilesInDir(inDir.c_str());
sort( files.begin(), files.end(), stringCompare );
int tiles_per_row = ((float) (HORIZONTAL_RESOLUTION - (PAGE_MARGIN_X * 2))) / ((float) TILE_WIDTH);
int lines = files.size() / (tiles_per_row);
int vertical_resolution = (lines * TILE_HEIGHT) + (PAGE_MARGIN_Y * 2) ;
cout << tiles_per_row << " : " << vertical_resolution << endl;
Mat bigTif = Mat::zeros(Size(HORIZONTAL_RESOLUTION, vertical_resolution), CV_8U);
bitwise_not(bigTif, bigTif);
stringstream boxFileOut;
for (int i = 0; i< files.size(); i++)
{
int col = i % tiles_per_row;
int line = i / tiles_per_row;
int xPos = (col * TILE_WIDTH) + PAGE_MARGIN_X;
int yPos = (line * TILE_HEIGHT) + PAGE_MARGIN_Y;
if (hasEnding(files[i], ".png") || hasEnding(files[i], ".jpg"))
{
string fullpath = inDir + "/" + files[i];
cout << "Processing file: " << (i + 1) << " of " << files.size() << endl;
char charcode = files[i][0];
Mat characterImg = imread(fullpath);
Mat charImgCopy = Mat::zeros(Size(150, 150), characterImg.type());
bitwise_not(charImgCopy, charImgCopy);
characterImg.copyTo(charImgCopy(Rect(X_OFFSET, Y_OFFSET, characterImg.cols, characterImg.rows)));
cvtColor(charImgCopy, charImgCopy, CV_BGR2GRAY);
bitwise_not(charImgCopy, charImgCopy);
vector<vector<Point> > contours;
//imshow("copy", charImgCopy);
findContours(charImgCopy, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
Rect tallestRect(0, 0, 0, 0);
for (int c = 0; c < contours.size(); c++)
{
Rect tmpRect = boundingRect(contours[c]);
if (tmpRect.height > tallestRect.height)
tallestRect = tmpRect;
}
//cout << tallestRect.x << ":" << tallestRect.y << " -- " << tallestRect.width << ":" << tallestRect.height << endl;
Rect cropRect(0, tallestRect.y - Y_OFFSET, tallestRect.width, tallestRect.height);
//cout << "Cropped: " << cropRect.x << ":" << cropRect.y << " -- " << cropRect.width << ":" << cropRect.height << endl;
Mat cropped(characterImg, cropRect);
cvtColor(cropped, cropped, CV_BGR2GRAY);
Rect destinationRect(xPos + (CHAR_HORIZ_OFFSET - tallestRect.width), yPos + (CHAR_VERT_OFFSET - tallestRect.height), tallestRect.width, tallestRect.height);
//cout << "1" << endl;
cropped.copyTo(bigTif(destinationRect));
int x1= destinationRect.x - 2;
int y1 = (vertical_resolution - destinationRect.y - destinationRect.height) - 2;
int x2 = (destinationRect.x + destinationRect.width) + 2;
int y2 = (vertical_resolution - destinationRect.y) + 2;
//0 70 5602 85 5636 0
boxFileOut << charcode << " " << x1 << " " << y1 << " ";
boxFileOut << x2 << " " << y2 ;
boxFileOut << " 0" << endl;
//rectangle(characterImg, tallestRect, Scalar(0, 255, 0));
//imshow("characterImg", cropped);
waitKey(2);
}
}
imwrite("combined.tif", bigTif);
ofstream boxFile("combined.box", std::ios::out);
boxFile << boxFileOut.str();
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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 "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <sys/stat.h>
#include "regiondetector.h"
#include "licenseplatecandidate.h"
#include "stateidentifier.h"
#include "utility.h"
#include "support/filesystem.h"
using namespace std;
using namespace cv;
// Given a directory full of pre-cropped images, identify the state that each image belongs to.
// This is used to sort our own positive image database as a first step before grabbing characters to use to train the OCR.
bool detectPlate( StateIdentifier* identifier, Mat frame);
int main( int argc, const char** argv )
{
string inDir;
string outDir;
Mat frame;
//Check if user specify image to process
if(argc == 3 )
{
inDir = argv[1];
outDir = argv[2];
outDir = outDir + "/";
}else{
printf("Use:\n\t%s directory \n",argv[0]);
printf("Ex: \n\t%s ./pics/ \n",argv[0]);
return 0;
}
Config config("us");
StateIdentifier identifier(&config);
if (DirectoryExists(outDir.c_str()) == false)
{
printf("Output dir does not exist\n");
return 0;
}
if (DirectoryExists(inDir.c_str()))
{
vector<string> files = getFilesInDir(inDir.c_str());
for (int i = 0; i< files.size(); i++)
{
if (hasEnding(files[i], ".png"))
{
string fullpath = inDir + "/" + files[i];
cout << fullpath << endl;
frame = imread( fullpath.c_str() );
char code[4];
int confidence = identifier.recognize(frame, code);
if (confidence <= 20)
{
code[0] = 'z';
code[1] = 'z';
confidence = 100;
}
//imshow("Plate", frame);
if (confidence > 20)
{
cout << confidence << " : " << code;
ostringstream convert; // stream used for the conversion
convert << i; // insert the textual representation of 'Number' in the characters in the stream
string copyCommand = "cp \"" + fullpath + "\" " + outDir + code + convert.str() + ".png";
system( copyCommand.c_str() );
waitKey(50);
//while ((char) waitKey(50) != 'c') { }
}
else
waitKey(50);
}
}
}
}
bool detectPlate( StateIdentifier* identifier, Mat frame);

View File

@@ -0,0 +1,34 @@
set(lpr_source_files
alpr.cpp
alpr_impl.cpp
config.cpp
regiondetector.cpp
licenseplatecandidate.cpp
utility.cpp
stateidentifier.cpp
featurematcher.cpp
ocr.cpp
postprocess.cpp
binarize_wolf.cpp
platelines.cpp
characterregion.cpp
charactersegmenter.cpp
platecorners.cpp
colorfilter.cpp
characteranalysis.cpp
verticalhistogram.cpp
trex.c
cjson.c
)
add_library(openalpr ${lpr_source_files})
add_subdirectory(simpleini)
add_subdirectory(support)

75
src/openalpr/TRexpp.h Normal file
View 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
View 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
View 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
View 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
View 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

View 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;
}
*/

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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

View 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;
}

View 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
View 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
View 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
View 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"

View 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;
}

View 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

View 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);
}
}

View 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
View 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"

Some files were not shown because too many files have changed in this diff Show More