├── .gitignore ├── Blob.cpp ├── Blob.h ├── CarsDrivingUnderBridge.mp4 ├── OpenCV_3_Car_Counting_Cpp.sln ├── OpenCV_3_Car_Counting_Cpp.vcxproj ├── OpenCV_3_Car_Counting_Cpp.vcxproj.filters ├── main.cpp ├── opencv_ffmpeg310_64.dll ├── packages.config └── readme.txt /.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 | -------------------------------------------------------------------------------- /Blob.cpp: -------------------------------------------------------------------------------- 1 | // Blob.cpp 2 | 3 | #include "Blob.h" 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////// 6 | Blob::Blob(std::vector _contour) { 7 | 8 | currentContour = _contour; 9 | 10 | currentBoundingRect = cv::boundingRect(currentContour); 11 | 12 | cv::Point currentCenter; 13 | 14 | currentCenter.x = (currentBoundingRect.x + currentBoundingRect.x + currentBoundingRect.width) / 2; 15 | currentCenter.y = (currentBoundingRect.y + currentBoundingRect.y + currentBoundingRect.height) / 2; 16 | 17 | centerPositions.push_back(currentCenter); 18 | 19 | dblCurrentDiagonalSize = sqrt(pow(currentBoundingRect.width, 2) + pow(currentBoundingRect.height, 2)); 20 | 21 | dblCurrentAspectRatio = (float)currentBoundingRect.width / (float)currentBoundingRect.height; 22 | 23 | blnStillBeingTracked = true; 24 | blnCurrentMatchFoundOrNewBlob = true; 25 | 26 | intNumOfConsecutiveFramesWithoutAMatch = 0; 27 | } 28 | 29 | /////////////////////////////////////////////////////////////////////////////////////////////////// 30 | void Blob::predictNextPosition(void) { 31 | 32 | int numPositions = (int)centerPositions.size(); 33 | 34 | if (numPositions == 1) { 35 | 36 | predictedNextPosition.x = centerPositions.back().x; 37 | predictedNextPosition.y = centerPositions.back().y; 38 | 39 | } else if (numPositions == 2) { 40 | 41 | int deltaX = centerPositions[1].x - centerPositions[0].x; 42 | int deltaY = centerPositions[1].y - centerPositions[0].y; 43 | 44 | predictedNextPosition.x = centerPositions.back().x + deltaX; 45 | predictedNextPosition.y = centerPositions.back().y + deltaY; 46 | 47 | } else if (numPositions == 3) { 48 | 49 | int sumOfXChanges = ((centerPositions[2].x - centerPositions[1].x) * 2) + 50 | ((centerPositions[1].x - centerPositions[0].x) * 1); 51 | 52 | int deltaX = (int)std::round((float)sumOfXChanges / 3.0); 53 | 54 | int sumOfYChanges = ((centerPositions[2].y - centerPositions[1].y) * 2) + 55 | ((centerPositions[1].y - centerPositions[0].y) * 1); 56 | 57 | int deltaY = (int)std::round((float)sumOfYChanges / 3.0); 58 | 59 | predictedNextPosition.x = centerPositions.back().x + deltaX; 60 | predictedNextPosition.y = centerPositions.back().y + deltaY; 61 | 62 | } else if (numPositions == 4) { 63 | 64 | int sumOfXChanges = ((centerPositions[3].x - centerPositions[2].x) * 3) + 65 | ((centerPositions[2].x - centerPositions[1].x) * 2) + 66 | ((centerPositions[1].x - centerPositions[0].x) * 1); 67 | 68 | int deltaX = (int)std::round((float)sumOfXChanges / 6.0); 69 | 70 | int sumOfYChanges = ((centerPositions[3].y - centerPositions[2].y) * 3) + 71 | ((centerPositions[2].y - centerPositions[1].y) * 2) + 72 | ((centerPositions[1].y - centerPositions[0].y) * 1); 73 | 74 | int deltaY = (int)std::round((float)sumOfYChanges / 6.0); 75 | 76 | predictedNextPosition.x = centerPositions.back().x + deltaX; 77 | predictedNextPosition.y = centerPositions.back().y + deltaY; 78 | 79 | } else if (numPositions >= 5) { 80 | 81 | int sumOfXChanges = ((centerPositions[numPositions - 1].x - centerPositions[numPositions - 2].x) * 4) + 82 | ((centerPositions[numPositions - 2].x - centerPositions[numPositions - 3].x) * 3) + 83 | ((centerPositions[numPositions - 3].x - centerPositions[numPositions - 4].x) * 2) + 84 | ((centerPositions[numPositions - 4].x - centerPositions[numPositions - 5].x) * 1); 85 | 86 | int deltaX = (int)std::round((float)sumOfXChanges / 10.0); 87 | 88 | int sumOfYChanges = ((centerPositions[numPositions - 1].y - centerPositions[numPositions - 2].y) * 4) + 89 | ((centerPositions[numPositions - 2].y - centerPositions[numPositions - 3].y) * 3) + 90 | ((centerPositions[numPositions - 3].y - centerPositions[numPositions - 4].y) * 2) + 91 | ((centerPositions[numPositions - 4].y - centerPositions[numPositions - 5].y) * 1); 92 | 93 | int deltaY = (int)std::round((float)sumOfYChanges / 10.0); 94 | 95 | predictedNextPosition.x = centerPositions.back().x + deltaX; 96 | predictedNextPosition.y = centerPositions.back().y + deltaY; 97 | 98 | } else { 99 | // should never get here 100 | } 101 | 102 | } 103 | 104 | -------------------------------------------------------------------------------- /Blob.h: -------------------------------------------------------------------------------- 1 | // Blob.h 2 | 3 | #ifndef MY_BLOB 4 | #define MY_BLOB 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /////////////////////////////////////////////////////////////////////////////////////////////////// 11 | class Blob { 12 | public: 13 | // member variables /////////////////////////////////////////////////////////////////////////// 14 | std::vector currentContour; 15 | 16 | cv::Rect currentBoundingRect; 17 | 18 | std::vector centerPositions; 19 | 20 | double dblCurrentDiagonalSize; 21 | double dblCurrentAspectRatio; 22 | 23 | bool blnCurrentMatchFoundOrNewBlob; 24 | 25 | bool blnStillBeingTracked; 26 | 27 | int intNumOfConsecutiveFramesWithoutAMatch; 28 | 29 | cv::Point predictedNextPosition; 30 | 31 | // function prototypes //////////////////////////////////////////////////////////////////////// 32 | Blob(std::vector _contour); 33 | void predictNextPosition(void); 34 | 35 | }; 36 | 37 | #endif // MY_BLOB 38 | 39 | -------------------------------------------------------------------------------- /CarsDrivingUnderBridge.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_Car_Counting_Cpp/1029f27b241133860790cd0d0e39b92b3f16669f/CarsDrivingUnderBridge.mp4 -------------------------------------------------------------------------------- /OpenCV_3_Car_Counting_Cpp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenCV_3_Car_Counting_Cpp", "OpenCV_3_Car_Counting_Cpp.vcxproj", "{C5DF94F5-EBB5-486F-B10D-2EA370AC2679}" 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 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Debug|x64.ActiveCfg = Debug|x64 17 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Debug|x64.Build.0 = Debug|x64 18 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Debug|x86.ActiveCfg = Debug|Win32 19 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Debug|x86.Build.0 = Debug|Win32 20 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Release|x64.ActiveCfg = Release|x64 21 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Release|x64.Build.0 = Release|x64 22 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Release|x86.ActiveCfg = Release|Win32 23 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /OpenCV_3_Car_Counting_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 | {C5DF94F5-EBB5-486F-B10D-2EA370AC2679} 23 | OpenCV_3_Car_Counting_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 | 80 | 81 | Level3 82 | Disabled 83 | true 84 | 85 | 86 | 87 | 88 | Level3 89 | MaxSpeed 90 | true 91 | true 92 | true 93 | 94 | 95 | true 96 | true 97 | 98 | 99 | 100 | 101 | Level3 102 | MaxSpeed 103 | true 104 | true 105 | true 106 | 107 | 108 | true 109 | true 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 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}. 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /OpenCV_3_Car_Counting_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 | 26 | 27 | Header Files 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // main.cpp 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include // it may be necessary to change or remove this line if not using Windows 9 | 10 | #include "Blob.h" 11 | 12 | #define SHOW_STEPS // un-comment or comment this line to show steps or not 13 | 14 | // global variables /////////////////////////////////////////////////////////////////////////////// 15 | const cv::Scalar SCALAR_BLACK = cv::Scalar(0.0, 0.0, 0.0); 16 | const cv::Scalar SCALAR_WHITE = cv::Scalar(255.0, 255.0, 255.0); 17 | const cv::Scalar SCALAR_YELLOW = cv::Scalar(0.0, 255.0, 255.0); 18 | const cv::Scalar SCALAR_GREEN = cv::Scalar(0.0, 200.0, 0.0); 19 | const cv::Scalar SCALAR_RED = cv::Scalar(0.0, 0.0, 255.0); 20 | 21 | // function prototypes //////////////////////////////////////////////////////////////////////////// 22 | void matchCurrentFrameBlobsToExistingBlobs(std::vector &existingBlobs, std::vector ¤tFrameBlobs); 23 | void addBlobToExistingBlobs(Blob ¤tFrameBlob, std::vector &existingBlobs, int &intIndex); 24 | void addNewBlob(Blob ¤tFrameBlob, std::vector &existingBlobs); 25 | double distanceBetweenPoints(cv::Point point1, cv::Point point2); 26 | void drawAndShowContours(cv::Size imageSize, std::vector > contours, std::string strImageName); 27 | void drawAndShowContours(cv::Size imageSize, std::vector blobs, std::string strImageName); 28 | bool checkIfBlobsCrossedTheLine(std::vector &blobs, int &intHorizontalLinePosition, int &carCount); 29 | void drawBlobInfoOnImage(std::vector &blobs, cv::Mat &imgFrame2Copy); 30 | void drawCarCountOnImage(int &carCount, cv::Mat &imgFrame2Copy); 31 | 32 | /////////////////////////////////////////////////////////////////////////////////////////////////// 33 | int main(void) { 34 | 35 | cv::VideoCapture capVideo; 36 | 37 | cv::Mat imgFrame1; 38 | cv::Mat imgFrame2; 39 | 40 | std::vector blobs; 41 | 42 | cv::Point crossingLine[2]; 43 | 44 | int carCount = 0; 45 | 46 | capVideo.open("CarsDrivingUnderBridge.mp4"); 47 | 48 | if (!capVideo.isOpened()) { // if unable to open video file 49 | std::cout << "error reading video file" << std::endl << std::endl; // show error message 50 | _getch(); // it may be necessary to change or remove this line if not using Windows 51 | return(0); // and exit program 52 | } 53 | 54 | if (capVideo.get(CV_CAP_PROP_FRAME_COUNT) < 2) { 55 | std::cout << "error: video file must have at least two frames"; 56 | _getch(); // it may be necessary to change or remove this line if not using Windows 57 | return(0); 58 | } 59 | 60 | capVideo.read(imgFrame1); 61 | capVideo.read(imgFrame2); 62 | 63 | int intHorizontalLinePosition = (int)std::round((double)imgFrame1.rows * 0.35); 64 | 65 | crossingLine[0].x = 0; 66 | crossingLine[0].y = intHorizontalLinePosition; 67 | 68 | crossingLine[1].x = imgFrame1.cols - 1; 69 | crossingLine[1].y = intHorizontalLinePosition; 70 | 71 | char chCheckForEscKey = 0; 72 | 73 | bool blnFirstFrame = true; 74 | 75 | int frameCount = 2; 76 | 77 | while (capVideo.isOpened() && chCheckForEscKey != 27) { 78 | 79 | std::vector currentFrameBlobs; 80 | 81 | cv::Mat imgFrame1Copy = imgFrame1.clone(); 82 | cv::Mat imgFrame2Copy = imgFrame2.clone(); 83 | 84 | cv::Mat imgDifference; 85 | cv::Mat imgThresh; 86 | 87 | cv::cvtColor(imgFrame1Copy, imgFrame1Copy, CV_BGR2GRAY); 88 | cv::cvtColor(imgFrame2Copy, imgFrame2Copy, CV_BGR2GRAY); 89 | 90 | cv::GaussianBlur(imgFrame1Copy, imgFrame1Copy, cv::Size(5, 5), 0); 91 | cv::GaussianBlur(imgFrame2Copy, imgFrame2Copy, cv::Size(5, 5), 0); 92 | 93 | cv::absdiff(imgFrame1Copy, imgFrame2Copy, imgDifference); 94 | 95 | cv::threshold(imgDifference, imgThresh, 30, 255.0, CV_THRESH_BINARY); 96 | 97 | cv::imshow("imgThresh", imgThresh); 98 | 99 | cv::Mat structuringElement3x3 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); 100 | cv::Mat structuringElement5x5 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); 101 | cv::Mat structuringElement7x7 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7)); 102 | cv::Mat structuringElement15x15 = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(15, 15)); 103 | 104 | for (unsigned int i = 0; i < 2; i++) { 105 | cv::dilate(imgThresh, imgThresh, structuringElement5x5); 106 | cv::dilate(imgThresh, imgThresh, structuringElement5x5); 107 | cv::erode(imgThresh, imgThresh, structuringElement5x5); 108 | } 109 | 110 | cv::Mat imgThreshCopy = imgThresh.clone(); 111 | 112 | std::vector > contours; 113 | 114 | cv::findContours(imgThreshCopy, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); 115 | 116 | drawAndShowContours(imgThresh.size(), contours, "imgContours"); 117 | 118 | std::vector > convexHulls(contours.size()); 119 | 120 | for (unsigned int i = 0; i < contours.size(); i++) { 121 | cv::convexHull(contours[i], convexHulls[i]); 122 | } 123 | 124 | drawAndShowContours(imgThresh.size(), convexHulls, "imgConvexHulls"); 125 | 126 | for (auto &convexHull : convexHulls) { 127 | Blob possibleBlob(convexHull); 128 | 129 | if (possibleBlob.currentBoundingRect.area() > 400 && 130 | possibleBlob.dblCurrentAspectRatio > 0.2 && 131 | possibleBlob.dblCurrentAspectRatio < 4.0 && 132 | possibleBlob.currentBoundingRect.width > 30 && 133 | possibleBlob.currentBoundingRect.height > 30 && 134 | possibleBlob.dblCurrentDiagonalSize > 60.0 && 135 | (cv::contourArea(possibleBlob.currentContour) / (double)possibleBlob.currentBoundingRect.area()) > 0.50) { 136 | currentFrameBlobs.push_back(possibleBlob); 137 | } 138 | } 139 | 140 | drawAndShowContours(imgThresh.size(), currentFrameBlobs, "imgCurrentFrameBlobs"); 141 | 142 | if (blnFirstFrame == true) { 143 | for (auto ¤tFrameBlob : currentFrameBlobs) { 144 | blobs.push_back(currentFrameBlob); 145 | } 146 | } else { 147 | matchCurrentFrameBlobsToExistingBlobs(blobs, currentFrameBlobs); 148 | } 149 | 150 | drawAndShowContours(imgThresh.size(), blobs, "imgBlobs"); 151 | 152 | imgFrame2Copy = imgFrame2.clone(); // get another copy of frame 2 since we changed the previous frame 2 copy in the processing above 153 | 154 | drawBlobInfoOnImage(blobs, imgFrame2Copy); 155 | 156 | bool blnAtLeastOneBlobCrossedTheLine = checkIfBlobsCrossedTheLine(blobs, intHorizontalLinePosition, carCount); 157 | 158 | if (blnAtLeastOneBlobCrossedTheLine == true) { 159 | cv::line(imgFrame2Copy, crossingLine[0], crossingLine[1], SCALAR_GREEN, 2); 160 | } else { 161 | cv::line(imgFrame2Copy, crossingLine[0], crossingLine[1], SCALAR_RED, 2); 162 | } 163 | 164 | drawCarCountOnImage(carCount, imgFrame2Copy); 165 | 166 | cv::imshow("imgFrame2Copy", imgFrame2Copy); 167 | 168 | //cv::waitKey(0); // uncomment this line to go frame by frame for debugging 169 | 170 | // now we prepare for the next iteration 171 | 172 | currentFrameBlobs.clear(); 173 | 174 | imgFrame1 = imgFrame2.clone(); // move frame 1 up to where frame 2 is 175 | 176 | if ((capVideo.get(CV_CAP_PROP_POS_FRAMES) + 1) < capVideo.get(CV_CAP_PROP_FRAME_COUNT)) { 177 | capVideo.read(imgFrame2); 178 | } else { 179 | std::cout << "end of video\n"; 180 | break; 181 | } 182 | 183 | blnFirstFrame = false; 184 | frameCount++; 185 | chCheckForEscKey = cv::waitKey(1); 186 | } 187 | 188 | if (chCheckForEscKey != 27) { // if the user did not press esc (i.e. we reached the end of the video) 189 | cv::waitKey(0); // hold the windows open to allow the "end of video" message to show 190 | } 191 | // note that if the user did press esc, we don't need to hold the windows open, we can simply let the program end which will close the windows 192 | 193 | return(0); 194 | } 195 | 196 | /////////////////////////////////////////////////////////////////////////////////////////////////// 197 | void matchCurrentFrameBlobsToExistingBlobs(std::vector &existingBlobs, std::vector ¤tFrameBlobs) { 198 | 199 | for (auto &existingBlob : existingBlobs) { 200 | 201 | existingBlob.blnCurrentMatchFoundOrNewBlob = false; 202 | 203 | existingBlob.predictNextPosition(); 204 | } 205 | 206 | for (auto ¤tFrameBlob : currentFrameBlobs) { 207 | 208 | int intIndexOfLeastDistance = 0; 209 | double dblLeastDistance = 100000.0; 210 | 211 | for (unsigned int i = 0; i < existingBlobs.size(); i++) { 212 | 213 | if (existingBlobs[i].blnStillBeingTracked == true) { 214 | 215 | double dblDistance = distanceBetweenPoints(currentFrameBlob.centerPositions.back(), existingBlobs[i].predictedNextPosition); 216 | 217 | if (dblDistance < dblLeastDistance) { 218 | dblLeastDistance = dblDistance; 219 | intIndexOfLeastDistance = i; 220 | } 221 | } 222 | } 223 | 224 | if (dblLeastDistance < currentFrameBlob.dblCurrentDiagonalSize * 0.5) { 225 | addBlobToExistingBlobs(currentFrameBlob, existingBlobs, intIndexOfLeastDistance); 226 | } 227 | else { 228 | addNewBlob(currentFrameBlob, existingBlobs); 229 | } 230 | 231 | } 232 | 233 | for (auto &existingBlob : existingBlobs) { 234 | 235 | if (existingBlob.blnCurrentMatchFoundOrNewBlob == false) { 236 | existingBlob.intNumOfConsecutiveFramesWithoutAMatch++; 237 | } 238 | 239 | if (existingBlob.intNumOfConsecutiveFramesWithoutAMatch >= 5) { 240 | existingBlob.blnStillBeingTracked = false; 241 | } 242 | 243 | } 244 | 245 | } 246 | 247 | /////////////////////////////////////////////////////////////////////////////////////////////////// 248 | void addBlobToExistingBlobs(Blob ¤tFrameBlob, std::vector &existingBlobs, int &intIndex) { 249 | 250 | existingBlobs[intIndex].currentContour = currentFrameBlob.currentContour; 251 | existingBlobs[intIndex].currentBoundingRect = currentFrameBlob.currentBoundingRect; 252 | 253 | existingBlobs[intIndex].centerPositions.push_back(currentFrameBlob.centerPositions.back()); 254 | 255 | existingBlobs[intIndex].dblCurrentDiagonalSize = currentFrameBlob.dblCurrentDiagonalSize; 256 | existingBlobs[intIndex].dblCurrentAspectRatio = currentFrameBlob.dblCurrentAspectRatio; 257 | 258 | existingBlobs[intIndex].blnStillBeingTracked = true; 259 | existingBlobs[intIndex].blnCurrentMatchFoundOrNewBlob = true; 260 | } 261 | 262 | /////////////////////////////////////////////////////////////////////////////////////////////////// 263 | void addNewBlob(Blob ¤tFrameBlob, std::vector &existingBlobs) { 264 | 265 | currentFrameBlob.blnCurrentMatchFoundOrNewBlob = true; 266 | 267 | existingBlobs.push_back(currentFrameBlob); 268 | } 269 | 270 | /////////////////////////////////////////////////////////////////////////////////////////////////// 271 | double distanceBetweenPoints(cv::Point point1, cv::Point point2) { 272 | 273 | int intX = abs(point1.x - point2.x); 274 | int intY = abs(point1.y - point2.y); 275 | 276 | return(sqrt(pow(intX, 2) + pow(intY, 2))); 277 | } 278 | 279 | /////////////////////////////////////////////////////////////////////////////////////////////////// 280 | void drawAndShowContours(cv::Size imageSize, std::vector > contours, std::string strImageName) { 281 | cv::Mat image(imageSize, CV_8UC3, SCALAR_BLACK); 282 | 283 | cv::drawContours(image, contours, -1, SCALAR_WHITE, -1); 284 | 285 | cv::imshow(strImageName, image); 286 | } 287 | 288 | /////////////////////////////////////////////////////////////////////////////////////////////////// 289 | void drawAndShowContours(cv::Size imageSize, std::vector blobs, std::string strImageName) { 290 | 291 | cv::Mat image(imageSize, CV_8UC3, SCALAR_BLACK); 292 | 293 | std::vector > contours; 294 | 295 | for (auto &blob : blobs) { 296 | if (blob.blnStillBeingTracked == true) { 297 | contours.push_back(blob.currentContour); 298 | } 299 | } 300 | 301 | cv::drawContours(image, contours, -1, SCALAR_WHITE, -1); 302 | 303 | cv::imshow(strImageName, image); 304 | } 305 | 306 | /////////////////////////////////////////////////////////////////////////////////////////////////// 307 | bool checkIfBlobsCrossedTheLine(std::vector &blobs, int &intHorizontalLinePosition, int &carCount) { 308 | bool blnAtLeastOneBlobCrossedTheLine = false; 309 | 310 | for (auto blob : blobs) { 311 | 312 | if (blob.blnStillBeingTracked == true && blob.centerPositions.size() >= 2) { 313 | int prevFrameIndex = (int)blob.centerPositions.size() - 2; 314 | int currFrameIndex = (int)blob.centerPositions.size() - 1; 315 | 316 | if (blob.centerPositions[prevFrameIndex].y > intHorizontalLinePosition && blob.centerPositions[currFrameIndex].y <= intHorizontalLinePosition) { 317 | carCount++; 318 | blnAtLeastOneBlobCrossedTheLine = true; 319 | } 320 | } 321 | 322 | } 323 | 324 | return blnAtLeastOneBlobCrossedTheLine; 325 | } 326 | 327 | /////////////////////////////////////////////////////////////////////////////////////////////////// 328 | void drawBlobInfoOnImage(std::vector &blobs, cv::Mat &imgFrame2Copy) { 329 | 330 | for (unsigned int i = 0; i < blobs.size(); i++) { 331 | 332 | if (blobs[i].blnStillBeingTracked == true) { 333 | cv::rectangle(imgFrame2Copy, blobs[i].currentBoundingRect, SCALAR_RED, 2); 334 | 335 | int intFontFace = CV_FONT_HERSHEY_SIMPLEX; 336 | double dblFontScale = blobs[i].dblCurrentDiagonalSize / 60.0; 337 | int intFontThickness = (int)std::round(dblFontScale * 1.0); 338 | 339 | cv::putText(imgFrame2Copy, std::to_string(i), blobs[i].centerPositions.back(), intFontFace, dblFontScale, SCALAR_GREEN, intFontThickness); 340 | } 341 | } 342 | } 343 | 344 | /////////////////////////////////////////////////////////////////////////////////////////////////// 345 | void drawCarCountOnImage(int &carCount, cv::Mat &imgFrame2Copy) { 346 | 347 | int intFontFace = CV_FONT_HERSHEY_SIMPLEX; 348 | double dblFontScale = (imgFrame2Copy.rows * imgFrame2Copy.cols) / 300000.0; 349 | int intFontThickness = (int)std::round(dblFontScale * 1.5); 350 | 351 | cv::Size textSize = cv::getTextSize(std::to_string(carCount), intFontFace, dblFontScale, intFontThickness, 0); 352 | 353 | cv::Point ptTextBottomLeftPosition; 354 | 355 | ptTextBottomLeftPosition.x = imgFrame2Copy.cols - 1 - (int)((double)textSize.width * 1.25); 356 | ptTextBottomLeftPosition.y = (int)((double)textSize.height * 1.25); 357 | 358 | cv::putText(imgFrame2Copy, std::to_string(carCount), ptTextBottomLeftPosition, intFontFace, dblFontScale, SCALAR_GREEN, intFontThickness); 359 | 360 | } 361 | 362 | -------------------------------------------------------------------------------- /opencv_ffmpeg310_64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MicrocontrollersAndMore/OpenCV_3_Car_Counting_Cpp/1029f27b241133860790cd0d0e39b92b3f16669f/opencv_ffmpeg310_64.dll -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | The video pretty much explains it all: 2 | https://www.youtube.com/watch?v=Y3ac5rFMNZ0 --------------------------------------------------------------------------------