├── PossiblePlate.cpp
├── archive
├── PossiblePlate.cpp
├── readme.txt
├── PossibleChar.cpp
├── Preprocess.h
├── classifications.xml
├── DetectPlates.h
├── PossiblePlate.h
├── Main.h
├── PossibleChar.h
├── Preprocess.cpp
├── DetectChars.h
├── Main.cpp
├── DetectPlates.cpp
└── DetectChars.cpp
├── readme.txt
├── image1.png
├── image2.png
├── image3.png
├── image4.png
├── image5.png
├── image6.png
├── image7.png
├── image8.png
├── image9.png
├── image10.png
├── image11.png
├── image12.png
├── image13.png
├── image14.png
├── image15.png
├── image16.png
├── imgOriginalScene.png
├── DocsAndPresentation
├── steps.png
├── Steps With Images.docx
├── Steps With Images.pdf
└── steps.txt
├── packages.config
├── PossibleChar.cpp
├── Preprocess.h
├── classifications.xml
├── DetectPlates.h
├── PossiblePlate.h
├── Main.h
├── OpenCV_3_License_Plate_Recognition_Cpp.sln
├── PossibleChar.h
├── Preprocess.cpp
├── OpenCV_3_License_Plate_Recognition_Cpp.vcxproj.filters
├── DetectChars.h
├── .gitignore
├── Main.cpp
├── OpenCV_3_License_Plate_Recognition_Cpp.vcxproj
├── DetectPlates.cpp
└── DetectChars.cpp
/PossiblePlate.cpp:
--------------------------------------------------------------------------------
1 | // PossiblePlate.cpp
2 |
3 | #include "PossiblePlate.h"
4 |
5 |
--------------------------------------------------------------------------------
/archive/PossiblePlate.cpp:
--------------------------------------------------------------------------------
1 | // PossiblePlate.cpp
2 |
3 | #include "PossiblePlate.h"
4 |
5 |
--------------------------------------------------------------------------------
/readme.txt:
--------------------------------------------------------------------------------
1 | The video pretty much explains it all:
2 | https://www.youtube.com/watch?v=euG7-o9oPKg
3 |
--------------------------------------------------------------------------------
/archive/readme.txt:
--------------------------------------------------------------------------------
1 | The video pretty much explains it all:
2 | https://www.youtube.com/watch?v=euG7-o9oPKg
3 |
--------------------------------------------------------------------------------
/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image1.png
--------------------------------------------------------------------------------
/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image2.png
--------------------------------------------------------------------------------
/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image3.png
--------------------------------------------------------------------------------
/image4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image4.png
--------------------------------------------------------------------------------
/image5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image5.png
--------------------------------------------------------------------------------
/image6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image6.png
--------------------------------------------------------------------------------
/image7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image7.png
--------------------------------------------------------------------------------
/image8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image8.png
--------------------------------------------------------------------------------
/image9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image9.png
--------------------------------------------------------------------------------
/image10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image10.png
--------------------------------------------------------------------------------
/image11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image11.png
--------------------------------------------------------------------------------
/image12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image12.png
--------------------------------------------------------------------------------
/image13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image13.png
--------------------------------------------------------------------------------
/image14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image14.png
--------------------------------------------------------------------------------
/image15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image15.png
--------------------------------------------------------------------------------
/image16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/image16.png
--------------------------------------------------------------------------------
/imgOriginalScene.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/imgOriginalScene.png
--------------------------------------------------------------------------------
/DocsAndPresentation/steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/DocsAndPresentation/steps.png
--------------------------------------------------------------------------------
/DocsAndPresentation/Steps With Images.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/DocsAndPresentation/Steps With Images.docx
--------------------------------------------------------------------------------
/DocsAndPresentation/Steps With Images.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_License_Plate_Recognition_Cpp/HEAD/DocsAndPresentation/Steps With Images.pdf
--------------------------------------------------------------------------------
/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/PossibleChar.cpp:
--------------------------------------------------------------------------------
1 | // PossibleChar.cpp
2 |
3 | #include "PossibleChar.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | PossibleChar::PossibleChar(std::vector _contour) {
7 | contour = _contour;
8 |
9 | boundingRect = cv::boundingRect(contour);
10 |
11 | intCenterX = (boundingRect.x + boundingRect.x + boundingRect.width) / 2;
12 | intCenterY = (boundingRect.y + boundingRect.y + boundingRect.height) / 2;
13 |
14 | dblDiagonalSize = sqrt(pow(boundingRect.width, 2) + pow(boundingRect.height, 2));
15 |
16 | dblAspectRatio = (float)boundingRect.width / (float)boundingRect.height;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/archive/PossibleChar.cpp:
--------------------------------------------------------------------------------
1 | // PossibleChar.cpp
2 |
3 | #include "PossibleChar.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | PossibleChar::PossibleChar(std::vector _contour) {
7 | contour = _contour;
8 |
9 | boundingRect = cv::boundingRect(contour);
10 |
11 | intCenterX = (boundingRect.x + boundingRect.x + boundingRect.width) / 2;
12 | intCenterY = (boundingRect.y + boundingRect.y + boundingRect.height) / 2;
13 |
14 | dblDiagonalSize = sqrt(pow(boundingRect.width, 2) + pow(boundingRect.height, 2));
15 |
16 | dblAspectRatio = (float)boundingRect.width / (float)boundingRect.height;
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/Preprocess.h:
--------------------------------------------------------------------------------
1 | // Preprocess.h
2 |
3 | #ifndef PREPROCESS_H
4 | #define PREPROCESS_H
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // global variables ///////////////////////////////////////////////////////////////////////////////
11 | const cv::Size GAUSSIAN_SMOOTH_FILTER_SIZE = cv::Size(5, 5);
12 | const int ADAPTIVE_THRESH_BLOCK_SIZE = 19;
13 | const int ADAPTIVE_THRESH_WEIGHT = 9;
14 |
15 | // function prototypes ////////////////////////////////////////////////////////////////////////////
16 |
17 | void preprocess(cv::Mat &imgOriginal, cv::Mat &imgGrayscale, cv::Mat &imgThresh);
18 |
19 | cv::Mat extractValue(cv::Mat &imgOriginal);
20 |
21 | cv::Mat maximizeContrast(cv::Mat &imgGrayscale);
22 |
23 |
24 | #endif // PREPROCESS_H
25 |
26 |
--------------------------------------------------------------------------------
/archive/Preprocess.h:
--------------------------------------------------------------------------------
1 | // Preprocess.h
2 |
3 | #ifndef PREPROCESS_H
4 | #define PREPROCESS_H
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // global variables ///////////////////////////////////////////////////////////////////////////////
11 | const cv::Size GAUSSIAN_SMOOTH_FILTER_SIZE = cv::Size(5, 5);
12 | const int ADAPTIVE_THRESH_BLOCK_SIZE = 19;
13 | const int ADAPTIVE_THRESH_WEIGHT = 9;
14 |
15 | // function prototypes ////////////////////////////////////////////////////////////////////////////
16 |
17 | void preprocess(cv::Mat &imgOriginal, cv::Mat &imgGrayscale, cv::Mat &imgThresh);
18 |
19 | cv::Mat extractValue(cv::Mat &imgOriginal);
20 |
21 | cv::Mat maximizeContrast(cv::Mat &imgGrayscale);
22 |
23 | #endif // PREPROCESS_H
24 |
25 |
--------------------------------------------------------------------------------
/classifications.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 180
5 | 1
6 | i
7 |
8 | 90 84 82 80 77 70 69 68 66 65 89 88 87 86 85 83 81 79 78 76 75 74 73
9 | 72 71 67 57 56 55 54 53 51 50 48 52 49 82 80 77 68 66 65 90 89 88 87
10 | 86 85 84 83 81 79 78 76 75 74 73 72 71 70 69 67 52 49 57 56 55 54 53
11 | 51 50 48 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71
12 | 70 69 68 67 66 65 49 57 56 55 54 53 52 51 50 48 90 89 88 87 86 85 84
13 | 82 80 78 77 76 75 74 73 72 70 69 68 66 65 83 81 79 71 67 55 51 50 49
14 | 57 56 54 53 52 48 82 80 77 68 66 65 90 89 88 87 86 85 84 83 81 79 78
15 | 76 75 74 73 72 71 70 69 67 55 53 52 57 56 54 51 50 49 48
16 |
17 |
--------------------------------------------------------------------------------
/archive/classifications.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 180
5 | 1
6 | i
7 |
8 | 90 84 82 80 77 70 69 68 66 65 89 88 87 86 85 83 81 79 78 76 75 74 73
9 | 72 71 67 57 56 55 54 53 51 50 48 52 49 82 80 77 68 66 65 90 89 88 87
10 | 86 85 84 83 81 79 78 76 75 74 73 72 71 70 69 67 52 49 57 56 55 54 53
11 | 51 50 48 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71
12 | 70 69 68 67 66 65 49 57 56 55 54 53 52 51 50 48 90 89 88 87 86 85 84
13 | 82 80 78 77 76 75 74 73 72 70 69 68 66 65 83 81 79 71 67 55 51 50 49
14 | 57 56 54 53 52 48 82 80 77 68 66 65 90 89 88 87 86 85 84 83 81 79 78
15 | 76 75 74 73 72 71 70 69 67 55 53 52 57 56 54 51 50 49 48
16 |
17 |
--------------------------------------------------------------------------------
/DetectPlates.h:
--------------------------------------------------------------------------------
1 | // DetectPlates.h
2 |
3 | #ifndef DETECT_PLATES_H
4 | #define DETECT_PLATES_H
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | #include "Main.h"
11 | #include "PossiblePlate.h"
12 | #include "PossibleChar.h"
13 | #include "Preprocess.h"
14 | #include "DetectChars.h"
15 |
16 | // global constants ///////////////////////////////////////////////////////////////////////////////
17 | const double PLATE_WIDTH_PADDING_FACTOR = 1.3;
18 | const double PLATE_HEIGHT_PADDING_FACTOR = 1.5;
19 |
20 | // function prototypes ////////////////////////////////////////////////////////////////////////////
21 | std::vector detectPlatesInScene(cv::Mat &imgOriginalScene);
22 |
23 | std::vector findPossibleCharsInScene(cv::Mat &imgThresh);
24 |
25 | PossiblePlate extractPlate(cv::Mat &imgOriginal, std::vector &vectorOfMatchingChars);
26 |
27 |
28 | # endif // DETECT_PLATES_H
29 |
30 |
--------------------------------------------------------------------------------
/archive/DetectPlates.h:
--------------------------------------------------------------------------------
1 | // DetectPlates.h
2 |
3 | #ifndef DETECT_PLATES_H
4 | #define DETECT_PLATES_H
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | #include "Main.h"
11 | #include "PossiblePlate.h"
12 | #include "PossibleChar.h"
13 | #include "Preprocess.h"
14 | #include "DetectChars.h"
15 |
16 | // global constants ///////////////////////////////////////////////////////////////////////////////
17 | const double PLATE_WIDTH_PADDING_FACTOR = 1.3;
18 | const double PLATE_HEIGHT_PADDING_FACTOR = 1.5;
19 |
20 | // function prototypes ////////////////////////////////////////////////////////////////////////////
21 | std::vector detectPlatesInScene(cv::Mat &imgOriginalScene);
22 |
23 | std::vector findPossibleCharsInScene(cv::Mat &imgThresh);
24 |
25 | PossiblePlate extractPlate(cv::Mat &imgOriginal, std::vector &vectorOfMatchingChars);
26 |
27 |
28 | # endif // DETECT_PLATES_H
29 |
30 |
--------------------------------------------------------------------------------
/PossiblePlate.h:
--------------------------------------------------------------------------------
1 | // PossiblePlate.h
2 |
3 | #ifndef POSSIBLE_PLATE_H
4 | #define POSSIBLE_PLATE_H
5 |
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | ///////////////////////////////////////////////////////////////////////////////////////////////////
13 | class PossiblePlate {
14 | public:
15 | // member variables ///////////////////////////////////////////////////////////////////////////
16 | cv::Mat imgPlate;
17 | cv::Mat imgGrayscale;
18 | cv::Mat imgThresh;
19 |
20 | cv::RotatedRect rrLocationOfPlateInScene;
21 |
22 | std::string strChars;
23 |
24 | ///////////////////////////////////////////////////////////////////////////////////////////////
25 | static bool sortDescendingByNumberOfChars(const PossiblePlate &ppLeft, const PossiblePlate &ppRight) {
26 | return(ppLeft.strChars.length() > ppRight.strChars.length());
27 | }
28 |
29 | };
30 |
31 |
32 | #endif // end #ifndef POSSIBLE_PLATE_H
33 |
34 |
--------------------------------------------------------------------------------
/archive/PossiblePlate.h:
--------------------------------------------------------------------------------
1 | // PossiblePlate.h
2 |
3 | #ifndef POSSIBLE_PLATE_H
4 | #define POSSIBLE_PLATE_H
5 |
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | ///////////////////////////////////////////////////////////////////////////////////////////////////
13 | class PossiblePlate {
14 | public:
15 | // member variables ///////////////////////////////////////////////////////////////////////////
16 | cv::Mat imgPlate;
17 | cv::Mat imgGrayscale;
18 | cv::Mat imgThresh;
19 |
20 | cv::RotatedRect rrLocationOfPlateInScene;
21 |
22 | std::string strChars;
23 |
24 | ///////////////////////////////////////////////////////////////////////////////////////////////
25 | static bool sortDescendingByNumberOfChars(const PossiblePlate &ppLeft, const PossiblePlate &ppRight) {
26 | return(ppLeft.strChars.length() > ppRight.strChars.length());
27 | }
28 |
29 | };
30 |
31 | #endif // end #ifndef POSSIBLE_PLATE_H
32 |
33 |
--------------------------------------------------------------------------------
/archive/Main.h:
--------------------------------------------------------------------------------
1 | // Main.h
2 |
3 | #ifndef MY_MAIN // used MY_MAIN for this include guard rather than MAIN just in case some compilers or environments #define MAIN already
4 | #define MY_MAIN
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | #include "DetectPlates.h"
11 | #include "PossiblePlate.h"
12 | #include "DetectChars.h"
13 |
14 | #include
15 |
16 | //#define SHOW_STEPS // un-comment or comment this line to show steps or not
17 |
18 | // global constants ///////////////////////////////////////////////////////////////////////////////
19 | const cv::Scalar SCALAR_BLACK = cv::Scalar(0.0, 0.0, 0.0);
20 | const cv::Scalar SCALAR_WHITE = cv::Scalar(255.0, 255.0, 255.0);
21 | const cv::Scalar SCALAR_YELLOW = cv::Scalar(0.0, 255.0, 255.0);
22 | const cv::Scalar SCALAR_GREEN = cv::Scalar(0.0, 255.0, 0.0);
23 | const cv::Scalar SCALAR_RED = cv::Scalar(0.0, 0.0, 255.0);
24 |
25 | // function prototypes ////////////////////////////////////////////////////////////////////////////
26 | int main(void);
27 | void drawRedRectangleAroundPlate(cv::Mat &imgOriginalScene, PossiblePlate &licPlate);
28 | void writeLicensePlateCharsOnImage(cv::Mat &imgOriginalScene, PossiblePlate &licPlate);
29 |
30 |
31 | # endif // MAIN
32 |
33 |
--------------------------------------------------------------------------------
/Main.h:
--------------------------------------------------------------------------------
1 | // Main.h
2 |
3 | #ifndef MY_MAIN // used MY_MAIN for this include guard rather than MAIN just in case some compilers or environments #define MAIN already
4 | #define MY_MAIN
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | #include "DetectPlates.h"
11 | #include "PossiblePlate.h"
12 | #include "DetectChars.h"
13 |
14 | #include
15 | #include // may have to modify this line if not using Windows
16 |
17 | //#define SHOW_STEPS // un-comment or comment this line to show steps or not
18 |
19 | // global constants ///////////////////////////////////////////////////////////////////////////////
20 | const cv::Scalar SCALAR_BLACK = cv::Scalar(0.0, 0.0, 0.0);
21 | const cv::Scalar SCALAR_WHITE = cv::Scalar(255.0, 255.0, 255.0);
22 | const cv::Scalar SCALAR_YELLOW = cv::Scalar(0.0, 255.0, 255.0);
23 | const cv::Scalar SCALAR_GREEN = cv::Scalar(0.0, 255.0, 0.0);
24 | const cv::Scalar SCALAR_RED = cv::Scalar(0.0, 0.0, 255.0);
25 |
26 | // function prototypes ////////////////////////////////////////////////////////////////////////////
27 | int main(void);
28 | void drawRedRectangleAroundPlate(cv::Mat &imgOriginalScene, PossiblePlate &licPlate);
29 | void writeLicensePlateCharsOnImage(cv::Mat &imgOriginalScene, PossiblePlate &licPlate);
30 |
31 |
32 | # endif // MAIN
33 |
34 |
--------------------------------------------------------------------------------
/OpenCV_3_License_Plate_Recognition_Cpp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.23107.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenCV_3_License_Plate_Recognition_Cpp", "OpenCV_3_License_Plate_Recognition_Cpp.vcxproj", "{09A38949-D191-4247-9CA9-D983A592F3BD}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Debug|x64.ActiveCfg = Debug|x64
17 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Debug|x64.Build.0 = Debug|x64
18 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Debug|x86.ActiveCfg = Debug|Win32
19 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Debug|x86.Build.0 = Debug|Win32
20 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Release|x64.ActiveCfg = Release|x64
21 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Release|x64.Build.0 = Release|x64
22 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Release|x86.ActiveCfg = Release|Win32
23 | {09A38949-D191-4247-9CA9-D983A592F3BD}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/PossibleChar.h:
--------------------------------------------------------------------------------
1 | // PossibleChar.h
2 |
3 | #ifndef POSSIBLE_CHAR_H
4 | #define POSSIBLE_CHAR_H
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | ///////////////////////////////////////////////////////////////////////////////////////////////////
11 | class PossibleChar {
12 | public:
13 | // member variables ///////////////////////////////////////////////////////////////////////////
14 | std::vector contour;
15 |
16 | cv::Rect boundingRect;
17 |
18 | int intCenterX;
19 | int intCenterY;
20 |
21 | double dblDiagonalSize;
22 | double dblAspectRatio;
23 |
24 | ///////////////////////////////////////////////////////////////////////////////////////////////
25 | static bool sortCharsLeftToRight(const PossibleChar &pcLeft, const PossibleChar & pcRight) {
26 | return(pcLeft.intCenterX < pcRight.intCenterX);
27 | }
28 |
29 | ///////////////////////////////////////////////////////////////////////////////////////////////
30 | bool operator == (const PossibleChar& otherPossibleChar) const {
31 | if (this->contour == otherPossibleChar.contour) return true;
32 | else return false;
33 | }
34 |
35 | ///////////////////////////////////////////////////////////////////////////////////////////////
36 | bool operator != (const PossibleChar& otherPossibleChar) const {
37 | if (this->contour != otherPossibleChar.contour) return true;
38 | else return false;
39 | }
40 |
41 | // function prototypes ////////////////////////////////////////////////////////////////////////
42 | PossibleChar(std::vector _contour);
43 |
44 | };
45 |
46 | #endif // POSSIBLE_CHAR_H
47 |
48 |
--------------------------------------------------------------------------------
/archive/PossibleChar.h:
--------------------------------------------------------------------------------
1 | // PossibleChar.h
2 |
3 | #ifndef POSSIBLE_CHAR_H
4 | #define POSSIBLE_CHAR_H
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | ///////////////////////////////////////////////////////////////////////////////////////////////////
11 | class PossibleChar {
12 | public:
13 | // member variables ///////////////////////////////////////////////////////////////////////////
14 | std::vector contour;
15 |
16 | cv::Rect boundingRect;
17 |
18 | int intCenterX;
19 | int intCenterY;
20 |
21 | double dblDiagonalSize;
22 | double dblAspectRatio;
23 |
24 | ///////////////////////////////////////////////////////////////////////////////////////////////
25 | static bool sortCharsLeftToRight(const PossibleChar &pcLeft, const PossibleChar & pcRight) {
26 | return(pcLeft.intCenterX < pcRight.intCenterX);
27 | }
28 |
29 | ///////////////////////////////////////////////////////////////////////////////////////////////
30 | bool operator == (const PossibleChar& otherPossibleChar) const {
31 | if (this->contour == otherPossibleChar.contour) return true;
32 | else return false;
33 | }
34 |
35 | ///////////////////////////////////////////////////////////////////////////////////////////////
36 | bool operator != (const PossibleChar& otherPossibleChar) const {
37 | if (this->contour != otherPossibleChar.contour) return true;
38 | else return false;
39 | }
40 |
41 | // function prototypes ////////////////////////////////////////////////////////////////////////
42 | PossibleChar(std::vector _contour);
43 |
44 | };
45 |
46 | #endif // POSSIBLE_CHAR_H
47 |
48 |
49 |
--------------------------------------------------------------------------------
/DocsAndPresentation/steps.txt:
--------------------------------------------------------------------------------
1 |
2 | 2 classes:
3 |
4 | PossiblePlate
5 |
6 | PossibleChar
7 |
8 |
9 |
10 | find plates
11 |
12 | find chars within plates
13 |
14 |
15 | detectPlatesInScene()
16 |
17 | detectCharsInPlates()
18 |
19 |
20 | imgOriginalScene
21 |
22 | preprocess()
23 |
24 | imgGrayscaleScene, imgThreshScene
25 |
26 | findPossibleCharsInScene()
27 |
28 | listOfPossibleCharsInScene
29 |
30 | findListOfListsOfMatchingChars()
31 |
32 | listOfListsOfMatchingCharsInScene
33 |
34 | extractPlate()
35 |
36 | listOfPossiblePlates
37 |
38 |
39 |
40 |
41 | loadKNNDataAndTrainKNN()
42 |
43 |
44 | listOfPossiblePlates
45 |
46 | preprocess()
47 |
48 | possiblePlate.imgGrayscale, possiblePlate.imgThresh
49 |
50 | findPossibleCharsInPlate()
51 |
52 | listOfPossibleCharsInPlate
53 |
54 | findListOfListsOfMatchingChars()
55 |
56 | listOfListsOfMatchingCharsInPlate
57 |
58 | removeInnerOverlappingChars()
59 |
60 | listOfListsOfMatchingCharsInPlate
61 |
62 | within each possible plate, suppose the longest list of potential matching chars is the actual list of chars
63 |
64 | longestListOfMatchingCharsInPlate
65 |
66 | recognizeCharsInPlate()
67 |
68 | possiblePlate.strChars
69 |
70 |
71 |
72 | listOfPossiblePlates
73 |
74 | suppose the plate with the most recognized chars is the actual plate
75 |
76 | licPlate
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | vectorOfPossibleCharsInScene
91 |
92 | findVectorOfVectorsOfMatchingChars()
93 |
94 | vectorOfVectorsOfMatchingCharsInScene
95 |
96 | vectorOfPossiblePlates
97 |
98 |
99 | vectorOfPossibleCharsInPlate
100 |
101 | findVectorOfVectorsOfMatchingChars()
102 |
103 | vectorOfVectorsOfMatchingCharsInPlate
104 |
105 | vectorOfVectorsOfMatchingCharsInPlate
106 |
107 | longestVectorOfMatchingCharsInPlate
108 |
109 | vectorOfPossiblePlates
110 |
111 | *loadKNNDataAndTrainKNN()
112 |
113 |
114 | *loadKNNDataAndTrainKNN() is actually called at the beginning of the program, it does not matter when this is called, so long as it's called before recognizeCharsInPlate()
--------------------------------------------------------------------------------
/archive/Preprocess.cpp:
--------------------------------------------------------------------------------
1 | // Preprocess.cpp
2 |
3 | #include "Preprocess.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | void preprocess(cv::Mat &imgOriginal, cv::Mat &imgGrayscale, cv::Mat &imgThresh) {
7 | imgGrayscale = extractValue(imgOriginal); // extract value channel only from original image to get imgGrayscale
8 |
9 | cv::Mat imgMaxContrastGrayscale = maximizeContrast(imgGrayscale); // maximize contrast with top hat and black hat
10 |
11 | cv::Mat imgBlurred;
12 |
13 | cv::GaussianBlur(imgMaxContrastGrayscale, imgBlurred, GAUSSIAN_SMOOTH_FILTER_SIZE, 0); // gaussian blur
14 |
15 | // call adaptive threshold to get imgThresh
16 | cv::adaptiveThreshold(imgBlurred, imgThresh, 255.0, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY_INV, ADAPTIVE_THRESH_BLOCK_SIZE, ADAPTIVE_THRESH_WEIGHT);
17 | }
18 |
19 | ///////////////////////////////////////////////////////////////////////////////////////////////////
20 | cv::Mat extractValue(cv::Mat &imgOriginal) {
21 | cv::Mat imgHSV;
22 | std::vector vectorOfHSVImages;
23 | cv::Mat imgValue;
24 |
25 | cv::cvtColor(imgOriginal, imgHSV, CV_BGR2HSV);
26 |
27 | cv::split(imgHSV, vectorOfHSVImages);
28 |
29 | imgValue = vectorOfHSVImages[2];
30 |
31 | return(imgValue);
32 | }
33 |
34 | ///////////////////////////////////////////////////////////////////////////////////////////////////
35 | cv::Mat maximizeContrast(cv::Mat &imgGrayscale) {
36 | cv::Mat imgTopHat;
37 | cv::Mat imgBlackHat;
38 | cv::Mat imgGrayscalePlusTopHat;
39 | cv::Mat imgGrayscalePlusTopHatMinusBlackHat;
40 |
41 | cv::Mat structuringElement = cv::getStructuringElement(CV_SHAPE_RECT, cv::Size(3, 3));
42 |
43 | cv::morphologyEx(imgGrayscale, imgTopHat, CV_MOP_TOPHAT, structuringElement);
44 | cv::morphologyEx(imgGrayscale, imgBlackHat, CV_MOP_BLACKHAT, structuringElement);
45 |
46 | imgGrayscalePlusTopHat = imgGrayscale + imgTopHat;
47 | imgGrayscalePlusTopHatMinusBlackHat = imgGrayscalePlusTopHat - imgBlackHat;
48 |
49 | return(imgGrayscalePlusTopHatMinusBlackHat);
50 | }
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Preprocess.cpp:
--------------------------------------------------------------------------------
1 | // Preprocess.cpp
2 |
3 | #include "Preprocess.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | void preprocess(cv::Mat &imgOriginal, cv::Mat &imgGrayscale, cv::Mat &imgThresh) {
7 | imgGrayscale = extractValue(imgOriginal); // extract value channel only from original image to get imgGrayscale
8 |
9 | cv::Mat imgMaxContrastGrayscale = maximizeContrast(imgGrayscale); // maximize contrast with top hat and black hat
10 |
11 | cv::Mat imgBlurred;
12 |
13 | cv::GaussianBlur(imgMaxContrastGrayscale, imgBlurred, GAUSSIAN_SMOOTH_FILTER_SIZE, 0); // gaussian blur
14 |
15 | // call adaptive threshold to get imgThresh
16 | cv::adaptiveThreshold(imgBlurred, imgThresh, 255.0, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY_INV, ADAPTIVE_THRESH_BLOCK_SIZE, ADAPTIVE_THRESH_WEIGHT);
17 | }
18 |
19 | ///////////////////////////////////////////////////////////////////////////////////////////////////
20 | cv::Mat extractValue(cv::Mat &imgOriginal) {
21 | cv::Mat imgHSV;
22 | std::vector vectorOfHSVImages;
23 | cv::Mat imgValue;
24 |
25 | cv::cvtColor(imgOriginal, imgHSV, CV_BGR2HSV);
26 |
27 | cv::split(imgHSV, vectorOfHSVImages);
28 |
29 | imgValue = vectorOfHSVImages[2];
30 |
31 | return(imgValue);
32 | }
33 |
34 | ///////////////////////////////////////////////////////////////////////////////////////////////////
35 | cv::Mat maximizeContrast(cv::Mat &imgGrayscale) {
36 | cv::Mat imgTopHat;
37 | cv::Mat imgBlackHat;
38 | cv::Mat imgGrayscalePlusTopHat;
39 | cv::Mat imgGrayscalePlusTopHatMinusBlackHat;
40 |
41 | cv::Mat structuringElement = cv::getStructuringElement(CV_SHAPE_RECT, cv::Size(3, 3));
42 |
43 | cv::morphologyEx(imgGrayscale, imgTopHat, CV_MOP_TOPHAT, structuringElement);
44 | cv::morphologyEx(imgGrayscale, imgBlackHat, CV_MOP_BLACKHAT, structuringElement);
45 |
46 | imgGrayscalePlusTopHat = imgGrayscale + imgTopHat;
47 | imgGrayscalePlusTopHatMinusBlackHat = imgGrayscalePlusTopHat - imgBlackHat;
48 |
49 | return(imgGrayscalePlusTopHatMinusBlackHat);
50 | }
51 |
52 |
53 |
--------------------------------------------------------------------------------
/OpenCV_3_License_Plate_Recognition_Cpp.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;hm;inl;inc;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 | Source Files
23 |
24 |
25 | Source Files
26 |
27 |
28 | Source Files
29 |
30 |
31 | Source Files
32 |
33 |
34 | Source Files
35 |
36 |
37 |
38 |
39 | Header Files
40 |
41 |
42 | Header Files
43 |
44 |
45 | Header Files
46 |
47 |
48 | Header Files
49 |
50 |
51 | Header Files
52 |
53 |
54 | Header Files
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/DetectChars.h:
--------------------------------------------------------------------------------
1 | // DetectChars.h
2 |
3 | #ifndef DETECT_CHARS_H
4 | #define DETECT_CHARS_H
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "Main.h"
12 | #include "PossibleChar.h"
13 | #include "PossiblePlate.h"
14 | #include "Preprocess.h"
15 |
16 | // global constants ///////////////////////////////////////////////////////////////////////////////
17 | // constants for checkIfPossibleChar, this checks one possible char only (does not compare to another char)
18 | const int MIN_PIXEL_WIDTH = 2;
19 | const int MIN_PIXEL_HEIGHT = 8;
20 |
21 | const double MIN_ASPECT_RATIO = 0.25;
22 | const double MAX_ASPECT_RATIO = 1.0;
23 |
24 | const int MIN_PIXEL_AREA = 80;
25 |
26 | // constants for comparing two chars
27 | const double MIN_DIAG_SIZE_MULTIPLE_AWAY = 0.3;
28 | const double MAX_DIAG_SIZE_MULTIPLE_AWAY = 5.0;
29 |
30 | const double MAX_CHANGE_IN_AREA = 0.5;
31 |
32 | const double MAX_CHANGE_IN_WIDTH = 0.8;
33 | const double MAX_CHANGE_IN_HEIGHT = 0.2;
34 |
35 | const double MAX_ANGLE_BETWEEN_CHARS = 12.0;
36 |
37 | // other constants
38 | const int MIN_NUMBER_OF_MATCHING_CHARS = 3;
39 |
40 | const int RESIZED_CHAR_IMAGE_WIDTH = 20;
41 | const int RESIZED_CHAR_IMAGE_HEIGHT = 30;
42 |
43 | const int MIN_CONTOUR_AREA = 100;
44 |
45 | // external global variables //////////////////////////////////////////////////////////////////////
46 | extern const bool blnShowSteps;
47 | extern cv::Ptr kNearest;
48 |
49 | // function prototypes ////////////////////////////////////////////////////////////////////////////
50 |
51 | bool loadKNNDataAndTrainKNN(void);
52 |
53 | std::vector detectCharsInPlates(std::vector &vectorOfPossiblePlates);
54 |
55 | std::vector findPossibleCharsInPlate(cv::Mat &imgGrayscale, cv::Mat &imgThresh);
56 |
57 | bool checkIfPossibleChar(PossibleChar &possibleChar);
58 |
59 | std::vector > findVectorOfVectorsOfMatchingChars(const std::vector &vectorOfPossibleChars);
60 |
61 | std::vector findVectorOfMatchingChars(const PossibleChar &possibleChar, const std::vector &vectorOfChars);
62 |
63 | double distanceBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar);
64 |
65 | double angleBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar);
66 |
67 | std::vector removeInnerOverlappingChars(std::vector &vectorOfMatchingChars);
68 |
69 | std::string recognizeCharsInPlate(cv::Mat &imgThresh, std::vector &vectorOfMatchingChars);
70 |
71 |
72 | #endif // DETECT_CHARS_H
73 |
74 |
--------------------------------------------------------------------------------
/archive/DetectChars.h:
--------------------------------------------------------------------------------
1 | // DetectChars.h
2 |
3 | #ifndef DETECT_CHARS_H
4 | #define DETECT_CHARS_H
5 |
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "Main.h"
12 | #include "PossibleChar.h"
13 | #include "PossiblePlate.h"
14 | #include "Preprocess.h"
15 |
16 | // global constants ///////////////////////////////////////////////////////////////////////////////
17 | // constants for checkIfPossibleChar, this checks one possible char only (does not compare to another char)
18 | const int MIN_PIXEL_WIDTH = 2;
19 | const int MIN_PIXEL_HEIGHT = 8;
20 |
21 | const double MIN_ASPECT_RATIO = 0.25;
22 | const double MAX_ASPECT_RATIO = 1.0;
23 |
24 | const int MIN_PIXEL_AREA = 80;
25 |
26 | // constants for comparing two chars
27 | const double MIN_DIAG_SIZE_MULTIPLE_AWAY = 0.3;
28 | const double MAX_DIAG_SIZE_MULTIPLE_AWAY = 5.0;
29 |
30 | const double MAX_CHANGE_IN_AREA = 0.5;
31 |
32 | const double MAX_CHANGE_IN_WIDTH = 0.8;
33 | const double MAX_CHANGE_IN_HEIGHT = 0.2;
34 |
35 | const double MAX_ANGLE_BETWEEN_CHARS = 12.0;
36 |
37 | // other constants
38 | const int MIN_NUMBER_OF_MATCHING_CHARS = 3;
39 |
40 | const int RESIZED_CHAR_IMAGE_WIDTH = 20;
41 | const int RESIZED_CHAR_IMAGE_HEIGHT = 30;
42 |
43 | const int MIN_CONTOUR_AREA = 100;
44 |
45 | // external global variables //////////////////////////////////////////////////////////////////////
46 | extern const bool blnShowSteps;
47 | extern cv::Ptr kNearest;
48 |
49 | // function prototypes ////////////////////////////////////////////////////////////////////////////
50 |
51 | bool loadKNNDataAndTrainKNN(void);
52 |
53 | std::vector detectCharsInPlates(std::vector &vectorOfPossiblePlates);
54 |
55 | std::vector findPossibleCharsInPlate(cv::Mat &imgGrayscale, cv::Mat &imgThresh);
56 |
57 | bool checkIfPossibleChar(PossibleChar &possibleChar);
58 |
59 | std::vector > findVectorOfVectorsOfMatchingChars(const std::vector &vectorOfPossibleChars);
60 |
61 | std::vector findVectorOfMatchingChars(const PossibleChar &possibleChar, const std::vector &vectorOfChars);
62 |
63 | double distanceBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar);
64 |
65 | double angleBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar);
66 |
67 | std::vector removeInnerOverlappingChars(std::vector &vectorOfMatchingChars);
68 |
69 | std::string recognizeCharsInPlate(cv::Mat &imgThresh, std::vector &vectorOfMatchingChars);
70 |
71 |
72 | #endif // DETECT_CHARS_H
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
--------------------------------------------------------------------------------
/archive/Main.cpp:
--------------------------------------------------------------------------------
1 | // Main.cpp
2 |
3 | #include "Main.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | int main(void) {
7 |
8 | bool blnKNNTrainingSuccessful = loadKNNDataAndTrainKNN(); // attempt KNN training
9 |
10 | if (blnKNNTrainingSuccessful == false) { // if KNN training was not successful
11 | // show error message
12 | std::cout << std::endl << std::endl << "error: error: KNN traning was not successful" << std::endl << std::endl;
13 | return(0); // and exit program
14 | }
15 |
16 | cv::Mat imgOriginalScene; // input image
17 |
18 | imgOriginalScene = cv::imread("1.png"); // open image
19 |
20 | if (imgOriginalScene.empty()) { // if unable to open image
21 | std::cout << "error: image not read from file\n\n"; // show error message on command line
22 | return(0); // and exit program
23 | }
24 |
25 | std::vector vectorOfPossiblePlates = detectPlatesInScene(imgOriginalScene); // detect plates
26 |
27 | vectorOfPossiblePlates = detectCharsInPlates(vectorOfPossiblePlates); // detect chars in plates
28 |
29 | cv::imshow("imgOriginalScene", imgOriginalScene); // show scene image
30 |
31 | if (vectorOfPossiblePlates.empty()) { // if no plates were found
32 | std::cout << std::endl << "no license plates were detected" << std::endl; // inform user no plates were found
33 | } else { // else
34 | // if we get in here vector of possible plates has at leat one plate
35 |
36 | // sort the vector of possible plates in DESCENDING order (most number of chars to least number of chars)
37 | std::sort(vectorOfPossiblePlates.begin(), vectorOfPossiblePlates.end(), PossiblePlate::sortDescendingByNumberOfChars);
38 |
39 | // suppose the plate with the most recognized chars (the first plate in sorted by string length descending order) is the actual plate
40 | PossiblePlate licPlate = vectorOfPossiblePlates.front();
41 |
42 | cv::imshow("imgPlate", licPlate.imgPlate); // show crop of plate and threshold of plate
43 | cv::imshow("imgThresh", licPlate.imgThresh);
44 |
45 | if (licPlate.strChars.length() == 0) { // if no chars were found in the plate
46 | std::cout << std::endl << "no characters were detected" << std::endl << std::endl; // show message
47 | return(0); // and exit program
48 | }
49 |
50 | drawRedRectangleAroundPlate(imgOriginalScene, licPlate); // draw red rectangle around plate
51 |
52 | std::cout << std::endl << "license plate read from image = " << licPlate.strChars << std::endl; // write license plate text to std out
53 | std::cout << std::endl << "-----------------------------------------" << std::endl;
54 |
55 | writeLicensePlateCharsOnImage(imgOriginalScene, licPlate); // write license plate text on the image
56 |
57 | cv::imshow("imgOriginalScene", imgOriginalScene); // re-show scene image
58 |
59 | cv::imwrite("imgOriginalScene.png", imgOriginalScene); // write image out to file
60 | }
61 |
62 | cv::waitKey(0); // hold windows open until user presses a key
63 |
64 | return(0);
65 | }
66 |
67 | ///////////////////////////////////////////////////////////////////////////////////////////////////
68 | void drawRedRectangleAroundPlate(cv::Mat &imgOriginalScene, PossiblePlate &licPlate) {
69 | cv::Point2f p2fRectPoints[4];
70 |
71 | licPlate.rrLocationOfPlateInScene.points(p2fRectPoints); // get 4 vertices of rotated rect
72 |
73 | for (int i = 0; i < 4; i++) { // draw 4 red lines
74 | cv::line(imgOriginalScene, p2fRectPoints[i], p2fRectPoints[(i + 1) % 4], SCALAR_RED, 2);
75 | }
76 | }
77 |
78 | ///////////////////////////////////////////////////////////////////////////////////////////////////
79 | void writeLicensePlateCharsOnImage(cv::Mat &imgOriginalScene, PossiblePlate &licPlate) {
80 | cv::Point ptCenterOfTextArea; // this will be the center of the area the text will be written to
81 | cv::Point ptLowerLeftTextOrigin; // this will be the bottom left of the area that the text will be written to
82 |
83 | int intFontFace = CV_FONT_HERSHEY_SIMPLEX; // choose a plain jane font
84 | double dblFontScale = (double)licPlate.imgPlate.rows / 30.0; // base font scale on height of plate area
85 | int intFontThickness = (int)std::round(dblFontScale * 1.5); // base font thickness on font scale
86 | int intBaseline = 0;
87 |
88 | cv::Size textSize = cv::getTextSize(licPlate.strChars, intFontFace, dblFontScale, intFontThickness, &intBaseline); // call getTextSize
89 |
90 | ptCenterOfTextArea.x = (int)licPlate.rrLocationOfPlateInScene.center.x; // the horizontal location of the text area is the same as the plate
91 |
92 | if (licPlate.rrLocationOfPlateInScene.center.y < (imgOriginalScene.rows * 0.75)) { // if the license plate is in the upper 3/4 of the image
93 | // write the chars in below the plate
94 | ptCenterOfTextArea.y = (int)std::round(licPlate.rrLocationOfPlateInScene.center.y) + (int)std::round((double)licPlate.imgPlate.rows * 1.6);
95 | } else { // else if the license plate is in the lower 1/4 of the image
96 | // write the chars in above the plate
97 | ptCenterOfTextArea.y = (int)std::round(licPlate.rrLocationOfPlateInScene.center.y) - (int)std::round((double)licPlate.imgPlate.rows * 1.6);
98 | }
99 |
100 | ptLowerLeftTextOrigin.x = (int)(ptCenterOfTextArea.x - (textSize.width / 2)); // calculate the lower left origin of the text area
101 | ptLowerLeftTextOrigin.y = (int)(ptCenterOfTextArea.y + (textSize.height / 2)); // based on the text area center, width, and height
102 |
103 | // write the text on the image
104 | cv::putText(imgOriginalScene, licPlate.strChars, ptLowerLeftTextOrigin, intFontFace, dblFontScale, SCALAR_YELLOW, intFontThickness);
105 | }
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/Main.cpp:
--------------------------------------------------------------------------------
1 | // Main.cpp
2 |
3 | #include "Main.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | int main(void) {
7 |
8 | bool blnKNNTrainingSuccessful = loadKNNDataAndTrainKNN(); // attempt KNN training
9 |
10 | if (blnKNNTrainingSuccessful == false) { // if KNN training was not successful
11 | // show error message
12 | std::cout << std::endl << std::endl << "error: error: KNN traning was not successful" << std::endl << std::endl;
13 | return(0); // and exit program
14 | }
15 |
16 | cv::Mat imgOriginalScene; // input image
17 |
18 | imgOriginalScene = cv::imread("image1.png"); // open image
19 |
20 | if (imgOriginalScene.empty()) { // if unable to open image
21 | std::cout << "error: image not read from file\n\n"; // show error message on command line
22 | _getch(); // may have to modify this line if not using Windows
23 | return(0); // and exit program
24 | }
25 |
26 | std::vector vectorOfPossiblePlates = detectPlatesInScene(imgOriginalScene); // detect plates
27 |
28 | vectorOfPossiblePlates = detectCharsInPlates(vectorOfPossiblePlates); // detect chars in plates
29 |
30 | cv::imshow("imgOriginalScene", imgOriginalScene); // show scene image
31 |
32 | if (vectorOfPossiblePlates.empty()) { // if no plates were found
33 | std::cout << std::endl << "no license plates were detected" << std::endl; // inform user no plates were found
34 | }
35 | else { // else
36 | // if we get in here vector of possible plates has at leat one plate
37 |
38 | // sort the vector of possible plates in DESCENDING order (most number of chars to least number of chars)
39 | std::sort(vectorOfPossiblePlates.begin(), vectorOfPossiblePlates.end(), PossiblePlate::sortDescendingByNumberOfChars);
40 |
41 | // suppose the plate with the most recognized chars (the first plate in sorted by string length descending order) is the actual plate
42 | PossiblePlate licPlate = vectorOfPossiblePlates.front();
43 |
44 | cv::imshow("imgPlate", licPlate.imgPlate); // show crop of plate and threshold of plate
45 | cv::imshow("imgThresh", licPlate.imgThresh);
46 |
47 | if (licPlate.strChars.length() == 0) { // if no chars were found in the plate
48 | std::cout << std::endl << "no characters were detected" << std::endl << std::endl; // show message
49 | return(0); // and exit program
50 | }
51 |
52 | drawRedRectangleAroundPlate(imgOriginalScene, licPlate); // draw red rectangle around plate
53 |
54 | std::cout << std::endl << "license plate read from image = " << licPlate.strChars << std::endl; // write license plate text to std out
55 | std::cout << std::endl << "-----------------------------------------" << std::endl;
56 |
57 | writeLicensePlateCharsOnImage(imgOriginalScene, licPlate); // write license plate text on the image
58 |
59 | cv::imshow("imgOriginalScene", imgOriginalScene); // re-show scene image
60 |
61 | cv::imwrite("imgOriginalScene.png", imgOriginalScene); // write image out to file
62 | }
63 |
64 | cv::waitKey(0); // hold windows open until user presses a key
65 |
66 | return(0);
67 | }
68 |
69 | ///////////////////////////////////////////////////////////////////////////////////////////////////
70 | void drawRedRectangleAroundPlate(cv::Mat &imgOriginalScene, PossiblePlate &licPlate) {
71 | cv::Point2f p2fRectPoints[4];
72 |
73 | licPlate.rrLocationOfPlateInScene.points(p2fRectPoints); // get 4 vertices of rotated rect
74 |
75 | for (int i = 0; i < 4; i++) { // draw 4 red lines
76 | cv::line(imgOriginalScene, p2fRectPoints[i], p2fRectPoints[(i + 1) % 4], SCALAR_RED, 2);
77 | }
78 | }
79 |
80 | ///////////////////////////////////////////////////////////////////////////////////////////////////
81 | void writeLicensePlateCharsOnImage(cv::Mat &imgOriginalScene, PossiblePlate &licPlate) {
82 | cv::Point ptCenterOfTextArea; // this will be the center of the area the text will be written to
83 | cv::Point ptLowerLeftTextOrigin; // this will be the bottom left of the area that the text will be written to
84 |
85 | int intFontFace = CV_FONT_HERSHEY_SIMPLEX; // choose a plain jane font
86 | double dblFontScale = (double)licPlate.imgPlate.rows / 30.0; // base font scale on height of plate area
87 | int intFontThickness = (int)std::round(dblFontScale * 1.5); // base font thickness on font scale
88 | int intBaseline = 0;
89 |
90 | cv::Size textSize = cv::getTextSize(licPlate.strChars, intFontFace, dblFontScale, intFontThickness, &intBaseline); // call getTextSize
91 |
92 | ptCenterOfTextArea.x = (int)licPlate.rrLocationOfPlateInScene.center.x; // the horizontal location of the text area is the same as the plate
93 |
94 | if (licPlate.rrLocationOfPlateInScene.center.y < (imgOriginalScene.rows * 0.75)) { // if the license plate is in the upper 3/4 of the image
95 | // write the chars in below the plate
96 | ptCenterOfTextArea.y = (int)std::round(licPlate.rrLocationOfPlateInScene.center.y) + (int)std::round((double)licPlate.imgPlate.rows * 1.6);
97 | }
98 | else { // else if the license plate is in the lower 1/4 of the image
99 | // write the chars in above the plate
100 | ptCenterOfTextArea.y = (int)std::round(licPlate.rrLocationOfPlateInScene.center.y) - (int)std::round((double)licPlate.imgPlate.rows * 1.6);
101 | }
102 |
103 | ptLowerLeftTextOrigin.x = (int)(ptCenterOfTextArea.x - (textSize.width / 2)); // calculate the lower left origin of the text area
104 | ptLowerLeftTextOrigin.y = (int)(ptCenterOfTextArea.y + (textSize.height / 2)); // based on the text area center, width, and height
105 |
106 | // write the text on the image
107 | cv::putText(imgOriginalScene, licPlate.strChars, ptLowerLeftTextOrigin, intFontFace, dblFontScale, SCALAR_YELLOW, intFontThickness);
108 | }
109 |
110 |
111 |
--------------------------------------------------------------------------------
/OpenCV_3_License_Plate_Recognition_Cpp.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | {09A38949-D191-4247-9CA9-D983A592F3BD}
23 | OpenCV_3_License_Plate_Recognition_Cpp
24 | 8.1
25 |
26 |
27 |
28 | Application
29 | true
30 | v140
31 | MultiByte
32 |
33 |
34 | Application
35 | false
36 | v140
37 | true
38 | MultiByte
39 |
40 |
41 | Application
42 | true
43 | v140
44 | MultiByte
45 |
46 |
47 | Application
48 | false
49 | v140
50 | true
51 | MultiByte
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Level3
75 | Disabled
76 | true
77 |
78 |
79 | true
80 |
81 |
82 |
83 |
84 | Level3
85 | Disabled
86 | true
87 |
88 |
89 | true
90 |
91 |
92 |
93 |
94 | Level3
95 | MaxSpeed
96 | true
97 | true
98 | true
99 |
100 |
101 | true
102 | true
103 | true
104 |
105 |
106 |
107 |
108 | Level3
109 | MaxSpeed
110 | true
111 | true
112 | true
113 |
114 |
115 | true
116 | true
117 | true
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
147 |
148 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/archive/DetectPlates.cpp:
--------------------------------------------------------------------------------
1 | // DetectPlates.cpp
2 |
3 | #include "DetectPlates.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | std::vector detectPlatesInScene(cv::Mat &imgOriginalScene) {
7 | std::vector vectorOfPossiblePlates; // this will be the return value
8 |
9 | cv::Mat imgGrayscaleScene;
10 | cv::Mat imgThreshScene;
11 | cv::Mat imgContours(imgOriginalScene.size(), CV_8UC3, SCALAR_BLACK);
12 |
13 | cv::RNG rng;
14 |
15 | cv::destroyAllWindows();
16 |
17 | #ifdef SHOW_STEPS
18 | cv::imshow("0", imgOriginalScene);
19 | #endif // SHOW_STEPS
20 |
21 | preprocess(imgOriginalScene, imgGrayscaleScene, imgThreshScene); // preprocess to get grayscale and threshold images
22 |
23 | #ifdef SHOW_STEPS
24 | cv::imshow("1a", imgGrayscaleScene);
25 | cv::imshow("1b", imgThreshScene);
26 | #endif // SHOW_STEPS
27 |
28 | // find all possible chars in the scene,
29 | // this function first finds all contours, then only includes contours that could be chars (without comparison to other chars yet)
30 | std::vector vectorOfPossibleCharsInScene = findPossibleCharsInScene(imgThreshScene);
31 |
32 | #ifdef SHOW_STEPS
33 | std::cout << "step 2 - vectorOfPossibleCharsInScene.Count = " << vectorOfPossibleCharsInScene.size() << std::endl; // 131 with MCLRNF1 image
34 |
35 | imgContours = cv::Mat(imgOriginalScene.size(), CV_8UC3, SCALAR_BLACK);
36 | std::vector > contours;
37 |
38 | for (auto &possibleChar : vectorOfPossibleCharsInScene) {
39 | contours.push_back(possibleChar.contour);
40 | }
41 | cv::drawContours(imgContours, contours, -1, SCALAR_WHITE);
42 | cv::imshow("2b", imgContours);
43 | #endif // SHOW_STEPS
44 |
45 | // given a vector of all possible chars, find groups of matching chars
46 | // in the next steps each group of matching chars will attempt to be recognized as a plate
47 | std::vector > vectorOfVectorsOfMatchingCharsInScene = findVectorOfVectorsOfMatchingChars(vectorOfPossibleCharsInScene);
48 |
49 | #ifdef SHOW_STEPS
50 | std::cout << "step 3 - vectorOfVectorsOfMatchingCharsInScene.size() = " << vectorOfVectorsOfMatchingCharsInScene.size() << std::endl; // 13 with MCLRNF1 image
51 |
52 | imgContours = cv::Mat(imgOriginalScene.size(), CV_8UC3, SCALAR_BLACK);
53 |
54 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInScene) {
55 | int intRandomBlue = rng.uniform(0, 256);
56 | int intRandomGreen = rng.uniform(0, 256);
57 | int intRandomRed = rng.uniform(0, 256);
58 |
59 | std::vector > contours;
60 |
61 | for (auto &matchingChar : vectorOfMatchingChars) {
62 | contours.push_back(matchingChar.contour);
63 | }
64 | cv::drawContours(imgContours, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
65 | }
66 | cv::imshow("3", imgContours);
67 | #endif // SHOW_STEPS
68 |
69 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInScene) { // for each group of matching chars
70 | PossiblePlate possiblePlate = extractPlate(imgOriginalScene, vectorOfMatchingChars); // attempt to extract plate
71 |
72 | if (possiblePlate.imgPlate.empty() == false) { // if plate was found
73 | vectorOfPossiblePlates.push_back(possiblePlate); // add to vector of possible plates
74 | }
75 | }
76 |
77 | std::cout << std::endl << vectorOfPossiblePlates.size() << " possible plates found" << std::endl; // 13 with MCLRNF1 image
78 |
79 | #ifdef SHOW_STEPS
80 | std::cout << std::endl;
81 | cv::imshow("4a", imgContours);
82 |
83 | for (unsigned int i = 0; i < vectorOfPossiblePlates.size(); i++) {
84 | cv::Point2f p2fRectPoints[4];
85 |
86 | vectorOfPossiblePlates[i].rrLocationOfPlateInScene.points(p2fRectPoints);
87 |
88 | for (int j = 0; j < 4; j++) {
89 | cv::line(imgContours, p2fRectPoints[j], p2fRectPoints[(j + 1) % 4], SCALAR_RED, 2);
90 | }
91 | cv::imshow("4a", imgContours);
92 |
93 | std::cout << "possible plate " << i << ", click on any image and press a key to continue . . ." << std::endl;
94 |
95 | cv::imshow("4b", vectorOfPossiblePlates[i].imgPlate);
96 | cv::waitKey(0);
97 | }
98 | std::cout << std::endl << "plate detection complete, click on any image and press a key to begin char recognition . . ." << std::endl << std::endl;
99 | cv::waitKey(0);
100 | #endif // SHOW_STEPS
101 |
102 | return vectorOfPossiblePlates;
103 | }
104 |
105 | ///////////////////////////////////////////////////////////////////////////////////////////////////
106 | std::vector findPossibleCharsInScene(cv::Mat &imgThresh) {
107 | std::vector vectorOfPossibleChars; // this will be the return value
108 |
109 | cv::Mat imgContours(imgThresh.size(), CV_8UC3, SCALAR_BLACK);
110 | int intCountOfPossibleChars = 0;
111 |
112 | cv::Mat imgThreshCopy = imgThresh.clone();
113 |
114 | std::vector > contours;
115 |
116 | cv::findContours(imgThreshCopy, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // find all contours
117 |
118 | for (unsigned int i = 0; i < contours.size(); i++) { // for each contour
119 | #ifdef SHOW_STEPS
120 | cv::drawContours(imgContours, contours, i, SCALAR_WHITE);
121 | #endif // SHOW_STEPS
122 | PossibleChar possibleChar(contours[i]);
123 |
124 | if (checkIfPossibleChar(possibleChar)) { // if contour is a possible char, note this does not compare to other chars (yet) . . .
125 | intCountOfPossibleChars++; // increment count of possible chars
126 | vectorOfPossibleChars.push_back(possibleChar); // and add to vector of possible chars
127 | }
128 | }
129 |
130 | #ifdef SHOW_STEPS
131 | std::cout << std::endl << "contours.size() = " << contours.size() << std::endl; // 2362 with MCLRNF1 image
132 | std::cout << "step 2 - intCountOfValidPossibleChars = " << intCountOfPossibleChars << std::endl; // 131 with MCLRNF1 image
133 | cv::imshow("2a", imgContours);
134 | #endif // SHOW_STEPS
135 |
136 | return(vectorOfPossibleChars);
137 | }
138 |
139 | ///////////////////////////////////////////////////////////////////////////////////////////////////
140 | PossiblePlate extractPlate(cv::Mat &imgOriginal, std::vector &vectorOfMatchingChars) {
141 | PossiblePlate possiblePlate; // this will be the return value
142 |
143 | // sort chars from left to right based on x position
144 | std::sort(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), PossibleChar::sortCharsLeftToRight);
145 |
146 | // calculate the center point of the plate
147 | double dblPlateCenterX = (double)(vectorOfMatchingChars[0].intCenterX + vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].intCenterX) / 2.0;
148 | double dblPlateCenterY = (double)(vectorOfMatchingChars[0].intCenterY + vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].intCenterY) / 2.0;
149 | cv::Point2d p2dPlateCenter(dblPlateCenterX, dblPlateCenterY);
150 |
151 | // calculate plate width and height
152 | int intPlateWidth = (int)((vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].boundingRect.x + vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].boundingRect.width - vectorOfMatchingChars[0].boundingRect.x) * PLATE_WIDTH_PADDING_FACTOR);
153 |
154 | double intTotalOfCharHeights = 0;
155 |
156 | for (auto &matchingChar : vectorOfMatchingChars) {
157 | intTotalOfCharHeights = intTotalOfCharHeights + matchingChar.boundingRect.height;
158 | }
159 |
160 | double dblAverageCharHeight = (double)intTotalOfCharHeights / vectorOfMatchingChars.size();
161 |
162 | int intPlateHeight = (int)(dblAverageCharHeight * PLATE_HEIGHT_PADDING_FACTOR);
163 |
164 | // calculate correction angle of plate region
165 | double dblOpposite = vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].intCenterY - vectorOfMatchingChars[0].intCenterY;
166 | double dblHypotenuse = distanceBetweenChars(vectorOfMatchingChars[0], vectorOfMatchingChars[vectorOfMatchingChars.size() - 1]);
167 | double dblCorrectionAngleInRad = asin(dblOpposite / dblHypotenuse);
168 | double dblCorrectionAngleInDeg = dblCorrectionAngleInRad * (180.0 / CV_PI);
169 |
170 | // assign rotated rect member variable of possible plate
171 | possiblePlate.rrLocationOfPlateInScene = cv::RotatedRect(p2dPlateCenter, cv::Size2f((float)intPlateWidth, (float)intPlateHeight), (float)dblCorrectionAngleInDeg);
172 |
173 | cv::Mat rotationMatrix; // final steps are to perform the actual rotation
174 | cv::Mat imgRotated;
175 | cv::Mat imgCropped;
176 |
177 | rotationMatrix = cv::getRotationMatrix2D(p2dPlateCenter, dblCorrectionAngleInDeg, 1.0); // get the rotation matrix for our calculated correction angle
178 |
179 | cv::warpAffine(imgOriginal, imgRotated, rotationMatrix, imgOriginal.size()); // rotate the entire image
180 |
181 | // crop out the actual plate portion of the rotated image
182 | cv::getRectSubPix(imgRotated, possiblePlate.rrLocationOfPlateInScene.size, possiblePlate.rrLocationOfPlateInScene.center, imgCropped);
183 |
184 | possiblePlate.imgPlate = imgCropped; // copy the cropped plate image into the applicable member variable of the possible plate
185 |
186 | return(possiblePlate);
187 | }
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/DetectPlates.cpp:
--------------------------------------------------------------------------------
1 | // DetectPlates.cpp
2 |
3 | #include "DetectPlates.h"
4 |
5 | ///////////////////////////////////////////////////////////////////////////////////////////////////
6 | std::vector detectPlatesInScene(cv::Mat &imgOriginalScene) {
7 | std::vector vectorOfPossiblePlates; // this will be the return value
8 |
9 | cv::Mat imgGrayscaleScene;
10 | cv::Mat imgThreshScene;
11 | cv::Mat imgContours(imgOriginalScene.size(), CV_8UC3, SCALAR_BLACK);
12 |
13 | cv::RNG rng;
14 |
15 | cv::destroyAllWindows();
16 |
17 | #ifdef SHOW_STEPS
18 | cv::imshow("0", imgOriginalScene);
19 | #endif // SHOW_STEPS
20 |
21 | preprocess(imgOriginalScene, imgGrayscaleScene, imgThreshScene); // preprocess to get grayscale and threshold images
22 |
23 | #ifdef SHOW_STEPS
24 | cv::imshow("1a", imgGrayscaleScene);
25 | cv::imshow("1b", imgThreshScene);
26 | #endif // SHOW_STEPS
27 |
28 | // find all possible chars in the scene,
29 | // this function first finds all contours, then only includes contours that could be chars (without comparison to other chars yet)
30 | std::vector vectorOfPossibleCharsInScene = findPossibleCharsInScene(imgThreshScene);
31 |
32 | #ifdef SHOW_STEPS
33 | std::cout << "step 2 - vectorOfPossibleCharsInScene.Count = " << vectorOfPossibleCharsInScene.size() << std::endl; // 131 with MCLRNF1 image
34 |
35 | imgContours = cv::Mat(imgOriginalScene.size(), CV_8UC3, SCALAR_BLACK);
36 | std::vector > contours;
37 |
38 | for (auto &possibleChar : vectorOfPossibleCharsInScene) {
39 | contours.push_back(possibleChar.contour);
40 | }
41 | cv::drawContours(imgContours, contours, -1, SCALAR_WHITE);
42 | cv::imshow("2b", imgContours);
43 | #endif // SHOW_STEPS
44 |
45 | // given a vector of all possible chars, find groups of matching chars
46 | // in the next steps each group of matching chars will attempt to be recognized as a plate
47 | std::vector > vectorOfVectorsOfMatchingCharsInScene = findVectorOfVectorsOfMatchingChars(vectorOfPossibleCharsInScene);
48 |
49 | #ifdef SHOW_STEPS
50 | std::cout << "step 3 - vectorOfVectorsOfMatchingCharsInScene.size() = " << vectorOfVectorsOfMatchingCharsInScene.size() << std::endl; // 13 with MCLRNF1 image
51 |
52 | imgContours = cv::Mat(imgOriginalScene.size(), CV_8UC3, SCALAR_BLACK);
53 |
54 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInScene) {
55 | int intRandomBlue = rng.uniform(0, 256);
56 | int intRandomGreen = rng.uniform(0, 256);
57 | int intRandomRed = rng.uniform(0, 256);
58 |
59 | std::vector > contours;
60 |
61 | for (auto &matchingChar : vectorOfMatchingChars) {
62 | contours.push_back(matchingChar.contour);
63 | }
64 | cv::drawContours(imgContours, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
65 | }
66 | cv::imshow("3", imgContours);
67 | #endif // SHOW_STEPS
68 |
69 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInScene) { // for each group of matching chars
70 | PossiblePlate possiblePlate = extractPlate(imgOriginalScene, vectorOfMatchingChars); // attempt to extract plate
71 |
72 | if (possiblePlate.imgPlate.empty() == false) { // if plate was found
73 | vectorOfPossiblePlates.push_back(possiblePlate); // add to vector of possible plates
74 | }
75 | }
76 |
77 | std::cout << std::endl << vectorOfPossiblePlates.size() << " possible plates found" << std::endl; // 13 with MCLRNF1 image
78 |
79 | #ifdef SHOW_STEPS
80 | std::cout << std::endl;
81 | cv::imshow("4a", imgContours);
82 |
83 | for (unsigned int i = 0; i < vectorOfPossiblePlates.size(); i++) {
84 | cv::Point2f p2fRectPoints[4];
85 |
86 | vectorOfPossiblePlates[i].rrLocationOfPlateInScene.points(p2fRectPoints);
87 |
88 | for (int j = 0; j < 4; j++) {
89 | cv::line(imgContours, p2fRectPoints[j], p2fRectPoints[(j + 1) % 4], SCALAR_RED, 2);
90 | }
91 | cv::imshow("4a", imgContours);
92 |
93 | std::cout << "possible plate " << i << ", click on any image and press a key to continue . . ." << std::endl;
94 |
95 | cv::imshow("4b", vectorOfPossiblePlates[i].imgPlate);
96 | cv::waitKey(0);
97 | }
98 | std::cout << std::endl << "plate detection complete, click on any image and press a key to begin char recognition . . ." << std::endl << std::endl;
99 | cv::waitKey(0);
100 | #endif // SHOW_STEPS
101 |
102 | return vectorOfPossiblePlates;
103 | }
104 |
105 | ///////////////////////////////////////////////////////////////////////////////////////////////////
106 | std::vector findPossibleCharsInScene(cv::Mat &imgThresh) {
107 | std::vector vectorOfPossibleChars; // this will be the return value
108 |
109 | cv::Mat imgContours(imgThresh.size(), CV_8UC3, SCALAR_BLACK);
110 | int intCountOfPossibleChars = 0;
111 |
112 | cv::Mat imgThreshCopy = imgThresh.clone();
113 |
114 | std::vector > contours;
115 |
116 | cv::findContours(imgThreshCopy, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // find all contours
117 |
118 | for (unsigned int i = 0; i < contours.size(); i++) { // for each contour
119 | #ifdef SHOW_STEPS
120 | cv::drawContours(imgContours, contours, i, SCALAR_WHITE);
121 | #endif // SHOW_STEPS
122 | PossibleChar possibleChar(contours[i]);
123 |
124 | if (checkIfPossibleChar(possibleChar)) { // if contour is a possible char, note this does not compare to other chars (yet) . . .
125 | intCountOfPossibleChars++; // increment count of possible chars
126 | vectorOfPossibleChars.push_back(possibleChar); // and add to vector of possible chars
127 | }
128 | }
129 |
130 | #ifdef SHOW_STEPS
131 | std::cout << std::endl << "contours.size() = " << contours.size() << std::endl; // 2362 with MCLRNF1 image
132 | std::cout << "step 2 - intCountOfValidPossibleChars = " << intCountOfPossibleChars << std::endl; // 131 with MCLRNF1 image
133 | cv::imshow("2a", imgContours);
134 | #endif // SHOW_STEPS
135 |
136 | return(vectorOfPossibleChars);
137 | }
138 |
139 | ///////////////////////////////////////////////////////////////////////////////////////////////////
140 | PossiblePlate extractPlate(cv::Mat &imgOriginal, std::vector &vectorOfMatchingChars) {
141 | PossiblePlate possiblePlate; // this will be the return value
142 |
143 | // sort chars from left to right based on x position
144 | std::sort(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), PossibleChar::sortCharsLeftToRight);
145 |
146 | // calculate the center point of the plate
147 | double dblPlateCenterX = (double)(vectorOfMatchingChars[0].intCenterX + vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].intCenterX) / 2.0;
148 | double dblPlateCenterY = (double)(vectorOfMatchingChars[0].intCenterY + vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].intCenterY) / 2.0;
149 | cv::Point2d p2dPlateCenter(dblPlateCenterX, dblPlateCenterY);
150 |
151 | // calculate plate width and height
152 | int intPlateWidth = (int)((vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].boundingRect.x + vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].boundingRect.width - vectorOfMatchingChars[0].boundingRect.x) * PLATE_WIDTH_PADDING_FACTOR);
153 |
154 | double intTotalOfCharHeights = 0;
155 |
156 | for (auto &matchingChar : vectorOfMatchingChars) {
157 | intTotalOfCharHeights = intTotalOfCharHeights + matchingChar.boundingRect.height;
158 | }
159 |
160 | double dblAverageCharHeight = (double)intTotalOfCharHeights / vectorOfMatchingChars.size();
161 |
162 | int intPlateHeight = (int)(dblAverageCharHeight * PLATE_HEIGHT_PADDING_FACTOR);
163 |
164 | // calculate correction angle of plate region
165 | double dblOpposite = vectorOfMatchingChars[vectorOfMatchingChars.size() - 1].intCenterY - vectorOfMatchingChars[0].intCenterY;
166 | double dblHypotenuse = distanceBetweenChars(vectorOfMatchingChars[0], vectorOfMatchingChars[vectorOfMatchingChars.size() - 1]);
167 | double dblCorrectionAngleInRad = asin(dblOpposite / dblHypotenuse);
168 | double dblCorrectionAngleInDeg = dblCorrectionAngleInRad * (180.0 / CV_PI);
169 |
170 | // assign rotated rect member variable of possible plate
171 | possiblePlate.rrLocationOfPlateInScene = cv::RotatedRect(p2dPlateCenter, cv::Size2f((float)intPlateWidth, (float)intPlateHeight), (float)dblCorrectionAngleInDeg);
172 |
173 | cv::Mat rotationMatrix; // final steps are to perform the actual rotation
174 | cv::Mat imgRotated;
175 | cv::Mat imgCropped;
176 |
177 | rotationMatrix = cv::getRotationMatrix2D(p2dPlateCenter, dblCorrectionAngleInDeg, 1.0); // get the rotation matrix for our calculated correction angle
178 |
179 | cv::warpAffine(imgOriginal, imgRotated, rotationMatrix, imgOriginal.size()); // rotate the entire image
180 |
181 | // crop out the actual plate portion of the rotated image
182 | cv::getRectSubPix(imgRotated, possiblePlate.rrLocationOfPlateInScene.size, possiblePlate.rrLocationOfPlateInScene.center, imgCropped);
183 |
184 | possiblePlate.imgPlate = imgCropped; // copy the cropped plate image into the applicable member variable of the possible plate
185 |
186 | return(possiblePlate);
187 | }
188 |
189 |
--------------------------------------------------------------------------------
/archive/DetectChars.cpp:
--------------------------------------------------------------------------------
1 | // DetectChars.cpp
2 |
3 | #include "DetectChars.h"
4 |
5 | // global variables ///////////////////////////////////////////////////////////////////////////////
6 | cv::Ptr kNearest = cv::ml::KNearest::create();
7 |
8 | ///////////////////////////////////////////////////////////////////////////////////////////////////
9 | bool loadKNNDataAndTrainKNN(void) {
10 |
11 | // read in training classifications ///////////////////////////////////////////////////
12 |
13 | cv::Mat matClassificationInts; // we will read the classification numbers into this variable as though it is a vector
14 |
15 | cv::FileStorage fsClassifications("classifications.xml", cv::FileStorage::READ); // open the classifications file
16 |
17 | if (fsClassifications.isOpened() == false) { // if the file was not opened successfully
18 | std::cout << "error, unable to open training classifications file, exiting program\n\n"; // show error message
19 | return(false); // and exit program
20 | }
21 |
22 | fsClassifications["classifications"] >> matClassificationInts; // read classifications section into Mat classifications variable
23 | fsClassifications.release(); // close the classifications file
24 |
25 | // read in training images ////////////////////////////////////////////////////////////
26 |
27 | cv::Mat matTrainingImagesAsFlattenedFloats; // we will read multiple images into this single image variable as though it is a vector
28 |
29 | cv::FileStorage fsTrainingImages("images.xml", cv::FileStorage::READ); // open the training images file
30 |
31 | if (fsTrainingImages.isOpened() == false) { // if the file was not opened successfully
32 | std::cout << "error, unable to open training images file, exiting program\n\n"; // show error message
33 | return(false); // and exit program
34 | }
35 |
36 | fsTrainingImages["images"] >> matTrainingImagesAsFlattenedFloats; // read images section into Mat training images variable
37 | fsTrainingImages.release(); // close the traning images file
38 |
39 | // train //////////////////////////////////////////////////////////////////////////////
40 |
41 | // finally we get to the call to train, note that both parameters have to be of type Mat (a single Mat)
42 | // even though in reality they are multiple images / numbers
43 | kNearest->setDefaultK(1);
44 |
45 | kNearest->train(matTrainingImagesAsFlattenedFloats, cv::ml::ROW_SAMPLE, matClassificationInts);
46 |
47 | return true;
48 | }
49 |
50 | ///////////////////////////////////////////////////////////////////////////////////////////////////
51 | std::vector detectCharsInPlates(std::vector &vectorOfPossiblePlates) {
52 | int intPlateCounter = 0; // this is only for showing steps
53 | cv::Mat imgContours;
54 | std::vector > contours;
55 | cv::RNG rng;
56 |
57 | if (vectorOfPossiblePlates.empty()) { // if vector of possible plates is empty
58 | return(vectorOfPossiblePlates); // return
59 | }
60 | // at this point we can be sure vector of possible plates has at least one plate
61 |
62 | for (auto &possiblePlate : vectorOfPossiblePlates) { // for each possible plate, this is a big for loop that takes up most of the function
63 |
64 | preprocess(possiblePlate.imgPlate, possiblePlate.imgGrayscale, possiblePlate.imgThresh); // preprocess to get grayscale and threshold images
65 |
66 | #ifdef SHOW_STEPS
67 | cv::imshow("5a", possiblePlate.imgPlate);
68 | cv::imshow("5b", possiblePlate.imgGrayscale);
69 | cv::imshow("5c", possiblePlate.imgThresh);
70 | #endif // SHOW_STEPS
71 |
72 | // upscale size by 60% for better viewing and character recognition
73 | cv::resize(possiblePlate.imgThresh, possiblePlate.imgThresh, cv::Size(), 1.6, 1.6);
74 |
75 | // threshold again to eliminate any gray areas
76 | cv::threshold(possiblePlate.imgThresh, possiblePlate.imgThresh, 0.0, 255.0, CV_THRESH_BINARY | CV_THRESH_OTSU);
77 |
78 | #ifdef SHOW_STEPS
79 | cv::imshow("5d", possiblePlate.imgThresh);
80 | #endif // SHOW_STEPS
81 |
82 | // find all possible chars in the plate,
83 | // this function first finds all contours, then only includes contours that could be chars (without comparison to other chars yet)
84 | std::vector vectorOfPossibleCharsInPlate = findPossibleCharsInPlate(possiblePlate.imgGrayscale, possiblePlate.imgThresh);
85 |
86 | #ifdef SHOW_STEPS
87 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
88 | contours.clear();
89 |
90 | for (auto &possibleChar : vectorOfPossibleCharsInPlate) {
91 | contours.push_back(possibleChar.contour);
92 | }
93 |
94 | cv::drawContours(imgContours, contours, -1, SCALAR_WHITE);
95 |
96 | cv::imshow("6", imgContours);
97 | #endif // SHOW_STEPS
98 |
99 | // given a vector of all possible chars, find groups of matching chars within the plate
100 | std::vector > vectorOfVectorsOfMatchingCharsInPlate = findVectorOfVectorsOfMatchingChars(vectorOfPossibleCharsInPlate);
101 |
102 | #ifdef SHOW_STEPS
103 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
104 |
105 | contours.clear();
106 |
107 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) {
108 | int intRandomBlue = rng.uniform(0, 256);
109 | int intRandomGreen = rng.uniform(0, 256);
110 | int intRandomRed = rng.uniform(0, 256);
111 |
112 | for (auto &matchingChar : vectorOfMatchingChars) {
113 | contours.push_back(matchingChar.contour);
114 | }
115 | cv::drawContours(imgContours, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
116 | }
117 | cv::imshow("7", imgContours);
118 | #endif // SHOW_STEPS
119 |
120 | if (vectorOfVectorsOfMatchingCharsInPlate.size() == 0) { // if no groups of matching chars were found in the plate
121 | #ifdef SHOW_STEPS
122 | std::cout << "chars found in plate number " << intPlateCounter << " = (none), click on any image and press a key to continue . . ." << std::endl;
123 | intPlateCounter++;
124 | cv::destroyWindow("8");
125 | cv::destroyWindow("9");
126 | cv::destroyWindow("10");
127 | cv::waitKey(0);
128 | #endif // SHOW_STEPS
129 | possiblePlate.strChars = ""; // set plate string member variable to empty string
130 | continue; // go back to top of for loop
131 | }
132 |
133 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) { // for each vector of matching chars in the current plate
134 | std::sort(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), PossibleChar::sortCharsLeftToRight); // sort the chars left to right
135 | vectorOfMatchingChars = removeInnerOverlappingChars(vectorOfMatchingChars); // and eliminate any overlapping chars
136 | }
137 |
138 | #ifdef SHOW_STEPS
139 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
140 |
141 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) {
142 | int intRandomBlue = rng.uniform(0, 256);
143 | int intRandomGreen = rng.uniform(0, 256);
144 | int intRandomRed = rng.uniform(0, 256);
145 |
146 | contours.clear();
147 |
148 | for (auto &matchingChar : vectorOfMatchingChars) {
149 | contours.push_back(matchingChar.contour);
150 | }
151 | cv::drawContours(imgContours, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
152 | }
153 | cv::imshow("8", imgContours);
154 | #endif // SHOW_STEPS
155 |
156 | // within each possible plate, suppose the longest vector of potential matching chars is the actual vector of chars
157 | unsigned int intLenOfLongestVectorOfChars = 0;
158 | unsigned int intIndexOfLongestVectorOfChars = 0;
159 | // loop through all the vectors of matching chars, get the index of the one with the most chars
160 | for (unsigned int i = 0; i < vectorOfVectorsOfMatchingCharsInPlate.size(); i++) {
161 | if (vectorOfVectorsOfMatchingCharsInPlate[i].size() > intLenOfLongestVectorOfChars) {
162 | intLenOfLongestVectorOfChars = vectorOfVectorsOfMatchingCharsInPlate[i].size();
163 | intIndexOfLongestVectorOfChars = i;
164 | }
165 | }
166 | // suppose that the longest vector of matching chars within the plate is the actual vector of chars
167 | std::vector longestVectorOfMatchingCharsInPlate = vectorOfVectorsOfMatchingCharsInPlate[intIndexOfLongestVectorOfChars];
168 |
169 | #ifdef SHOW_STEPS
170 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
171 |
172 | contours.clear();
173 |
174 | for (auto &matchingChar : longestVectorOfMatchingCharsInPlate) {
175 | contours.push_back(matchingChar.contour);
176 | }
177 | cv::drawContours(imgContours, contours, -1, SCALAR_WHITE);
178 |
179 | cv::imshow("9", imgContours);
180 | #endif // SHOW_STEPS
181 |
182 | // perform char recognition on the longest vector of matching chars in the plate
183 | possiblePlate.strChars = recognizeCharsInPlate(possiblePlate.imgThresh, longestVectorOfMatchingCharsInPlate);
184 |
185 | #ifdef SHOW_STEPS
186 | std::cout << "chars found in plate number " << intPlateCounter << " = " << possiblePlate.strChars << ", click on any image and press a key to continue . . ." << std::endl;
187 | intPlateCounter++;
188 | cv::waitKey(0);
189 | #endif // SHOW_STEPS
190 |
191 | } // end for each possible plate big for loop that takes up most of the function
192 |
193 | #ifdef SHOW_STEPS
194 | std::cout << std::endl << "char detection complete, click on any image and press a key to continue . . ." << std::endl;
195 | cv::waitKey(0);
196 | #endif // SHOW_STEPS
197 |
198 | return(vectorOfPossiblePlates);
199 | }
200 |
201 | ///////////////////////////////////////////////////////////////////////////////////////////////////
202 | std::vector findPossibleCharsInPlate(cv::Mat &imgGrayscale, cv::Mat &imgThresh) {
203 | std::vector vectorOfPossibleChars; // this will be the return value
204 |
205 | cv::Mat imgThreshCopy;
206 |
207 | std::vector > contours;
208 |
209 | imgThreshCopy = imgThresh.clone(); // make a copy of the thresh image, this in necessary b/c findContours modifies the image
210 |
211 | cv::findContours(imgThreshCopy, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // find all contours in plate
212 |
213 | for (auto &contour : contours) { // for each contour
214 | PossibleChar possibleChar(contour);
215 |
216 | if (checkIfPossibleChar(possibleChar)) { // if contour is a possible char, note this does not compare to other chars (yet) . . .
217 | vectorOfPossibleChars.push_back(possibleChar); // add to vector of possible chars
218 | }
219 | }
220 |
221 | return(vectorOfPossibleChars);
222 | }
223 |
224 | ///////////////////////////////////////////////////////////////////////////////////////////////////
225 | bool checkIfPossibleChar(PossibleChar &possibleChar) {
226 | // this function is a 'first pass' that does a rough check on a contour to see if it could be a char,
227 | // note that we are not (yet) comparing the char to other chars to look for a group
228 | if (possibleChar.boundingRect.area() > MIN_PIXEL_AREA &&
229 | possibleChar.boundingRect.width > MIN_PIXEL_WIDTH && possibleChar.boundingRect.height > MIN_PIXEL_HEIGHT &&
230 | MIN_ASPECT_RATIO < possibleChar.dblAspectRatio && possibleChar.dblAspectRatio < MAX_ASPECT_RATIO) {
231 | return(true);
232 | } else {
233 | return(false);
234 | }
235 | }
236 |
237 | ///////////////////////////////////////////////////////////////////////////////////////////////////
238 | std::vector > findVectorOfVectorsOfMatchingChars(const std::vector &vectorOfPossibleChars) {
239 | // with this function, we start off with all the possible chars in one big vector
240 | // the purpose of this function is to re-arrange the one big vector of chars into a vector of vectors of matching chars,
241 | // note that chars that are not found to be in a group of matches do not need to be considered further
242 | std::vector > vectorOfVectorsOfMatchingChars; // this will be the return value
243 |
244 | for (auto &possibleChar : vectorOfPossibleChars) { // for each possible char in the one big vector of chars
245 |
246 | // find all chars in the big vector that match the current char
247 | std::vector vectorOfMatchingChars = findVectorOfMatchingChars(possibleChar, vectorOfPossibleChars);
248 |
249 | vectorOfMatchingChars.push_back(possibleChar); // also add the current char to current possible vector of matching chars
250 |
251 | // if current possible vector of matching chars is not long enough to constitute a possible plate
252 | if (vectorOfMatchingChars.size() < MIN_NUMBER_OF_MATCHING_CHARS) {
253 | continue; // jump back to the top of the for loop and try again with next char, note that it's not necessary
254 | // to save the vector in any way since it did not have enough chars to be a possible plate
255 | }
256 | // if we get here, the current vector passed test as a "group" or "cluster" of matching chars
257 | vectorOfVectorsOfMatchingChars.push_back(vectorOfMatchingChars); // so add to our vector of vectors of matching chars
258 |
259 | // remove the current vector of matching chars from the big vector so we don't use those same chars twice,
260 | // make sure to make a new big vector for this since we don't want to change the original big vector
261 | std::vector vectorOfPossibleCharsWithCurrentMatchesRemoved;
262 |
263 | for (auto &possChar : vectorOfPossibleChars) {
264 | if (std::find(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), possChar) == vectorOfMatchingChars.end()) {
265 | vectorOfPossibleCharsWithCurrentMatchesRemoved.push_back(possChar);
266 | }
267 | }
268 | // declare new vector of vectors of chars to get result from recursive call
269 | std::vector > recursiveVectorOfVectorsOfMatchingChars;
270 |
271 | // recursive call
272 | recursiveVectorOfVectorsOfMatchingChars = findVectorOfVectorsOfMatchingChars(vectorOfPossibleCharsWithCurrentMatchesRemoved); // recursive call !!
273 |
274 | for (auto &recursiveVectorOfMatchingChars : recursiveVectorOfVectorsOfMatchingChars) { // for each vector of matching chars found by recursive call
275 | vectorOfVectorsOfMatchingChars.push_back(recursiveVectorOfMatchingChars); // add to our original vector of vectors of matching chars
276 | }
277 |
278 | break; // exit for loop
279 | }
280 |
281 | return(vectorOfVectorsOfMatchingChars);
282 | }
283 |
284 | ///////////////////////////////////////////////////////////////////////////////////////////////////
285 | std::vector findVectorOfMatchingChars(const PossibleChar &possibleChar, const std::vector &vectorOfChars) {
286 | // the purpose of this function is, given a possible char and a big vector of possible chars,
287 | // find all chars in the big vector that are a match for the single possible char, and return those matching chars as a vector
288 | std::vector vectorOfMatchingChars; // this will be the return value
289 |
290 | for (auto &possibleMatchingChar : vectorOfChars) { // for each char in big vector
291 |
292 | // if the char we attempting to find matches for is the exact same char as the char in the big vector we are currently checking
293 | if (possibleMatchingChar == possibleChar) {
294 | // then we should not include it in the vector of matches b/c that would end up double including the current char
295 | continue; // so do not add to vector of matches and jump back to top of for loop
296 | }
297 | // compute stuff to see if chars are a match
298 | double dblDistanceBetweenChars = distanceBetweenChars(possibleChar, possibleMatchingChar);
299 | double dblAngleBetweenChars = angleBetweenChars(possibleChar, possibleMatchingChar);
300 | double dblChangeInArea = (double)abs(possibleMatchingChar.boundingRect.area() - possibleChar.boundingRect.area()) / (double)possibleChar.boundingRect.area();
301 | double dblChangeInWidth = (double)abs(possibleMatchingChar.boundingRect.width - possibleChar.boundingRect.width) / (double)possibleChar.boundingRect.width;
302 | double dblChangeInHeight = (double)abs(possibleMatchingChar.boundingRect.height - possibleChar.boundingRect.height) / (double)possibleChar.boundingRect.height;
303 |
304 | // check if chars match
305 | if (dblDistanceBetweenChars < (possibleChar.dblDiagonalSize * MAX_DIAG_SIZE_MULTIPLE_AWAY) &&
306 | dblAngleBetweenChars < MAX_ANGLE_BETWEEN_CHARS &&
307 | dblChangeInArea < MAX_CHANGE_IN_AREA &&
308 | dblChangeInWidth < MAX_CHANGE_IN_WIDTH &&
309 | dblChangeInHeight < MAX_CHANGE_IN_HEIGHT) {
310 | vectorOfMatchingChars.push_back(possibleMatchingChar); // if the chars are a match, add the current char to vector of matching chars
311 | }
312 | }
313 |
314 | return(vectorOfMatchingChars); // return result
315 | }
316 |
317 | ///////////////////////////////////////////////////////////////////////////////////////////////////
318 | // use Pythagorean theorem to calculate distance between two chars
319 | double distanceBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar) {
320 | int intX = abs(firstChar.intCenterX - secondChar.intCenterX);
321 | int intY = abs(firstChar.intCenterY - secondChar.intCenterY);
322 |
323 | return(sqrt(pow(intX, 2) + pow(intY, 2)));
324 | }
325 |
326 | ///////////////////////////////////////////////////////////////////////////////////////////////////
327 | // use basic trigonometry(SOH CAH TOA) to calculate angle between chars
328 | double angleBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar) {
329 | double dblAdj = abs(firstChar.intCenterX - secondChar.intCenterX);
330 | double dblOpp = abs(firstChar.intCenterY - secondChar.intCenterY);
331 |
332 | double dblAngleInRad = atan(dblOpp / dblAdj);
333 |
334 | double dblAngleInDeg = dblAngleInRad * (180.0 / CV_PI);
335 |
336 | return(dblAngleInDeg);
337 | }
338 |
339 | ///////////////////////////////////////////////////////////////////////////////////////////////////
340 | // if we have two chars overlapping or to close to each other to possibly be separate chars, remove the inner (smaller) char,
341 | // this is to prevent including the same char twice if two contours are found for the same char,
342 | // for example for the letter 'O' both the inner ring and the outer ring may be found as contours, but we should only include the char once
343 | std::vector removeInnerOverlappingChars(std::vector &vectorOfMatchingChars) {
344 | std::vector vectorOfMatchingCharsWithInnerCharRemoved(vectorOfMatchingChars);
345 |
346 | for (auto ¤tChar : vectorOfMatchingChars) {
347 | for (auto &otherChar : vectorOfMatchingChars) {
348 | if (currentChar != otherChar) { // if current char and other char are not the same char . . .
349 | // if current char and other char have center points at almost the same location . . .
350 | if (distanceBetweenChars(currentChar, otherChar) < (currentChar.dblDiagonalSize * MIN_DIAG_SIZE_MULTIPLE_AWAY)) {
351 | // if we get in here we have found overlapping chars
352 | // next we identify which char is smaller, then if that char was not already removed on a previous pass, remove it
353 |
354 | // if current char is smaller than other char
355 | if (currentChar.boundingRect.area() < otherChar.boundingRect.area()) {
356 | // look for char in vector with an iterator
357 | std::vector::iterator currentCharIterator = std::find(vectorOfMatchingCharsWithInnerCharRemoved.begin(), vectorOfMatchingCharsWithInnerCharRemoved.end(), currentChar);
358 | // if iterator did not get to end, then the char was found in the vector
359 | if (currentCharIterator != vectorOfMatchingCharsWithInnerCharRemoved.end()) {
360 | vectorOfMatchingCharsWithInnerCharRemoved.erase(currentCharIterator); // so remove the char
361 | }
362 | } else { // else if other char is smaller than current char
363 | // look for char in vector with an iterator
364 | std::vector::iterator otherCharIterator = std::find(vectorOfMatchingCharsWithInnerCharRemoved.begin(), vectorOfMatchingCharsWithInnerCharRemoved.end(), otherChar);
365 | // if iterator did not get to end, then the char was found in the vector
366 | if (otherCharIterator != vectorOfMatchingCharsWithInnerCharRemoved.end()) {
367 | vectorOfMatchingCharsWithInnerCharRemoved.erase(otherCharIterator); // so remove the char
368 | }
369 | }
370 | }
371 | }
372 | }
373 | }
374 |
375 | return(vectorOfMatchingCharsWithInnerCharRemoved);
376 | }
377 |
378 | ///////////////////////////////////////////////////////////////////////////////////////////////////
379 | // this is where we apply the actual char recognition
380 | std::string recognizeCharsInPlate(cv::Mat &imgThresh, std::vector &vectorOfMatchingChars) {
381 | std::string strChars; // this will be the return value, the chars in the lic plate
382 |
383 | cv::Mat imgThreshColor;
384 |
385 | // sort chars from left to right
386 | std::sort(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), PossibleChar::sortCharsLeftToRight);
387 |
388 | cv::cvtColor(imgThresh, imgThreshColor, CV_GRAY2BGR); // make color version of threshold image so we can draw contours in color on it
389 |
390 | for (auto ¤tChar : vectorOfMatchingChars) { // for each char in plate
391 | cv::rectangle(imgThreshColor, currentChar.boundingRect, SCALAR_GREEN, 2); // draw green box around the char
392 |
393 | cv::Mat imgROItoBeCloned = imgThresh(currentChar.boundingRect); // get ROI image of bounding rect
394 |
395 | cv::Mat imgROI = imgROItoBeCloned.clone(); // clone ROI image so we don't change original when we resize
396 |
397 | cv::Mat imgROIResized;
398 | // resize image, this is necessary for char recognition
399 | cv::resize(imgROI, imgROIResized, cv::Size(RESIZED_CHAR_IMAGE_WIDTH, RESIZED_CHAR_IMAGE_HEIGHT));
400 |
401 | cv::Mat matROIFloat;
402 |
403 | imgROIResized.convertTo(matROIFloat, CV_32FC1); // convert Mat to float, necessary for call to findNearest
404 |
405 | cv::Mat matROIFlattenedFloat = matROIFloat.reshape(1, 1); // flatten Matrix into one row
406 |
407 | cv::Mat matCurrentChar(0, 0, CV_32F); // declare Mat to read current char into, this is necessary b/c findNearest requires a Mat
408 |
409 | kNearest->findNearest(matROIFlattenedFloat, 1, matCurrentChar); // finally we can call find_nearest !!!
410 |
411 | float fltCurrentChar = (float)matCurrentChar.at(0, 0); // convert current char from Mat to float
412 |
413 | strChars = strChars + char(int(fltCurrentChar)); // append current char to full string
414 | }
415 |
416 | #ifdef SHOW_STEPS
417 | cv::imshow("10", imgThreshColor);
418 | #endif // SHOW_STEPS
419 |
420 | return(strChars); // return result
421 | }
422 |
423 |
424 |
425 |
426 |
--------------------------------------------------------------------------------
/DetectChars.cpp:
--------------------------------------------------------------------------------
1 | // DetectChars.cpp
2 |
3 | #include "DetectChars.h"
4 |
5 | // global variables ///////////////////////////////////////////////////////////////////////////////
6 | cv::Ptr kNearest = cv::ml::KNearest::create();
7 |
8 | ///////////////////////////////////////////////////////////////////////////////////////////////////
9 | bool loadKNNDataAndTrainKNN(void) {
10 |
11 | // read in training classifications ///////////////////////////////////////////////////
12 |
13 | cv::Mat matClassificationInts; // we will read the classification numbers into this variable as though it is a vector
14 |
15 | cv::FileStorage fsClassifications("classifications.xml", cv::FileStorage::READ); // open the classifications file
16 |
17 | if (fsClassifications.isOpened() == false) { // if the file was not opened successfully
18 | std::cout << "error, unable to open training classifications file, exiting program\n\n"; // show error message
19 | return(false); // and exit program
20 | }
21 |
22 | fsClassifications["classifications"] >> matClassificationInts; // read classifications section into Mat classifications variable
23 | fsClassifications.release(); // close the classifications file
24 |
25 | // read in training images ////////////////////////////////////////////////////////////
26 |
27 | cv::Mat matTrainingImagesAsFlattenedFloats; // we will read multiple images into this single image variable as though it is a vector
28 |
29 | cv::FileStorage fsTrainingImages("images.xml", cv::FileStorage::READ); // open the training images file
30 |
31 | if (fsTrainingImages.isOpened() == false) { // if the file was not opened successfully
32 | std::cout << "error, unable to open training images file, exiting program\n\n"; // show error message
33 | return(false); // and exit program
34 | }
35 |
36 | fsTrainingImages["images"] >> matTrainingImagesAsFlattenedFloats; // read images section into Mat training images variable
37 | fsTrainingImages.release(); // close the traning images file
38 |
39 | // train //////////////////////////////////////////////////////////////////////////////
40 |
41 | // finally we get to the call to train, note that both parameters have to be of type Mat (a single Mat)
42 | // even though in reality they are multiple images / numbers
43 | kNearest->setDefaultK(1);
44 |
45 | kNearest->train(matTrainingImagesAsFlattenedFloats, cv::ml::ROW_SAMPLE, matClassificationInts);
46 |
47 | return true;
48 | }
49 |
50 | ///////////////////////////////////////////////////////////////////////////////////////////////////
51 | std::vector detectCharsInPlates(std::vector &vectorOfPossiblePlates) {
52 | int intPlateCounter = 0; // this is only for showing steps
53 | cv::Mat imgContours;
54 | std::vector > contours;
55 | cv::RNG rng;
56 |
57 | if (vectorOfPossiblePlates.empty()) { // if vector of possible plates is empty
58 | return(vectorOfPossiblePlates); // return
59 | }
60 | // at this point we can be sure vector of possible plates has at least one plate
61 |
62 | for (auto &possiblePlate : vectorOfPossiblePlates) { // for each possible plate, this is a big for loop that takes up most of the function
63 |
64 | preprocess(possiblePlate.imgPlate, possiblePlate.imgGrayscale, possiblePlate.imgThresh); // preprocess to get grayscale and threshold images
65 |
66 | #ifdef SHOW_STEPS
67 | cv::imshow("5a", possiblePlate.imgPlate);
68 | cv::imshow("5b", possiblePlate.imgGrayscale);
69 | cv::imshow("5c", possiblePlate.imgThresh);
70 | #endif // SHOW_STEPS
71 |
72 | // upscale size by 60% for better viewing and character recognition
73 | cv::resize(possiblePlate.imgThresh, possiblePlate.imgThresh, cv::Size(), 1.6, 1.6);
74 |
75 | // threshold again to eliminate any gray areas
76 | cv::threshold(possiblePlate.imgThresh, possiblePlate.imgThresh, 0.0, 255.0, CV_THRESH_BINARY | CV_THRESH_OTSU);
77 |
78 | #ifdef SHOW_STEPS
79 | cv::imshow("5d", possiblePlate.imgThresh);
80 | #endif // SHOW_STEPS
81 |
82 | // find all possible chars in the plate,
83 | // this function first finds all contours, then only includes contours that could be chars (without comparison to other chars yet)
84 | std::vector vectorOfPossibleCharsInPlate = findPossibleCharsInPlate(possiblePlate.imgGrayscale, possiblePlate.imgThresh);
85 |
86 | #ifdef SHOW_STEPS
87 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
88 | contours.clear();
89 |
90 | for (auto &possibleChar : vectorOfPossibleCharsInPlate) {
91 | contours.push_back(possibleChar.contour);
92 | }
93 |
94 | cv::drawContours(imgContours, contours, -1, SCALAR_WHITE);
95 |
96 | cv::imshow("6", imgContours);
97 | #endif // SHOW_STEPS
98 |
99 | // given a vector of all possible chars, find groups of matching chars within the plate
100 | std::vector > vectorOfVectorsOfMatchingCharsInPlate = findVectorOfVectorsOfMatchingChars(vectorOfPossibleCharsInPlate);
101 |
102 | #ifdef SHOW_STEPS
103 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
104 |
105 | contours.clear();
106 |
107 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) {
108 | int intRandomBlue = rng.uniform(0, 256);
109 | int intRandomGreen = rng.uniform(0, 256);
110 | int intRandomRed = rng.uniform(0, 256);
111 |
112 | for (auto &matchingChar : vectorOfMatchingChars) {
113 | contours.push_back(matchingChar.contour);
114 | }
115 | cv::drawContours(imgContours, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
116 | }
117 | cv::imshow("7", imgContours);
118 | #endif // SHOW_STEPS
119 |
120 | if (vectorOfVectorsOfMatchingCharsInPlate.size() == 0) { // if no groups of matching chars were found in the plate
121 | #ifdef SHOW_STEPS
122 | std::cout << "chars found in plate number " << intPlateCounter << " = (none), click on any image and press a key to continue . . ." << std::endl;
123 | intPlateCounter++;
124 | cv::destroyWindow("8");
125 | cv::destroyWindow("9");
126 | cv::destroyWindow("10");
127 | cv::waitKey(0);
128 | #endif // SHOW_STEPS
129 | possiblePlate.strChars = ""; // set plate string member variable to empty string
130 | continue; // go back to top of for loop
131 | }
132 |
133 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) { // for each vector of matching chars in the current plate
134 | std::sort(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), PossibleChar::sortCharsLeftToRight); // sort the chars left to right
135 | vectorOfMatchingChars = removeInnerOverlappingChars(vectorOfMatchingChars); // and eliminate any overlapping chars
136 | }
137 |
138 | #ifdef SHOW_STEPS
139 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
140 |
141 | for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) {
142 | int intRandomBlue = rng.uniform(0, 256);
143 | int intRandomGreen = rng.uniform(0, 256);
144 | int intRandomRed = rng.uniform(0, 256);
145 |
146 | contours.clear();
147 |
148 | for (auto &matchingChar : vectorOfMatchingChars) {
149 | contours.push_back(matchingChar.contour);
150 | }
151 | cv::drawContours(imgContours, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
152 | }
153 | cv::imshow("8", imgContours);
154 | #endif // SHOW_STEPS
155 |
156 | // within each possible plate, suppose the longest vector of potential matching chars is the actual vector of chars
157 | unsigned int intLenOfLongestVectorOfChars = 0;
158 | unsigned int intIndexOfLongestVectorOfChars = 0;
159 | // loop through all the vectors of matching chars, get the index of the one with the most chars
160 | for (unsigned int i = 0; i < vectorOfVectorsOfMatchingCharsInPlate.size(); i++) {
161 | if (vectorOfVectorsOfMatchingCharsInPlate[i].size() > intLenOfLongestVectorOfChars) {
162 | intLenOfLongestVectorOfChars = vectorOfVectorsOfMatchingCharsInPlate[i].size();
163 | intIndexOfLongestVectorOfChars = i;
164 | }
165 | }
166 | // suppose that the longest vector of matching chars within the plate is the actual vector of chars
167 | std::vector longestVectorOfMatchingCharsInPlate = vectorOfVectorsOfMatchingCharsInPlate[intIndexOfLongestVectorOfChars];
168 |
169 | #ifdef SHOW_STEPS
170 | imgContours = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
171 |
172 | contours.clear();
173 |
174 | for (auto &matchingChar : longestVectorOfMatchingCharsInPlate) {
175 | contours.push_back(matchingChar.contour);
176 | }
177 | cv::drawContours(imgContours, contours, -1, SCALAR_WHITE);
178 |
179 | cv::imshow("9", imgContours);
180 | #endif // SHOW_STEPS
181 |
182 | // perform char recognition on the longest vector of matching chars in the plate
183 | possiblePlate.strChars = recognizeCharsInPlate(possiblePlate.imgThresh, longestVectorOfMatchingCharsInPlate);
184 |
185 | #ifdef SHOW_STEPS
186 | std::cout << "chars found in plate number " << intPlateCounter << " = " << possiblePlate.strChars << ", click on any image and press a key to continue . . ." << std::endl;
187 | intPlateCounter++;
188 | cv::waitKey(0);
189 | #endif // SHOW_STEPS
190 |
191 | } // end for each possible plate big for loop that takes up most of the function
192 |
193 | #ifdef SHOW_STEPS
194 | std::cout << std::endl << "char detection complete, click on any image and press a key to continue . . ." << std::endl;
195 | cv::waitKey(0);
196 | #endif // SHOW_STEPS
197 |
198 | return(vectorOfPossiblePlates);
199 | }
200 |
201 | ///////////////////////////////////////////////////////////////////////////////////////////////////
202 | std::vector findPossibleCharsInPlate(cv::Mat &imgGrayscale, cv::Mat &imgThresh) {
203 | std::vector vectorOfPossibleChars; // this will be the return value
204 |
205 | cv::Mat imgThreshCopy;
206 |
207 | std::vector > contours;
208 |
209 | imgThreshCopy = imgThresh.clone(); // make a copy of the thresh image, this in necessary b/c findContours modifies the image
210 |
211 | cv::findContours(imgThreshCopy, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // find all contours in plate
212 |
213 | for (auto &contour : contours) { // for each contour
214 | PossibleChar possibleChar(contour);
215 |
216 | if (checkIfPossibleChar(possibleChar)) { // if contour is a possible char, note this does not compare to other chars (yet) . . .
217 | vectorOfPossibleChars.push_back(possibleChar); // add to vector of possible chars
218 | }
219 | }
220 |
221 | return(vectorOfPossibleChars);
222 | }
223 |
224 | ///////////////////////////////////////////////////////////////////////////////////////////////////
225 | bool checkIfPossibleChar(PossibleChar &possibleChar) {
226 | // this function is a 'first pass' that does a rough check on a contour to see if it could be a char,
227 | // note that we are not (yet) comparing the char to other chars to look for a group
228 | if (possibleChar.boundingRect.area() > MIN_PIXEL_AREA &&
229 | possibleChar.boundingRect.width > MIN_PIXEL_WIDTH && possibleChar.boundingRect.height > MIN_PIXEL_HEIGHT &&
230 | MIN_ASPECT_RATIO < possibleChar.dblAspectRatio && possibleChar.dblAspectRatio < MAX_ASPECT_RATIO) {
231 | return(true);
232 | }
233 | else {
234 | return(false);
235 | }
236 | }
237 |
238 | ///////////////////////////////////////////////////////////////////////////////////////////////////
239 | std::vector > findVectorOfVectorsOfMatchingChars(const std::vector &vectorOfPossibleChars) {
240 | // with this function, we start off with all the possible chars in one big vector
241 | // the purpose of this function is to re-arrange the one big vector of chars into a vector of vectors of matching chars,
242 | // note that chars that are not found to be in a group of matches do not need to be considered further
243 | std::vector > vectorOfVectorsOfMatchingChars; // this will be the return value
244 |
245 | for (auto &possibleChar : vectorOfPossibleChars) { // for each possible char in the one big vector of chars
246 |
247 | // find all chars in the big vector that match the current char
248 | std::vector vectorOfMatchingChars = findVectorOfMatchingChars(possibleChar, vectorOfPossibleChars);
249 |
250 | vectorOfMatchingChars.push_back(possibleChar); // also add the current char to current possible vector of matching chars
251 |
252 | // if current possible vector of matching chars is not long enough to constitute a possible plate
253 | if (vectorOfMatchingChars.size() < MIN_NUMBER_OF_MATCHING_CHARS) {
254 | continue; // jump back to the top of the for loop and try again with next char, note that it's not necessary
255 | // to save the vector in any way since it did not have enough chars to be a possible plate
256 | }
257 | // if we get here, the current vector passed test as a "group" or "cluster" of matching chars
258 | vectorOfVectorsOfMatchingChars.push_back(vectorOfMatchingChars); // so add to our vector of vectors of matching chars
259 |
260 | // remove the current vector of matching chars from the big vector so we don't use those same chars twice,
261 | // make sure to make a new big vector for this since we don't want to change the original big vector
262 | std::vector vectorOfPossibleCharsWithCurrentMatchesRemoved;
263 |
264 | for (auto &possChar : vectorOfPossibleChars) {
265 | if (std::find(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), possChar) == vectorOfMatchingChars.end()) {
266 | vectorOfPossibleCharsWithCurrentMatchesRemoved.push_back(possChar);
267 | }
268 | }
269 | // declare new vector of vectors of chars to get result from recursive call
270 | std::vector > recursiveVectorOfVectorsOfMatchingChars;
271 |
272 | // recursive call
273 | recursiveVectorOfVectorsOfMatchingChars = findVectorOfVectorsOfMatchingChars(vectorOfPossibleCharsWithCurrentMatchesRemoved); // recursive call !!
274 |
275 | for (auto &recursiveVectorOfMatchingChars : recursiveVectorOfVectorsOfMatchingChars) { // for each vector of matching chars found by recursive call
276 | vectorOfVectorsOfMatchingChars.push_back(recursiveVectorOfMatchingChars); // add to our original vector of vectors of matching chars
277 | }
278 |
279 | break; // exit for loop
280 | }
281 |
282 | return(vectorOfVectorsOfMatchingChars);
283 | }
284 |
285 | ///////////////////////////////////////////////////////////////////////////////////////////////////
286 | std::vector findVectorOfMatchingChars(const PossibleChar &possibleChar, const std::vector &vectorOfChars) {
287 | // the purpose of this function is, given a possible char and a big vector of possible chars,
288 | // find all chars in the big vector that are a match for the single possible char, and return those matching chars as a vector
289 | std::vector vectorOfMatchingChars; // this will be the return value
290 |
291 | for (auto &possibleMatchingChar : vectorOfChars) { // for each char in big vector
292 |
293 | // if the char we attempting to find matches for is the exact same char as the char in the big vector we are currently checking
294 | if (possibleMatchingChar == possibleChar) {
295 | // then we should not include it in the vector of matches b/c that would end up double including the current char
296 | continue; // so do not add to vector of matches and jump back to top of for loop
297 | }
298 | // compute stuff to see if chars are a match
299 | double dblDistanceBetweenChars = distanceBetweenChars(possibleChar, possibleMatchingChar);
300 | double dblAngleBetweenChars = angleBetweenChars(possibleChar, possibleMatchingChar);
301 | double dblChangeInArea = (double)abs(possibleMatchingChar.boundingRect.area() - possibleChar.boundingRect.area()) / (double)possibleChar.boundingRect.area();
302 | double dblChangeInWidth = (double)abs(possibleMatchingChar.boundingRect.width - possibleChar.boundingRect.width) / (double)possibleChar.boundingRect.width;
303 | double dblChangeInHeight = (double)abs(possibleMatchingChar.boundingRect.height - possibleChar.boundingRect.height) / (double)possibleChar.boundingRect.height;
304 |
305 | // check if chars match
306 | if (dblDistanceBetweenChars < (possibleChar.dblDiagonalSize * MAX_DIAG_SIZE_MULTIPLE_AWAY) &&
307 | dblAngleBetweenChars < MAX_ANGLE_BETWEEN_CHARS &&
308 | dblChangeInArea < MAX_CHANGE_IN_AREA &&
309 | dblChangeInWidth < MAX_CHANGE_IN_WIDTH &&
310 | dblChangeInHeight < MAX_CHANGE_IN_HEIGHT) {
311 | vectorOfMatchingChars.push_back(possibleMatchingChar); // if the chars are a match, add the current char to vector of matching chars
312 | }
313 | }
314 |
315 | return(vectorOfMatchingChars); // return result
316 | }
317 |
318 | ///////////////////////////////////////////////////////////////////////////////////////////////////
319 | // use Pythagorean theorem to calculate distance between two chars
320 | double distanceBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar) {
321 | int intX = abs(firstChar.intCenterX - secondChar.intCenterX);
322 | int intY = abs(firstChar.intCenterY - secondChar.intCenterY);
323 |
324 | return(sqrt(pow(intX, 2) + pow(intY, 2)));
325 | }
326 |
327 | ///////////////////////////////////////////////////////////////////////////////////////////////////
328 | // use basic trigonometry(SOH CAH TOA) to calculate angle between chars
329 | double angleBetweenChars(const PossibleChar &firstChar, const PossibleChar &secondChar) {
330 | double dblAdj = abs(firstChar.intCenterX - secondChar.intCenterX);
331 | double dblOpp = abs(firstChar.intCenterY - secondChar.intCenterY);
332 |
333 | double dblAngleInRad = atan(dblOpp / dblAdj);
334 |
335 | double dblAngleInDeg = dblAngleInRad * (180.0 / CV_PI);
336 |
337 | return(dblAngleInDeg);
338 | }
339 |
340 | ///////////////////////////////////////////////////////////////////////////////////////////////////
341 | // if we have two chars overlapping or to close to each other to possibly be separate chars, remove the inner (smaller) char,
342 | // this is to prevent including the same char twice if two contours are found for the same char,
343 | // for example for the letter 'O' both the inner ring and the outer ring may be found as contours, but we should only include the char once
344 | std::vector removeInnerOverlappingChars(std::vector &vectorOfMatchingChars) {
345 | std::vector vectorOfMatchingCharsWithInnerCharRemoved(vectorOfMatchingChars);
346 |
347 | for (auto ¤tChar : vectorOfMatchingChars) {
348 | for (auto &otherChar : vectorOfMatchingChars) {
349 | if (currentChar != otherChar) { // if current char and other char are not the same char . . .
350 | // if current char and other char have center points at almost the same location . . .
351 | if (distanceBetweenChars(currentChar, otherChar) < (currentChar.dblDiagonalSize * MIN_DIAG_SIZE_MULTIPLE_AWAY)) {
352 | // if we get in here we have found overlapping chars
353 | // next we identify which char is smaller, then if that char was not already removed on a previous pass, remove it
354 |
355 | // if current char is smaller than other char
356 | if (currentChar.boundingRect.area() < otherChar.boundingRect.area()) {
357 | // look for char in vector with an iterator
358 | std::vector::iterator currentCharIterator = std::find(vectorOfMatchingCharsWithInnerCharRemoved.begin(), vectorOfMatchingCharsWithInnerCharRemoved.end(), currentChar);
359 | // if iterator did not get to end, then the char was found in the vector
360 | if (currentCharIterator != vectorOfMatchingCharsWithInnerCharRemoved.end()) {
361 | vectorOfMatchingCharsWithInnerCharRemoved.erase(currentCharIterator); // so remove the char
362 | }
363 | }
364 | else { // else if other char is smaller than current char
365 | // look for char in vector with an iterator
366 | std::vector::iterator otherCharIterator = std::find(vectorOfMatchingCharsWithInnerCharRemoved.begin(), vectorOfMatchingCharsWithInnerCharRemoved.end(), otherChar);
367 | // if iterator did not get to end, then the char was found in the vector
368 | if (otherCharIterator != vectorOfMatchingCharsWithInnerCharRemoved.end()) {
369 | vectorOfMatchingCharsWithInnerCharRemoved.erase(otherCharIterator); // so remove the char
370 | }
371 | }
372 | }
373 | }
374 | }
375 | }
376 |
377 | return(vectorOfMatchingCharsWithInnerCharRemoved);
378 | }
379 |
380 | ///////////////////////////////////////////////////////////////////////////////////////////////////
381 | // this is where we apply the actual char recognition
382 | std::string recognizeCharsInPlate(cv::Mat &imgThresh, std::vector &vectorOfMatchingChars) {
383 | std::string strChars; // this will be the return value, the chars in the lic plate
384 |
385 | cv::Mat imgThreshColor;
386 |
387 | // sort chars from left to right
388 | std::sort(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), PossibleChar::sortCharsLeftToRight);
389 |
390 | cv::cvtColor(imgThresh, imgThreshColor, CV_GRAY2BGR); // make color version of threshold image so we can draw contours in color on it
391 |
392 | for (auto ¤tChar : vectorOfMatchingChars) { // for each char in plate
393 | cv::rectangle(imgThreshColor, currentChar.boundingRect, SCALAR_GREEN, 2); // draw green box around the char
394 |
395 | cv::Mat imgROItoBeCloned = imgThresh(currentChar.boundingRect); // get ROI image of bounding rect
396 |
397 | cv::Mat imgROI = imgROItoBeCloned.clone(); // clone ROI image so we don't change original when we resize
398 |
399 | cv::Mat imgROIResized;
400 | // resize image, this is necessary for char recognition
401 | cv::resize(imgROI, imgROIResized, cv::Size(RESIZED_CHAR_IMAGE_WIDTH, RESIZED_CHAR_IMAGE_HEIGHT));
402 |
403 | cv::Mat matROIFloat;
404 |
405 | imgROIResized.convertTo(matROIFloat, CV_32FC1); // convert Mat to float, necessary for call to findNearest
406 |
407 | cv::Mat matROIFlattenedFloat = matROIFloat.reshape(1, 1); // flatten Matrix into one row
408 |
409 | cv::Mat matCurrentChar(0, 0, CV_32F); // declare Mat to read current char into, this is necessary b/c findNearest requires a Mat
410 |
411 | kNearest->findNearest(matROIFlattenedFloat, 1, matCurrentChar); // finally we can call find_nearest !!!
412 |
413 | float fltCurrentChar = (float)matCurrentChar.at(0, 0); // convert current char from Mat to float
414 |
415 | strChars = strChars + char(int(fltCurrentChar)); // append current char to full string
416 | }
417 |
418 | #ifdef SHOW_STEPS
419 | cv::imshow("10", imgThreshColor);
420 | #endif // SHOW_STEPS
421 |
422 | return(strChars); // return result
423 | }
424 |
425 |
--------------------------------------------------------------------------------