├── .gitignore ├── EllipseDetectorLib ├── AssemblyInfo.cpp ├── EllipseDetectorLib.cpp ├── EllipseDetectorLib.h ├── EllipseDetectorLib.vcxproj ├── EllipseResult.cpp ├── EllipseResult.h ├── ExecutionTime.h ├── ReadMe.txt ├── Stdafx.cpp ├── Stdafx.h ├── app.ico ├── app.rc └── resource.h ├── ImageDetector.sln ├── ImageDetector ├── ImageDetector.cpp ├── ImageDetector.vcxproj ├── ReadMe.txt ├── stdafx.cpp ├── stdafx.h └── targetver.h ├── README.md ├── TestCSharp ├── App.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── TestCSharp.csproj ├── libellipsedetect ├── EllipseDetectorYaed.cpp ├── EllipseDetectorYaed.h ├── ReadMe.txt ├── common.cpp ├── common.h └── libellipsedetect.vcxproj └── test-data ├── README.md ├── outputsample01.jpg ├── outputsample02.jpg ├── outputsample03.jpg ├── outputsample04.jpg ├── outputsample05.jpg ├── outputsample06.jpg ├── sample01.jpg ├── sample02.jpg ├── sample03.jpg ├── sample04.jpg ├── sample05.jpg └── sample06.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | 32 | .idea/ 33 | .hg/ 34 | .hgignore 35 | .research/ 36 | .settings/ 37 | .kdev4/ 38 | 39 | logs/ 40 | bin/ 41 | obj/ 42 | out/ 43 | gen/ 44 | Debug/ 45 | Release/ 46 | 47 | *~ 48 | *.*~ 49 | *.kdev4 50 | *.suo 51 | *.sdf 52 | *.pyc 53 | *.opensdf 54 | *.user 55 | *.ipr 56 | *.iws 57 | *.iml 58 | *.so 59 | .kate* 60 | .project 61 | .kdev_include_paths 62 | 63 | target/ 64 | build/ 65 | ipch/ 66 | x64/ 67 | x86/ 68 | *.batch 69 | *.class 70 | *.opensdf 71 | *.vcxproj.filters 72 | *.vcxproj.user 73 | *.sdf 74 | *.suo 75 | *.o 76 | *.swp 77 | *.swo 78 | *.gch 79 | -------------------------------------------------------------------------------- /EllipseDetectorLib/AssemblyInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | using namespace System; 4 | using namespace System::Reflection; 5 | using namespace System::Runtime::CompilerServices; 6 | using namespace System::Runtime::InteropServices; 7 | using namespace System::Security::Permissions; 8 | 9 | // 10 | // General Information about an assembly is controlled through the following 11 | // set of attributes. Change these attribute values to modify the information 12 | // associated with an assembly. 13 | // 14 | [assembly:AssemblyTitleAttribute("EllipseDetectorLib")]; 15 | [assembly:AssemblyDescriptionAttribute("")]; 16 | [assembly:AssemblyConfigurationAttribute("")]; 17 | [assembly:AssemblyCompanyAttribute("")]; 18 | [assembly:AssemblyProductAttribute("EllipseDetectorLib")]; 19 | [assembly:AssemblyCopyrightAttribute("Copyright (c) 2017")]; 20 | [assembly:AssemblyTrademarkAttribute("")]; 21 | [assembly:AssemblyCultureAttribute("")]; 22 | 23 | // 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the value or you can default the Revision and Build Numbers 32 | // by using the '*' as shown below: 33 | 34 | [assembly:AssemblyVersionAttribute("1.0.*")]; 35 | 36 | [assembly:ComVisible(false)]; 37 | 38 | [assembly:CLSCompliantAttribute(true)]; 39 | 40 | [assembly:SecurityPermission(SecurityAction::RequestMinimum, UnmanagedCode = true)]; 41 | -------------------------------------------------------------------------------- /EllipseDetectorLib/EllipseDetectorLib.cpp: -------------------------------------------------------------------------------- 1 | // This is the main DLL file. 2 | 3 | #include "stdafx.h" 4 | 5 | #include "EllipseDetectorLib.h" 6 | #include "EllipseDetectorYaed.h" 7 | 8 | using System::Collections::Generic::List; 9 | namespace EllipseDetectorLib { 10 | /* 11 | * EllipseDetector is the main class 12 | * This class implements a .NET wrapper of very fast ellipse detector, codename: YAED (Yet Another Ellipse Detector) 13 | * Methods Implementation 14 | */ 15 | EllipseDetector::EllipseDetector(System::String ^fileName) 16 | : _sFileName(fileName), _searchResults(nullptr), _etExecutionTime(nullptr), _result(nullptr), 17 | _iThLength(16), 18 | _fThObb(3.0f), 19 | _fThPos(1.0f), 20 | _fTaoCenters(0.05f), 21 | _iNs(16), 22 | _fThScoreScore(0.7f), 23 | _dPreProcessingGaussSigma(1.0), 24 | _fDistanceToEllipseContour(0.1f), 25 | _fMinReliability(0.5), 26 | _pPreProcessingGaussKernelSize(System::Drawing::Point(5,5)) 27 | { 28 | 29 | } 30 | EllipseDetector::~EllipseDetector(void) 31 | { 32 | 33 | } 34 | 35 | EllipseDetector::!EllipseDetector() 36 | { 37 | 38 | if(_searchResults != nullptr) 39 | { 40 | delete _searchResults; 41 | } 42 | 43 | if(_result != nullptr) 44 | { 45 | delete _result; 46 | } 47 | 48 | if(_etExecutionTime != nullptr) 49 | { 50 | delete _etExecutionTime; 51 | } 52 | 53 | if(_sFileName != nullptr) 54 | { 55 | delete _sFileName; 56 | } 57 | 58 | //Release managed resources 59 | GC::Collect(); 60 | 61 | } 62 | 63 | System::Tuple^>^ EllipseDetector::Search() 64 | { 65 | if(_searchResults == nullptr) { 66 | _searchResults = gcnew System::Collections::Generic::List(); 67 | } 68 | else 69 | { 70 | _searchResults->Clear(); 71 | } 72 | 73 | if(_result != nullptr) 74 | { 75 | delete _result; 76 | } 77 | 78 | if(_sFileName == nullptr) 79 | { 80 | _result = gcnew System::Tuple^>(System::Drawing::Size::Empty, _searchResults); 81 | return _result; 82 | } 83 | 84 | IntPtr ptr = Marshal::StringToCoTaskMemAnsi(this->_sFileName); 85 | char *singleByte= (char*)ptr.ToPointer(); 86 | cv::String* image_name = NULL; 87 | 88 | try 89 | { 90 | image_name = new cv::String(singleByte); 91 | } 92 | finally 93 | { 94 | Marshal::FreeCoTaskMem(ptr); 95 | } 96 | 97 | cv::Mat3b image = cv::imread(*image_name); 98 | cv::Size sz = image.size(); 99 | System::Drawing::Size resultSize(sz.width, sz.height); 100 | _result = gcnew System::Tuple^>(resultSize, _searchResults); 101 | 102 | // Convert to grayscale 103 | cv::Mat1b gray; 104 | cv::cvtColor(image, gray, CV_BGR2GRAY); 105 | 106 | float fMaxCenterDistance = sqrt(float(sz.width*sz.width + sz.height*sz.height)) * this->_fTaoCenters; 107 | cv::Size szPreProcessingGaussKernelSize = cv::Size(this->_pPreProcessingGaussKernelSize.Width, this->_pPreProcessingGaussKernelSize.Height); 108 | // Initialize Detector with selected parameters 109 | Yaed::CEllipseDetectorYaed yaed; 110 | yaed.SetParameters(szPreProcessingGaussKernelSize, 111 | this->_dPreProcessingGaussSigma, 112 | this->_fThPos, 113 | fMaxCenterDistance, 114 | this->_iThLength, 115 | this->_fThObb, 116 | this->_fDistanceToEllipseContour, 117 | this->_fThScoreScore, 118 | this->_fMinReliability, 119 | this->_iNs 120 | ); 121 | 122 | 123 | // Detect 124 | std::vector ellsYaed; 125 | yaed.Detect(gray.clone(), ellsYaed); 126 | 127 | vector times = yaed.GetTimes(); 128 | 129 | int sz_ell = int(ellsYaed.size()); 130 | //int n = (iTopN == 0) ? sz_ell : min(iTopN, sz_ell); 131 | for (int i = 0; i < sz_ell; ++i) 132 | { 133 | Yaed::Ellipse& e = ellsYaed[i]; 134 | _searchResults->Add(gcnew EllipseResult(e._xc, e._yc, e._a, e._b, e._rad)); 135 | } 136 | 137 | if(_etExecutionTime != nullptr) 138 | { 139 | delete _etExecutionTime; 140 | } 141 | 142 | _etExecutionTime = gcnew ExecutionTime(times[0], times[1], times[2], times[3], times[4], times[5], yaed.GetExecTime()); 143 | 144 | delete image_name; 145 | return _result; 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /EllipseDetectorLib/EllipseDetectorLib.h: -------------------------------------------------------------------------------- 1 | // EllipseDetectorLib.h 2 | 3 | #pragma once 4 | 5 | using namespace System; 6 | 7 | #include "EllipseResult.h" 8 | #include "ExecutionTime.h" 9 | 10 | #include 11 | 12 | using namespace std; 13 | using namespace cv; 14 | 15 | using namespace System; 16 | using namespace System::Drawing; 17 | using namespace System::Runtime::InteropServices; 18 | 19 | namespace EllipseDetectorLib { 20 | /* 21 | * EllipseDetector is the main class 22 | * This class implements a .NET wrapper of very fast ellipse detector, codename: YAED (Yet Another Ellipse Detector) 23 | */ 24 | public ref class EllipseDetector 25 | { 26 | 27 | public: 28 | 29 | EllipseDetector(System::String ^fileName); 30 | ~EllipseDetector(void); 31 | !EllipseDetector(); 32 | 33 | property System::String ^FileName 34 | { 35 | System::String ^get() 36 | { 37 | return _sFileName; 38 | } 39 | 40 | void set(System::String ^value) 41 | { 42 | if(_sFileName != nullptr) 43 | { 44 | delete _sFileName; 45 | } 46 | _sFileName = value; 47 | } 48 | } 49 | 50 | //System::Collections::Generic::List ^Search(); 51 | System::Tuple^> ^EllipseDetector::Search(); 52 | 53 | property int ThLength { 54 | int get() 55 | { 56 | return _iThLength; 57 | } 58 | void set(int value) 59 | { 60 | _iThLength = value; 61 | } 62 | } 63 | 64 | property float ThObb { 65 | float get() 66 | { 67 | return _fThObb; 68 | } 69 | void set(float value) 70 | { 71 | _fThObb = value; 72 | } 73 | } 74 | 75 | property float ThPos { 76 | float get() 77 | { 78 | return _fThPos; 79 | } 80 | void set(float value) 81 | { 82 | _fThPos = value; 83 | } 84 | } 85 | 86 | property float TaoCenters { 87 | float get() 88 | { 89 | return _fTaoCenters; 90 | } 91 | void set(float value) 92 | { 93 | _fTaoCenters = value; 94 | } 95 | } 96 | 97 | property int Ns { 98 | int get() 99 | { 100 | return _iNs; 101 | } 102 | void set(int value) 103 | { 104 | _iNs = value; 105 | } 106 | } 107 | 108 | property float ThScoreScore { 109 | float get() 110 | { 111 | return _fThScoreScore; 112 | } 113 | void set(float value) 114 | { 115 | _fThScoreScore = value; 116 | } 117 | } 118 | 119 | 120 | 121 | property System::Drawing::Size PreProcessingGaussKernelSize 122 | { 123 | System::Drawing::Size get(){ 124 | return _pPreProcessingGaussKernelSize; 125 | } 126 | void set(System::Drawing::Size value){ 127 | _pPreProcessingGaussKernelSize = value; 128 | } 129 | } 130 | 131 | property float DistanceToEllipseContour { 132 | float get() 133 | { 134 | return _fDistanceToEllipseContour; 135 | } 136 | void set(float value) 137 | { 138 | _fDistanceToEllipseContour = value; 139 | } 140 | } 141 | 142 | property double PreProcessingGaussSigma { 143 | double get() 144 | { 145 | return _dPreProcessingGaussSigma; 146 | } 147 | void set(double value) 148 | { 149 | _dPreProcessingGaussSigma = value; 150 | } 151 | } 152 | 153 | property float MinReliability { 154 | float get() 155 | { 156 | return _fMinReliability; 157 | } 158 | void set(float value) 159 | { 160 | _fMinReliability = value; 161 | } 162 | } 163 | 164 | property ExecutionTime^ Execution { 165 | ExecutionTime^ get() 166 | { 167 | return _etExecutionTime; 168 | } 169 | 170 | } 171 | 172 | // TODO: Add your methods for this class here. 173 | private: 174 | System::Collections::Generic::List ^_searchResults; 175 | System::Tuple^> ^_result; 176 | System::String ^_sFileName; 177 | ExecutionTime ^ _etExecutionTime; 178 | // Parameters Settings (Sect. 4.2) 179 | int _iThLength; 180 | float _fThObb; 181 | float _fThPos; 182 | float _fTaoCenters; 183 | int _iNs; 184 | float _fThScoreScore; 185 | 186 | System::Drawing::Size _pPreProcessingGaussKernelSize;// Gaussian filter parameters, in pre-processing 187 | double _dPreProcessingGaussSigma; 188 | float _fDistanceToEllipseContour; // (Sect. 3.3.1 - Validation) 189 | float _fMinReliability; // Const parameters to discard bad ellipses*/ 190 | 191 | 192 | }; 193 | } 194 | -------------------------------------------------------------------------------- /EllipseDetectorLib/EllipseDetectorLib.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97} 23 | v4.0 24 | ManagedCProj 25 | EllipseDetectorLib 26 | 27 | 28 | 29 | DynamicLibrary 30 | true 31 | v110 32 | true 33 | Unicode 34 | 35 | 36 | DynamicLibrary 37 | true 38 | v110 39 | true 40 | Unicode 41 | 42 | 43 | DynamicLibrary 44 | false 45 | v110 46 | true 47 | Unicode 48 | 49 | 50 | DynamicLibrary 51 | false 52 | v110 53 | true 54 | Unicode 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | WIN32;_DEBUG;%(PreprocessorDefinitions) 89 | Use 90 | $(OPENCV_DIR)\include;..\libellipsedetect;%(AdditionalIncludeDirectories) 91 | 92 | 93 | true 94 | opencv_ts300d.lib;opencv_world300d.lib 95 | $(OPENCV_DIR)\x86\vc11\lib; 96 | 97 | 98 | 99 | 100 | Level3 101 | Disabled 102 | WIN32;_DEBUG;%(PreprocessorDefinitions) 103 | Use 104 | $(OPENCV_DIR)\include;..\libellipsedetect;%(AdditionalIncludeDirectories) 105 | 106 | 107 | true 108 | opencv_ts300d.lib;opencv_world300d.lib 109 | $(OPENCV_DIR)\x64\vc11\lib; 110 | 111 | 112 | 113 | 114 | Level3 115 | WIN32;NDEBUG;%(PreprocessorDefinitions) 116 | Use 117 | 118 | 119 | true 120 | 121 | 122 | 123 | 124 | 125 | Level3 126 | WIN32;NDEBUG;%(PreprocessorDefinitions) 127 | Use 128 | $(OPENCV_DIR)\include;..\libellipsedetect 129 | 130 | 131 | true 132 | opencv_ts300.lib;opencv_world300.lib 133 | $(OPENCV_DIR)\x64\vc11\lib 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Create 155 | Create 156 | Create 157 | Create 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | {cd398248-a2b7-4f68-8f27-3a75ec4b1d8a} 172 | true 173 | true 174 | false 175 | true 176 | false 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /EllipseDetectorLib/EllipseResult.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | 3 | #include "EllipseResult.h" 4 | 5 | namespace EllipseDetectorLib { 6 | /* 7 | * EllipseResult is the result class 8 | * This class implements a result of running .NET wrapper of YAED (Yet Another Ellipse Detector) 9 | */ 10 | EllipseResult::EllipseResult(): _xc(0), _yc(0), _a(0), _b(0), _rad(0) 11 | {} 12 | 13 | EllipseResult::EllipseResult(float xc, float yc, float a, float b, float rad): _xc(xc), _yc(yc), _a(a), _b(b), _rad(rad) 14 | {} 15 | } -------------------------------------------------------------------------------- /EllipseDetectorLib/EllipseResult.h: -------------------------------------------------------------------------------- 1 | // EllipsResult.h 2 | 3 | #pragma once 4 | 5 | using namespace System; 6 | 7 | namespace EllipseDetectorLib { 8 | /* 9 | * EllipseResult is the result class 10 | * This class implements a result of running .NET wrapper of YAED (Yet Another Ellipse Detector) 11 | */ 12 | public ref class EllipseResult 13 | { 14 | public: 15 | EllipseResult(); 16 | 17 | EllipseResult(float xc, float yc, float a, float b, float rad); 18 | ~EllipseResult(void) {} 19 | !EllipseResult() {} 20 | 21 | property System::Drawing::PointF Center { 22 | System::Drawing::PointF get() 23 | { 24 | return System::Drawing::PointF(_xc, _yc); 25 | } 26 | void set(System::Drawing::PointF value) 27 | { 28 | _xc = value.X; 29 | _yc = value.Y; 30 | } 31 | } 32 | 33 | property float RadiusA { 34 | float get() 35 | { 36 | return _a; 37 | } 38 | void set(float value) 39 | { 40 | _a = value; 41 | } 42 | } 43 | 44 | property float RadiusB { 45 | float get() 46 | { 47 | return _b; 48 | } 49 | void set(float value) 50 | { 51 | _b = value; 52 | } 53 | } 54 | 55 | property float Radian { 56 | float get() 57 | { 58 | return _rad; 59 | } 60 | void set(float value) 61 | { 62 | _rad = value; 63 | } 64 | } 65 | 66 | 67 | private: 68 | float _xc; 69 | float _yc; 70 | float _a; 71 | float _b; 72 | float _rad; 73 | 74 | }; 75 | } -------------------------------------------------------------------------------- /EllipseDetectorLib/ExecutionTime.h: -------------------------------------------------------------------------------- 1 | // ExecutionTime.h 2 | 3 | #pragma once 4 | 5 | using namespace System; 6 | 7 | namespace EllipseDetectorLib { 8 | /* 9 | * ExecutionTime is the result class 10 | * This class implements result time of running .NET wrapper of YAED (Yet Another Ellipse Detector) 11 | */ 12 | public ref class ExecutionTime 13 | { 14 | public: 15 | ExecutionTime() : 16 | _fEdgeDetection(0), _fPreProcessing(0), _fGrouping(0), 17 | _fEstimation(0), _fValidation(0), 18 | _fClustering(0), _fTotal(0) 19 | { 20 | } 21 | ExecutionTime(float fEdgeDetection, float fPreProcessing, float fGrouping, float fEstimation, float fValidation, float fClustering, float fTotal) : 22 | _fEdgeDetection(fEdgeDetection), _fPreProcessing(fPreProcessing), _fGrouping(fGrouping), 23 | _fEstimation(fEstimation), _fValidation(fValidation), 24 | _fClustering(fClustering), _fTotal(fTotal) 25 | { 26 | } 27 | 28 | ~ExecutionTime(void) {} 29 | !ExecutionTime() {} 30 | 31 | property float EdgeDetection { 32 | float get() 33 | { 34 | return _fEdgeDetection; 35 | } 36 | void set(float value) 37 | { 38 | _fEdgeDetection = value; 39 | } 40 | } 41 | 42 | property float PreProcessing { 43 | float get() 44 | { 45 | return _fPreProcessing; 46 | } 47 | void set(float value) 48 | { 49 | _fPreProcessing = value; 50 | } 51 | } 52 | 53 | property float Grouping { 54 | float get() 55 | { 56 | return _fGrouping; 57 | } 58 | void set(float value) 59 | { 60 | _fGrouping = value; 61 | } 62 | } 63 | 64 | property float Estimation { 65 | float get() 66 | { 67 | return _fEstimation; 68 | } 69 | void set(float value) 70 | { 71 | _fEstimation = value; 72 | } 73 | } 74 | 75 | property float Validation { 76 | float get() 77 | { 78 | return _fValidation; 79 | } 80 | void set(float value) 81 | { 82 | _fValidation = value; 83 | } 84 | } 85 | 86 | property float Clustering { 87 | float get() 88 | { 89 | return _fClustering; 90 | } 91 | void set(float value) 92 | { 93 | _fClustering = value; 94 | } 95 | } 96 | 97 | property float Total { 98 | float get() 99 | { 100 | return _fTotal; 101 | } 102 | void set(float value) 103 | { 104 | _fTotal = value; 105 | } 106 | } 107 | private: 108 | float _fEdgeDetection; 109 | float _fPreProcessing; 110 | float _fGrouping; 111 | float _fEstimation; 112 | float _fValidation; 113 | float _fClustering; 114 | float _fTotal; 115 | 116 | }; 117 | } -------------------------------------------------------------------------------- /EllipseDetectorLib/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | DYNAMIC LINK LIBRARY : EllipseDetectorLib Project Overview 3 | ======================================================================== 4 | 5 | EllipseDetectorLib DLL is a wrapper for codename: YAED (Yet Another Ellipse Detector). 6 | 7 | This file contains a summary of what you will find in each of the files that 8 | make up your EllipseDetectorLib application. 9 | 10 | EllipseDetectorLib.vcxproj 11 | This is the main project file for VC++ projects generated using an Application Wizard. 12 | It contains information about the version of Visual C++ that generated the file, and 13 | information about the platforms, configurations, and project features selected with the 14 | Application Wizard. 15 | 16 | 17 | EllipseDetectorLib.cpp 18 | This is the main DLL source file. 19 | 20 | EllipseDetectorLib.h 21 | This file contains a class declaration. 22 | 23 | AssemblyInfo.cpp 24 | Contains custom attributes for modifying assembly metadata. 25 | 26 | ///////////////////////////////////////////////////////////////////////////// 27 | Other notes: 28 | 29 | 30 | 31 | ///////////////////////////////////////////////////////////////////////////// 32 | -------------------------------------------------------------------------------- /EllipseDetectorLib/Stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // EllipseDetectorLib.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | -------------------------------------------------------------------------------- /EllipseDetectorLib/Stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, 3 | // but are changed infrequently 4 | 5 | #pragma once 6 | 7 | 8 | -------------------------------------------------------------------------------- /EllipseDetectorLib/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/EllipseDetectorLib/app.ico -------------------------------------------------------------------------------- /EllipseDetectorLib/app.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/EllipseDetectorLib/app.rc -------------------------------------------------------------------------------- /EllipseDetectorLib/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by app.rc 4 | -------------------------------------------------------------------------------- /ImageDetector.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageDetector", "ImageDetector\ImageDetector.vcxproj", "{4E0931A1-9867-4D3A-81BE-FEE56F113271}" 5 | EndProject 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libellipsedetect", "libellipsedetect\libellipsedetect.vcxproj", "{CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EllipseDetectorLib", "EllipseDetectorLib\EllipseDetectorLib.vcxproj", "{5251952C-3AB8-4B6F-9D38-0685C6E80B97}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCSharp", "TestCSharp\TestCSharp.csproj", "{9FC7807A-25F6-4B90-BC61-6C7D58F62188}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|Mixed Platforms = Debug|Mixed Platforms 16 | Debug|Win32 = Debug|Win32 17 | Debug|x64 = Debug|x64 18 | Release|Any CPU = Release|Any CPU 19 | Release|Mixed Platforms = Release|Mixed Platforms 20 | Release|Win32 = Release|Win32 21 | Release|x64 = Release|x64 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Debug|Any CPU.ActiveCfg = Debug|Win32 25 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 26 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Debug|Mixed Platforms.Build.0 = Debug|Win32 27 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Debug|Win32.ActiveCfg = Debug|Win32 28 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Debug|Win32.Build.0 = Debug|Win32 29 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Debug|x64.ActiveCfg = Debug|x64 30 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Debug|x64.Build.0 = Debug|x64 31 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Release|Any CPU.ActiveCfg = Release|Win32 32 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Release|Mixed Platforms.ActiveCfg = Release|Win32 33 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Release|Mixed Platforms.Build.0 = Release|Win32 34 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Release|Win32.ActiveCfg = Release|Win32 35 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Release|Win32.Build.0 = Release|Win32 36 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Release|x64.ActiveCfg = Release|x64 37 | {4E0931A1-9867-4D3A-81BE-FEE56F113271}.Release|x64.Build.0 = Release|x64 38 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Debug|Any CPU.ActiveCfg = Debug|Win32 39 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 40 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Debug|Mixed Platforms.Build.0 = Debug|Win32 41 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Debug|Win32.ActiveCfg = Debug|Win32 42 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Debug|Win32.Build.0 = Debug|Win32 43 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Debug|x64.ActiveCfg = Debug|x64 44 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Debug|x64.Build.0 = Debug|x64 45 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Release|Any CPU.ActiveCfg = Release|Win32 46 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Release|Mixed Platforms.ActiveCfg = Release|Win32 47 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Release|Mixed Platforms.Build.0 = Release|Win32 48 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Release|Win32.ActiveCfg = Release|Win32 49 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Release|Win32.Build.0 = Release|Win32 50 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Release|x64.ActiveCfg = Release|x64 51 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A}.Release|x64.Build.0 = Release|x64 52 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Debug|Any CPU.ActiveCfg = Debug|Win32 53 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 54 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Debug|Mixed Platforms.Build.0 = Debug|Win32 55 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Debug|Win32.ActiveCfg = Debug|Win32 56 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Debug|Win32.Build.0 = Debug|Win32 57 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Debug|x64.ActiveCfg = Debug|x64 58 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Debug|x64.Build.0 = Debug|x64 59 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Release|Any CPU.ActiveCfg = Release|Win32 60 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Release|Mixed Platforms.ActiveCfg = Release|Win32 61 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Release|Mixed Platforms.Build.0 = Release|Win32 62 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Release|Win32.ActiveCfg = Release|Win32 63 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Release|Win32.Build.0 = Release|Win32 64 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Release|x64.ActiveCfg = Release|x64 65 | {5251952C-3AB8-4B6F-9D38-0685C6E80B97}.Release|x64.Build.0 = Release|x64 66 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 69 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 70 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|Win32.ActiveCfg = Debug|Any CPU 71 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|Win32.Build.0 = Debug|Any CPU 72 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|x64.ActiveCfg = Debug|x64 73 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Debug|x64.Build.0 = Debug|x64 74 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 77 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Release|Mixed Platforms.Build.0 = Release|Any CPU 78 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Release|Win32.ActiveCfg = Release|Any CPU 79 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Release|x64.ActiveCfg = Release|x64 80 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188}.Release|x64.Build.0 = Release|x64 81 | EndGlobalSection 82 | GlobalSection(SolutionProperties) = preSolution 83 | HideSolutionNode = FALSE 84 | EndGlobalSection 85 | EndGlobal 86 | -------------------------------------------------------------------------------- /ImageDetector/ImageDetector.cpp: -------------------------------------------------------------------------------- 1 | /* ImageDetector.cpp : Defines the entry point for the console application. 2 | This example is base on an article 3 | Michele Fornaciari, Andrea Prati, Rita Cucchiara, 4 | A fast and effective ellipse detector for embedded vision applications 5 | Pattern Recognition, Volume 47, Issue 11, November 2014, Pages 3693-3708, ISSN 0031-3203, 6 | http://dx.doi.org/10.1016/j.patcog.2014.05.012. 7 | (http://www.sciencedirect.com/science/article/pii/S0031320314001976) 8 | */ 9 | #include "stdafx.h" 10 | 11 | #include 12 | 13 | #include "EllipseDetectorYaed.h" 14 | #include "common.h" 15 | 16 | using namespace std; 17 | using namespace cv; 18 | using namespace Yaed; 19 | 20 | // Test on single image 21 | int main2(int argc, char** argv) 22 | { 23 | string images_folder = "."; 24 | string out_folder = "."; 25 | if (argc > 1) 26 | { 27 | images_folder = string(argv[1]); 28 | out_folder = images_folder; 29 | } 30 | 31 | if (argc > 2) 32 | { 33 | out_folder = string(argv[2]); 34 | } 35 | string filter = "*.jpg"; 36 | if (argc > 3) 37 | { 38 | filter = string(argv[3]); 39 | } 40 | 41 | cout << "OpenCV version : " << CV_VERSION << endl; 42 | cout << "Major version : " << CV_MAJOR_VERSION << endl; 43 | cout << "Minor version : " << CV_MINOR_VERSION << endl; 44 | cout << "Subminor version : " << CV_SUBMINOR_VERSION << endl; 45 | cout << "=========================" << endl; 46 | 47 | cout << "Execution Params: " << endl; 48 | cout << "Input folder: \t" << images_folder << endl; 49 | cout << "Output folder: \t" << out_folder << endl; 50 | cout << "Filter: \t" << filter << endl; 51 | 52 | vector names;// = new vector(); 53 | glob(images_folder + filter, names); 54 | 55 | for (const auto& image_name : names) 56 | { 57 | string name = image_name.substr(image_name.find_last_of("\\") + 1); 58 | name = name.substr(0, name.find_last_of(".")); 59 | 60 | Mat3b image = imread(image_name); 61 | Size sz = image.size(); 62 | 63 | // Convert to grayscale 64 | Mat1b gray; 65 | cvtColor(image, gray, CV_BGR2GRAY); 66 | 67 | // Parameters Settings (Sect. 4.2) 68 | int iThLength = 16; 69 | float fThObb = 3.0f; 70 | float fThPos = 1.0f; 71 | float fTaoCenters = 0.05f; 72 | int iNs = 16; 73 | float fThScoreScore = 0.7f; 74 | 75 | float fMaxCenterDistance = sqrt(float(sz.width*sz.width + sz.height*sz.height)) * fTaoCenters; 76 | 77 | // Other constant parameters settings. 78 | 79 | // Gaussian filter parameters, in pre-processing 80 | Size szPreProcessingGaussKernelSize = Size(5, 5); 81 | double dPreProcessingGaussSigma = 1.0; 82 | 83 | float fDistanceToEllipseContour = 0.1f; // (Sect. 3.3.1 - Validation) 84 | float fMinReliability = 0.5; // Const parameters to discard bad ellipses 85 | 86 | 87 | // Initialize Detector with selected parameters 88 | CEllipseDetectorYaed yaed; 89 | yaed.SetParameters(szPreProcessingGaussKernelSize, 90 | dPreProcessingGaussSigma, 91 | fThPos, 92 | fMaxCenterDistance, 93 | iThLength, 94 | fThObb, 95 | fDistanceToEllipseContour, 96 | fThScoreScore, 97 | fMinReliability, 98 | iNs 99 | ); 100 | 101 | 102 | // Detect 103 | vector ellsYaed; 104 | yaed.Detect(gray.clone(), ellsYaed); 105 | 106 | vector times = yaed.GetTimes(); 107 | cout << "--------------------------------" << endl; 108 | cout << "Execution Time: " << endl; 109 | cout << "Edge Detection: \t" << times[0] << endl; 110 | cout << "Pre processing: \t" << times[1] << endl; 111 | cout << "Grouping: \t" << times[2] << endl; 112 | cout << "Estimation: \t" << times[3] << endl; 113 | cout << "Validation: \t" << times[4] << endl; 114 | cout << "Clustering: \t" << times[5] << endl; 115 | cout << "--------------------------------" << endl; 116 | cout << "Total: \t" << yaed.GetExecTime() << endl; 117 | cout << "--------------------------------" << endl; 118 | 119 | 120 | 121 | Mat3b resultImage = image.clone(); 122 | yaed.DrawDetectedEllipses(resultImage, ellsYaed); 123 | 124 | imwrite(out_folder + name + ".png", resultImage); 125 | 126 | imshow("Yaed", resultImage); 127 | waitKey(); 128 | 129 | 130 | int yghds = 0; 131 | } 132 | return 0; 133 | } 134 | 135 | int main(int argc, char** argv) 136 | { 137 | main2(argc, argv); 138 | return 0; 139 | } -------------------------------------------------------------------------------- /ImageDetector/ImageDetector.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {4E0931A1-9867-4D3A-81BE-FEE56F113271} 23 | Win32Proj 24 | ImageDetector 25 | 26 | 27 | 28 | Application 29 | true 30 | v110 31 | Unicode 32 | 33 | 34 | Application 35 | true 36 | v110 37 | Unicode 38 | 39 | 40 | Application 41 | false 42 | v110 43 | true 44 | Unicode 45 | 46 | 47 | Application 48 | false 49 | v110 50 | true 51 | Unicode 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | true 71 | 72 | 73 | true 74 | 75 | 76 | false 77 | 78 | 79 | false 80 | 81 | 82 | 83 | Use 84 | Level3 85 | Disabled 86 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 87 | true 88 | $(OPENCV_DIR)\include;%(AdditionalIncludeDirectories);..\libellipsedetect\ 89 | 90 | 91 | Console 92 | true 93 | $(OPENCV_DIR)\x86\vc11\lib;%(AdditionalLibraryDirectories) 94 | opencv_ts300d.lib;opencv_world300d.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 95 | 96 | 97 | 98 | 99 | Use 100 | Level3 101 | Disabled 102 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 103 | true 104 | $(OPENCV_DIR)\include;%(AdditionalIncludeDirectories);..\libellipsedetect\ 105 | 106 | 107 | Console 108 | true 109 | $(OPENCV_DIR)\x64\vc11\lib;%(AdditionalLibraryDirectories) 110 | opencv_ts300d.lib;opencv_world300d.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 111 | 112 | 113 | 114 | 115 | Level3 116 | Use 117 | MaxSpeed 118 | true 119 | true 120 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 121 | true 122 | 123 | 124 | Console 125 | true 126 | true 127 | true 128 | 129 | 130 | 131 | 132 | Level3 133 | Use 134 | MaxSpeed 135 | true 136 | true 137 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 138 | true 139 | $(OPENCV_DIR)\include;..\libellipsedetect 140 | 141 | 142 | Console 143 | true 144 | true 145 | true 146 | $(OPENCV_DIR)\x64\vc11\lib 147 | opencv_ts300.lib;opencv_world300.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | Create 161 | Create 162 | Create 163 | Create 164 | 165 | 166 | 167 | 168 | {cd398248-a2b7-4f68-8f27-3a75ec4b1d8a} 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /ImageDetector/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | CONSOLE APPLICATION : ImageDetector Project Overview 3 | ======================================================================== 4 | 5 | Console application for testing purposes 6 | 7 | ImageDetector.vcxproj 8 | This is the main project file for VC++ projects generated using an Application Wizard. 9 | It contains information about the version of Visual C++ that generated the file, and 10 | information about the platforms, configurations, and project features selected with the 11 | Application Wizard. 12 | 13 | ImageDetector.cpp 14 | This is the main application source file. 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | Other standard files: 18 | 19 | StdAfx.h, StdAfx.cpp 20 | These files are used to build a precompiled header (PCH) file 21 | named ImageDetector.pch and a precompiled types file named StdAfx.obj. 22 | 23 | ///////////////////////////////////////////////////////////////////////////// 24 | Other notes: 25 | 26 | ///////////////////////////////////////////////////////////////////////////// 27 | -------------------------------------------------------------------------------- /ImageDetector/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // ImageDetector.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /ImageDetector/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #include 11 | #include 12 | 13 | 14 | 15 | // TODO: reference additional headers your program requires here 16 | -------------------------------------------------------------------------------- /ImageDetector/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EllipseDetectorLib 2 | .Net library for ellipse detection based on the algorithm of very fast ellipse detector, codename: YAED (Yet Another Ellipse Detector). 3 | 4 | #### What is EllipseDetectorLib ? 5 | Simple .Net wrapper for a very fast ellipse detection algorithm. 6 | 7 | #### Third party libraries 8 | 9 | It uses: 10 | * [Open Source Computer Vision Library](https://github.com/opencv/opencv) for image reading and processing. 11 | 12 | #### The main advantage 13 | 14 | Real-time ellipse detection is an important yet challenging task, since the estimation of the five parameters of an ellipse requires heavy computation. 15 | 16 | A new efficient ellipse detection method (Y. Xie and Q. Ji) has remained ineffective and inappropriate for realtime processing. 17 | 18 | This library is suitable in programs which require an approximate ellipse detection and have to do this in realtime. 19 | 20 | #### Projects 21 | There are four projects: 22 | * [EllipseDetectorLib](EllipseDetectorLib/): the main C++ .Net library. In other words this is the wrapper. 23 | * [ImageDetector](ImageDetector/): the example written in C++ and based on the initial code of YAED. 24 | * [libellipsedetect](libellipsedetect/): the code of the YAED library. 25 | * [TestCSharp](TestCSharp/): the example written in C# which uses EllipseDetectorLib library. 26 | 27 | #### Build and deploy 28 | This library has been written in Microsoft Visual Studio 2012. Files *.csproj contain all configurations for different platforms x64, x86 as well as Debug and Release modes. 29 | For successfull build [OpenCV](https://github.com/opencv/opencv) has to be installed. In addition environment variable OPENCV_DIR where opencv is installed should be set. 30 | 31 | #### Features and Capabilities 32 | Here are just a few examples of what EllipseDetectorLib can do: 33 | * [Samples](test-data/): A dozen files shows the initial and result images. 34 | 35 | #### What is YAED (Yet Another Ellipse Detector)? 36 | 37 | ##### According to the comments in the code: 38 | -------------------------------------------------------------- 39 | This code is intended for academic use only. 40 | You are free to use and modify the code, at your own risk. 41 | 42 | If you use this code, or find it useful, please refer to the paper: 43 | 44 | Michele Fornaciari, Andrea Prati, Rita Cucchiara, 45 | A fast and effective ellipse detector for embedded vision applications 46 | Pattern Recognition, Volume 47, Issue 11, November 2014, Pages 3693-3708, ISSN 0031-3203, 47 | http://dx.doi.org/10.1016/j.patcog.2014.05.012 48 | http://www.sciencedirect.com/science/article/pii/S0031320314001976 49 | 50 | 51 | The comments in the code refer to the abovementioned paper. 52 | If you need further details about the code or the algorithm, please contact me at: 53 | 54 | michele.fornaciari@unimore.it 55 | 56 | last update: 23/12/2014 57 | 58 | -------------------------------------------------------------- -------------------------------------------------------------------------------- /TestCSharp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TestCSharp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using EllipseDetectorLib; 6 | 7 | namespace TestCSharp 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | string path = Path.Combine(typeof(Program).Assembly.CodeBase.Replace("file:///", "").Replace("/", "\\"), ".."); 14 | string fileName = args.Length > 0 ? args[0] : string.Empty; 15 | if (!string.IsNullOrEmpty(fileName)) 16 | { 17 | 18 | EllipseDetector detector = new EllipseDetector(fileName); 19 | Tuple> res = detector.Search(); 20 | List resultEllipses = res.Item2; 21 | 22 | using (var bitmap = new Bitmap(fileName)) 23 | using (Graphics graphics = Graphics.FromImage(bitmap)) 24 | { 25 | Brush aBrushCenter = Brushes.Blue; 26 | 27 | int i = 0; 28 | foreach (var result in resultEllipses) 29 | { 30 | var aBrush = i % 2 == 0 ? Brushes.Red : Brushes.Green; 31 | graphics.FillRectangle(aBrushCenter, result.Center.X, result.Center.Y, 5, 5); 32 | for (double angle = 0.0f; angle < Math.PI * 2; angle += 0.01f) 33 | { 34 | var v2X = result.RadiusA * Math.Cos(angle); 35 | var v2Y = result.RadiusB * Math.Sin(angle); 36 | var vx = v2X * Math.Cos(result.Radian) - v2Y * Math.Sin(result.Radian) + result.Center.X; 37 | var vy = v2Y * Math.Cos(result.Radian) + v2X * Math.Sin(result.Radian) + result.Center.Y; 38 | 39 | var points2 = new[] { new PointF(), new PointF() }; 40 | points2[0].X = (float)vx; 41 | points2[0].Y = (float)vy; 42 | points2[1].X = (float)vx; 43 | points2[1].Y = (float)vy; 44 | graphics.FillRectangle(aBrush, points2[0].X, points2[0].Y, 3, 3); 45 | } 46 | 47 | i++; 48 | } 49 | var fi = new FileInfo(fileName); 50 | 51 | bitmap.Save(Path.Combine(path, "output" + fi.Name), bitmap.RawFormat); 52 | Console.WriteLine("--------------------------------"); 53 | Console.WriteLine("Execution Time: "); 54 | var times = detector.Execution; 55 | 56 | Console.WriteLine("Edge Detection: \t{0}" , times.EdgeDetection); 57 | Console.WriteLine("Pre processing: \t{0}" , times.PreProcessing); 58 | Console.WriteLine("Grouping: \t{0}" , times.Grouping); 59 | Console.WriteLine("Estimation: \t{0}" , times.Estimation); 60 | Console.WriteLine("Validation: \t{0}" , times.Validation); 61 | Console.WriteLine("Clustering: \t{0}" , times.Clustering); 62 | Console.WriteLine("--------------------------------"); 63 | Console.WriteLine("Total: \t{0}", times.Total); 64 | Console.WriteLine("--------------------------------"); 65 | 66 | Console.ReadKey(); 67 | } 68 | 69 | } 70 | 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /TestCSharp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TestCSharp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TestCSharp")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("afa4fee7-d509-4efa-8820-96e10ab24d31")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TestCSharp/TestCSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9FC7807A-25F6-4B90-BC61-6C7D58F62188} 8 | Exe 9 | Properties 10 | TestCSharp 11 | TestCSharp 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | true 37 | bin\x64\Debug\ 38 | DEBUG;TRACE 39 | full 40 | x64 41 | prompt 42 | MinimumRecommendedRules.ruleset 43 | true 44 | 45 | 46 | bin\x64\Release\ 47 | TRACE 48 | true 49 | pdbonly 50 | x64 51 | prompt 52 | MinimumRecommendedRules.ruleset 53 | true 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {5251952c-3ab8-4b6f-9d38-0685c6e80b97} 75 | EllipseDetectorLib 76 | 77 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /libellipsedetect/EllipseDetectorYaed.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is intended for academic use only. 3 | You are free to use and modify the code, at your own risk. 4 | 5 | If you use this code, or find it useful, please refer to the paper: 6 | 7 | Michele Fornaciari, Andrea Prati, Rita Cucchiara, 8 | A fast and effective ellipse detector for embedded vision applications 9 | Pattern Recognition, Volume 47, Issue 11, November 2014, Pages 3693-3708, ISSN 0031-3203, 10 | http://dx.doi.org/10.1016/j.patcog.2014.05.012. 11 | (http://www.sciencedirect.com/science/article/pii/S0031320314001976) 12 | 13 | 14 | The comments in the code refer to the abovementioned paper. 15 | If you need further details about the code or the algorithm, please contact me at: 16 | 17 | michele.fornaciari@unimore.it 18 | 19 | last update: 23/12/2014 20 | */ 21 | 22 | #include "EllipseDetectorYaed.h" 23 | 24 | namespace Yaed 25 | { 26 | CEllipseDetectorYaed::CEllipseDetectorYaed(void) : _times(6, 0.0), _timesHelper(6, 0.0) 27 | { 28 | // Default Parameters Settings 29 | _szPreProcessingGaussKernelSize = Size(5, 5); 30 | _dPreProcessingGaussSigma = 1.0; 31 | _fThPosition = 1.0f; 32 | _fMaxCenterDistance = 100.0f * 0.05f; 33 | _fMaxCenterDistance2 = _fMaxCenterDistance * _fMaxCenterDistance; 34 | _iMinEdgeLength = 16; 35 | _fMinOrientedRectSide = 3.0f; 36 | _fDistanceToEllipseContour = 0.1f; 37 | _fMinScore = 0.4f; 38 | _fMinReliability = 0.4f; 39 | _uNs = 16; 40 | 41 | srand(unsigned(time(NULL))); 42 | } 43 | 44 | 45 | CEllipseDetectorYaed::~CEllipseDetectorYaed(void) 46 | { 47 | } 48 | 49 | void CEllipseDetectorYaed::SetParameters(Size szPreProcessingGaussKernelSize, 50 | double dPreProcessingGaussSigma, 51 | float fThPosition, 52 | float fMaxCenterDistance, 53 | int iMinEdgeLength, 54 | float fMinOrientedRectSide, 55 | float fDistanceToEllipseContour, 56 | float fMinScore, 57 | float fMinReliability, 58 | int iNs 59 | ) 60 | { 61 | _szPreProcessingGaussKernelSize = szPreProcessingGaussKernelSize; 62 | _dPreProcessingGaussSigma = dPreProcessingGaussSigma; 63 | _fThPosition = fThPosition; 64 | _fMaxCenterDistance = fMaxCenterDistance; 65 | _iMinEdgeLength = iMinEdgeLength; 66 | _fMinOrientedRectSide = fMinOrientedRectSide; 67 | _fDistanceToEllipseContour = fDistanceToEllipseContour; 68 | _fMinScore = fMinScore; 69 | _fMinReliability = fMinReliability; 70 | _uNs = iNs; 71 | 72 | _fMaxCenterDistance2 = _fMaxCenterDistance * _fMaxCenterDistance; 73 | 74 | } 75 | 76 | uint inline CEllipseDetectorYaed::GenerateKey(uchar pair, ushort u, ushort v) 77 | { 78 | return (pair << 30) + (u << 15) + v; 79 | }; 80 | 81 | 82 | 83 | int CEllipseDetectorYaed::FindMaxK(const int* v) const 84 | { 85 | int max_val = 0; 86 | int max_idx = 0; 87 | for (int i = 0; i max_val) ? max_val = v[i], max_idx = i : NULL; 90 | } 91 | 92 | return max_idx + 90; 93 | }; 94 | 95 | int CEllipseDetectorYaed::FindMaxN(const int* v) const 96 | { 97 | int max_val = 0; 98 | int max_idx = 0; 99 | for (int i = 0; i max_val) ? max_val = v[i], max_idx = i : NULL; 102 | } 103 | 104 | return max_idx; 105 | }; 106 | 107 | int CEllipseDetectorYaed::FindMaxA(const int* v) const 108 | { 109 | int max_val = 0; 110 | int max_idx = 0; 111 | for (int i = 0; i max_val) ? max_val = v[i], max_idx = i : NULL; 114 | } 115 | 116 | return max_idx; 117 | }; 118 | 119 | 120 | float CEllipseDetectorYaed::GetMedianSlope(vector& med, Point2f& M, vector& slopes) 121 | { 122 | // med : vector of points 123 | // M : centroid of the points in med 124 | // slopes : vector of the slopes 125 | 126 | unsigned iNofPoints = med.size(); 127 | //CV_Assert(iNofPoints >= 2); 128 | 129 | unsigned halfSize = iNofPoints >> 1; 130 | unsigned quarterSize = halfSize >> 1; 131 | 132 | vector xx, yy; 133 | slopes.reserve(halfSize); 134 | xx.reserve(iNofPoints); 135 | yy.reserve(iNofPoints); 136 | 137 | for (unsigned i = 0; i < halfSize; ++i) 138 | { 139 | Point2f& p1 = med[i]; 140 | Point2f& p2 = med[halfSize + i]; 141 | 142 | xx.push_back(p1.x); 143 | xx.push_back(p2.x); 144 | yy.push_back(p1.y); 145 | yy.push_back(p2.y); 146 | 147 | float den = (p2.x - p1.x); 148 | float num = (p2.y - p1.y); 149 | 150 | if (den == 0) den = 0.00001f; 151 | 152 | slopes.push_back(num / den); 153 | } 154 | 155 | nth_element(slopes.begin(), slopes.begin() + quarterSize, slopes.end()); 156 | nth_element(xx.begin(), xx.begin() + halfSize, xx.end()); 157 | nth_element(yy.begin(), yy.begin() + halfSize, yy.end()); 158 | M.x = xx[halfSize]; 159 | M.y = yy[halfSize]; 160 | 161 | return slopes[quarterSize]; 162 | }; 163 | 164 | 165 | 166 | 167 | void CEllipseDetectorYaed::GetFastCenter(vector& e1, vector& e2, EllipseData& data) 168 | { 169 | data.isValid = true; 170 | 171 | unsigned size_1 = unsigned(e1.size()); 172 | unsigned size_2 = unsigned(e2.size()); 173 | 174 | unsigned hsize_1 = size_1 >> 1; 175 | unsigned hsize_2 = size_2 >> 1; 176 | 177 | Point& med1 = e1[hsize_1]; 178 | Point& med2 = e2[hsize_2]; 179 | 180 | Point2f M12, M34; 181 | float q2, q4; 182 | 183 | { 184 | // First to second 185 | 186 | // Reference slope 187 | 188 | float dx_ref = float(e1[0].x - med2.x); 189 | float dy_ref = float(e1[0].y - med2.y); 190 | 191 | if (dy_ref == 0) dy_ref = 0.00001f; 192 | 193 | float m_ref = dy_ref / dx_ref; 194 | data.ra = m_ref; 195 | 196 | // Find points with same slope as reference 197 | vector med; 198 | med.reserve(hsize_2); 199 | 200 | unsigned minPoints = (_uNs < hsize_2) ? _uNs : hsize_2; 201 | 202 | vector indexes(minPoints); 203 | if (_uNs < hsize_2) 204 | { 205 | unsigned iSzBin = hsize_2 / unsigned(_uNs); 206 | unsigned iIdx = hsize_2 + (iSzBin / 2); 207 | 208 | for (unsigned i = 0; i<_uNs; ++i) 209 | { 210 | indexes[i] = iIdx; 211 | iIdx += iSzBin; 212 | } 213 | } 214 | else 215 | { 216 | iota(indexes.begin(), indexes.end(), hsize_2); 217 | } 218 | 219 | 220 | 221 | for (uint ii = 0; ii> 1; 259 | 260 | while (end - begin > 2) 261 | { 262 | float x2 = float(e1[j].x); 263 | float y2 = float(e1[j].y); 264 | float res = ((x2 - x1) * dy_ref) - ((y2 - y1) * dx_ref); 265 | int sign_res = sgn(res); 266 | 267 | if (sign_res == 0) 268 | { 269 | //found 270 | med.push_back(Point2f((x2 + x1)* 0.5f, (y2 + y1)* 0.5f)); 271 | break; 272 | } 273 | 274 | if (sign_res + sign_begin == 0) 275 | { 276 | sign_end = sign_res; 277 | end = j; 278 | } 279 | else 280 | { 281 | sign_begin = sign_res; 282 | begin = j; 283 | } 284 | j = (begin + end) >> 1; 285 | } 286 | 287 | med.push_back(Point2f((e1[j].x + x1)* 0.5f, (e1[j].y + y1)* 0.5f)); 288 | } 289 | 290 | if (med.size() < 2) 291 | { 292 | data.isValid = false; 293 | return; 294 | } 295 | 296 | q2 = GetMedianSlope(med, M12, data.Sa); 297 | } 298 | 299 | { 300 | // Second to first 301 | 302 | // Reference slope 303 | float dx_ref = float(med1.x - e2[0].x); 304 | float dy_ref = float(med1.y - e2[0].y); 305 | 306 | if (dy_ref == 0) dy_ref = 0.00001f; 307 | 308 | float m_ref = dy_ref / dx_ref; 309 | data.rb = m_ref; 310 | 311 | // Find points with same slope as reference 312 | vector med; 313 | med.reserve(hsize_1); 314 | 315 | uint minPoints = (_uNs < hsize_1) ? _uNs : hsize_1; 316 | 317 | vector indexes(minPoints); 318 | if (_uNs < hsize_1) 319 | { 320 | unsigned iSzBin = hsize_1 / unsigned(_uNs); 321 | unsigned iIdx = hsize_1 + (iSzBin / 2); 322 | 323 | for (unsigned i = 0; i<_uNs; ++i) 324 | { 325 | indexes[i] = iIdx; 326 | iIdx += iSzBin; 327 | } 328 | } 329 | else 330 | { 331 | iota(indexes.begin(), indexes.end(), hsize_1); 332 | } 333 | 334 | 335 | for (uint ii = 0; ii> 1; 373 | 374 | while (end - begin > 2) 375 | { 376 | float x2 = float(e2[j].x); 377 | float y2 = float(e2[j].y); 378 | float res = ((x2 - x1) * dy_ref) - ((y2 - y1) * dx_ref); 379 | int sign_res = sgn(res); 380 | 381 | if (sign_res == 0) 382 | { 383 | //found 384 | med.push_back(Point2f((x2 + x1)* 0.5f, (y2 + y1)* 0.5f)); 385 | break; 386 | } 387 | 388 | if (sign_res + sign_begin == 0) 389 | { 390 | sign_end = sign_res; 391 | end = j; 392 | } 393 | else 394 | { 395 | sign_begin = sign_res; 396 | begin = j; 397 | } 398 | j = (begin + end) >> 1; 399 | } 400 | 401 | med.push_back(Point2f((e2[j].x + x1)* 0.5f, (e2[j].y + y1)* 0.5f)); 402 | } 403 | 404 | if (med.size() < 2) 405 | { 406 | data.isValid = false; 407 | return; 408 | } 409 | q4 = GetMedianSlope(med, M34, data.Sb); 410 | } 411 | 412 | if (q2 == q4) 413 | { 414 | data.isValid = false; 415 | return; 416 | } 417 | 418 | float invDen = 1 / (q2 - q4); 419 | data.Cab.x = (M34.y - q4*M34.x - M12.y + q2*M12.x) * invDen; 420 | data.Cab.y = (q2*M34.y - q4*M12.y + q2*q4*(M12.x - M34.x)) * invDen; 421 | data.ta = q2; 422 | data.tb = q4; 423 | data.Ma = M12; 424 | data.Mb = M34; 425 | }; 426 | 427 | 428 | 429 | 430 | void CEllipseDetectorYaed::DetectEdges13(Mat1b& DP, VVP& points_1, VVP& points_3) 431 | { 432 | // Vector of connected edge points 433 | VVP contours; 434 | 435 | // Labeling 8-connected edge points, discarding edge too small 436 | Labeling(DP, contours, _iMinEdgeLength); 437 | int iContoursSize = int(contours.size()); 438 | 439 | // For each edge 440 | for (int i = 0; i < iContoursSize; ++i) 441 | { 442 | VP& edgeSegment = contours[i]; 443 | 444 | #ifndef DISCARD_CONSTRAINT_OBOX 445 | 446 | // Selection strategy - Step 1 - See Sect [3.1.2] of the paper 447 | // Constraint on axes aspect ratio 448 | RotatedRect oriented = minAreaRect(edgeSegment); 449 | float o_min = min(oriented.size.width, oriented.size.height); 450 | 451 | if (o_min < _fMinOrientedRectSide) 452 | { 453 | continue; 454 | } 455 | #endif 456 | 457 | // Order edge points of the same arc 458 | sort(edgeSegment.begin(), edgeSegment.end(), SortTopLeft2BottomRight); 459 | int iEdgeSegmentSize = unsigned(edgeSegment.size()); 460 | 461 | // Get extrema of the arc 462 | Point& left = edgeSegment[0]; 463 | Point& right = edgeSegment[iEdgeSegmentSize - 1]; 464 | 465 | // Find convexity - See Sect [3.1.3] of the paper 466 | int iCountTop = 0; 467 | int xx = left.x; 468 | for (int k = 1; k < iEdgeSegmentSize; ++k) 469 | { 470 | if (edgeSegment[k].x == xx) continue; 471 | 472 | iCountTop += (edgeSegment[k].y - left.y); 473 | xx = edgeSegment[k].x; 474 | } 475 | 476 | int width = abs(right.x - left.x) + 1; 477 | int height = abs(right.y - left.y) + 1; 478 | int iCountBottom = (width * height) - iEdgeSegmentSize - iCountTop; 479 | 480 | if (iCountBottom > iCountTop) 481 | { //1 482 | points_1.push_back(edgeSegment); 483 | } 484 | else if (iCountBottom < iCountTop) 485 | { //3 486 | points_3.push_back(edgeSegment); 487 | } 488 | } 489 | }; 490 | 491 | 492 | void CEllipseDetectorYaed::DetectEdges24(Mat1b& DN, VVP& points_2, VVP& points_4 ) 493 | { 494 | // Vector of connected edge points 495 | VVP contours; 496 | 497 | /// Labeling 8-connected edge points, discarding edge too small 498 | Labeling(DN, contours, _iMinEdgeLength); 499 | 500 | int iContoursSize = unsigned(contours.size()); 501 | 502 | // For each edge 503 | for (int i = 0; i < iContoursSize; ++i) 504 | { 505 | VP& edgeSegment = contours[i]; 506 | 507 | 508 | #ifndef DISCARD_CONSTRAINT_OBOX 509 | 510 | // Selection strategy - Step 1 - See Sect [3.1.2] of the paper 511 | // Constraint on axes aspect ratio 512 | RotatedRect oriented = minAreaRect(edgeSegment); 513 | float o_min = min(oriented.size.width, oriented.size.height); 514 | 515 | if (o_min < _fMinOrientedRectSide) 516 | { 517 | continue; 518 | } 519 | 520 | #endif 521 | 522 | // Order edge points of the same arc 523 | sort(edgeSegment.begin(), edgeSegment.end(), SortBottomLeft2TopRight); 524 | int iEdgeSegmentSize = unsigned(edgeSegment.size()); 525 | 526 | // Get extrema of the arc 527 | Point& left = edgeSegment[0]; 528 | Point& right = edgeSegment[iEdgeSegmentSize - 1]; 529 | 530 | // Find convexity - See Sect [3.1.3] of the paper 531 | int iCountBottom = 0; 532 | int xx = left.x; 533 | for (int k = 1; k < iEdgeSegmentSize; ++k) 534 | { 535 | if (edgeSegment[k].x == xx) continue; 536 | 537 | iCountBottom += (left.y - edgeSegment[k].y); 538 | xx = edgeSegment[k].x; 539 | } 540 | 541 | int width = abs(right.x - left.x) + 1; 542 | int height = abs(right.y - left.y) + 1; 543 | int iCountTop = (width *height) - iEdgeSegmentSize - iCountBottom; 544 | 545 | if (iCountBottom > iCountTop) 546 | { 547 | //2 548 | points_2.push_back(edgeSegment); 549 | } 550 | else if (iCountBottom < iCountTop) 551 | { 552 | //4 553 | points_4.push_back(edgeSegment); 554 | } 555 | } 556 | }; 557 | 558 | // Most important function for detecting ellipses. See Sect[3.2.3] of the paper 559 | void CEllipseDetectorYaed::FindEllipses( Point2f& center, 560 | VP& edge_i, 561 | VP& edge_j, 562 | VP& edge_k, 563 | EllipseData& data_ij, 564 | EllipseData& data_ik, 565 | vector& ellipses 566 | ) 567 | { 568 | // Find ellipse parameters 569 | 570 | // 0-initialize accumulators 571 | memset(accN, 0, sizeof(int)*ACC_N_SIZE); 572 | memset(accR, 0, sizeof(int)*ACC_R_SIZE); 573 | memset(accA, 0, sizeof(int)*ACC_A_SIZE); 574 | 575 | Tac(3); //estimation 576 | 577 | // Get size of the 4 vectors of slopes (2 pairs of arcs) 578 | int sz_ij1 = int(data_ij.Sa.size()); 579 | int sz_ij2 = int(data_ij.Sb.size()); 580 | int sz_ik1 = int(data_ik.Sa.size()); 581 | int sz_ik2 = int(data_ik.Sb.size()); 582 | 583 | // Get the size of the 3 arcs 584 | size_t sz_ei = edge_i.size(); 585 | size_t sz_ej = edge_j.size(); 586 | size_t sz_ek = edge_k.size(); 587 | 588 | // Center of the estimated ellipse 589 | float a0 = center.x; 590 | float b0 = center.y; 591 | 592 | 593 | // Estimation of remaining parameters 594 | // Uses 4 combinations of parameters. See Table 1 and Sect [3.2.3] of the paper. 595 | { 596 | float q1 = data_ij.ra; 597 | float q3 = data_ik.ra; 598 | float q5 = data_ik.rb; 599 | 600 | for (int ij1 = 0; ij1 < sz_ij1; ++ij1) 601 | { 602 | float q2 = data_ij.Sa[ij1]; 603 | 604 | float q1xq2 = q1*q2; 605 | 606 | for (int ik1 = 0; ik1 < sz_ik1; ++ik1) 607 | { 608 | float q4 = data_ik.Sa[ik1]; 609 | 610 | float q3xq4 = q3*q4; 611 | 612 | // See Eq. [13-18] in the paper 613 | 614 | float a = (q1xq2 - q3xq4); 615 | float b = (q3xq4 + 1)*(q1 + q2) - (q1xq2 + 1)*(q3 + q4); 616 | float Kp = (-b + sqrt(b*b + 4 * a*a)) / (2 * a); 617 | float zplus = ((q1 - Kp)*(q2 - Kp)) / ((1 + q1*Kp)*(1 + q2*Kp)); 618 | 619 | if (zplus >= 0.0f) 620 | { 621 | continue; 622 | } 623 | 624 | float Np = sqrt(-zplus); 625 | float rho = atan(Kp); 626 | int rhoDeg; 627 | if (Np > 1.f) 628 | { 629 | Np = 1.f / Np; 630 | rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; // [0,180) 631 | } 632 | else 633 | { 634 | rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; // [0,180) 635 | } 636 | 637 | int iNp = cvRound(Np * 100); // [0, 100] 638 | 639 | if (0 <= iNp && iNp < ACC_N_SIZE && 640 | 0 <= rhoDeg && rhoDeg < ACC_R_SIZE 641 | ) 642 | { 643 | ++accN[iNp]; // Increment N accumulator 644 | ++accR[rhoDeg]; // Increment R accumulator 645 | } 646 | } 647 | 648 | 649 | for (int ik2 = 0; ik2 < sz_ik2; ++ik2) 650 | { 651 | float q4 = data_ik.Sb[ik2]; 652 | 653 | float q5xq4 = q5*q4; 654 | 655 | // See Eq. [13-18] in the paper 656 | 657 | float a = (q1xq2 - q5xq4); 658 | float b = (q5xq4 + 1)*(q1 + q2) - (q1xq2 + 1)*(q5 + q4); 659 | float Kp = (-b + sqrt(b*b + 4 * a*a)) / (2 * a); 660 | float zplus = ((q1 - Kp)*(q2 - Kp)) / ((1 + q1*Kp)*(1 + q2*Kp)); 661 | 662 | if (zplus >= 0.0f) 663 | { 664 | continue; 665 | } 666 | 667 | float Np = sqrt(-zplus); 668 | float rho = atan(Kp); 669 | int rhoDeg; 670 | if (Np > 1.f) 671 | { 672 | Np = 1.f / Np; 673 | rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; // [0,180) 674 | } 675 | else 676 | { 677 | rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; // [0,180) 678 | } 679 | 680 | int iNp = cvRound(Np * 100); // [0, 100] 681 | 682 | if (0 <= iNp && iNp < ACC_N_SIZE && 683 | 0 <= rhoDeg && rhoDeg < ACC_R_SIZE 684 | ) 685 | { 686 | ++accN[iNp]; // Increment N accumulator 687 | ++accR[rhoDeg]; // Increment R accumulator 688 | } 689 | } 690 | 691 | } 692 | } 693 | 694 | 695 | { 696 | float q1 = data_ij.rb; 697 | float q3 = data_ik.rb; 698 | float q5 = data_ik.ra; 699 | 700 | for (int ij2 = 0; ij2 < sz_ij2; ++ij2) 701 | { 702 | float q2 = data_ij.Sb[ij2]; 703 | 704 | float q1xq2 = q1*q2; 705 | 706 | for (int ik2 = 0; ik2 < sz_ik2; ++ik2) 707 | { 708 | float q4 = data_ik.Sb[ik2]; 709 | 710 | float q3xq4 = q3*q4; 711 | 712 | // See Eq. [13-18] in the paper 713 | 714 | float a = (q1xq2 - q3xq4); 715 | float b = (q3xq4 + 1)*(q1 + q2) - (q1xq2 + 1)*(q3 + q4); 716 | float Kp = (-b + sqrt(b*b + 4 * a*a)) / (2 * a); 717 | float zplus = ((q1 - Kp)*(q2 - Kp)) / ((1 + q1*Kp)*(1 + q2*Kp)); 718 | 719 | if (zplus >= 0.0f) 720 | { 721 | continue; 722 | } 723 | 724 | float Np = sqrt(-zplus); 725 | float rho = atan(Kp); 726 | int rhoDeg; 727 | if (Np > 1.f) 728 | { 729 | Np = 1.f / Np; 730 | rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; // [0,180) 731 | } 732 | else 733 | { 734 | rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; // [0,180) 735 | } 736 | 737 | int iNp = cvRound(Np * 100); // [0, 100] 738 | 739 | if (0 <= iNp && iNp < ACC_N_SIZE && 740 | 0 <= rhoDeg && rhoDeg < ACC_R_SIZE 741 | ) 742 | { 743 | ++accN[iNp]; // Increment N accumulator 744 | ++accR[rhoDeg]; // Increment R accumulator 745 | } 746 | } 747 | 748 | 749 | for (int ik1 = 0; ik1 < sz_ik1; ++ik1) 750 | { 751 | float q4 = data_ik.Sa[ik1]; 752 | 753 | float q5xq4 = q5*q4; 754 | 755 | // See Eq. [13-18] in the paper 756 | 757 | float a = (q1xq2 - q5xq4); 758 | float b = (q5xq4 + 1)*(q1 + q2) - (q1xq2 + 1)*(q5 + q4); 759 | float Kp = (-b + sqrt(b*b + 4 * a*a)) / (2 * a); 760 | float zplus = ((q1 - Kp)*(q2 - Kp)) / ((1 + q1*Kp)*(1 + q2*Kp)); 761 | 762 | if (zplus >= 0.0f) 763 | { 764 | continue; 765 | } 766 | 767 | float Np = sqrt(-zplus); 768 | float rho = atan(Kp); 769 | int rhoDeg; 770 | if (Np > 1.f) 771 | { 772 | Np = 1.f / Np; 773 | rhoDeg = cvRound((rho * 180 / CV_PI) + 180) % 180; // [0,180) 774 | } 775 | else 776 | { 777 | rhoDeg = cvRound((rho * 180 / CV_PI) + 90) % 180; // [0,180) 778 | } 779 | 780 | int iNp = cvRound(Np * 100); // [0, 100] 781 | 782 | if (0 <= iNp && iNp < ACC_N_SIZE && 783 | 0 <= rhoDeg && rhoDeg < ACC_R_SIZE 784 | ) 785 | { 786 | ++accN[iNp]; // Increment N accumulator 787 | ++accR[rhoDeg]; // Increment R accumulator 788 | } 789 | } 790 | 791 | } 792 | } 793 | 794 | // Find peak in N and K accumulator 795 | int iN = FindMaxN(accN); 796 | int iK = FindMaxK(accR); 797 | 798 | // Recover real values 799 | float fK = float(iK); 800 | float Np = float(iN) * 0.01f; 801 | float rho = fK * float(CV_PI) / 180.f; //deg 2 rad 802 | float Kp = tan(rho); 803 | 804 | // Estimate A. See Eq. [19 - 22] in Sect [3.2.3] of the paper 805 | 806 | for (ushort l = 0; l < sz_ei; ++l) 807 | { 808 | Point& pp = edge_i[l]; 809 | float sk = 1.f / sqrt(Kp*Kp + 1.f); 810 | float x0 = ((pp.x - a0) * sk) + (((pp.y - b0)*Kp) * sk); 811 | float y0 = -(((pp.x - a0) * Kp) * sk) + ((pp.y - b0) * sk); 812 | float Ax = sqrt((x0*x0*Np*Np + y0*y0) / ((Np*Np)*(1.f + Kp*Kp))); 813 | int A = cvRound(abs(Ax / cos(rho))); 814 | if ((0 <= A) && (A < ACC_A_SIZE)) 815 | { 816 | ++accA[A]; 817 | } 818 | } 819 | 820 | for (ushort l = 0; l < sz_ej; ++l) 821 | { 822 | Point& pp = edge_j[l]; 823 | float sk = 1.f / sqrt(Kp*Kp + 1.f); 824 | float x0 = ((pp.x - a0) * sk) + (((pp.y - b0)*Kp) * sk); 825 | float y0 = -(((pp.x - a0) * Kp) * sk) + ((pp.y - b0) * sk); 826 | float Ax = sqrt((x0*x0*Np*Np + y0*y0) / ((Np*Np)*(1.f + Kp*Kp))); 827 | int A = cvRound(abs(Ax / cos(rho))); 828 | if ((0 <= A) && (A < ACC_A_SIZE)) 829 | { 830 | ++accA[A]; 831 | } 832 | } 833 | 834 | for (ushort l = 0; l < sz_ek; ++l) 835 | { 836 | Point& pp = edge_k[l]; 837 | float sk = 1.f / sqrt(Kp*Kp + 1.f); 838 | float x0 = ((pp.x - a0) * sk) + (((pp.y - b0)*Kp) * sk); 839 | float y0 = -(((pp.x - a0) * Kp) * sk) + ((pp.y - b0) * sk); 840 | float Ax = sqrt((x0*x0*Np*Np + y0*y0) / ((Np*Np)*(1.f + Kp*Kp))); 841 | int A = cvRound(abs(Ax / cos(rho))); 842 | if ((0 <= A) && (A < ACC_A_SIZE)) 843 | { 844 | ++accA[A]; 845 | } 846 | } 847 | 848 | // Find peak in A accumulator 849 | int A = FindMaxA(accA); 850 | float fA = float(A); 851 | 852 | // Find B value. See Eq [23] in the paper 853 | float fB = abs(fA * Np); 854 | 855 | // Got all ellipse parameters! 856 | Ellipse ell(a0, b0, fA, fB, fmod(rho + float(CV_PI)*2.f, float(CV_PI))); 857 | 858 | Toc(3); //estimation 859 | Tac(4); //validation 860 | 861 | // Get the score. See Sect [3.3.1] in the paper 862 | 863 | // Find the number of edge pixel lying on the ellipse 864 | float _cos = cos(-ell._rad); 865 | float _sin = sin(-ell._rad); 866 | 867 | float invA2 = 1.f / (ell._a * ell._a); 868 | float invB2 = 1.f / (ell._b * ell._b); 869 | 870 | float invNofPoints = 1.f / float(sz_ei + sz_ej + sz_ek); 871 | int counter_on_perimeter = 0; 872 | 873 | for (ushort l = 0; l < sz_ei; ++l) 874 | { 875 | float tx = float(edge_i[l].x) - ell._xc; 876 | float ty = float(edge_i[l].y) - ell._yc; 877 | float rx = (tx*_cos - ty*_sin); 878 | float ry = (tx*_sin + ty*_cos); 879 | 880 | float h = (rx*rx)*invA2 + (ry*ry)*invB2; 881 | if (abs(h - 1.f) < _fDistanceToEllipseContour) 882 | { 883 | ++counter_on_perimeter; 884 | } 885 | } 886 | 887 | for (ushort l = 0; l < sz_ej; ++l) 888 | { 889 | float tx = float(edge_j[l].x) - ell._xc; 890 | float ty = float(edge_j[l].y) - ell._yc; 891 | float rx = (tx*_cos - ty*_sin); 892 | float ry = (tx*_sin + ty*_cos); 893 | 894 | float h = (rx*rx)*invA2 + (ry*ry)*invB2; 895 | if (abs(h - 1.f) < _fDistanceToEllipseContour) 896 | { 897 | ++counter_on_perimeter; 898 | } 899 | } 900 | 901 | for (ushort l = 0; l < sz_ek; ++l) 902 | { 903 | float tx = float(edge_k[l].x) - ell._xc; 904 | float ty = float(edge_k[l].y) - ell._yc; 905 | float rx = (tx*_cos - ty*_sin); 906 | float ry = (tx*_sin + ty*_cos); 907 | 908 | float h = (rx*rx)*invA2 + (ry*ry)*invB2; 909 | if (abs(h - 1.f) < _fDistanceToEllipseContour) 910 | { 911 | ++counter_on_perimeter; 912 | } 913 | } 914 | 915 | //no points found on the ellipse 916 | if (counter_on_perimeter <= 0) 917 | { 918 | Toc(4); //validation 919 | return; 920 | } 921 | 922 | // Compute score 923 | float score = float(counter_on_perimeter) * invNofPoints; 924 | if (score < _fMinScore) 925 | { 926 | Toc(4); //validation 927 | return; 928 | } 929 | 930 | // Compute reliability 931 | // this metric is not described in the paper, mostly due to space limitations. 932 | // The main idea is that for a given ellipse (TD) even if the score is high, the arcs 933 | // can cover only a small amount of the contour of the estimated ellipse. 934 | // A low reliability indicate that the arcs form an elliptic shape by chance, but do not underlie 935 | // an actual ellipse. The value is normalized between 0 and 1. 936 | // The default value is 0.4. 937 | 938 | // It is somehow similar to the "Angular Circumreference Ratio" saliency criteria 939 | // as in the paper: 940 | // D. K. Prasad, M. K. Leung, S.-Y. Cho, Edge curvature and convexity 941 | // based ellipse detection method, Pattern Recognition 45 (2012) 3204-3221. 942 | 943 | float di, dj, dk; 944 | { 945 | Point2f p1(float(edge_i[0].x), float(edge_i[0].y)); 946 | Point2f p2(float(edge_i[sz_ei - 1].x), float(edge_i[sz_ei - 1].y)); 947 | p1.x -= ell._xc; 948 | p1.y -= ell._yc; 949 | p2.x -= ell._xc; 950 | p2.y -= ell._yc; 951 | Point2f r1((p1.x*_cos - p1.y*_sin), (p1.x*_sin + p1.y*_cos)); 952 | Point2f r2((p2.x*_cos - p2.y*_sin), (p2.x*_sin + p2.y*_cos)); 953 | di = abs(r2.x - r1.x) + abs(r2.y - r1.y); 954 | } 955 | { 956 | Point2f p1(float(edge_j[0].x), float(edge_j[0].y)); 957 | Point2f p2(float(edge_j[sz_ej - 1].x), float(edge_j[sz_ej - 1].y)); 958 | p1.x -= ell._xc; 959 | p1.y -= ell._yc; 960 | p2.x -= ell._xc; 961 | p2.y -= ell._yc; 962 | Point2f r1((p1.x*_cos - p1.y*_sin), (p1.x*_sin + p1.y*_cos)); 963 | Point2f r2((p2.x*_cos - p2.y*_sin), (p2.x*_sin + p2.y*_cos)); 964 | dj = abs(r2.x - r1.x) + abs(r2.y - r1.y); 965 | } 966 | { 967 | Point2f p1(float(edge_k[0].x), float(edge_k[0].y)); 968 | Point2f p2(float(edge_k[sz_ek - 1].x), float(edge_k[sz_ek - 1].y)); 969 | p1.x -= ell._xc; 970 | p1.y -= ell._yc; 971 | p2.x -= ell._xc; 972 | p2.y -= ell._yc; 973 | Point2f r1((p1.x*_cos - p1.y*_sin), (p1.x*_sin + p1.y*_cos)); 974 | Point2f r2((p2.x*_cos - p2.y*_sin), (p2.x*_sin + p2.y*_cos)); 975 | dk = abs(r2.x - r1.x) + abs(r2.y - r1.y); 976 | } 977 | 978 | // This allows to get rid of thick edges 979 | float rel = min(1.f, ((di + dj + dk) / (3 * (ell._a + ell._b)))); 980 | 981 | if (rel < _fMinReliability) 982 | { 983 | Toc(4); //validation 984 | return; 985 | } 986 | 987 | // Assign the new score! 988 | ell._score = (score + rel) * 0.5f; 989 | //ell._score = score; 990 | 991 | // The tentative detection has been confirmed. Save it! 992 | ellipses.push_back(ell); 993 | 994 | Toc(4); // Validation 995 | }; 996 | 997 | // Get the coordinates of the center, given the intersection of the estimated lines. See Fig. [8] in Sect [3.2.3] in the paper. 998 | Point2f CEllipseDetectorYaed::GetCenterCoordinates(EllipseData& data_ij, EllipseData& data_ik) 999 | { 1000 | float xx[7]; 1001 | float yy[7]; 1002 | 1003 | xx[0] = data_ij.Cab.x; 1004 | xx[1] = data_ik.Cab.x; 1005 | yy[0] = data_ij.Cab.y; 1006 | yy[1] = data_ik.Cab.y; 1007 | 1008 | { 1009 | //1-1 1010 | float q2 = data_ij.ta; 1011 | float q4 = data_ik.ta; 1012 | Point2f& M12 = data_ij.Ma; 1013 | Point2f& M34 = data_ik.Ma; 1014 | 1015 | float invDen = 1 / (q2 - q4); 1016 | xx[2] = (M34.y - q4*M34.x - M12.y + q2*M12.x) * invDen; 1017 | yy[2] = (q2*M34.y - q4*M12.y + q2*q4*(M12.x - M34.x)) * invDen; 1018 | } 1019 | 1020 | { 1021 | //1-2 1022 | float q2 = data_ij.ta; 1023 | float q4 = data_ik.tb; 1024 | Point2f& M12 = data_ij.Ma; 1025 | Point2f& M34 = data_ik.Mb; 1026 | 1027 | float invDen = 1 / (q2 - q4); 1028 | xx[3] = (M34.y - q4*M34.x - M12.y + q2*M12.x) * invDen; 1029 | yy[3] = (q2*M34.y - q4*M12.y + q2*q4*(M12.x - M34.x)) * invDen; 1030 | } 1031 | 1032 | { 1033 | //2-2 1034 | float q2 = data_ij.tb; 1035 | float q4 = data_ik.tb; 1036 | Point2f& M12 = data_ij.Mb; 1037 | Point2f& M34 = data_ik.Mb; 1038 | 1039 | float invDen = 1 / (q2 - q4); 1040 | xx[4] = (M34.y - q4*M34.x - M12.y + q2*M12.x) * invDen; 1041 | yy[4] = (q2*M34.y - q4*M12.y + q2*q4*(M12.x - M34.x)) * invDen; 1042 | } 1043 | 1044 | { 1045 | //2-1 1046 | float q2 = data_ij.tb; 1047 | float q4 = data_ik.ta; 1048 | Point2f& M12 = data_ij.Mb; 1049 | Point2f& M34 = data_ik.Ma; 1050 | 1051 | float invDen = 1 / (q2 - q4); 1052 | xx[5] = (M34.y - q4*M34.x - M12.y + q2*M12.x) * invDen; 1053 | yy[5] = (q2*M34.y - q4*M12.y + q2*q4*(M12.x - M34.x)) * invDen; 1054 | } 1055 | 1056 | xx[6] = (xx[0] + xx[1]) * 0.5f; 1057 | yy[6] = (yy[0] + yy[1]) * 0.5f; 1058 | 1059 | 1060 | // Median 1061 | nth_element(xx, xx + 3, xx + 7); 1062 | nth_element(yy, yy + 3, yy + 7); 1063 | float xc = xx[3]; 1064 | float yc = yy[3]; 1065 | 1066 | return Point2f(xc, yc); 1067 | }; 1068 | 1069 | 1070 | // Verify triplets of arcs with convexity: i=1, j=2, k=4 1071 | void CEllipseDetectorYaed::Triplets124(VVP& pi, 1072 | VVP& pj, 1073 | VVP& pk, 1074 | unordered_map& data, 1075 | vector& ellipses 1076 | ) 1077 | { 1078 | // get arcs length 1079 | ushort sz_i = ushort(pi.size()); 1080 | ushort sz_j = ushort(pj.size()); 1081 | ushort sz_k = ushort(pk.size()); 1082 | 1083 | // For each edge i 1084 | for (ushort i = 0; i < sz_i; ++i) 1085 | { 1086 | VP& edge_i = pi[i]; 1087 | ushort sz_ei = ushort(edge_i.size()); 1088 | 1089 | Point& pif = edge_i[0]; 1090 | Point& pil = edge_i[sz_ei - 1]; 1091 | 1092 | // 1,2 -> reverse 1, swap 1093 | VP rev_i(edge_i.size()); 1094 | reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); 1095 | 1096 | // For each edge j 1097 | for (ushort j = 0; j < sz_j; ++j) 1098 | { 1099 | VP& edge_j = pj[j]; 1100 | ushort sz_ej = ushort(edge_j.size()); 1101 | 1102 | Point& pjf = edge_j[0]; 1103 | Point& pjl = edge_j[sz_ej - 1]; 1104 | 1105 | #ifndef DISCARD_CONSTRAINT_POSITION 1106 | // CONSTRAINTS on position 1107 | if (pjl.x > pif.x + _fThPosition) //is right 1108 | { 1109 | //discard 1110 | continue; 1111 | } 1112 | #endif 1113 | 1114 | uint key_ij = GenerateKey(PAIR_12, i, j); 1115 | 1116 | //for each edge k 1117 | for (ushort k = 0; k < sz_k; ++k) 1118 | { 1119 | VP& edge_k = pk[k]; 1120 | ushort sz_ek = ushort(edge_k.size()); 1121 | 1122 | Point& pkf = edge_k[0]; 1123 | Point& pkl = edge_k[sz_ek - 1]; 1124 | 1125 | #ifndef DISCARD_CONSTRAINT_POSITION 1126 | //CONSTRAINTS on position 1127 | if (pkl.y < pil.y - _fThPosition) 1128 | { 1129 | //discard 1130 | continue; 1131 | } 1132 | #endif 1133 | 1134 | uint key_ik = GenerateKey(PAIR_14, i, k); 1135 | 1136 | // Find centers 1137 | 1138 | EllipseData data_ij, data_ik; 1139 | 1140 | // If the data for the pair i-j have not been computed yet 1141 | if (data.count(key_ij) == 0) 1142 | { 1143 | //1,2 -> reverse 1, swap 1144 | 1145 | // Compute data! 1146 | GetFastCenter(edge_j, rev_i, data_ij); 1147 | // Insert computed data in the hash table 1148 | data.insert(pair(key_ij, data_ij)); 1149 | } 1150 | else 1151 | { 1152 | // Otherwise, just lookup the data in the hash table 1153 | data_ij = data.at(key_ij); 1154 | } 1155 | 1156 | // If the data for the pair i-k have not been computed yet 1157 | if (data.count(key_ik) == 0) 1158 | { 1159 | //1,4 -> ok 1160 | 1161 | // Compute data! 1162 | GetFastCenter(edge_i, edge_k, data_ik); 1163 | // Insert computed data in the hash table 1164 | data.insert(pair(key_ik, data_ik)); 1165 | } 1166 | else 1167 | { 1168 | // Otherwise, just lookup the data in the hash table 1169 | data_ik = data.at(key_ik); 1170 | } 1171 | 1172 | // INVALID CENTERS 1173 | if (!data_ij.isValid || !data_ik.isValid) 1174 | { 1175 | continue; 1176 | } 1177 | 1178 | #ifndef DISCARD_CONSTRAINT_CENTER 1179 | // Selection strategy - Step 3. See Sect [3.2.2] in the paper 1180 | // The computed centers are not close enough 1181 | if (ed2(data_ij.Cab, data_ik.Cab) > _fMaxCenterDistance2) 1182 | { 1183 | //discard 1184 | continue; 1185 | } 1186 | #endif 1187 | // If all constraints of the selection strategy have been satisfied, 1188 | // we can start estimating the ellipse parameters 1189 | 1190 | // Find ellipse parameters 1191 | 1192 | // Get the coordinates of the center (xc, yc) 1193 | Point2f center = GetCenterCoordinates(data_ij, data_ik); 1194 | 1195 | // Find remaining paramters (A,B,rho) 1196 | FindEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); 1197 | } 1198 | } 1199 | } 1200 | }; 1201 | 1202 | 1203 | 1204 | void CEllipseDetectorYaed::Triplets231(VVP& pi, 1205 | VVP& pj, 1206 | VVP& pk, 1207 | unordered_map& data, 1208 | vector& ellipses 1209 | ) 1210 | { 1211 | ushort sz_i = ushort(pi.size()); 1212 | ushort sz_j = ushort(pj.size()); 1213 | ushort sz_k = ushort(pk.size()); 1214 | 1215 | // For each edge i 1216 | for (ushort i = 0; i < sz_i; ++i) 1217 | { 1218 | VP& edge_i = pi[i]; 1219 | ushort sz_ei = ushort(edge_i.size()); 1220 | 1221 | Point& pif = edge_i[0]; 1222 | Point& pil = edge_i[sz_ei - 1]; 1223 | 1224 | VP rev_i(edge_i.size()); 1225 | reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); 1226 | 1227 | // For each edge j 1228 | for (ushort j = 0; j < sz_j; ++j) 1229 | { 1230 | VP& edge_j = pj[j]; 1231 | ushort sz_ej = ushort(edge_j.size()); 1232 | 1233 | Point& pjf = edge_j[0]; 1234 | Point& pjl = edge_j[sz_ej - 1]; 1235 | 1236 | #ifndef DISCARD_CONSTRAINT_POSITION 1237 | // CONSTRAINTS on position 1238 | if (pjf.y < pif.y - _fThPosition) 1239 | { 1240 | //discard 1241 | continue; 1242 | } 1243 | #endif 1244 | 1245 | VP rev_j(edge_j.size()); 1246 | reverse_copy(edge_j.begin(), edge_j.end(), rev_j.begin()); 1247 | 1248 | uint key_ij = GenerateKey(PAIR_23, i, j); 1249 | 1250 | // For each edge k 1251 | for (ushort k = 0; k < sz_k; ++k) 1252 | { 1253 | VP& edge_k = pk[k]; 1254 | ushort sz_ek = ushort(edge_k.size()); 1255 | 1256 | Point& pkf = edge_k[0]; 1257 | Point& pkl = edge_k[sz_ek - 1]; 1258 | 1259 | #ifndef DISCARD_CONSTRAINT_POSITION 1260 | // CONSTRAINTS on position 1261 | if (pkf.x < pil.x - _fThPosition) 1262 | { 1263 | //discard 1264 | continue; 1265 | } 1266 | #endif 1267 | uint key_ik = GenerateKey(PAIR_12, k, i); 1268 | 1269 | // Find centers 1270 | 1271 | EllipseData data_ij, data_ik; 1272 | 1273 | if (data.count(key_ij) == 0) 1274 | { 1275 | // 2,3 -> reverse 2,3 1276 | 1277 | GetFastCenter(rev_i, rev_j, data_ij); 1278 | data.insert(pair(key_ij, data_ij)); 1279 | } 1280 | else 1281 | { 1282 | data_ij = data.at(key_ij); 1283 | } 1284 | 1285 | if (data.count(key_ik) == 0) 1286 | { 1287 | // 2,1 -> reverse 1 1288 | VP rev_k(edge_k.size()); 1289 | reverse_copy(edge_k.begin(), edge_k.end(), rev_k.begin()); 1290 | 1291 | GetFastCenter(edge_i, rev_k, data_ik); 1292 | data.insert(pair(key_ik, data_ik)); 1293 | } 1294 | else 1295 | { 1296 | data_ik = data.at(key_ik); 1297 | } 1298 | 1299 | // INVALID CENTERS 1300 | if (!data_ij.isValid || !data_ik.isValid) 1301 | { 1302 | continue; 1303 | } 1304 | 1305 | #ifndef DISCARD_CONSTRAINT_CENTER 1306 | // CONSTRAINT ON CENTERS 1307 | if (ed2(data_ij.Cab, data_ik.Cab) > _fMaxCenterDistance2) 1308 | { 1309 | //discard 1310 | continue; 1311 | } 1312 | #endif 1313 | // Find ellipse parameters 1314 | Point2f center = GetCenterCoordinates(data_ij, data_ik); 1315 | 1316 | FindEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); 1317 | 1318 | } 1319 | } 1320 | } 1321 | }; 1322 | 1323 | 1324 | void CEllipseDetectorYaed::Triplets342(VVP& pi, 1325 | VVP& pj, 1326 | VVP& pk, 1327 | unordered_map& data, 1328 | vector& ellipses 1329 | ) 1330 | { 1331 | ushort sz_i = ushort(pi.size()); 1332 | ushort sz_j = ushort(pj.size()); 1333 | ushort sz_k = ushort(pk.size()); 1334 | 1335 | // For each edge i 1336 | for (ushort i = 0; i < sz_i; ++i) 1337 | { 1338 | VP& edge_i = pi[i]; 1339 | ushort sz_ei = ushort(edge_i.size()); 1340 | 1341 | Point& pif = edge_i[0]; 1342 | Point& pil = edge_i[sz_ei - 1]; 1343 | 1344 | VP rev_i(edge_i.size()); 1345 | reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); 1346 | 1347 | // For each edge j 1348 | for (ushort j = 0; j < sz_j; ++j) 1349 | { 1350 | VP& edge_j = pj[j]; 1351 | ushort sz_ej = ushort(edge_j.size()); 1352 | 1353 | Point& pjf = edge_j[0]; 1354 | Point& pjl = edge_j[sz_ej - 1]; 1355 | 1356 | #ifndef DISCARD_CONSTRAINT_POSITION 1357 | //CONSTRAINTS on position 1358 | if (pjf.x < pil.x - _fThPosition) //is left 1359 | { 1360 | //discard 1361 | continue; 1362 | } 1363 | #endif 1364 | 1365 | VP rev_j(edge_j.size()); 1366 | reverse_copy(edge_j.begin(), edge_j.end(), rev_j.begin()); 1367 | 1368 | uint key_ij = GenerateKey(PAIR_34, i, j); 1369 | 1370 | // For each edge k 1371 | for (ushort k = 0; k < sz_k; ++k) 1372 | { 1373 | VP& edge_k = pk[k]; 1374 | ushort sz_ek = ushort(edge_k.size()); 1375 | 1376 | Point& pkf = edge_k[0]; 1377 | Point& pkl = edge_k[sz_ek - 1]; 1378 | 1379 | #ifndef DISCARD_CONSTRAINT_POSITION 1380 | //CONSTRAINTS on position 1381 | if (pkf.y > pif.y + _fThPosition) 1382 | { 1383 | //discard 1384 | continue; 1385 | } 1386 | #endif 1387 | uint key_ik = GenerateKey(PAIR_23, k, i); 1388 | 1389 | // Find centers 1390 | 1391 | EllipseData data_ij, data_ik; 1392 | 1393 | if (data.count(key_ij) == 0) 1394 | { 1395 | //3,4 -> reverse 4 1396 | 1397 | GetFastCenter(edge_i, rev_j, data_ij); 1398 | data.insert(pair(key_ij, data_ij)); 1399 | } 1400 | else 1401 | { 1402 | data_ij = data.at(key_ij); 1403 | } 1404 | 1405 | if (data.count(key_ik) == 0) 1406 | { 1407 | //3,2 -> reverse 3,2 1408 | 1409 | VP rev_k(edge_k.size()); 1410 | reverse_copy(edge_k.begin(), edge_k.end(), rev_k.begin()); 1411 | 1412 | GetFastCenter(rev_i, rev_k, data_ik); 1413 | 1414 | data.insert(pair(key_ik, data_ik)); 1415 | } 1416 | else 1417 | { 1418 | data_ik = data.at(key_ik); 1419 | } 1420 | 1421 | 1422 | // INVALID CENTERS 1423 | if (!data_ij.isValid || !data_ik.isValid) 1424 | { 1425 | continue; 1426 | } 1427 | 1428 | #ifndef DISCARD_CONSTRAINT_CENTER 1429 | // CONSTRAINT ON CENTERS 1430 | if (ed2(data_ij.Cab, data_ik.Cab) > _fMaxCenterDistance2) 1431 | { 1432 | //discard 1433 | continue; 1434 | } 1435 | #endif 1436 | // Find ellipse parameters 1437 | Point2f center = GetCenterCoordinates(data_ij, data_ik); 1438 | FindEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); 1439 | } 1440 | } 1441 | 1442 | } 1443 | }; 1444 | 1445 | 1446 | 1447 | void CEllipseDetectorYaed::Triplets413(VVP& pi, 1448 | VVP& pj, 1449 | VVP& pk, 1450 | unordered_map& data, 1451 | vector& ellipses 1452 | ) 1453 | { 1454 | ushort sz_i = ushort(pi.size()); 1455 | ushort sz_j = ushort(pj.size()); 1456 | ushort sz_k = ushort(pk.size()); 1457 | 1458 | // For each edge i 1459 | for (ushort i = 0; i < sz_i; ++i) 1460 | { 1461 | VP& edge_i = pi[i]; 1462 | ushort sz_ei = ushort(edge_i.size()); 1463 | 1464 | Point& pif = edge_i[0]; 1465 | Point& pil = edge_i[sz_ei - 1]; 1466 | 1467 | VP rev_i(edge_i.size()); 1468 | reverse_copy(edge_i.begin(), edge_i.end(), rev_i.begin()); 1469 | 1470 | // For each edge j 1471 | for (ushort j = 0; j < sz_j; ++j) 1472 | { 1473 | VP& edge_j = pj[j]; 1474 | ushort sz_ej = ushort(edge_j.size()); 1475 | 1476 | Point& pjf = edge_j[0]; 1477 | Point& pjl = edge_j[sz_ej - 1]; 1478 | 1479 | #ifndef DISCARD_CONSTRAINT_POSITION 1480 | //CONSTRAINTS on position 1481 | if (pjl.y > pil.y + _fThPosition) //is below 1482 | { 1483 | //discard 1484 | continue; 1485 | } 1486 | #endif 1487 | 1488 | uint key_ij = GenerateKey(PAIR_14, j, i); 1489 | 1490 | // For each edge k 1491 | for (ushort k = 0; k < sz_k; ++k) 1492 | { 1493 | VP& edge_k = pk[k]; 1494 | ushort sz_ek = ushort(edge_k.size()); 1495 | 1496 | Point& pkf = edge_k[0]; 1497 | Point& pkl = edge_k[sz_ek - 1]; 1498 | 1499 | #ifndef DISCARD_CONSTRAINT_POSITION 1500 | //CONSTRAINTS on position 1501 | if (pkl.x > pif.x + _fThPosition) 1502 | { 1503 | //discard 1504 | continue; 1505 | } 1506 | #endif 1507 | uint key_ik = GenerateKey(PAIR_34, k, i); 1508 | 1509 | // Find centers 1510 | 1511 | EllipseData data_ij, data_ik; 1512 | 1513 | if (data.count(key_ij) == 0) 1514 | { 1515 | // 4,1 -> OK 1516 | GetFastCenter(edge_i, edge_j, data_ij); 1517 | data.insert(pair(key_ij, data_ij)); 1518 | } 1519 | else 1520 | { 1521 | data_ij = data.at(key_ij); 1522 | } 1523 | 1524 | if (data.count(key_ik) == 0) 1525 | { 1526 | // 4,3 -> reverse 4 1527 | GetFastCenter(rev_i, edge_k, data_ik); 1528 | data.insert(pair(key_ik, data_ik)); 1529 | } 1530 | else 1531 | { 1532 | data_ik = data.at(key_ik); 1533 | } 1534 | 1535 | // INVALID CENTERS 1536 | if (!data_ij.isValid || !data_ik.isValid) 1537 | { 1538 | continue; 1539 | } 1540 | 1541 | #ifndef DISCARD_CONSTRAINT_CENTER 1542 | // CONSTRAIN ON CENTERS 1543 | if (ed2(data_ij.Cab, data_ik.Cab) > _fMaxCenterDistance2) 1544 | { 1545 | //discard 1546 | continue; 1547 | } 1548 | #endif 1549 | // Find ellipse parameters 1550 | Point2f center = GetCenterCoordinates(data_ij, data_ik); 1551 | 1552 | FindEllipses(center, edge_i, edge_j, edge_k, data_ij, data_ik, ellipses); 1553 | 1554 | } 1555 | } 1556 | } 1557 | }; 1558 | 1559 | 1560 | void CEllipseDetectorYaed::RemoveShortEdges(Mat1b& edges, Mat1b& clean) 1561 | { 1562 | VVP contours; 1563 | 1564 | // Labeling and contraints on length 1565 | Labeling(edges, contours, _iMinEdgeLength); 1566 | 1567 | int iContoursSize = contours.size(); 1568 | for (int i = 0; i < iContoursSize; ++i) 1569 | { 1570 | VP& edge = contours[i]; 1571 | unsigned szEdge = edge.size(); 1572 | 1573 | // Constraint on axes aspect ratio 1574 | RotatedRect oriented = minAreaRect(edge); 1575 | if (oriented.size.width < _fMinOrientedRectSide || 1576 | oriented.size.height < _fMinOrientedRectSide || 1577 | oriented.size.width > oriented.size.height * _fMaxRectAxesRatio || 1578 | oriented.size.height > oriented.size.width * _fMaxRectAxesRatio) 1579 | { 1580 | continue; 1581 | } 1582 | 1583 | for (unsigned j = 0; j < szEdge; ++j) 1584 | { 1585 | clean(edge[j]) = (uchar)255; 1586 | } 1587 | } 1588 | } 1589 | 1590 | 1591 | 1592 | void CEllipseDetectorYaed::PrePeocessing(Mat1b& I, 1593 | Mat1b& DP, 1594 | Mat1b& DN 1595 | ) 1596 | { 1597 | 1598 | Tic(0); //edge detection 1599 | 1600 | // Smooth image 1601 | GaussianBlur(I, I, _szPreProcessingGaussKernelSize, _dPreProcessingGaussSigma); 1602 | 1603 | // Temp variables 1604 | Mat1b E; //edge mask 1605 | Mat1s DX, DY; //sobel derivatives 1606 | 1607 | // Detect edges 1608 | Canny3(I, E, DX, DY, 3, false); 1609 | 1610 | Toc(0); //edge detection 1611 | 1612 | Tac(1); //preprocessing 1613 | 1614 | // For each edge points, compute the edge direction 1615 | for (int i = 0; i<_szImg.height; ++i) 1616 | { 1617 | short* _dx = DX.ptr(i); 1618 | short* _dy = DY.ptr(i); 1619 | uchar* _e = E.ptr(i); 1620 | uchar* _dp = DP.ptr(i); 1621 | uchar* _dn = DN.ptr(i); 1622 | 1623 | for (int j = 0; j<_szImg.width; ++j) 1624 | { 1625 | if (!((_e[j] <= 0) || (_dx[j] == 0) || (_dy[j] == 0))) 1626 | { 1627 | // Angle of the tangent 1628 | float phi = -(float(_dx[j]) / float(_dy[j])); 1629 | 1630 | // Along positive or negative diagonal 1631 | if (phi > 0) _dp[j] = (uchar)255; 1632 | else if (phi < 0) _dn[j] = (uchar)255; 1633 | } 1634 | } 1635 | } 1636 | }; 1637 | 1638 | 1639 | void CEllipseDetectorYaed::DetectAfterPreProcessing(vector& ellipses, Mat1b& E, Mat1f& PHI) 1640 | { 1641 | // Set the image size 1642 | _szImg = E.size(); 1643 | 1644 | // Initialize temporary data structures 1645 | Mat1b DP = Mat1b::zeros(_szImg); // arcs along positive diagonal 1646 | Mat1b DN = Mat1b::zeros(_szImg); // arcs along negative diagonal 1647 | 1648 | // For each edge points, compute the edge direction 1649 | for (int i = 0; i<_szImg.height; ++i) 1650 | { 1651 | float* _phi = PHI.ptr(i); 1652 | uchar* _e = E.ptr(i); 1653 | uchar* _dp = DP.ptr(i); 1654 | uchar* _dn = DN.ptr(i); 1655 | 1656 | for (int j = 0; j<_szImg.width; ++j) 1657 | { 1658 | if ((_e[j] > 0) && (_phi[j] != 0)) 1659 | { 1660 | // Angle 1661 | 1662 | // along positive or negative diagonal 1663 | if (_phi[j] > 0) _dp[j] = (uchar)255; 1664 | else if (_phi[j] < 0) _dn[j] = (uchar)255; 1665 | } 1666 | } 1667 | } 1668 | 1669 | // Initialize accumulator dimensions 1670 | ACC_N_SIZE = 101; 1671 | ACC_R_SIZE = 180; 1672 | ACC_A_SIZE = max(_szImg.height, _szImg.width); 1673 | 1674 | // Allocate accumulators 1675 | accN = new int[ACC_N_SIZE]; 1676 | accR = new int[ACC_R_SIZE]; 1677 | accA = new int[ACC_A_SIZE]; 1678 | 1679 | // Other temporary 1680 | VVP points_1, points_2, points_3, points_4; //vector of points, one for each convexity class 1681 | unordered_map centers; //hash map for reusing already computed EllipseData 1682 | 1683 | // Detect edges and find convexities 1684 | DetectEdges13(DP, points_1, points_3); 1685 | DetectEdges24(DN, points_2, points_4); 1686 | 1687 | // Find triplets 1688 | Triplets124(points_1, points_2, points_4, centers, ellipses); 1689 | Triplets231(points_2, points_3, points_1, centers, ellipses); 1690 | Triplets342(points_3, points_4, points_2, centers, ellipses); 1691 | Triplets413(points_4, points_1, points_3, centers, ellipses); 1692 | 1693 | // Sort detected ellipses with respect to score 1694 | sort(ellipses.begin(), ellipses.end()); 1695 | 1696 | //free accumulator memory 1697 | delete[] accN; 1698 | delete[] accR; 1699 | delete[] accA; 1700 | 1701 | //cluster detections 1702 | //ClusterEllipses(ellipses); 1703 | }; 1704 | 1705 | 1706 | void CEllipseDetectorYaed::Detect(Mat1b& I, vector& ellipses) 1707 | { 1708 | Tic(1); //prepare data structure 1709 | 1710 | // Set the image size 1711 | _szImg = I.size(); 1712 | 1713 | // Initialize temporary data structures 1714 | Mat1b DP = Mat1b::zeros(_szImg); // arcs along positive diagonal 1715 | Mat1b DN = Mat1b::zeros(_szImg); // arcs along negative diagonal 1716 | 1717 | // Initialize accumulator dimensions 1718 | ACC_N_SIZE = 101; 1719 | ACC_R_SIZE = 180; 1720 | ACC_A_SIZE = max(_szImg.height, _szImg.width); 1721 | 1722 | // Allocate accumulators 1723 | accN = new int[ACC_N_SIZE]; 1724 | accR = new int[ACC_R_SIZE]; 1725 | accA = new int[ACC_A_SIZE]; 1726 | 1727 | // Other temporary 1728 | VVP points_1, points_2, points_3, points_4; //vector of points, one for each convexity class 1729 | unordered_map centers; //hash map for reusing already computed EllipseData 1730 | 1731 | Toc(1); //prepare data structure 1732 | 1733 | // Preprocessing 1734 | // From input image I, find edge point with coarse convexity along positive (DP) or negative (DN) diagonal 1735 | PrePeocessing(I, DP, DN); 1736 | 1737 | // Detect edges and find convexities 1738 | DetectEdges13(DP, points_1, points_3); 1739 | DetectEdges24(DN, points_2, points_4); 1740 | 1741 | Toc(1); //preprocessing 1742 | 1743 | 1744 | // DEBUG 1745 | Mat3b out(I.rows, I.cols, Vec3b(0,0,0)); 1746 | for(unsigned i=0; i& ellipses) 1813 | { 1814 | float th_Da = 0.1f; 1815 | float th_Db = 0.1f; 1816 | float th_Dr = 0.1f; 1817 | 1818 | float th_Dc_ratio = 0.1f; 1819 | float th_Dr_circle = 0.9f; 1820 | 1821 | int iNumOfEllipses = int(ellipses.size()); 1822 | if (iNumOfEllipses == 0) return; 1823 | 1824 | // The first ellipse is assigned to a cluster 1825 | vector clusters; 1826 | clusters.push_back(ellipses[0]); 1827 | 1828 | bool bFoundCluster = false; 1829 | 1830 | for (int i = 1; i th_Dc) 1851 | { 1852 | //not same cluster 1853 | continue; 1854 | } 1855 | 1856 | // a 1857 | float Da = abs(e1._a - e2._a) / max(e1._a, e2._a); 1858 | if (Da > th_Da) 1859 | { 1860 | //not same cluster 1861 | continue; 1862 | } 1863 | 1864 | // b 1865 | float Db = abs(e1._b - e2._b) / min(e1._b, e2._b); 1866 | if (Db > th_Db) 1867 | { 1868 | //not same cluster 1869 | continue; 1870 | } 1871 | 1872 | // angle 1873 | float Dr = GetMinAnglePI(e1._rad, e2._rad) / float(CV_PI); 1874 | if ((Dr > th_Dr) && (ba_e1 < th_Dr_circle) && (ba_e2 < th_Dr_circle)) 1875 | { 1876 | //not same cluster 1877 | continue; 1878 | } 1879 | 1880 | // Same cluster as e2 1881 | bFoundCluster = true; 1882 | // Discard, no need to create a new cluster 1883 | break; 1884 | } 1885 | 1886 | if (!bFoundCluster) 1887 | { 1888 | // Create a new cluster 1889 | clusters.push_back(e1); 1890 | } 1891 | } 1892 | 1893 | clusters.swap(ellipses); 1894 | }; 1895 | 1896 | 1897 | 1898 | //Draw at most iTopN detected ellipses. 1899 | void CEllipseDetectorYaed::DrawDetectedEllipses(Mat3b& output, vector& ellipses, int iTopN, int thickness) 1900 | { 1901 | int sz_ell = int(ellipses.size()); 1902 | int n = (iTopN == 0) ? sz_ell : min(iTopN, sz_ell); 1903 | for (int i = 0; i < n; ++i) 1904 | { 1905 | Ellipse& e = ellipses[n - i - 1]; 1906 | int g = cvRound(e._score * 255.f); 1907 | Scalar color(0, g, 0); 1908 | ellipse(output, Point(cvRound(e._xc), cvRound(e._yc)), Size(cvRound(e._a), cvRound(e._b)), e._rad*180.0 / CV_PI, 0.0, 360.0, color, thickness); 1909 | } 1910 | } 1911 | } -------------------------------------------------------------------------------- /libellipsedetect/EllipseDetectorYaed.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is intended for academic use only. 3 | You are free to use and modify the code, at your own risk. 4 | 5 | If you use this code, or find it useful, please refer to the paper: 6 | 7 | Michele Fornaciari, Andrea Prati, Rita Cucchiara, 8 | A fast and effective ellipse detector for embedded vision applications 9 | Pattern Recognition, Volume 47, Issue 11, November 2014, Pages 3693-3708, ISSN 0031-3203, 10 | http://dx.doi.org/10.1016/j.patcog.2014.05.012. 11 | (http://www.sciencedirect.com/science/article/pii/S0031320314001976) 12 | 13 | 14 | The comments in the code refer to the abovementioned paper. 15 | If you need further details about the code or the algorithm, please contact me at: 16 | 17 | michele.fornaciari@unimore.it 18 | 19 | last update: 23/12/2014 20 | */ 21 | 22 | /* 23 | 24 | This class implements a very fast ellipse detector, codename: YAED (Yet Another Ellipse Detector) 25 | 26 | */ 27 | 28 | #pragma once 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | //#include "Ellipse.h" 41 | #include "common.h" 42 | #include 43 | 44 | using namespace std; 45 | using namespace cv; 46 | 47 | //#define DISCARD_CONSTRAINT_OBOX 48 | //#define DISCARD_CONSTRAINT_CONVEXITY 49 | //#define DISCARD_CONSTRAINT_POSITION 50 | //#define DISCARD_CONSTRAINT_CENTER 51 | 52 | 53 | // Data available after selection strategy. 54 | // They are kept in an associative array to: 55 | // 1) avoid recomputing data when starting from same arcs 56 | // 2) be reused in firther proprecessing 57 | // See Sect [] in the paper 58 | 59 | 60 | namespace Yaed 61 | { 62 | struct EllipseData 63 | { 64 | bool isValid; 65 | float ta; 66 | float tb; 67 | float ra; 68 | float rb; 69 | Point2f Ma; 70 | Point2f Mb; 71 | Point2f Cab; 72 | vector Sa; 73 | vector Sb; 74 | }; 75 | 76 | class CEllipseDetectorYaed 77 | { 78 | // Parameters 79 | 80 | // Preprocessing - Gaussian filter. See Sect [] in the paper 81 | cv::Size _szPreProcessingGaussKernelSize; // size of the Gaussian filter in preprocessing step 82 | double _dPreProcessingGaussSigma; // sigma of the Gaussian filter in the preprocessing step 83 | 84 | 85 | 86 | // Selection strategy - Step 1 - Discard noisy or straight arcs. See Sect [] in the paper 87 | int _iMinEdgeLength; // minimum edge size 88 | float _fMinOrientedRectSide; // minumum size of the oriented bounding box containing the arc 89 | float _fMaxRectAxesRatio; // maximum aspect ratio of the oriented bounding box containing the arc 90 | 91 | // Selection strategy - Step 2 - Remove according to mutual convexities. See Sect [] in the paper 92 | float _fThPosition; 93 | 94 | // Selection Strategy - Step 3 - Number of points considered for slope estimation when estimating the center. See Sect [] in the paper 95 | unsigned _uNs; // Find at most Ns parallel chords. 96 | 97 | // Selection strategy - Step 3 - Discard pairs of arcs if their estimated center is not close enough. See Sect [] in the paper 98 | float _fMaxCenterDistance; // maximum distance in pixel between 2 center points 99 | float _fMaxCenterDistance2; // _fMaxCenterDistance * _fMaxCenterDistance 100 | 101 | // Validation - Points within a this threshold are considered to lie on the ellipse contour. See Sect [] in the paper 102 | float _fDistanceToEllipseContour; // maximum distance between a point and the contour. See equation [] in the paper 103 | 104 | // Validation - Assign a score. See Sect [] in the paper 105 | float _fMinScore; // minimum score to confirm a detection 106 | float _fMinReliability; // minimum auxiliary score to confirm a detection 107 | 108 | 109 | // auxiliary variables 110 | cv::Size _szImg; // input image size 111 | vector _timesHelper; 112 | vector _times; // _times is a vector containing the execution time of each step. 113 | // _times[0] : time for edge detection 114 | // _times[1] : time for pre processing 115 | // _times[2] : time for grouping 116 | // _times[3] : time for estimation 117 | // _times[4] : time for validation 118 | // _times[5] : time for clustering 119 | 120 | int ACC_N_SIZE; // size of accumulator N = B/A 121 | int ACC_R_SIZE; // size of accumulator R = rho = atan(K) 122 | int ACC_A_SIZE; // size of accumulator A 123 | 124 | int* accN; // pointer to accumulator N 125 | int* accR; // pointer to accumulator R 126 | int* accA; // pointer to accumulator A 127 | 128 | public: 129 | 130 | //Constructor and Destructor 131 | CEllipseDetectorYaed(void); 132 | ~CEllipseDetectorYaed(void); 133 | 134 | void DetectAfterPreProcessing(vector& ellipses, Mat1b& E, Mat1f& PHI=Mat1f()); 135 | 136 | //Detect the ellipses in the gray image 137 | void Detect(Mat1b& gray, vector& ellipses); 138 | 139 | //Draw the first iTopN ellipses on output 140 | void DrawDetectedEllipses(Mat3b& output, vector& ellipses, int iTopN=0, int thickness=2); 141 | 142 | //Set the parameters of the detector 143 | void SetParameters ( cv::Size szPreProcessingGaussKernelSize, 144 | double dPreProcessingGaussSigma, 145 | float fThPosition, 146 | float fMaxCenterDistance, 147 | int iMinEdgeLength, 148 | float fMinOrientedRectSide, 149 | float fDistanceToEllipseContour, 150 | float fMinScore, 151 | float fMinReliability, 152 | int iNs 153 | ); 154 | 155 | // Return the execution time 156 | double GetExecTime() { return _times[0] + _times[1] + _times[2] + _times[3] + _times[4] + _times[5]; } 157 | vector GetTimes() { return _times; } 158 | 159 | private: 160 | 161 | //keys for hash table 162 | static const ushort PAIR_12 = 0x00; 163 | static const ushort PAIR_23 = 0x01; 164 | static const ushort PAIR_34 = 0x02; 165 | static const ushort PAIR_14 = 0x03; 166 | 167 | //generate keys from pair and indicse 168 | uint inline GenerateKey(uchar pair, ushort u, ushort v); 169 | 170 | void PrePeocessing(Mat1b& I, Mat1b& DP, Mat1b& DN); 171 | 172 | void RemoveShortEdges(Mat1b& edges, Mat1b& clean); 173 | 174 | void ClusterEllipses(vector& ellipses); 175 | 176 | int FindMaxK(const vector& v) const; 177 | int FindMaxN(const vector& v) const; 178 | int FindMaxA(const vector& v) const; 179 | 180 | int FindMaxK(const int* v) const; 181 | int FindMaxN(const int* v) const; 182 | int FindMaxA(const int* v) const; 183 | 184 | float GetMedianSlope(vector& med, Point2f& M, vector& slopes); 185 | void GetFastCenter (vector& e1, vector& e2, EllipseData& data); 186 | 187 | 188 | void DetectEdges13(Mat1b& DP, VVP& points_1, VVP& points_3); 189 | void DetectEdges24(Mat1b& DN, VVP& points_2, VVP& points_4); 190 | 191 | void FindEllipses ( Point2f& center, 192 | VP& edge_i, 193 | VP& edge_j, 194 | VP& edge_k, 195 | EllipseData& data_ij, 196 | EllipseData& data_ik, 197 | vector& ellipses 198 | ); 199 | 200 | Point2f GetCenterCoordinates(EllipseData& data_ij, EllipseData& data_ik); 201 | Point2f _GetCenterCoordinates(EllipseData& data_ij, EllipseData& data_ik); 202 | 203 | 204 | 205 | void Triplets124 ( VVP& pi, 206 | VVP& pj, 207 | VVP& pk, 208 | unordered_map& data, 209 | vector& ellipses 210 | ); 211 | 212 | void Triplets231 ( VVP& pi, 213 | VVP& pj, 214 | VVP& pk, 215 | unordered_map& data, 216 | vector& ellipses 217 | ); 218 | 219 | void Triplets342 ( VVP& pi, 220 | VVP& pj, 221 | VVP& pk, 222 | unordered_map& data, 223 | vector& ellipses 224 | ); 225 | 226 | void Triplets413 ( VVP& pi, 227 | VVP& pj, 228 | VVP& pk, 229 | unordered_map& data, 230 | vector& ellipses 231 | ); 232 | 233 | void Tic(unsigned idx) //start 234 | { 235 | _timesHelper[idx] = 0.0; 236 | _times[idx] = (double)cv::getTickCount(); 237 | }; 238 | 239 | void Tac(unsigned idx) //restart 240 | { 241 | _timesHelper[idx] = _times[idx]; 242 | _times[idx] = (double)cv::getTickCount(); 243 | }; 244 | 245 | void Toc(unsigned idx) //stop 246 | { 247 | _times[idx] = ((double)cv::getTickCount() - _times[idx])*1000. / cv::getTickFrequency(); 248 | _times[idx] += _timesHelper[idx]; 249 | }; 250 | 251 | 252 | 253 | 254 | }; 255 | } 256 | -------------------------------------------------------------------------------- /libellipsedetect/ReadMe.txt: -------------------------------------------------------------------------------- 1 | ======================================================================== 2 | STATIC LIBRARY : libellipsedetect Project Overview 3 | ======================================================================== 4 | 5 | This code is intended for academic use only. 6 | You are free to use and modify the code, at your own risk. 7 | 8 | If you use this code, or find it useful, please refer to the paper: 9 | 10 | Michele Fornaciari, Andrea Prati, Rita Cucchiara, 11 | A fast and effective ellipse detector for embedded vision applications 12 | Pattern Recognition, Volume 47, Issue 11, November 2014, Pages 3693-3708, ISSN 0031-3203, 13 | http://dx.doi.org/10.1016/j.patcog.2014.05.012. 14 | (http://www.sciencedirect.com/science/article/pii/S0031320314001976) 15 | 16 | 17 | The comments in the code refer to the abovementioned paper. 18 | If you need further details about the code or the algorithm, please contact me at: 19 | 20 | michele.fornaciari@unimore.it 21 | 22 | last update: 23/12/2014 23 | 24 | 25 | ///////////////////////////////////////////////////////////////////////////// 26 | Other notes: 27 | This library is based on above article and source code provided by the authors 28 | Michele Fornaciari, Andrea Prati, Rita Cucchiara 29 | 30 | ///////////////////////////////////////////////////////////////////////////// 31 | -------------------------------------------------------------------------------- /libellipsedetect/common.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This code is intended for academic use only. 3 | You are free to use and modify the code, at your own risk. 4 | 5 | If you use this code, or find it useful, please refer to the paper: 6 | 7 | Michele Fornaciari, Andrea Prati, Rita Cucchiara, 8 | A fast and effective ellipse detector for embedded vision applications 9 | Pattern Recognition, Volume 47, Issue 11, November 2014, Pages 3693-3708, ISSN 0031-3203, 10 | http://dx.doi.org/10.1016/j.patcog.2014.05.012. 11 | (http://www.sciencedirect.com/science/article/pii/S0031320314001976) 12 | 13 | 14 | The comments in the code refer to the abovementioned paper. 15 | If you need further details about the code or the algorithm, please contact me at: 16 | 17 | michele.fornaciari@unimore.it 18 | 19 | last update: 23/12/2014 20 | */ 21 | 22 | #include "common.h" 23 | 24 | namespace Yaed 25 | { 26 | void cvCanny2( const void* srcarr, void* dstarr, 27 | double low_thresh, double high_thresh, 28 | void* dxarr, void* dyarr, 29 | int aperture_size ) 30 | { 31 | //cv::Ptr dx, dy; 32 | cv::AutoBuffer buffer; 33 | std::vector stack; 34 | uchar **stack_top = 0, **stack_bottom = 0; 35 | 36 | CvMat srcstub, *src = cvGetMat( srcarr, &srcstub ); 37 | CvMat dststub, *dst = cvGetMat( dstarr, &dststub ); 38 | 39 | CvMat dxstub, *dx = cvGetMat( dxarr, &dxstub ); 40 | CvMat dystub, *dy = cvGetMat( dyarr, &dystub ); 41 | 42 | 43 | CvSize size; 44 | int flags = aperture_size; 45 | int low, high; 46 | int* mag_buf[3]; 47 | uchar* map; 48 | ptrdiff_t mapstep; 49 | int maxsize; 50 | int i, j; 51 | CvMat mag_row; 52 | 53 | if( CV_MAT_TYPE( src->type ) != CV_8UC1 || 54 | CV_MAT_TYPE( dst->type ) != CV_8UC1 || 55 | CV_MAT_TYPE( dx->type ) != CV_16SC1 || 56 | CV_MAT_TYPE( dy->type ) != CV_16SC1 ) 57 | CV_Error( CV_StsUnsupportedFormat, "" ); 58 | 59 | if( !CV_ARE_SIZES_EQ( src, dst )) 60 | CV_Error( CV_StsUnmatchedSizes, "" ); 61 | 62 | if( low_thresh > high_thresh ) 63 | { 64 | double t; 65 | CV_SWAP( low_thresh, high_thresh, t ); 66 | } 67 | 68 | aperture_size &= INT_MAX; 69 | if( (aperture_size & 1) == 0 || aperture_size < 3 || aperture_size > 7 ) 70 | CV_Error( CV_StsBadFlag, "" ); 71 | 72 | size.width = src->cols; 73 | size.height = src->rows; 74 | 75 | //size = cvGetMatSize( src ); 76 | 77 | //dx = cvCreateMat( size.height, size.width, CV_16SC1 ); 78 | //dy = cvCreateMat( size.height, size.width, CV_16SC1 ); 79 | 80 | //aperture_size = -1; //SCHARR 81 | cvSobel( src, dx, 1, 0, aperture_size ); 82 | cvSobel( src, dy, 0, 1, aperture_size ); 83 | 84 | //Mat ddx(dx,true); 85 | //Mat ddy(dy,true); 86 | 87 | 88 | if( flags & CV_CANNY_L2_GRADIENT ) 89 | { 90 | Cv32suf ul, uh; 91 | ul.f = (float)low_thresh; 92 | uh.f = (float)high_thresh; 93 | 94 | low = ul.i; 95 | high = uh.i; 96 | } 97 | else 98 | { 99 | low = cvFloor( low_thresh ); 100 | high = cvFloor( high_thresh ); 101 | } 102 | 103 | buffer.allocate( (size.width+2)*(size.height+2) + (size.width+2)*3*sizeof(int) ); 104 | 105 | mag_buf[0] = (int*)(char*)buffer; 106 | mag_buf[1] = mag_buf[0] + size.width + 2; 107 | mag_buf[2] = mag_buf[1] + size.width + 2; 108 | map = (uchar*)(mag_buf[2] + size.width + 2); 109 | mapstep = size.width + 2; 110 | 111 | maxsize = MAX( 1 << 10, size.width*size.height/10 ); 112 | stack.resize( maxsize ); 113 | stack_top = stack_bottom = &stack[0]; 114 | 115 | memset( mag_buf[0], 0, (size.width+2)*sizeof(int) ); 116 | memset( map, 1, mapstep ); 117 | memset( map + mapstep*(size.height + 1), 1, mapstep ); 118 | 119 | /* sector numbers 120 | (Top-Left Origin) 121 | 122 | 1 2 3 123 | * * * 124 | * * * 125 | 0*******0 126 | * * * 127 | * * * 128 | 3 2 1 129 | */ 130 | 131 | #define CANNY_PUSH(d) *(d) = (uchar)2, *stack_top++ = (d) 132 | #define CANNY_POP(d) (d) = *--stack_top 133 | 134 | mag_row = cvMat( 1, size.width, CV_32F ); 135 | 136 | // calculate magnitude and angle of gradient, perform non-maxima supression. 137 | // fill the map with one of the following values: 138 | // 0 - the pixel might belong to an edge 139 | // 1 - the pixel can not belong to an edge 140 | // 2 - the pixel does belong to an edge 141 | for( i = 0; i <= size.height; i++ ) 142 | { 143 | int* _mag = mag_buf[(i > 0) + 1] + 1; 144 | float* _magf = (float*)_mag; 145 | const short* _dx = (short*)(dx->data.ptr + dx->step*i); 146 | const short* _dy = (short*)(dy->data.ptr + dy->step*i); 147 | uchar* _map; 148 | int x, y; 149 | ptrdiff_t magstep1, magstep2; 150 | int prev_flag = 0; 151 | 152 | if( i < size.height ) 153 | { 154 | _mag[-1] = _mag[size.width] = 0; 155 | 156 | if( !(flags & CV_CANNY_L2_GRADIENT) ) 157 | for( j = 0; j < size.width; j++ ) 158 | _mag[j] = abs(_dx[j]) + abs(_dy[j]); 159 | 160 | else 161 | { 162 | for( j = 0; j < size.width; j++ ) 163 | { 164 | x = _dx[j]; y = _dy[j]; 165 | _magf[j] = (float)std::sqrt((double)x*x + (double)y*y); 166 | } 167 | } 168 | } 169 | else 170 | memset( _mag-1, 0, (size.width + 2)*sizeof(int) ); 171 | 172 | // at the very beginning we do not have a complete ring 173 | // buffer of 3 magnitude rows for non-maxima suppression 174 | if( i == 0 ) 175 | continue; 176 | 177 | _map = map + mapstep*i + 1; 178 | _map[-1] = _map[size.width] = 1; 179 | 180 | _mag = mag_buf[1] + 1; // take the central row 181 | _dx = (short*)(dx->data.ptr + dx->step*(i-1)); 182 | _dy = (short*)(dy->data.ptr + dy->step*(i-1)); 183 | 184 | magstep1 = mag_buf[2] - mag_buf[1]; 185 | magstep2 = mag_buf[0] - mag_buf[1]; 186 | 187 | if( (stack_top - stack_bottom) + size.width > maxsize ) 188 | { 189 | int sz = (int)(stack_top - stack_bottom); 190 | maxsize = MAX( maxsize * 3/2, maxsize + 8 ); 191 | stack.resize(maxsize); 192 | stack_bottom = &stack[0]; 193 | stack_top = stack_bottom + sz; 194 | } 195 | 196 | for( j = 0; j < size.width; j++ ) 197 | { 198 | #define CANNY_SHIFT 15 199 | #define TG22 (int)(0.4142135623730950488016887242097*(1< low ) 209 | { 210 | int tg22x = x * TG22; 211 | int tg67x = tg22x + ((x + x) << CANNY_SHIFT); 212 | 213 | y <<= CANNY_SHIFT; 214 | 215 | if( y < tg22x ) 216 | { 217 | if( m > _mag[j-1] && m >= _mag[j+1] ) 218 | { 219 | if( m > high && !prev_flag && _map[j-mapstep] != 2 ) 220 | { 221 | CANNY_PUSH( _map + j ); 222 | prev_flag = 1; 223 | } 224 | else 225 | _map[j] = (uchar)0; 226 | continue; 227 | } 228 | } 229 | else if( y > tg67x ) 230 | { 231 | if( m > _mag[j+magstep2] && m >= _mag[j+magstep1] ) 232 | { 233 | if( m > high && !prev_flag && _map[j-mapstep] != 2 ) 234 | { 235 | CANNY_PUSH( _map + j ); 236 | prev_flag = 1; 237 | } 238 | else 239 | _map[j] = (uchar)0; 240 | continue; 241 | } 242 | } 243 | else 244 | { 245 | s = s < 0 ? -1 : 1; 246 | if( m > _mag[j+magstep2-s] && m > _mag[j+magstep1+s] ) 247 | { 248 | if( m > high && !prev_flag && _map[j-mapstep] != 2 ) 249 | { 250 | CANNY_PUSH( _map + j ); 251 | prev_flag = 1; 252 | } 253 | else 254 | _map[j] = (uchar)0; 255 | continue; 256 | } 257 | } 258 | } 259 | prev_flag = 0; 260 | _map[j] = (uchar)1; 261 | } 262 | 263 | // scroll the ring buffer 264 | _mag = mag_buf[0]; 265 | mag_buf[0] = mag_buf[1]; 266 | mag_buf[1] = mag_buf[2]; 267 | mag_buf[2] = _mag; 268 | } 269 | 270 | // now track the edges (hysteresis thresholding) 271 | while( stack_top > stack_bottom ) 272 | { 273 | uchar* m; 274 | if( (stack_top - stack_bottom) + 8 > maxsize ) 275 | { 276 | int sz = (int)(stack_top - stack_bottom); 277 | maxsize = MAX( maxsize * 3/2, maxsize + 8 ); 278 | stack.resize(maxsize); 279 | stack_bottom = &stack[0]; 280 | stack_top = stack_bottom + sz; 281 | } 282 | 283 | CANNY_POP(m); 284 | 285 | if( !m[-1] ) 286 | CANNY_PUSH( m - 1 ); 287 | if( !m[1] ) 288 | CANNY_PUSH( m + 1 ); 289 | if( !m[-mapstep-1] ) 290 | CANNY_PUSH( m - mapstep - 1 ); 291 | if( !m[-mapstep] ) 292 | CANNY_PUSH( m - mapstep ); 293 | if( !m[-mapstep+1] ) 294 | CANNY_PUSH( m - mapstep + 1 ); 295 | if( !m[mapstep-1] ) 296 | CANNY_PUSH( m + mapstep - 1 ); 297 | if( !m[mapstep] ) 298 | CANNY_PUSH( m + mapstep ); 299 | if( !m[mapstep+1] ) 300 | CANNY_PUSH( m + mapstep + 1 ); 301 | } 302 | 303 | // the final pass, form the final image 304 | for( i = 0; i < size.height; i++ ) 305 | { 306 | const uchar* _map = map + mapstep*(i+1) + 1; 307 | uchar* _dst = dst->data.ptr + dst->step*i; 308 | 309 | for( j = 0; j < size.width; j++ ) 310 | { 311 | _dst[j] = (uchar)-(_map[j] >> 1); 312 | } 313 | } 314 | }; 315 | 316 | void Canny2( InputArray image, OutputArray _edges, 317 | OutputArray _sobel_x, OutputArray _sobel_y, 318 | double threshold1, double threshold2, 319 | int apertureSize, bool L2gradient ) 320 | { 321 | Mat src = image.getMat(); 322 | _edges.create(src.size(), CV_8U); 323 | _sobel_x.create(src.size(), CV_16S); 324 | _sobel_y.create(src.size(), CV_16S); 325 | 326 | 327 | CvMat c_src = src, c_dst = _edges.getMat(); 328 | CvMat c_dx = _sobel_x.getMat(); 329 | CvMat c_dy = _sobel_y.getMat(); 330 | 331 | 332 | cvCanny2( &c_src, &c_dst, threshold1, threshold2, 333 | &c_dx, &c_dy, 334 | apertureSize + (L2gradient ? CV_CANNY_L2_GRADIENT : 0)); 335 | }; 336 | 337 | 338 | void Labeling(Mat1b& image, vector >& segments, int iMinLength) 339 | { 340 | #define RG_STACK_SIZE 2048 341 | 342 | // Uso stack globali per velocizzare l'elaborazione (anche a scapito della memoria occupata) 343 | int stack2[RG_STACK_SIZE]; 344 | #define RG_PUSH2(a) (stack2[sp2] = (a) , sp2++) 345 | #define RG_POP2(a) (sp2-- , (a) = stack2[sp2]) 346 | 347 | // Uso stack globali per velocizzare l'elaborazione (anche a scapito della memoria occupata) 348 | Point stack3[RG_STACK_SIZE]; 349 | #define RG_PUSH3(a) (stack3[sp3] = (a) , sp3++) 350 | #define RG_POP3(a) (sp3-- , (a) = stack3[sp3]) 351 | 352 | int i,w,h, iDim; 353 | int x,y; 354 | int x2,y2; 355 | int sp2; // stack pointer 356 | int sp3; 357 | 358 | Mat_ src = image.clone(); 359 | w = src.cols; 360 | h = src.rows; 361 | iDim = w*h; 362 | 363 | Point point; 364 | for (y=0; y0) 378 | {// rg tradizionale 379 | 380 | RG_POP2(i); 381 | x2=i%w; 382 | y2=i/w; 383 | 384 | 385 | 386 | point.x=x2; 387 | point.y=y2; 388 | 389 | if(src(y2,x2)) 390 | { 391 | RG_PUSH3(point); 392 | src(y2,x2) = 0; 393 | } 394 | 395 | // Inserisco i nuovi punti nello stack solo se esistono 396 | // e sono punti da etichettare 397 | 398 | // 4 connessi 399 | // sx 400 | if (x2>0 && (src(y2, x2-1)!=0)) 401 | RG_PUSH2(i-1); 402 | // sotto 403 | if (y2>0 && (src(y2-1, x2)!=0)) 404 | RG_PUSH2(i-w); 405 | // sopra 406 | if (y20 && y2>0 && (src(y2-1,x2-1)!=0)) 414 | RG_PUSH2(i-w-1); 415 | if (x2>0 && y20 && (src(y2-1, x2+1)!=0)) 418 | RG_PUSH2(i-w+1); 419 | if (x2= iMinLength) 425 | { 426 | vector component; 427 | component.reserve(sp3); 428 | 429 | // etichetto il punto 430 | for (i=0; i& bboxes) 445 | { 446 | 447 | #define _RG_STACK_SIZE 10000 448 | 449 | // Uso stack globali per velocizzare l'elaborazione (anche a scapito della memoria occupata) 450 | int stack2[_RG_STACK_SIZE]; 451 | #define _RG_PUSH2(a) (stack2[sp2] = (a) , sp2++) 452 | #define _RG_POP2(a) (sp2-- , (a) = stack2[sp2]) 453 | 454 | // Uso stack globali per velocizzare l'elaborazione (anche a scapito della memoria occupata) 455 | Point stack3[_RG_STACK_SIZE]; 456 | #define _RG_PUSH3(a) (stack3[sp3] = (a) , sp3++) 457 | #define _RG_POP3(a) (sp3-- , (a) = stack3[sp3]) 458 | 459 | int i,w,h, iDim; 460 | int x,y; 461 | int x2,y2; 462 | int sp2; /* stack pointer */ 463 | int sp3; 464 | 465 | Mat_ src = image.clone(); 466 | w = src.cols; 467 | h = src.rows; 468 | iDim = w*h; 469 | 470 | Point point; 471 | for (y=0; y0) 485 | {// rg tradizionale 486 | 487 | _RG_POP2(i); 488 | x2=i%w; 489 | y2=i/w; 490 | 491 | src(y2,x2) = 0; 492 | 493 | point.x=x2; 494 | point.y=y2; 495 | _RG_PUSH3(point); 496 | 497 | // Inserisco i nuovi punti nello stack solo se esistono 498 | // e sono punti da etichettare 499 | 500 | // 4 connessi 501 | // sx 502 | if (x2>0 && (src(y2, x2-1)!=0)) 503 | _RG_PUSH2(i-1); 504 | // sotto 505 | if (y2>0 && (src(y2-1, x2)!=0)) 506 | _RG_PUSH2(i-w); 507 | // sopra 508 | if (y20 && y2>0 && (src(y2-1,x2-1)!=0)) 516 | _RG_PUSH2(i-w-1); 517 | if (x2>0 && y20 && (src(y2-1, x2+1)!=0)) 520 | _RG_PUSH2(i-w+1); 521 | if (x2= iMinLength) 527 | { 528 | vector component; 529 | 530 | int iMinx, iMaxx, iMiny,iMaxy; 531 | iMinx = iMaxx = stack3[0].x; 532 | iMiny = iMaxy = stack3[0].y; 533 | 534 | // etichetto il punto 535 | for (i=0; i point.x) iMinx = point.x; 541 | if (iMiny > point.y) iMiny = point.y; 542 | if (iMaxx < point.x) iMaxx = point.x; 543 | if (iMaxy < point.y) iMaxy = point.y; 544 | } 545 | 546 | bboxes.push_back(Rect(Point(iMinx, iMiny), Point(iMaxx+1, iMaxy+1))); 547 | segments.push_back(component); 548 | 549 | } 550 | } 551 | } 552 | } 553 | } 554 | 555 | 556 | 557 | // Thinning Zhang e Suen 558 | void Thinning(Mat1b& imgMask, uchar byF, uchar byB) 559 | { 560 | int r = imgMask.rows; 561 | int c = imgMask.cols; 562 | 563 | Mat_ imgIT(r,c),imgM(r,c); 564 | 565 | for(int i=0; i rhs.y; 992 | } 993 | return lhs.x < rhs.x; 994 | }; 995 | 996 | bool SortBottomLeft2TopRight2f(const Point2f& lhs, const Point2f& rhs) 997 | { 998 | if(lhs.x == rhs.x) 999 | { 1000 | return lhs.y > rhs.y; 1001 | } 1002 | return lhs.x < rhs.x; 1003 | }; 1004 | 1005 | 1006 | bool SortTopLeft2BottomRight(const Point& lhs, const Point& rhs) 1007 | { 1008 | if(lhs.x == rhs.x) 1009 | { 1010 | return lhs.y < rhs.y; 1011 | } 1012 | return lhs.x < rhs.x; 1013 | }; 1014 | 1015 | 1016 | void cvCanny3( const void* srcarr, void* dstarr, 1017 | void* dxarr, void* dyarr, 1018 | int aperture_size ) 1019 | { 1020 | //cv::Ptr dx, dy; 1021 | cv::AutoBuffer buffer; 1022 | std::vector stack; 1023 | uchar **stack_top = 0, **stack_bottom = 0; 1024 | 1025 | CvMat srcstub, *src = cvGetMat( srcarr, &srcstub ); 1026 | CvMat dststub, *dst = cvGetMat( dstarr, &dststub ); 1027 | 1028 | CvMat dxstub, *dx = cvGetMat( dxarr, &dxstub ); 1029 | CvMat dystub, *dy = cvGetMat( dyarr, &dystub ); 1030 | 1031 | 1032 | CvSize size; 1033 | int flags = aperture_size; 1034 | int low, high; 1035 | int* mag_buf[3]; 1036 | uchar* map; 1037 | ptrdiff_t mapstep; 1038 | int maxsize; 1039 | int i, j; 1040 | CvMat mag_row; 1041 | 1042 | if( CV_MAT_TYPE( src->type ) != CV_8UC1 || 1043 | CV_MAT_TYPE( dst->type ) != CV_8UC1 || 1044 | CV_MAT_TYPE( dx->type ) != CV_16SC1 || 1045 | CV_MAT_TYPE( dy->type ) != CV_16SC1 ) 1046 | CV_Error( CV_StsUnsupportedFormat, "" ); 1047 | 1048 | if( !CV_ARE_SIZES_EQ( src, dst )) 1049 | CV_Error( CV_StsUnmatchedSizes, "" ); 1050 | 1051 | aperture_size &= INT_MAX; 1052 | if( (aperture_size & 1) == 0 || aperture_size < 3 || aperture_size > 7 ) 1053 | CV_Error( CV_StsBadFlag, "" ); 1054 | 1055 | 1056 | size.width = src->cols; 1057 | size.height = src->rows; 1058 | 1059 | //aperture_size = -1; //SCHARR 1060 | cvSobel( src, dx, 1, 0, aperture_size ); 1061 | cvSobel( src, dy, 0, 1, aperture_size ); 1062 | 1063 | Mat1f magGrad(size.height, size.width, 0.f); 1064 | float maxGrad(0); 1065 | float val(0); 1066 | for(i=0; i(i); 1069 | const short* _dx = (short*)(dx->data.ptr + dx->step*i); 1070 | const short* _dy = (short*)(dy->data.ptr + dy->step*i); 1071 | for(j=0; j maxGrad) ? val : maxGrad; 1076 | } 1077 | } 1078 | 1079 | //% Normalize for threshold selection 1080 | //normalize(magGrad, magGrad, 0.0, 1.0, NORM_MINMAX); 1081 | 1082 | //% Determine Hysteresis Thresholds 1083 | 1084 | //set magic numbers 1085 | const int NUM_BINS = 64; 1086 | const double percent_of_pixels_not_edges = 0.9; 1087 | const double threshold_ratio = 0.3; 1088 | 1089 | //compute histogram 1090 | int bin_size = cvFloor(maxGrad / float(NUM_BINS) + 0.5f) + 1; 1091 | if (bin_size < 1) bin_size = 1; 1092 | int bins[NUM_BINS] = { 0 }; 1093 | for (i=0; i(i); 1096 | for(j=0; j 0) + 1] + 1; 1175 | float* _magf = (float*)_mag; 1176 | const short* _dx = (short*)(dx->data.ptr + dx->step*i); 1177 | const short* _dy = (short*)(dy->data.ptr + dy->step*i); 1178 | uchar* _map; 1179 | int x, y; 1180 | ptrdiff_t magstep1, magstep2; 1181 | int prev_flag = 0; 1182 | 1183 | if( i < size.height ) 1184 | { 1185 | _mag[-1] = _mag[size.width] = 0; 1186 | 1187 | if( !(flags & CV_CANNY_L2_GRADIENT) ) 1188 | for( j = 0; j < size.width; j++ ) 1189 | _mag[j] = abs(_dx[j]) + abs(_dy[j]); 1190 | 1191 | else 1192 | { 1193 | for( j = 0; j < size.width; j++ ) 1194 | { 1195 | x = _dx[j]; y = _dy[j]; 1196 | _magf[j] = (float)std::sqrt((double)x*x + (double)y*y); 1197 | } 1198 | } 1199 | } 1200 | else 1201 | memset( _mag-1, 0, (size.width + 2)*sizeof(int) ); 1202 | 1203 | // at the very beginning we do not have a complete ring 1204 | // buffer of 3 magnitude rows for non-maxima suppression 1205 | if( i == 0 ) 1206 | continue; 1207 | 1208 | _map = map + mapstep*i + 1; 1209 | _map[-1] = _map[size.width] = 1; 1210 | 1211 | _mag = mag_buf[1] + 1; // take the central row 1212 | _dx = (short*)(dx->data.ptr + dx->step*(i-1)); 1213 | _dy = (short*)(dy->data.ptr + dy->step*(i-1)); 1214 | 1215 | magstep1 = mag_buf[2] - mag_buf[1]; 1216 | magstep2 = mag_buf[0] - mag_buf[1]; 1217 | 1218 | if( (stack_top - stack_bottom) + size.width > maxsize ) 1219 | { 1220 | int sz = (int)(stack_top - stack_bottom); 1221 | maxsize = MAX( maxsize * 3/2, maxsize + 8 ); 1222 | stack.resize(maxsize); 1223 | stack_bottom = &stack[0]; 1224 | stack_top = stack_bottom + sz; 1225 | } 1226 | 1227 | for( j = 0; j < size.width; j++ ) 1228 | { 1229 | #define CANNY_SHIFT 15 1230 | #define TG22 (int)(0.4142135623730950488016887242097*(1< low ) 1240 | { 1241 | int tg22x = x * TG22; 1242 | int tg67x = tg22x + ((x + x) << CANNY_SHIFT); 1243 | 1244 | y <<= CANNY_SHIFT; 1245 | 1246 | if( y < tg22x ) 1247 | { 1248 | if( m > _mag[j-1] && m >= _mag[j+1] ) 1249 | { 1250 | if( m > high && !prev_flag && _map[j-mapstep] != 2 ) 1251 | { 1252 | CANNY_PUSH( _map + j ); 1253 | prev_flag = 1; 1254 | } 1255 | else 1256 | _map[j] = (uchar)0; 1257 | continue; 1258 | } 1259 | } 1260 | else if( y > tg67x ) 1261 | { 1262 | if( m > _mag[j+magstep2] && m >= _mag[j+magstep1] ) 1263 | { 1264 | if( m > high && !prev_flag && _map[j-mapstep] != 2 ) 1265 | { 1266 | CANNY_PUSH( _map + j ); 1267 | prev_flag = 1; 1268 | } 1269 | else 1270 | _map[j] = (uchar)0; 1271 | continue; 1272 | } 1273 | } 1274 | else 1275 | { 1276 | s = s < 0 ? -1 : 1; 1277 | if( m > _mag[j+magstep2-s] && m > _mag[j+magstep1+s] ) 1278 | { 1279 | if( m > high && !prev_flag && _map[j-mapstep] != 2 ) 1280 | { 1281 | CANNY_PUSH( _map + j ); 1282 | prev_flag = 1; 1283 | } 1284 | else 1285 | _map[j] = (uchar)0; 1286 | continue; 1287 | } 1288 | } 1289 | } 1290 | prev_flag = 0; 1291 | _map[j] = (uchar)1; 1292 | } 1293 | 1294 | // scroll the ring buffer 1295 | _mag = mag_buf[0]; 1296 | mag_buf[0] = mag_buf[1]; 1297 | mag_buf[1] = mag_buf[2]; 1298 | mag_buf[2] = _mag; 1299 | } 1300 | 1301 | // now track the edges (hysteresis thresholding) 1302 | while( stack_top > stack_bottom ) 1303 | { 1304 | uchar* m; 1305 | if( (stack_top - stack_bottom) + 8 > maxsize ) 1306 | { 1307 | int sz = (int)(stack_top - stack_bottom); 1308 | maxsize = MAX( maxsize * 3/2, maxsize + 8 ); 1309 | stack.resize(maxsize); 1310 | stack_bottom = &stack[0]; 1311 | stack_top = stack_bottom + sz; 1312 | } 1313 | 1314 | CANNY_POP(m); 1315 | 1316 | if( !m[-1] ) 1317 | CANNY_PUSH( m - 1 ); 1318 | if( !m[1] ) 1319 | CANNY_PUSH( m + 1 ); 1320 | if( !m[-mapstep-1] ) 1321 | CANNY_PUSH( m - mapstep - 1 ); 1322 | if( !m[-mapstep] ) 1323 | CANNY_PUSH( m - mapstep ); 1324 | if( !m[-mapstep+1] ) 1325 | CANNY_PUSH( m - mapstep + 1 ); 1326 | if( !m[mapstep-1] ) 1327 | CANNY_PUSH( m + mapstep - 1 ); 1328 | if( !m[mapstep] ) 1329 | CANNY_PUSH( m + mapstep ); 1330 | if( !m[mapstep+1] ) 1331 | CANNY_PUSH( m + mapstep + 1 ); 1332 | } 1333 | 1334 | // the final pass, form the final image 1335 | for( i = 0; i < size.height; i++ ) 1336 | { 1337 | const uchar* _map = map + mapstep*(i+1) + 1; 1338 | uchar* _dst = dst->data.ptr + dst->step*i; 1339 | 1340 | for( j = 0; j < size.width; j++ ) 1341 | { 1342 | _dst[j] = (uchar)-(_map[j] >> 1); 1343 | } 1344 | } 1345 | }; 1346 | 1347 | void Canny3( InputArray image, OutputArray _edges, 1348 | OutputArray _sobel_x, OutputArray _sobel_y, 1349 | int apertureSize, bool L2gradient ) 1350 | { 1351 | Mat src = image.getMat(); 1352 | _edges.create(src.size(), CV_8U); 1353 | _sobel_x.create(src.size(), CV_16S); 1354 | _sobel_y.create(src.size(), CV_16S); 1355 | 1356 | 1357 | CvMat c_src = src, c_dst = _edges.getMat(); 1358 | CvMat c_dx = _sobel_x.getMat(); 1359 | CvMat c_dy = _sobel_y.getMat(); 1360 | 1361 | 1362 | cvCanny3( &c_src, &c_dst, 1363 | &c_dx, &c_dy, 1364 | apertureSize + (L2gradient ? CV_CANNY_L2_GRADIENT : 0)); 1365 | }; 1366 | 1367 | 1368 | 1369 | 1370 | float GetMinAnglePI(float alpha, float beta) 1371 | { 1372 | float pi = float(CV_PI); 1373 | float pi2 = float(2.0 * CV_PI); 1374 | 1375 | //normalize data in [0, 2*pi] 1376 | float a = fmod(alpha + pi2, pi2); 1377 | float b = fmod(beta + pi2, pi2); 1378 | 1379 | //normalize data in [0, pi] 1380 | if (a > pi) 1381 | a -= pi; 1382 | if (b > pi) 1383 | b -= pi; 1384 | 1385 | if (a > b) 1386 | { 1387 | swap(a, b); 1388 | } 1389 | 1390 | float diff1 = b - a; 1391 | float diff2 = pi - diff1; 1392 | return min(diff1, diff2); 1393 | } 1394 | } -------------------------------------------------------------------------------- /libellipsedetect/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code is intended for academic use only. 3 | You are free to use and modify the code, at your own risk. 4 | 5 | If you use this code, or find it useful, please refer to the paper: 6 | 7 | Michele Fornaciari, Andrea Prati, Rita Cucchiara, 8 | A fast and effective ellipse detector for embedded vision applications 9 | Pattern Recognition, Volume 47, Issue 11, November 2014, Pages 3693-3708, ISSN 0031-3203, 10 | http://dx.doi.org/10.1016/j.patcog.2014.05.012. 11 | (http://www.sciencedirect.com/science/article/pii/S0031320314001976) 12 | 13 | 14 | The comments in the code refer to the abovementioned paper. 15 | If you need further details about the code or the algorithm, please contact me at: 16 | 17 | michele.fornaciari@unimore.it 18 | 19 | last update: 23/12/2014 20 | */ 21 | 22 | #pragma once 23 | #include 24 | //#include "opencv2\core\internal.hpp" 25 | 26 | 27 | using namespace std; 28 | using namespace cv; 29 | 30 | typedef vector VP; 31 | typedef vector< VP > VVP; 32 | typedef unsigned int uint; 33 | 34 | #define _INFINITY 1024 35 | 36 | namespace Yaed 37 | { 38 | int inline sgn(float val) { 39 | return (0.f < val) - (val < 0.f); 40 | }; 41 | 42 | 43 | bool inline isInf(float x) 44 | { 45 | union 46 | { 47 | float f; 48 | int i; 49 | } u; 50 | 51 | u.f = x; 52 | u.i &= 0x7fffffff; 53 | return !(u.i ^ 0x7f800000); 54 | }; 55 | 56 | 57 | float inline Slope(float x1, float y1, float x2, float y2) 58 | { 59 | //reference slope 60 | float den = float(x2 - x1); 61 | float num = float(y2 - y1); 62 | if(den != 0) 63 | { 64 | return (num / den); 65 | } 66 | else 67 | { 68 | return ((num > 0) ? float(_INFINITY) : float(-_INFINITY)); 69 | } 70 | }; 71 | 72 | //void cvCanny2( const void* srcarr, void* dstarr, 73 | // double low_thresh, double high_thresh, 74 | // void* dxarr, void* dyarr, 75 | // int aperture_size ); 76 | // 77 | //void cvCanny3( const void* srcarr, void* dstarr, 78 | // void* dxarr, void* dyarr, 79 | // int aperture_size ); 80 | 81 | void Canny2( InputArray image, OutputArray _edges, 82 | OutputArray _sobel_x, OutputArray _sobel_y, 83 | double threshold1, double threshold2, 84 | int apertureSize, bool L2gradient ); 85 | 86 | void Canny3( InputArray image, OutputArray _edges, 87 | OutputArray _sobel_x, OutputArray _sobel_y, 88 | int apertureSize, bool L2gradient ); 89 | 90 | 91 | float inline ed2(const cv::Point& A, const cv::Point& B) 92 | { 93 | return float(((B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y))); 94 | } 95 | 96 | float inline ed2f(const cv::Point2f& A, const cv::Point2f& B) 97 | { 98 | return (B.x - A.x)*(B.x - A.x) + (B.y - A.y)*(B.y - A.y); 99 | } 100 | 101 | 102 | void Labeling(Mat1b& image, vector >& segments, int iMinLength); 103 | void LabelingRect(Mat1b& image, VVP& segments, int iMinLength, vector& bboxes); 104 | void Thinning(Mat1b& imgMask, uchar byF=255, uchar byB=0); 105 | 106 | bool SortBottomLeft2TopRight(const cv::Point& lhs, const cv::Point& rhs); 107 | bool SortTopLeft2BottomRight(const cv::Point& lhs, const cv::Point& rhs); 108 | 109 | bool SortBottomLeft2TopRight2f(const cv::Point2f& lhs, const cv::Point2f& rhs); 110 | 111 | 112 | struct Ellipse 113 | { 114 | float _xc; 115 | float _yc; 116 | float _a; 117 | float _b; 118 | float _rad; 119 | float _score; 120 | 121 | Ellipse() : _xc(0.f), _yc(0.f), _a(0.f), _b(0.f), _rad(0.f), _score(0.f) {}; 122 | Ellipse(float xc, float yc, float a, float b, float rad, float score = 0.f) : _xc(xc), _yc(yc), _a(a), _b(b), _rad(rad), _score(score) {}; 123 | Ellipse(const Ellipse& other) : _xc(other._xc), _yc(other._yc), _a(other._a), _b(other._b), _rad(other._rad), _score(other._score) {}; 124 | 125 | void Ellipse::Draw(Mat& img, const Scalar& color, const int thickness) 126 | { 127 | ellipse(img, cv::Point(cvRound(_xc),cvRound(_yc)), cv::Size(cvRound(_a),cvRound(_b)), _rad * 180.0 / CV_PI, 0.0, 360.0, color, thickness); 128 | }; 129 | 130 | void Ellipse::Draw(Mat3b& img, const int thickness) 131 | { 132 | Scalar color(0, cvFloor(255.f * _score), 0); 133 | ellipse(img, cv::Point(cvRound(_xc),cvRound(_yc)), cv::Size(cvRound(_a),cvRound(_b)), _rad * 180.0 / CV_PI, 0.0, 360.0, color, thickness); 134 | }; 135 | 136 | bool Ellipse::operator<(const Ellipse& other) const 137 | { 138 | if(_score == other._score) 139 | { 140 | float lhs_e = _b / _a; 141 | float rhs_e = other._b / other._a; 142 | if(lhs_e == rhs_e) 143 | { 144 | return false; 145 | } 146 | return lhs_e > rhs_e; 147 | } 148 | return _score > other._score; 149 | }; 150 | }; 151 | 152 | 153 | float GetMinAnglePI(float alpha, float beta); 154 | 155 | } 156 | -------------------------------------------------------------------------------- /libellipsedetect/libellipsedetect.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {CD398248-A2B7-4F68-8F27-3A75EC4B1D8A} 23 | Win32Proj 24 | libellipsedetect 25 | 26 | 27 | 28 | StaticLibrary 29 | true 30 | v110 31 | Unicode 32 | 33 | 34 | StaticLibrary 35 | true 36 | v110 37 | Unicode 38 | 39 | 40 | StaticLibrary 41 | false 42 | v110 43 | true 44 | Unicode 45 | 46 | 47 | StaticLibrary 48 | false 49 | v110 50 | true 51 | Unicode 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 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 77 | $(OPENCV_DIR)\include;$(SolutionDir)\libellipsedetector\include 78 | 79 | 80 | Windows 81 | true 82 | 83 | 84 | 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 91 | $(OPENCV_DIR)\include;$(SolutionDir)\libellipsedetector\include 92 | 93 | 94 | Windows 95 | true 96 | 97 | 98 | 99 | 100 | Level3 101 | 102 | 103 | MaxSpeed 104 | true 105 | true 106 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 107 | 108 | 109 | Windows 110 | true 111 | true 112 | true 113 | 114 | 115 | 116 | 117 | Level3 118 | 119 | 120 | MaxSpeed 121 | true 122 | true 123 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 124 | $(OPENCV_DIR)\include 125 | 126 | 127 | Windows 128 | true 129 | true 130 | true 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /test-data/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | Input | Output 3 | --- | --- 4 | ![s01i] | ![s01o] 5 | ![s02i] | ![s02o] 6 | ![s03i] | ![s03o] 7 | ![s04i] | ![s04o] 8 | ![s05i] | ![s05o] 9 | ![s06i] | ![s06o] 10 | 11 | [s01i]: sample01.jpg "Input 1 .Net library C# ellipse detection" 12 | [s01o]: outputsample01.jpg "Result 1 .Net library C# ellipse detection" 13 | [s02i]: sample02.jpg "Input 2 .Net library C# ellipse detection" 14 | [s02o]: outputsample02.jpg "Result 2 .Net library C# ellipse detection" 15 | [s03i]: sample03.jpg "Input 3 .Net library C# ellipse detection" 16 | [s03o]: outputsample03.jpg "Result 3 .Net library C# ellipse detection" 17 | [s04i]: sample04.jpg "Input 4 .Net library C# ellipse detection" 18 | [s04o]: outputsample04.jpg "Result 4 .Net library C# ellipse detection" 19 | [s05i]: sample05.jpg "Input 5 .Net library C# ellipse detection" 20 | [s05o]: outputsample05.jpg "Result 5 .Net library C# ellipse detection" 21 | [s06i]: sample06.jpg "Input 6 .Net library C# ellipse detection" 22 | [s06o]: outputsample06.jpg "Result 6 .Net library C# ellipse detection" 23 | -------------------------------------------------------------------------------- /test-data/outputsample01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/outputsample01.jpg -------------------------------------------------------------------------------- /test-data/outputsample02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/outputsample02.jpg -------------------------------------------------------------------------------- /test-data/outputsample03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/outputsample03.jpg -------------------------------------------------------------------------------- /test-data/outputsample04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/outputsample04.jpg -------------------------------------------------------------------------------- /test-data/outputsample05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/outputsample05.jpg -------------------------------------------------------------------------------- /test-data/outputsample06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/outputsample06.jpg -------------------------------------------------------------------------------- /test-data/sample01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/sample01.jpg -------------------------------------------------------------------------------- /test-data/sample02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/sample02.jpg -------------------------------------------------------------------------------- /test-data/sample03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/sample03.jpg -------------------------------------------------------------------------------- /test-data/sample04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/sample04.jpg -------------------------------------------------------------------------------- /test-data/sample05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/sample05.jpg -------------------------------------------------------------------------------- /test-data/sample06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glani/EllipseDetectorLib/660e60c464760349dc580ba02272b1f295064ad6/test-data/sample06.jpg --------------------------------------------------------------------------------