├── 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 | --------------------------------------------------------------------------------