├── CMakeLists.txt ├── LICENSE ├── README.md ├── data ├── result.png └── test.png ├── include ├── edline_detector.h └── line.h ├── src ├── CMakeLists.txt └── edline_detector.cpp └── test ├── CMakeLists.txt └── test_edline_detector.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(EDLINE_PARALLEL) 3 | 4 | 5 | LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 6 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 7 | set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) 8 | 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O3") 10 | add_definitions(-DCOMPILEDWITHC11) 11 | 12 | 13 | if(NOT CMAKE_BUILD_TYPE) 14 | set(CMAKE_BUILD_TYPE Release) 15 | endif() 16 | 17 | # opencv 18 | find_package(OpenCV REQUIRED) 19 | 20 | include_directories( 21 | ${PROJECT_SOURCE_DIR}/include 22 | ) 23 | 24 | link_directories( 25 | ${PROJECT_BINARY_DIR}/lib 26 | ) 27 | 28 | 29 | add_subdirectory(src) 30 | add_subdirectory(test) 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EDLine Parallel 2 | A parallel implementation of EDLine algorithm which based on the `Line Segment Detector` module of [opencv_contrib](https://github.com/opencv/opencv_contrib). Compared to the original version, the parallel one results in a almost 50% time reduction on PC with 4 cores CPU. 3 | 4 | Related project: [Line Matching](https://github.com/HanjieLuo/line_matching) 5 | 6 | ![test image](./data/result.png) 7 | 8 | ## Requirements ## 9 | The code is tested on Ubuntu 14.04. It requires the following tools and libraries: CMake, OpenCV 3.4. 10 | 11 | ## Building ## 12 | 13 | ``` 14 | #!bash 15 | 16 | cd EDLine_parallel 17 | mkdir build 18 | cd build 19 | cmake .. 20 | make 21 | ``` 22 | 23 | Test: 24 | 25 | ``` 26 | #!bash 27 | ./bin/test_edline_detector 28 | ``` 29 | 30 | ## Contact information ## 31 | Hanjie Luo [luohanjie@gmail.com](mailto:luohanjie@gmail.com) 32 | -------------------------------------------------------------------------------- /data/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HanjieLuo/EDLine_parallel/808d5ebfee900f631cbcdad4a9565d5f45bb228c/data/result.png -------------------------------------------------------------------------------- /data/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HanjieLuo/EDLine_parallel/808d5ebfee900f631cbcdad4a9565d5f45bb228c/data/test.png -------------------------------------------------------------------------------- /include/edline_detector.h: -------------------------------------------------------------------------------- 1 | #ifndef EDLINEDETECTOR_PARALLEL_HH_ 2 | #define EDLINEDETECTOR_PARALLEL_HH_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "line.h" 12 | 13 | struct Pixel { 14 | unsigned int x; //X coordinate 15 | unsigned int y; //Y coordinate 16 | }; 17 | struct EdgeChains { 18 | std::vector xCors; //all the x coordinates of edge points 19 | std::vector yCors; //all the y coordinates of edge points 20 | std::vector sId; //the start index of each edge in the coordinate arrays 21 | unsigned int numOfEdges; //the number of edges whose length are larger than minLineLen; numOfEdges < sId.size; 22 | }; 23 | struct LineChains { 24 | std::vector xCors; //all the x coordinates of line points 25 | std::vector yCors; //all the y coordinates of line points 26 | std::vector sId; //the start index of each line in the coordinate arrays 27 | unsigned int numOfLines; //the number of lines whose length are larger than minLineLen; numOfLines < sId.size; 28 | }; 29 | 30 | typedef std::list PixelChain; //each edge is a pixel chain 31 | 32 | struct EDLineParam { 33 | int ksize; 34 | float sigma; 35 | float gradientThreshold; 36 | float anchorThreshold; 37 | int scanIntervals; 38 | int minLineLen; 39 | double lineFitErrThreshold; 40 | }; 41 | 42 | #define RELATIVE_ERROR_FACTOR 100.0 43 | #define M_LN10 2.30258509299404568402 44 | #define log_gamma(x) ((x) > 15.0 ? log_gamma_windschitl(x) : log_gamma_lanczos(x)) 45 | 46 | /* This class is used to detect lines from input image. 47 | * First, edges are extracted from input image following the method presented in Cihan Topal and 48 | * Cuneyt Akinlar's paper:"Edge Drawing: A Heuristic Approach to Robust Real-Time Edge Detection", 2010. 49 | * Then, lines are extracted from the edge image following the method presented in Cuneyt Akinlar and 50 | * Cihan Topal's paper:"EDLines: A real-time line segment detector with a false detection control", 2011 51 | * PS: The linking step of edge detection has a little bit difference with the Edge drawing algorithm 52 | * described in the paper. The edge chain doesn't stop when the pixel direction is changed. 53 | */ 54 | class EDLineDetector { 55 | public: 56 | EDLineDetector(); 57 | EDLineDetector(EDLineParam param); 58 | ~EDLineDetector(); 59 | 60 | /*extract edges from image 61 | *image: In, gray image; 62 | *edges: Out, store the edges, each edge is a pixel chain 63 | *smoothed: In, flag to mark whether the image has already been smoothed by Gaussian filter. 64 | *return -1: error happen 65 | */ 66 | int EdgeDrawing(cv::Mat &image, EdgeChains &edgeChains, bool smoothed = false); 67 | /*extract lines from image 68 | *image: In, gray image; 69 | *lines: Out, store the extracted lines, 70 | *smoothed: In, flag to mark whether the image has already been smoothed by Gaussian filter. 71 | *return -1: error happen 72 | */ 73 | // int EDline(cv::Mat &image, LineChains &lines, bool smoothed = false); 74 | /*extract line from image, and store them*/ 75 | // int EDline(cv::Mat &image, bool smoothed = false); 76 | 77 | //My version 78 | 79 | int EDline(cv::Mat &image, 80 | std::vector &lines, 81 | bool smoothed = false); 82 | 83 | cv::Mat dxImg_; //store the dxImg; 84 | cv::Mat dyImg_; //store the dyImg; 85 | cv::Mat gImgWO_; //store the gradient image without threshold; 86 | // LineChains lines_; //store the detected line chains; 87 | 88 | cv::Mat gImg_; //store the gradient image; 89 | cv::Mat dirImg_; //store the direction image 90 | 91 | cv::Mat dxABS_m_; 92 | cv::Mat dyABS_m_; 93 | cv::Mat sumDxDy_; 94 | 95 | //store the line Equation coefficients, vec3=[w1,w2,w3] for line w1*x + w2*y + w3=0; 96 | std::vector > lineEquations_; 97 | //store the line endpoints, [x1,y1,x2,y3] 98 | std::vector > lineEndpoints_; 99 | //store the line direction 100 | std::vector lineDirection_; 101 | //store the line salience, which is the summation of gradients of pixels on line 102 | std::vector lineSalience_; 103 | unsigned int imageWidth; 104 | unsigned int imageHeight; 105 | 106 | // float abs_time_sum_ = 0.0; 107 | 108 | private: 109 | void InitEDLine_(); 110 | 111 | EdgeChains edges_; 112 | 113 | int ksize_; //the size of Gaussian kernel: ksize X ksize, default value is 5. 114 | float sigma_; //the sigma of Gaussian kernal, default value is 1.0. 115 | /*the threshold of pixel gradient magnitude. 116 | *Only those pixel whose gradient magnitude are larger than this threshold will be 117 | *taken as possible edge points. Default value is 36*/ 118 | short gradienThreshold_; 119 | /*If the pixel's gradient value is bigger than both of its neighbors by a 120 | *certain threshold (ANCHOR_THRESHOLD), the pixel is marked to be an anchor. 121 | *Default value is 8*/ 122 | unsigned char anchorThreshold_; 123 | /*anchor testing can be performed at different scan intervals, i.e., 124 | *every row/column, every second row/column etc. 125 | *Default value is 2*/ 126 | unsigned int scanIntervals_; 127 | int minLineLen_; //minimal acceptable line length 128 | /*For example, there two edges in the image: 129 | *edge1 = [(7,4), (8,5), (9,6),| (10,7)|, (11, 8), (12,9)] and 130 | *edge2 = [(14,9), (15,10), (16,11), (17,12),| (18, 13)|, (19,14)] ; then we store them as following: 131 | *pFirstPartEdgeX_ = [10, 11, 12, 18, 19];//store the first part of each edge[from middle to end] 132 | *pFirstPartEdgeY_ = [7, 8, 9, 13, 14]; 133 | *pFirstPartEdgeS_ = [0,3,5];// the index of start point of first part of each edge 134 | *pSecondPartEdgeX_ = [10, 9, 8, 7, 18, 17, 16, 15, 14];//store the second part of each edge[from middle to front] 135 | *pSecondPartEdgeY_ = [7, 6, 5, 4, 13, 12, 11, 10, 9];//anchor points(10, 7) and (18, 13) are stored again 136 | *pSecondPartEdgeS_ = [0, 4, 9];// the index of start point of second part of each edge 137 | *This type of storage order is because of the order of edge detection process. 138 | *For each edge, start from one anchor point, first go right, then go left or first go down, then go up*/ 139 | unsigned int *pFirstPartEdgeX_; //store the X coordinates of the first part of the pixels for chains 140 | unsigned int *pFirstPartEdgeY_; //store the Y coordinates of the first part of the pixels for chains 141 | unsigned int *pFirstPartEdgeS_; //store the start index of every edge chain in the first part arrays 142 | unsigned int *pSecondPartEdgeX_; //store the X coordinates of the second part of the pixels for chains 143 | unsigned int *pSecondPartEdgeY_; //store the Y coordinates of the second part of the pixels for chains 144 | unsigned int *pSecondPartEdgeS_; //store the start index of every edge chain in the second part arrays 145 | unsigned int *pAnchorX_; //store the X coordinates of anchors 146 | unsigned int *pAnchorY_; //store the Y coordinates of anchors 147 | cv::Mat edgeImage_; 148 | /*The threshold of line fit error; 149 | *If lineFitErr is large than this threshold, then 150 | *the pixel chain is not accepted as a single line segment.*/ 151 | double lineFitErrThreshold_; 152 | 153 | cv::Mat image_; 154 | cv::Mat zeroMat_; 155 | 156 | // std::mutex g_lock_; 157 | 158 | double logNT_; 159 | 160 | // void AbsAdd(const cv::Mat &src1, const cv::Mat &src2, cv::Mat &dst); 161 | 162 | /** Compare doubles by relative error. 163 | The resulting rounding error after floating point computations 164 | depend on the specific operations done. The same number computed by 165 | different algorithms could present different rounding errors. For a 166 | useful comparison, an estimation of the relative rounding error 167 | should be considered and compared to a factor times EPS. The factor 168 | should be related to the accumulated rounding error in the chain of 169 | computation. Here, as a simplification, a fixed factor is used. 170 | */ 171 | static int double_equal(double a, double b) { 172 | double abs_diff, aa, bb, abs_max; 173 | /* trivial case */ 174 | if (a == b) return true; 175 | abs_diff = fabs(a - b); 176 | aa = fabs(a); 177 | bb = fabs(b); 178 | abs_max = aa > bb ? aa : bb; 179 | /* DBL_MIN is the smallest normalized number, thus, the smallest 180 | number whose relative error is bounded by DBL_EPSILON. For 181 | smaller numbers, the same quantization steps as for DBL_MIN 182 | are used. Then, for smaller numbers, a meaningful "relative" 183 | error should be computed by dividing the difference by DBL_MIN. */ 184 | if (abs_max < DBL_MIN) abs_max = DBL_MIN; 185 | /* equal if relative error <= factor x eps */ 186 | return (abs_diff / abs_max) <= (RELATIVE_ERROR_FACTOR * DBL_EPSILON); 187 | } 188 | /** Computes the natural logarithm of the absolute value of 189 | the gamma function of x using the Lanczos approximation. 190 | See http://www.rskey.org/gamma.htm 191 | The formula used is 192 | @f[ 193 | \Gamma(x) = \frac{ \sum_{n=0}^{N} q_n x^n }{ \Pi_{n=0}^{N} (x+n) } 194 | (x+5.5)^{x+0.5} e^{-(x+5.5)} 195 | @f] 196 | so 197 | @f[ 198 | \log\Gamma(x) = \log\left( \sum_{n=0}^{N} q_n x^n \right) 199 | + (x+0.5) \log(x+5.5) - (x+5.5) - \sum_{n=0}^{N} \log(x+n) 200 | @f] 201 | and 202 | q0 = 75122.6331530, 203 | q1 = 80916.6278952, 204 | q2 = 36308.2951477, 205 | q3 = 8687.24529705, 206 | q4 = 1168.92649479, 207 | q5 = 83.8676043424, 208 | q6 = 2.50662827511. 209 | */ 210 | static double log_gamma_lanczos(double x) { 211 | static double q[7] = {75122.6331530, 80916.6278952, 36308.2951477, 212 | 8687.24529705, 1168.92649479, 83.8676043424, 213 | 2.50662827511}; 214 | double a = (x + 0.5) * log(x + 5.5) - (x + 5.5); 215 | double b = 0.0; 216 | int n; 217 | for (n = 0; n < 7; n++) { 218 | a -= log(x + (double)n); 219 | b += q[n] * pow(x, (double)n); 220 | } 221 | return a + log(b); 222 | } 223 | /** Computes the natural logarithm of the absolute value of 224 | the gamma function of x using Windschitl method. 225 | See http://www.rskey.org/gamma.htm 226 | The formula used is 227 | @f[ 228 | \Gamma(x) = \sqrt{\frac{2\pi}{x}} \left( \frac{x}{e} 229 | \sqrt{ x\sinh(1/x) + \frac{1}{810x^6} } \right)^x 230 | @f] 231 | so 232 | @f[ 233 | \log\Gamma(x) = 0.5\log(2\pi) + (x-0.5)\log(x) - x 234 | + 0.5x\log\left( x\sinh(1/x) + \frac{1}{810x^6} \right). 235 | @f] 236 | This formula is a good approximation when x > 15. 237 | */ 238 | static double log_gamma_windschitl(double x) { 239 | return 0.918938533204673 + (x - 0.5) * log(x) - x + 0.5 * x * log(x * sinh(1 / x) + 1 / (810.0 * pow(x, 6.0))); 240 | } 241 | /** Computes -log10(NFA). 242 | NFA stands for Number of False Alarms: 243 | @f[ 244 | \mathrm{NFA} = NT \cdot B(n,k,p) 245 | @f] 246 | - NT - number of tests 247 | - B(n,k,p) - tail of binomial distribution with parameters n,k and p: 248 | @f[ 249 | B(n,k,p) = \sum_{j=k}^n 250 | \left(\begin{array}{c}n\\j\end{array}\right) 251 | p^{j} (1-p)^{n-j} 252 | @f] 253 | The value -log10(NFA) is equivalent but more intuitive than NFA: 254 | - -1 corresponds to 10 mean false alarms 255 | - 0 corresponds to 1 mean false alarm 256 | - 1 corresponds to 0.1 mean false alarms 257 | - 2 corresponds to 0.01 mean false alarms 258 | - ... 259 | Used this way, the bigger the value, better the detection, 260 | and a logarithmic scale is used. 261 | @param n,k,p binomial parameters. 262 | @param logNT logarithm of Number of Tests 263 | The computation is based in the gamma function by the following 264 | relation: 265 | @f[ 266 | \left(\begin{array}{c}n\\k\end{array}\right) 267 | = \frac{ \Gamma(n+1) }{ \Gamma(k+1) \cdot \Gamma(n-k+1) }. 268 | @f] 269 | We use efficient algorithms to compute the logarithm of 270 | the gamma function. 271 | To make the computation faster, not all the sum is computed, part 272 | of the terms are neglected based on a bound to the error obtained 273 | (an error of 10% in the result is accepted). 274 | */ 275 | static double nfa(int n, int k, double p, double logNT) { 276 | double tolerance = 0.1; /* an error of 10% in the result is accepted */ 277 | double log1term, term, bin_term, mult_term, bin_tail, err, p_term; 278 | int i; 279 | 280 | /* check parameters */ 281 | if (n < 0 || k < 0 || k > n || p <= 0.0 || p >= 1.0) { 282 | std::cout << "nfa: wrong n, k or p values." << std::endl; 283 | exit(0); 284 | } 285 | /* trivial cases */ 286 | if (n == 0 || k == 0) return -logNT; 287 | if (n == k) return -logNT - (double)n * log10(p); 288 | 289 | /* probability term */ 290 | p_term = p / (1.0 - p); 291 | 292 | /* compute the first term of the series */ 293 | /* 294 | binomial_tail(n,k,p) = sum_{i=k}^n bincoef(n,i) * p^i * (1-p)^{n-i} 295 | where bincoef(n,i) are the binomial coefficients. 296 | But 297 | bincoef(n,k) = gamma(n+1) / ( gamma(k+1) * gamma(n-k+1) ). 298 | We use this to compute the first term. Actually the log of it. 299 | */ 300 | log1term = log_gamma((double)n + 1.0) - log_gamma((double)k + 1.0) - log_gamma((double)(n - k) + 1.0) + (double)k * log(p) + (double)(n - k) * log(1.0 - p); 301 | term = exp(log1term); 302 | 303 | /* in some cases no more computations are needed */ 304 | if (double_equal(term, 0.0)) { /* the first term is almost zero */ 305 | if ((double)k > (double)n * p) /* at begin or end of the tail? */ 306 | return -log1term / M_LN10 - logNT; /* end: use just the first term */ 307 | else 308 | return -logNT; /* begin: the tail is roughly 1 */ 309 | } 310 | 311 | /* compute more terms if needed */ 312 | bin_tail = term; 313 | for (i = k + 1; i <= n; i++) { 314 | /* As 315 | term_i = bincoef(n,i) * p^i * (1-p)^(n-i) 316 | and 317 | bincoef(n,i)/bincoef(n,i-1) = n-i+1 / i, 318 | then, 319 | term_i / term_i-1 = (n-i+1)/i * p/(1-p) 320 | and 321 | term_i = term_i-1 * (n-i+1)/i * p/(1-p). 322 | p/(1-p) is computed only once and stored in 'p_term'. 323 | */ 324 | bin_term = (double)(n - i + 1) / (double)i; 325 | mult_term = bin_term * p_term; 326 | term *= mult_term; 327 | bin_tail += term; 328 | if (bin_term < 1.0) { 329 | /* When bin_term<1 then mult_term_ji. 330 | Then, the error on the binomial tail when truncated at 331 | the i term can be bounded by a geometric series of form 332 | term_i * sum mult_term_i^j. */ 333 | err = term * ((1.0 - pow(mult_term, (double)(n - i + 1))) / 334 | (1.0 - mult_term) - 335 | 1.0); 336 | /* One wants an error at most of tolerance*final_result, or: 337 | tolerance * abs(-log10(bin_tail)-logNT). 338 | Now, the error that can be accepted on bin_tail is 339 | given by tolerance*final_result divided by the derivative 340 | of -log10(x) when x=bin_tail. that is: 341 | tolerance * abs(-log10(bin_tail)-logNT) / (1/bin_tail) 342 | Finally, we truncate the tail if the error is less than: 343 | tolerance * abs(-log10(bin_tail)-logNT) * bin_tail */ 344 | if (err < tolerance * fabs(-log10(bin_tail) - logNT) * bin_tail) break; 345 | } 346 | } 347 | return -log10(bin_tail) - logNT; 348 | } 349 | 350 | class EDLineDetectorParallel : public cv::ParallelLoopBody { 351 | public: 352 | EDLineDetectorParallel(EDLineDetector *_edline, 353 | EdgeChains *_edges, 354 | // unsigned char *_pdirImg, 355 | // unsigned int *_pEdgeXCors, 356 | // unsigned int *_pEdgeYCors, 357 | // unsigned int *_pEdgeSID, 358 | double _logNT, 359 | cv::Mutex &_lock, 360 | std::vector *_lines); 361 | 362 | EDLineDetectorParallel &operator=(const EDLineDetectorParallel &) { 363 | return *this; 364 | }; 365 | 366 | virtual void operator()(const cv::Range &range) const; 367 | 368 | /*For an input edge chain, find the best fit line, the default chain length is minLineLen_ 369 | *xCors: In, pointer to the X coordinates of pixel chain; 370 | *yCors: In, pointer to the Y coordinates of pixel chain; 371 | *offsetS:In, start index of this chain in array; 372 | *lineEquation: Out, [a,b] which are the coefficient of lines y=ax+b(horizontal) or x=ay+b(vertical); 373 | *return: line fit error; -1:error happens; 374 | */ 375 | double LeastSquaresLineFit(cv::Mat_ &ATA, cv::Mat_ &ATV, cv::Mat_ &fitMatT, cv::Mat_ &fitVec, 376 | unsigned int *xCors, unsigned int *yCors, 377 | unsigned int offsetS, std::array &lineEquation) const; 378 | /*For an input pixel chain, find the best fit line. Only do the update based on new points. 379 | *For A*x=v, Least square estimation of x = Inv(A^T * A) * (A^T * v); 380 | *If some new observations are added, i.e, [A; A'] * x = [v; v'], 381 | *then x' = Inv(A^T * A + (A')^T * A') * (A^T * v + (A')^T * v'); 382 | *xCors: In, pointer to the X coordinates of pixel chain; 383 | *yCors: In, pointer to the Y coordinates of pixel chain; 384 | *offsetS:In, start index of this chain in array; 385 | *newOffsetS: In, start index of extended part; 386 | *offsetE:In, end index of this chain in array; 387 | *lineEquation: Out, [a,b] which are the coefficient of lines y=ax+b(horizontal) or x=ay+b(vertical); 388 | *return: line fit error; -1:error happens; 389 | */ 390 | double LeastSquaresLineFit(cv::Mat_ &ATA, cv::Mat_ &ATV, cv::Mat_ &tempMatLineFit, cv::Mat_ &tempVecLineFit, 391 | unsigned int *xCors, unsigned int *yCors, 392 | unsigned int offsetS, unsigned int newOffsetS, 393 | unsigned int offsetE, std::array &lineEquation) const; 394 | /* Validate line based on the Helmholtz principle, which basically states that 395 | * for a structure to be perceptually meaningful, the expectation of this structure 396 | * by chance must be very low. 397 | */ 398 | bool LineValidation(unsigned int *xCors, unsigned int *yCors, 399 | unsigned int offsetS, unsigned int offsetE, 400 | std::array &lineEquation, float &direction) const; 401 | 402 | private: 403 | EDLineDetector *edline; 404 | EdgeChains *edges; 405 | int minLineLen; 406 | double lineFitErrThreshold; 407 | unsigned int imageWidth; 408 | unsigned int imageHeight; 409 | // unsigned char *pdirImg; 410 | unsigned int *pEdgeXCors; 411 | unsigned int *pEdgeYCors; 412 | unsigned int *pEdgeSID; 413 | double logNT; 414 | cv::Mutex &lock; 415 | std::vector *lines; 416 | 417 | bool bValidate; //flag to decide whether line will be validated 418 | 419 | cv::Mat dirImg; //store the direction image 420 | cv::Mat dxImg; //store the dxImg; 421 | cv::Mat dyImg; //store the dyImg; 422 | }; 423 | }; 424 | 425 | #endif /* EDLINEDETECTOR_PARALLEL_HH_ */ 426 | -------------------------------------------------------------------------------- /include/line.h: -------------------------------------------------------------------------------- 1 | #ifndef LINE_H 2 | #define LINE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct Line { 9 | std::array line_endpoint; 10 | std::array line_equation; 11 | std::array center; 12 | float length; 13 | 14 | std::vector kps; 15 | std::vector kps_init; 16 | std::vector dirs; 17 | }; 18 | 19 | #endif -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | 3 | find_package(OpenCV REQUIRED) 4 | 5 | include_directories( 6 | ${OpenCV_INCLUDE_DIRS} 7 | ) 8 | 9 | link_directories( 10 | ${OpenCV_LIBRARY_DIRS} 11 | ) 12 | 13 | add_library(EDLine SHARED edline_detector.cpp) 14 | target_link_libraries(EDLine ${OpenCV_LIBS}) 15 | -------------------------------------------------------------------------------- /src/edline_detector.cpp: -------------------------------------------------------------------------------- 1 | #include "edline_detector.h" 2 | 3 | #include 4 | 5 | #define Horizontal 255 //if |dx|<|dy|; 6 | #define Vertical 0 //if |dy|<=|dx|; 7 | #define UpDir 1 8 | #define RightDir 2 9 | #define DownDir 3 10 | #define LeftDir 4 11 | #define TryTime 6 12 | #define SkipEdgePoint 2 13 | 14 | //#define DEBUGEdgeDrawing 15 | //#define DEBUGEDLine 16 | 17 | using namespace std; 18 | EDLineDetector::EDLineDetector() { 19 | // cout<<"Call EDLineDetector constructor function"<> s; 74 | string fileNameConf = name + s; 75 | cv::FileStorage fsConf(fileNameConf, cv::FileStorage::WRITE); 76 | fsConf << "m" << m; 77 | 78 | fsConf.release(); 79 | } 80 | 81 | int EDLineDetector::EdgeDrawing(cv::Mat &image, EdgeChains &edgeChains, bool smoothed) { 82 | if (!smoothed) { //input image hasn't been smoothed. 83 | cv::GaussianBlur(image, image_, cv::Size(ksize_, ksize_), sigma_); 84 | } else { 85 | image_ = image; 86 | } 87 | 88 | imageWidth = image_.cols; 89 | imageHeight = image_.rows; 90 | unsigned int pixelNum = imageWidth * imageHeight; 91 | 92 | unsigned int edgePixelArraySize = pixelNum / 5; 93 | unsigned int maxNumOfEdge = edgePixelArraySize / 20; 94 | //compute dx, dy images 95 | if (gImg_.cols != (int)imageWidth || gImg_.rows != (int)imageHeight) { 96 | if (pFirstPartEdgeX_ != NULL) { 97 | delete[] pFirstPartEdgeX_; 98 | delete[] pFirstPartEdgeY_; 99 | delete[] pSecondPartEdgeX_; 100 | delete[] pSecondPartEdgeY_; 101 | delete[] pFirstPartEdgeS_; 102 | delete[] pSecondPartEdgeS_; 103 | delete[] pAnchorX_; 104 | delete[] pAnchorY_; 105 | } 106 | 107 | dxImg_.create(imageHeight, imageWidth, CV_16SC1); 108 | dyImg_.create(imageHeight, imageWidth, CV_16SC1); 109 | gImgWO_.create(imageHeight, imageWidth, CV_16SC1); 110 | gImg_.create(imageHeight, imageWidth, CV_16SC1); 111 | dirImg_.create(imageHeight, imageWidth, CV_8UC1); 112 | edgeImage_.create(imageHeight, imageWidth, CV_8UC1); 113 | pFirstPartEdgeX_ = new unsigned int[edgePixelArraySize]; 114 | pFirstPartEdgeY_ = new unsigned int[edgePixelArraySize]; 115 | pSecondPartEdgeX_ = new unsigned int[edgePixelArraySize]; 116 | pSecondPartEdgeY_ = new unsigned int[edgePixelArraySize]; 117 | pFirstPartEdgeS_ = new unsigned int[maxNumOfEdge]; 118 | pSecondPartEdgeS_ = new unsigned int[maxNumOfEdge]; 119 | pAnchorX_ = new unsigned int[edgePixelArraySize]; 120 | pAnchorY_ = new unsigned int[edgePixelArraySize]; 121 | 122 | zeroMat_ = cv::Mat::zeros(imageHeight, imageWidth, CV_16SC1); 123 | } 124 | 125 | cv::Sobel(image_, dxImg_, CV_16SC1, 1, 0, 3); 126 | cv::Sobel(image_, dyImg_, CV_16SC1, 0, 1, 3); 127 | 128 | cv::absdiff(dxImg_, zeroMat_, dxABS_m_); 129 | cv::absdiff(dyImg_, zeroMat_, dyABS_m_); 130 | 131 | cv::add(dyABS_m_, dxABS_m_, sumDxDy_); 132 | 133 | cv::threshold(sumDxDy_, gImg_, gradienThreshold_ + 1, 255, cv::THRESH_TOZERO); 134 | gImg_ = gImg_ / 4; 135 | gImgWO_ = sumDxDy_ / 4; 136 | cv::compare(dxABS_m_, dyABS_m_, dirImg_, cv::CMP_LT); 137 | 138 | short *pgImg = gImg_.ptr(); 139 | unsigned char *pdirImg = dirImg_.ptr(); 140 | 141 | //extract the anchors in the gradient image, store into a vector 142 | memset(pAnchorX_, 0, edgePixelArraySize * sizeof(unsigned int)); //initialization 143 | memset(pAnchorY_, 0, edgePixelArraySize * sizeof(unsigned int)); 144 | unsigned int anchorsSize = 0; 145 | int indexInArray; 146 | unsigned char gValue1, gValue2, gValue3; 147 | 148 | for (unsigned int w = 1; w < imageWidth - 1; w = w + scanIntervals_) { 149 | for (unsigned int h = 1; h < imageHeight - 1; h = h + scanIntervals_) { 150 | indexInArray = h * imageWidth + w; 151 | if (pdirImg[indexInArray] == Horizontal) { //if the direction of pixel is horizontal, then compare with up and down 152 | //gValue2 = pgImg[indexInArray]; 153 | if (pgImg[indexInArray] >= pgImg[indexInArray - imageWidth] + anchorThreshold_ && pgImg[indexInArray] >= pgImg[indexInArray + imageWidth] + anchorThreshold_) { // (w,h) is accepted as an anchor 154 | pAnchorX_[anchorsSize] = w; 155 | pAnchorY_[anchorsSize++] = h; 156 | } 157 | } else { 158 | if (pgImg[indexInArray] >= pgImg[indexInArray - 1] + anchorThreshold_ && pgImg[indexInArray] >= pgImg[indexInArray + 1] + anchorThreshold_) { // (w,h) is accepted as an anchor 159 | pAnchorX_[anchorsSize] = w; 160 | pAnchorY_[anchorsSize++] = h; 161 | } 162 | } 163 | } 164 | } 165 | 166 | if (anchorsSize > edgePixelArraySize) { 167 | std::cout << "anchor size is larger than its maximal size. anchorsSize=" << anchorsSize << ", maximal size = " << edgePixelArraySize << std::endl; 168 | return -1; 169 | } 170 | 171 | //link the anchors by smart routing 172 | edgeImage_.setTo(0); 173 | unsigned char *pEdgeImg = edgeImage_.data; 174 | memset(pFirstPartEdgeX_, 0, edgePixelArraySize * sizeof(unsigned int)); //initialization 175 | memset(pFirstPartEdgeY_, 0, edgePixelArraySize * sizeof(unsigned int)); 176 | memset(pSecondPartEdgeX_, 0, edgePixelArraySize * sizeof(unsigned int)); 177 | memset(pSecondPartEdgeY_, 0, edgePixelArraySize * sizeof(unsigned int)); 178 | memset(pFirstPartEdgeS_, 0, maxNumOfEdge * sizeof(unsigned int)); 179 | memset(pSecondPartEdgeS_, 0, maxNumOfEdge * sizeof(unsigned int)); 180 | unsigned int offsetPFirst = 0, offsetPSecond = 0; 181 | unsigned int offsetPS = 0; 182 | 183 | unsigned int x, y; 184 | unsigned int lastX = 0; 185 | unsigned int lastY = 0; 186 | unsigned char lastDirection; //up = 1, right = 2, down = 3, left = 4; 187 | unsigned char shouldGoDirection; //up = 1, right = 2, down = 3, left = 4; 188 | int edgeLenFirst, edgeLenSecond; 189 | 190 | // gettimeofday(&t1, NULL); 191 | for (unsigned int i = 0; i < anchorsSize; i++) { 192 | x = pAnchorX_[i]; 193 | y = pAnchorY_[i]; 194 | indexInArray = y * imageWidth + x; 195 | if (pEdgeImg[indexInArray]) { //if anchor i is already been an edge pixel. 196 | continue; 197 | } 198 | /*The walk stops under 3 conditions: 199 | * 1. We move out of the edge areas, i.e., the thresholded gradient value 200 | * of the current pixel is 0. 201 | * 2. The current direction of the edge changes, i.e., from horizontal 202 | * to vertical or vice versa.?? (This is turned out not correct. From the online edge draw demo 203 | * we can figure out that authors don't implement this rule either because their extracted edge 204 | * chain could be a circle which means pixel directions would definitely be different 205 | * in somewhere on the chain.) 206 | * 3. We encounter a previously detected edge pixel. */ 207 | pFirstPartEdgeS_[offsetPS] = offsetPFirst; 208 | if (pdirImg[indexInArray] == Horizontal) { //if the direction of this pixel is horizontal, then go left and right. 209 | //fist go right, pixel direction may be different during linking. 210 | lastDirection = RightDir; 211 | while (pgImg[indexInArray] > 0 && !pEdgeImg[indexInArray]) { 212 | pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel 213 | pFirstPartEdgeX_[offsetPFirst] = x; 214 | pFirstPartEdgeY_[offsetPFirst++] = y; 215 | shouldGoDirection = 0; //unknown 216 | if (pdirImg[indexInArray] == Horizontal) { //should go left or right 217 | if (lastDirection == UpDir || lastDirection == DownDir) { //change the pixel direction now 218 | if (x > lastX) { //should go right 219 | shouldGoDirection = RightDir; 220 | } else { //should go left 221 | shouldGoDirection = LeftDir; 222 | } 223 | } 224 | lastX = x; 225 | lastY = y; 226 | if (lastDirection == RightDir || shouldGoDirection == RightDir) { //go right 227 | if (x == imageWidth - 1 || y == 0 || y == imageHeight - 1) { //reach the image border 228 | break; 229 | } 230 | // Look at 3 neighbors to the right and pick the one with the max. gradient value 231 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 232 | gValue2 = (unsigned char)pgImg[indexInArray + 1]; 233 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 234 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 235 | x = x + 1; 236 | y = y - 1; 237 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-right 238 | x = x + 1; 239 | y = y + 1; 240 | } else { //straight-right 241 | x = x + 1; 242 | } 243 | lastDirection = RightDir; 244 | } else if (lastDirection == LeftDir || shouldGoDirection == LeftDir) { //go left 245 | if (x == 0 || y == 0 || y == imageHeight - 1) { //reach the image border 246 | break; 247 | } 248 | // Look at 3 neighbors to the left and pick the one with the max. gradient value 249 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 250 | gValue2 = (unsigned char)pgImg[indexInArray - 1]; 251 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 252 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-left 253 | x = x - 1; 254 | y = y - 1; 255 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 256 | x = x - 1; 257 | y = y + 1; 258 | } else { //straight-left 259 | x = x - 1; 260 | } 261 | lastDirection = LeftDir; 262 | } 263 | } else { //should go up or down. 264 | if (lastDirection == RightDir || lastDirection == LeftDir) { //change the pixel direction now 265 | if (y > lastY) { //should go down 266 | shouldGoDirection = DownDir; 267 | } else { //should go up 268 | shouldGoDirection = UpDir; 269 | } 270 | } 271 | lastX = x; 272 | lastY = y; 273 | if (lastDirection == DownDir || shouldGoDirection == DownDir) { //go down 274 | if (x == 0 || x == imageWidth - 1 || y == imageHeight - 1) { //reach the image border 275 | break; 276 | } 277 | // Look at 3 neighbors to the down and pick the one with the max. gradient value 278 | gValue1 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 279 | gValue2 = (unsigned char)pgImg[indexInArray + imageWidth]; 280 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 281 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //down-right 282 | x = x + 1; 283 | y = y + 1; 284 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 285 | x = x - 1; 286 | y = y + 1; 287 | } else { //straight-down 288 | y = y + 1; 289 | } 290 | lastDirection = DownDir; 291 | } else if (lastDirection == UpDir || shouldGoDirection == UpDir) { //go up 292 | if (x == 0 || x == imageWidth - 1 || y == 0) { //reach the image border 293 | break; 294 | } 295 | // Look at 3 neighbors to the up and pick the one with the max. gradient value 296 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 297 | gValue2 = (unsigned char)pgImg[indexInArray - imageWidth]; 298 | gValue3 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 299 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 300 | x = x + 1; 301 | y = y - 1; 302 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //up-left 303 | x = x - 1; 304 | y = y - 1; 305 | } else { //straight-up 306 | y = y - 1; 307 | } 308 | lastDirection = UpDir; 309 | } 310 | } 311 | indexInArray = y * imageWidth + x; 312 | } //end while go right 313 | //then go left, pixel direction may be different during linking. 314 | x = pAnchorX_[i]; 315 | y = pAnchorY_[i]; 316 | indexInArray = y * imageWidth + x; 317 | pEdgeImg[indexInArray] = 0; //mark the anchor point be a non-edge pixel and 318 | lastDirection = LeftDir; 319 | pSecondPartEdgeS_[offsetPS] = offsetPSecond; 320 | while (pgImg[indexInArray] > 0 && !pEdgeImg[indexInArray]) { 321 | pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel 322 | pSecondPartEdgeX_[offsetPSecond] = x; 323 | pSecondPartEdgeY_[offsetPSecond++] = y; 324 | shouldGoDirection = 0; //unknown 325 | if (pdirImg[indexInArray] == Horizontal) { //should go left or right 326 | if (lastDirection == UpDir || lastDirection == DownDir) { //change the pixel direction now 327 | if (x > lastX) { //should go right 328 | shouldGoDirection = RightDir; 329 | } else { //should go left 330 | shouldGoDirection = LeftDir; 331 | } 332 | } 333 | lastX = x; 334 | lastY = y; 335 | if (lastDirection == RightDir || shouldGoDirection == RightDir) { //go right 336 | if (x == imageWidth - 1 || y == 0 || y == imageHeight - 1) { //reach the image border 337 | break; 338 | } 339 | // Look at 3 neighbors to the right and pick the one with the max. gradient value 340 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 341 | gValue2 = (unsigned char)pgImg[indexInArray + 1]; 342 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 343 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 344 | x = x + 1; 345 | y = y - 1; 346 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-right 347 | x = x + 1; 348 | y = y + 1; 349 | } else { //straight-right 350 | x = x + 1; 351 | } 352 | lastDirection = RightDir; 353 | } else if (lastDirection == LeftDir || shouldGoDirection == LeftDir) { //go left 354 | if (x == 0 || y == 0 || y == imageHeight - 1) { //reach the image border 355 | break; 356 | } 357 | // Look at 3 neighbors to the left and pick the one with the max. gradient value 358 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 359 | gValue2 = (unsigned char)pgImg[indexInArray - 1]; 360 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 361 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-left 362 | x = x - 1; 363 | y = y - 1; 364 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 365 | x = x - 1; 366 | y = y + 1; 367 | } else { //straight-left 368 | x = x - 1; 369 | } 370 | lastDirection = LeftDir; 371 | } 372 | } else { //should go up or down. 373 | if (lastDirection == RightDir || lastDirection == LeftDir) { //change the pixel direction now 374 | if (y > lastY) { //should go down 375 | shouldGoDirection = DownDir; 376 | } else { //should go up 377 | shouldGoDirection = UpDir; 378 | } 379 | } 380 | lastX = x; 381 | lastY = y; 382 | if (lastDirection == DownDir || shouldGoDirection == DownDir) { //go down 383 | if (x == 0 || x == imageWidth - 1 || y == imageHeight - 1) { //reach the image border 384 | break; 385 | } 386 | // Look at 3 neighbors to the down and pick the one with the max. gradient value 387 | gValue1 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 388 | gValue2 = (unsigned char)pgImg[indexInArray + imageWidth]; 389 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 390 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //down-right 391 | x = x + 1; 392 | y = y + 1; 393 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 394 | x = x - 1; 395 | y = y + 1; 396 | } else { //straight-down 397 | y = y + 1; 398 | } 399 | lastDirection = DownDir; 400 | } else if (lastDirection == UpDir || shouldGoDirection == UpDir) { //go up 401 | if (x == 0 || x == imageWidth - 1 || y == 0) { //reach the image border 402 | break; 403 | } 404 | // Look at 3 neighbors to the up and pick the one with the max. gradient value 405 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 406 | gValue2 = (unsigned char)pgImg[indexInArray - imageWidth]; 407 | gValue3 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 408 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 409 | x = x + 1; 410 | y = y - 1; 411 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //up-left 412 | x = x - 1; 413 | y = y - 1; 414 | } else { //straight-up 415 | y = y - 1; 416 | } 417 | lastDirection = UpDir; 418 | } 419 | } 420 | indexInArray = y * imageWidth + x; 421 | } //end while go left 422 | //end anchor is Horizontal 423 | } else { //the direction of this pixel is vertical, go up and down 424 | //fist go down, pixel direction may be different during linking. 425 | lastDirection = DownDir; 426 | while (pgImg[indexInArray] > 0 && !pEdgeImg[indexInArray]) { 427 | pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel 428 | pFirstPartEdgeX_[offsetPFirst] = x; 429 | pFirstPartEdgeY_[offsetPFirst++] = y; 430 | shouldGoDirection = 0; //unknown 431 | if (pdirImg[indexInArray] == Horizontal) { //should go left or right 432 | if (lastDirection == UpDir || lastDirection == DownDir) { //change the pixel direction now 433 | if (x > lastX) { //should go right 434 | shouldGoDirection = RightDir; 435 | } else { //should go left 436 | shouldGoDirection = LeftDir; 437 | } 438 | } 439 | lastX = x; 440 | lastY = y; 441 | if (lastDirection == RightDir || shouldGoDirection == RightDir) { //go right 442 | if (x == imageWidth - 1 || y == 0 || y == imageHeight - 1) { //reach the image border 443 | break; 444 | } 445 | // Look at 3 neighbors to the right and pick the one with the max. gradient value 446 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 447 | gValue2 = (unsigned char)pgImg[indexInArray + 1]; 448 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 449 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 450 | x = x + 1; 451 | y = y - 1; 452 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-right 453 | x = x + 1; 454 | y = y + 1; 455 | } else { //straight-right 456 | x = x + 1; 457 | } 458 | lastDirection = RightDir; 459 | } else if (lastDirection == LeftDir || shouldGoDirection == LeftDir) { //go left 460 | if (x == 0 || y == 0 || y == imageHeight - 1) { //reach the image border 461 | break; 462 | } 463 | // Look at 3 neighbors to the left and pick the one with the max. gradient value 464 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 465 | gValue2 = (unsigned char)pgImg[indexInArray - 1]; 466 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 467 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-left 468 | x = x - 1; 469 | y = y - 1; 470 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 471 | x = x - 1; 472 | y = y + 1; 473 | } else { //straight-left 474 | x = x - 1; 475 | } 476 | lastDirection = LeftDir; 477 | } 478 | } else { //should go up or down. 479 | if (lastDirection == RightDir || lastDirection == LeftDir) { //change the pixel direction now 480 | if (y > lastY) { //should go down 481 | shouldGoDirection = DownDir; 482 | } else { //should go up 483 | shouldGoDirection = UpDir; 484 | } 485 | } 486 | lastX = x; 487 | lastY = y; 488 | if (lastDirection == DownDir || shouldGoDirection == DownDir) { //go down 489 | if (x == 0 || x == imageWidth - 1 || y == imageHeight - 1) { //reach the image border 490 | break; 491 | } 492 | // Look at 3 neighbors to the down and pick the one with the max. gradient value 493 | gValue1 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 494 | gValue2 = (unsigned char)pgImg[indexInArray + imageWidth]; 495 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 496 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //down-right 497 | x = x + 1; 498 | y = y + 1; 499 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 500 | x = x - 1; 501 | y = y + 1; 502 | } else { //straight-down 503 | y = y + 1; 504 | } 505 | lastDirection = DownDir; 506 | } else if (lastDirection == UpDir || shouldGoDirection == UpDir) { //go up 507 | if (x == 0 || x == imageWidth - 1 || y == 0) { //reach the image border 508 | break; 509 | } 510 | // Look at 3 neighbors to the up and pick the one with the max. gradient value 511 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 512 | gValue2 = (unsigned char)pgImg[indexInArray - imageWidth]; 513 | gValue3 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 514 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 515 | x = x + 1; 516 | y = y - 1; 517 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //up-left 518 | x = x - 1; 519 | y = y - 1; 520 | } else { //straight-up 521 | y = y - 1; 522 | } 523 | lastDirection = UpDir; 524 | } 525 | } 526 | indexInArray = y * imageWidth + x; 527 | } //end while go down 528 | //then go up, pixel direction may be different during linking. 529 | lastDirection = UpDir; 530 | x = pAnchorX_[i]; 531 | y = pAnchorY_[i]; 532 | indexInArray = y * imageWidth + x; 533 | pEdgeImg[indexInArray] = 0; //mark the anchor point be a non-edge pixel and 534 | pSecondPartEdgeS_[offsetPS] = offsetPSecond; 535 | while (pgImg[indexInArray] > 0 && !pEdgeImg[indexInArray]) { 536 | pEdgeImg[indexInArray] = 1; // Mark this pixel as an edge pixel 537 | pSecondPartEdgeX_[offsetPSecond] = x; 538 | pSecondPartEdgeY_[offsetPSecond++] = y; 539 | shouldGoDirection = 0; //unknown 540 | if (pdirImg[indexInArray] == Horizontal) { //should go left or right 541 | if (lastDirection == UpDir || lastDirection == DownDir) { //change the pixel direction now 542 | if (x > lastX) { //should go right 543 | shouldGoDirection = RightDir; 544 | } else { //should go left 545 | shouldGoDirection = LeftDir; 546 | } 547 | } 548 | lastX = x; 549 | lastY = y; 550 | if (lastDirection == RightDir || shouldGoDirection == RightDir) { //go right 551 | if (x == imageWidth - 1 || y == 0 || y == imageHeight - 1) { //reach the image border 552 | break; 553 | } 554 | // Look at 3 neighbors to the right and pick the one with the max. gradient value 555 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 556 | gValue2 = (unsigned char)pgImg[indexInArray + 1]; 557 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 558 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 559 | x = x + 1; 560 | y = y - 1; 561 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-right 562 | x = x + 1; 563 | y = y + 1; 564 | } else { //straight-right 565 | x = x + 1; 566 | } 567 | lastDirection = RightDir; 568 | } else if (lastDirection == LeftDir || shouldGoDirection == LeftDir) { //go left 569 | if (x == 0 || y == 0 || y == imageHeight - 1) { //reach the image border 570 | break; 571 | } 572 | // Look at 3 neighbors to the left and pick the one with the max. gradient value 573 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 574 | gValue2 = (unsigned char)pgImg[indexInArray - 1]; 575 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 576 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-left 577 | x = x - 1; 578 | y = y - 1; 579 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 580 | x = x - 1; 581 | y = y + 1; 582 | } else { //straight-left 583 | x = x - 1; 584 | } 585 | lastDirection = LeftDir; 586 | } 587 | } else { //should go up or down. 588 | if (lastDirection == RightDir || lastDirection == LeftDir) { //change the pixel direction now 589 | if (y > lastY) { //should go down 590 | shouldGoDirection = DownDir; 591 | } else { //should go up 592 | shouldGoDirection = UpDir; 593 | } 594 | } 595 | lastX = x; 596 | lastY = y; 597 | if (lastDirection == DownDir || shouldGoDirection == DownDir) { //go down 598 | if (x == 0 || x == imageWidth - 1 || y == imageHeight - 1) { //reach the image border 599 | break; 600 | } 601 | // Look at 3 neighbors to the down and pick the one with the max. gradient value 602 | gValue1 = (unsigned char)pgImg[indexInArray + imageWidth + 1]; 603 | gValue2 = (unsigned char)pgImg[indexInArray + imageWidth]; 604 | gValue3 = (unsigned char)pgImg[indexInArray + imageWidth - 1]; 605 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //down-right 606 | x = x + 1; 607 | y = y + 1; 608 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //down-left 609 | x = x - 1; 610 | y = y + 1; 611 | } else { //straight-down 612 | y = y + 1; 613 | } 614 | lastDirection = DownDir; 615 | } else if (lastDirection == UpDir || shouldGoDirection == UpDir) { //go up 616 | if (x == 0 || x == imageWidth - 1 || y == 0) { //reach the image border 617 | break; 618 | } 619 | // Look at 3 neighbors to the up and pick the one with the max. gradient value 620 | gValue1 = (unsigned char)pgImg[indexInArray - imageWidth + 1]; 621 | gValue2 = (unsigned char)pgImg[indexInArray - imageWidth]; 622 | gValue3 = (unsigned char)pgImg[indexInArray - imageWidth - 1]; 623 | if (gValue1 >= gValue2 && gValue1 >= gValue3) { //up-right 624 | x = x + 1; 625 | y = y - 1; 626 | } else if (gValue3 >= gValue2 && gValue3 >= gValue1) { //up-left 627 | x = x - 1; 628 | y = y - 1; 629 | } else { //straight-up 630 | y = y - 1; 631 | } 632 | lastDirection = UpDir; 633 | } 634 | } 635 | indexInArray = y * imageWidth + x; 636 | } //end while go up 637 | } //end anchor is Vertical 638 | //only keep the edge chains whose length is larger than the minLineLen_; 639 | edgeLenFirst = offsetPFirst - pFirstPartEdgeS_[offsetPS]; 640 | edgeLenSecond = offsetPSecond - pSecondPartEdgeS_[offsetPS]; 641 | if (edgeLenFirst + edgeLenSecond < minLineLen_ + 1) { //short edge, drop it 642 | offsetPFirst = pFirstPartEdgeS_[offsetPS]; 643 | offsetPSecond = pSecondPartEdgeS_[offsetPS]; 644 | } else { 645 | offsetPS++; 646 | } 647 | } 648 | 649 | // gettimeofday(&t2, NULL); 650 | // std::cout << "4:" << (t2.tv_sec - t1.tv_sec) * 1000. + (t2.tv_usec - t1.tv_usec) / 1000. << std::endl; 651 | 652 | //store the last index 653 | pFirstPartEdgeS_[offsetPS] = offsetPFirst; 654 | pSecondPartEdgeS_[offsetPS] = offsetPSecond; 655 | if (offsetPS > maxNumOfEdge) { 656 | std::cout << "Edge drawing Error: The total number of edges is larger than MaxNumOfEdge, " 657 | "numofedge = " 658 | << offsetPS << ", MaxNumOfEdge=" << maxNumOfEdge << std::endl; 659 | return -1; 660 | } 661 | if (offsetPFirst > edgePixelArraySize || offsetPSecond > edgePixelArraySize) { 662 | std::cout << "Edge drawing Error: The total number of edge pixels is larger than MaxNumOfEdgePixels, " 663 | "numofedgePixel1 = " 664 | << offsetPFirst << ", numofedgePixel2 = " << offsetPSecond << ", MaxNumOfEdgePixel=" << edgePixelArraySize << std::endl; 665 | return -1; 666 | } 667 | if (!(offsetPFirst && offsetPSecond)) { 668 | std::cout << "Edge drawing Error: lines not found" << std::endl; 669 | return -1; 670 | } 671 | 672 | /*now all the edge information are stored in pFirstPartEdgeX_, pFirstPartEdgeY_, 673 | *pFirstPartEdgeS_, pSecondPartEdgeX_, pSecondPartEdgeY_, pSecondPartEdgeS_; 674 | *we should reorganize them into edgeChains for easily using. */ 675 | int tempID; 676 | edgeChains.xCors.resize(offsetPFirst + offsetPSecond); 677 | edgeChains.yCors.resize(offsetPFirst + offsetPSecond); 678 | edgeChains.sId.resize(offsetPS + 1); 679 | unsigned int *pxCors = &edgeChains.xCors.front(); 680 | unsigned int *pyCors = &edgeChains.yCors.front(); 681 | unsigned int *psId = &edgeChains.sId.front(); 682 | offsetPFirst = 0; 683 | offsetPSecond = 0; 684 | unsigned int indexInCors = 0; 685 | unsigned int numOfEdges = 0; 686 | // gettimeofday(&t1, NULL); 687 | for (unsigned int edgeId = 0; edgeId < offsetPS; edgeId++) { 688 | //step1, put the first and second parts edge coordinates together from edge start to edge end 689 | psId[numOfEdges++] = indexInCors; 690 | indexInArray = pFirstPartEdgeS_[edgeId]; 691 | offsetPFirst = pFirstPartEdgeS_[edgeId + 1]; 692 | for (tempID = offsetPFirst - 1; tempID >= indexInArray; tempID--) { //add first part edge 693 | pxCors[indexInCors] = pFirstPartEdgeX_[tempID]; 694 | pyCors[indexInCors++] = pFirstPartEdgeY_[tempID]; 695 | } 696 | indexInArray = pSecondPartEdgeS_[edgeId]; 697 | offsetPSecond = pSecondPartEdgeS_[edgeId + 1]; 698 | for (tempID = indexInArray + 1; tempID < (int)offsetPSecond; tempID++) { //add second part edge 699 | pxCors[indexInCors] = pSecondPartEdgeX_[tempID]; 700 | pyCors[indexInCors++] = pSecondPartEdgeY_[tempID]; 701 | } 702 | } 703 | // gettimeofday(&t2, NULL); 704 | // std::cout << "5:" << (t2.tv_sec - t1.tv_sec) * 1000. + (t2.tv_usec - t1.tv_usec) / 1000. << std::endl; 705 | 706 | psId[numOfEdges] = indexInCors; //the end index of the last edge 707 | edgeChains.numOfEdges = numOfEdges; 708 | 709 | return 1; 710 | } 711 | 712 | EDLineDetector::EDLineDetectorParallel::EDLineDetectorParallel(EDLineDetector *_edline, 713 | EdgeChains *_edges, 714 | double _logNT, 715 | cv::Mutex &_lock, 716 | std::vector *_lines) : edline(_edline), edges(_edges), logNT(_logNT), lock(_lock), lines(_lines) { 717 | minLineLen = edline->minLineLen_; 718 | lineFitErrThreshold = edline->lineFitErrThreshold_; 719 | imageWidth = edline->imageWidth; 720 | imageHeight = edline->imageHeight; 721 | 722 | dirImg = edline->dirImg_; 723 | dxImg = edline->dxImg_; //store the dxImg; 724 | dyImg = edline->dyImg_; //store the dyImg; 725 | 726 | bValidate = true; 727 | } 728 | 729 | double EDLineDetector::EDLineDetectorParallel::LeastSquaresLineFit(cv::Mat_ &ATA, cv::Mat_ &ATV, cv::Mat_ &fitMatT, cv::Mat_ &fitVec, 730 | unsigned int *xCors, unsigned int *yCors, 731 | unsigned int offsetS, std::array &lineEquation) const { 732 | if (lineEquation.size() != 2) { 733 | std::cout << "SHOULD NOT BE != 2" << std::endl; 734 | CV_Assert(false); 735 | } 736 | 737 | float *pMatT; 738 | float *pATA; 739 | double fitError = 0; 740 | double coef; 741 | unsigned char *pdirImg = dirImg.data; 742 | unsigned int offset = offsetS; 743 | /*If the first pixel in this chain is horizontal, 744 | *then we try to find a horizontal line, y=ax+b;*/ 745 | if (pdirImg[yCors[offsetS] * imageWidth + xCors[offsetS]] == Horizontal) { 746 | /*Build the system,and solve it using least square regression: mat * [a,b]^T = vec 747 | * [x0,1] [y0] 748 | * [x1,1] [a] [y1] 749 | * . [b] = . 750 | * [xn,1] [yn]*/ 751 | pMatT = fitMatT.ptr(); //fitMatT = [x0, x1, ... xn; 1,1,...,1]; 752 | for (int i = 0; i < minLineLen; i++) { 753 | //*(pMatT+minLineLen_) = 1; //the value are not changed; 754 | *(pMatT++) = xCors[offsetS]; 755 | fitVec[0][i] = yCors[offsetS++]; 756 | } 757 | ATA = fitMatT * fitMatT.t(); 758 | ATV = fitMatT * fitVec.t(); 759 | /* [a,b]^T = Inv(mat^T * mat) * mat^T * vec */ 760 | pATA = ATA.ptr(); 761 | coef = 1.0 / (double(pATA[0]) * double(pATA[3]) - double(pATA[1]) * double(pATA[2])); 762 | // lineEquation = svd.Invert(ATA) * matT * vec; 763 | lineEquation[0] = coef * (double(pATA[3]) * double(ATV[0][0]) - double(pATA[1]) * double(ATV[0][1])); 764 | lineEquation[1] = coef * (double(pATA[0]) * double(ATV[0][1]) - double(pATA[2]) * double(ATV[0][0])); 765 | 766 | /*compute line fit error */ 767 | for (int i = 0; i < minLineLen; i++) { 768 | coef = double(yCors[offset]) - double(xCors[offset++]) * lineEquation[0] - lineEquation[1]; 769 | fitError += coef * coef; 770 | } 771 | return sqrt(fitError); 772 | } 773 | /*If the first pixel in this chain is vertical, 774 | *then we try to find a vertical line, x=ay+b;*/ 775 | if (pdirImg[yCors[offsetS] * imageWidth + xCors[offsetS]] == Vertical) { 776 | /*Build the system,and solve it using least square regression: mat * [a,b]^T = vec 777 | * [y0,1] [x0] 778 | * [y1,1] [a] [x1] 779 | * . [b] = . 780 | * [yn,1] [xn]*/ 781 | pMatT = fitMatT.ptr(); //fitMatT = [y0, y1, ... yn; 1,1,...,1]; 782 | for (int i = 0; i < minLineLen; i++) { 783 | //*(pMatT+minLineLen_) = 1;//the value are not changed; 784 | *(pMatT++) = yCors[offsetS]; 785 | fitVec[0][i] = xCors[offsetS++]; 786 | } 787 | ATA = fitMatT * (fitMatT.t()); 788 | ATV = fitMatT * fitVec.t(); 789 | /* [a,b]^T = Inv(mat^T * mat) * mat^T * vec */ 790 | pATA = ATA.ptr(); 791 | coef = 1.0 / (double(pATA[0]) * double(pATA[3]) - double(pATA[1]) * double(pATA[2])); 792 | // lineEquation = svd.Invert(ATA) * matT * vec; 793 | lineEquation[0] = coef * (double(pATA[3]) * double(ATV[0][0]) - double(pATA[1]) * double(ATV[0][1])); 794 | lineEquation[1] = coef * (double(pATA[0]) * double(ATV[0][1]) - double(pATA[2]) * double(ATV[0][0])); 795 | /*compute line fit error */ 796 | for (int i = 0; i < minLineLen; i++) { 797 | coef = double(xCors[offset]) - double(yCors[offset++]) * lineEquation[0] - lineEquation[1]; 798 | fitError += coef * coef; 799 | } 800 | return sqrt(fitError); 801 | } 802 | return 0; 803 | } 804 | 805 | double EDLineDetector::EDLineDetectorParallel::LeastSquaresLineFit(cv::Mat_ &ATA, cv::Mat_ &ATV, cv::Mat_ &tempMatLineFit, cv::Mat_ &tempVecLineFit, 806 | unsigned int *xCors, unsigned int *yCors, 807 | unsigned int offsetS, unsigned int newOffsetS, 808 | unsigned int offsetE, std::array &lineEquation) const { 809 | int length = offsetE - offsetS; 810 | int newLength = offsetE - newOffsetS; 811 | if (length <= 0 || newLength <= 0) { 812 | cout << "EDLineDetector::LeastSquaresLineFit_ Error:" 813 | " the expected line index is wrong...offsetE = " 814 | << offsetE << ", offsetS=" << offsetS << ", newOffsetS=" << newOffsetS << endl; 815 | return -1; 816 | } 817 | if (lineEquation.size() != 2) { 818 | std::cout << "SHOULD NOT BE != 2" << std::endl; 819 | CV_Assert(false); 820 | } 821 | cv::Mat_ matT(2, newLength); 822 | cv::Mat_ vec(newLength, 1); 823 | float *pMatT; 824 | float *pATA; 825 | // double fitError = 0; 826 | double coef; 827 | unsigned char *pdirImg = dirImg.data; 828 | /*If the first pixel in this chain is horizontal, 829 | *then we try to find a horizontal line, y=ax+b;*/ 830 | if (pdirImg[yCors[offsetS] * imageWidth + xCors[offsetS]] == Horizontal) { 831 | /*Build the new system,and solve it using least square regression: mat * [a,b]^T = vec 832 | * [x0',1] [y0'] 833 | * [x1',1] [a] [y1'] 834 | * . [b] = . 835 | * [xn',1] [yn']*/ 836 | pMatT = matT.ptr(); //matT = [x0', x1', ... xn'; 1,1,...,1] 837 | for (int i = 0; i < newLength; i++) { 838 | *(pMatT + newLength) = 1; 839 | *(pMatT++) = xCors[newOffsetS]; 840 | vec[0][i] = yCors[newOffsetS++]; 841 | } 842 | /* [a,b]^T = Inv(ATA + mat^T * mat) * (ATV + mat^T * vec) */ 843 | tempMatLineFit = matT * matT.t(); 844 | tempVecLineFit = matT * vec; 845 | ATA = ATA + tempMatLineFit; 846 | ATV = ATV + tempVecLineFit; 847 | pATA = ATA.ptr(); 848 | coef = 1.0 / (double(pATA[0]) * double(pATA[3]) - double(pATA[1]) * double(pATA[2])); 849 | lineEquation[0] = coef * (double(pATA[3]) * double(ATV[0][0]) - double(pATA[1]) * double(ATV[0][1])); 850 | lineEquation[1] = coef * (double(pATA[0]) * double(ATV[0][1]) - double(pATA[2]) * double(ATV[0][0])); 851 | /*compute line fit error */ 852 | // for(int i=0; i(); //matT = [y0', y1', ... yn'; 1,1,...,1] 868 | for (int i = 0; i < newLength; i++) { 869 | *(pMatT + newLength) = 1; 870 | *(pMatT++) = yCors[newOffsetS]; 871 | vec[0][i] = xCors[newOffsetS++]; 872 | } 873 | /* [a,b]^T = Inv(ATA + mat^T * mat) * (ATV + mat^T * vec) */ 874 | // matT.MultiplyWithTransposeOf(matT, tempMatLineFit); 875 | tempMatLineFit = matT * matT.t(); 876 | tempVecLineFit = matT * vec; 877 | ATA = ATA + tempMatLineFit; 878 | ATV = ATV + tempVecLineFit; 879 | // pATA = ATA.GetData(); 880 | pATA = ATA.ptr(); 881 | coef = 1.0 / (double(pATA[0]) * double(pATA[3]) - double(pATA[1]) * double(pATA[2])); 882 | lineEquation[0] = coef * (double(pATA[3]) * double(ATV[0][0]) - double(pATA[1]) * double(ATV[0][1])); 883 | lineEquation[1] = coef * (double(pATA[0]) * double(ATV[0][1]) - double(pATA[2]) * double(ATV[0][0])); 884 | /*compute line fit error */ 885 | // for(int i=0; i &lineEquation, float &direction) const { 896 | if (bValidate) { 897 | int n = offsetE - offsetS; 898 | /*first compute the direction of line, make sure that the dark side always be the 899 | *left side of a line.*/ 900 | int meanGradientX = 0, meanGradientY = 0; 901 | const short *pdxImg = dxImg.ptr(); 902 | const short *pdyImg = dyImg.ptr(); 903 | double dx, dy; 904 | std::vector pointDirection; 905 | int index; 906 | for (int i = 0; i < n; i++) { 907 | index = yCors[offsetS] * imageWidth + xCors[offsetS++]; 908 | meanGradientX += pdxImg[index]; 909 | meanGradientY += pdyImg[index]; 910 | dx = (double)pdxImg[index]; 911 | dy = (double)pdyImg[index]; 912 | pointDirection.push_back(atan2(-dx, dy)); 913 | } 914 | dx = fabs(lineEquation[1]); 915 | dy = fabs(lineEquation[0]); 916 | if (meanGradientX == 0 && meanGradientY == 0) { //not possible, if happens, it must be a wrong line, 917 | return false; 918 | } 919 | if (meanGradientX > 0 && meanGradientY >= 0) { //first quadrant, and positive direction of X axis. 920 | direction = atan2(-dy, dx); //line direction is in fourth quadrant 921 | } 922 | if (meanGradientX <= 0 && meanGradientY > 0) { //second quadrant, and positive direction of Y axis. 923 | direction = atan2(dy, dx); //line direction is in first quadrant 924 | } 925 | if (meanGradientX < 0 && meanGradientY <= 0) { //third quadrant, and negative direction of X axis. 926 | direction = atan2(dy, -dx); //line direction is in second quadrant 927 | } 928 | if (meanGradientX >= 0 && meanGradientY < 0) { //fourth quadrant, and negative direction of Y axis. 929 | direction = atan2(-dy, -dx); //line direction is in third quadrant 930 | } 931 | /*then check whether the line is on the border of the image. We don't keep the border line.*/ 932 | if (fabs(direction) < 0.15 || M_PI - fabs(direction) < 0.15) { //Horizontal line 933 | if (fabs(lineEquation[2]) < 10 || fabs(imageHeight - fabs(lineEquation[2])) < 10) { //upper border or lower border 934 | return false; 935 | } 936 | } 937 | if (fabs(fabs(direction) - M_PI * 0.5) < 0.15) { //Vertical line 938 | if (fabs(lineEquation[2]) < 10 || fabs(imageWidth - fabs(lineEquation[2])) < 10) { //left border or right border 939 | return false; 940 | } 941 | } 942 | //count the aligned points on the line which have the same direction as the line. 943 | double disDirection; 944 | int k = 0; 945 | for (int i = 0; i < n; i++) { 946 | disDirection = fabs(direction - pointDirection[i]); 947 | if (fabs(2 * M_PI - disDirection) < 0.392699 || disDirection < 0.392699) { //same direction, pi/8 = 0.392699081698724 948 | k++; 949 | } 950 | } 951 | //now compute NFA(Number of False Alarms) 952 | double ret = nfa(n, k, 0.125, logNT); 953 | 954 | return (ret > 0); //0 corresponds to 1 mean false alarm 955 | } else { 956 | return true; 957 | } 958 | } 959 | 960 | void EDLineDetector::EDLineDetectorParallel::operator()(const cv::Range &range) const { 961 | cv::Mat_ ATA = cv::Mat_(2, 2); //the previous matrix of A^T * A; 962 | cv::Mat_ ATV = cv::Mat_(1, 2); //the previous vector of A^T * V; 963 | cv::Mat_ fitMatT = cv::Mat_(2, minLineLen); //the matrix used in line fit function; 964 | cv::Mat_ fitVec = cv::Mat_(1, minLineLen); //the vector used in line fit function; 965 | cv::Mat_ tempMatLineFit = cv::Mat_(2, 2); //the matrix used in line fit function; 966 | cv::Mat_ tempVecLineFit = cv::Mat_(1, 2); //the vector used in line fit function; 967 | 968 | for (int i = 0; i < minLineLen; i++) { 969 | fitMatT[1][i] = 1; 970 | } 971 | 972 | double lineFitErr = 0; //the line fit error; 973 | unsigned int newOffsetS = 0; 974 | std::array lineEquation; 975 | unsigned int offsetInEdgeArrayS, offsetInEdgeArrayE, offsetInEdgeArrayS_init; //start index and end index 976 | float direction; 977 | 978 | unsigned int *pEdgeXCors = &edges->xCors.front(); 979 | unsigned int *pEdgeYCors = &edges->yCors.front(); 980 | unsigned int *pEdgeSID = &edges->sId.front(); 981 | unsigned char *pdirImg = dirImg.data; //line direction 982 | 983 | for (int edgeID = range.start; edgeID < range.end; edgeID++) { 984 | offsetInEdgeArrayS = pEdgeSID[edgeID]; 985 | offsetInEdgeArrayE = pEdgeSID[edgeID + 1]; 986 | offsetInEdgeArrayS_init = offsetInEdgeArrayS; 987 | while (offsetInEdgeArrayE > offsetInEdgeArrayS + minLineLen) { //extract line segments from an edge, may find more than one segments 988 | //find an initial line segment 989 | while (offsetInEdgeArrayE > offsetInEdgeArrayS + minLineLen) { 990 | lineFitErr = LeastSquaresLineFit(ATA, ATV, fitMatT, fitVec, 991 | pEdgeXCors, pEdgeYCors, offsetInEdgeArrayS, lineEquation); 992 | if (lineFitErr <= lineFitErrThreshold) 993 | break; //ok, an initial line segment detected 994 | offsetInEdgeArrayS += SkipEdgePoint; //skip the first two pixel in the chain and try with the remaining pixels 995 | } 996 | if (lineFitErr > lineFitErrThreshold) 997 | break; //no line is detected 998 | //An initial line segment is detected. Try to extend this line segment 999 | double coef1 = 0; //for a line ax+by+c=0, coef1 = 1/sqrt(a^2+b^2); 1000 | double pointToLineDis; //for a line ax+by+c=0 and a point(xi, yi), pointToLineDis = coef1*|a*xi+b*yi+c| 1001 | bool bExtended = true; 1002 | bool bFirstTry = true; 1003 | int numOfOutlier; //to against noise, we accept a few outlier of a line. 1004 | int tryTimes = 0; 1005 | if (pdirImg[pEdgeYCors[offsetInEdgeArrayS] * imageWidth + pEdgeXCors[offsetInEdgeArrayS]] == Horizontal) { //y=ax+b, i.e. ax-y+b=0 1006 | // if (false) { //y=ax+b, i.e. ax-y+b=0 1007 | offsetInEdgeArrayS_init = offsetInEdgeArrayS; 1008 | while (bExtended) { 1009 | tryTimes++; 1010 | if (bFirstTry) { 1011 | bFirstTry = false; 1012 | offsetInEdgeArrayS += minLineLen; 1013 | } else { //after each try, line is extended, line equation should be re-estimated 1014 | //adjust the line equation 1015 | lineFitErr = LeastSquaresLineFit(ATA, ATV, tempMatLineFit, tempVecLineFit, 1016 | pEdgeXCors, pEdgeYCors, offsetInEdgeArrayS_init, newOffsetS, offsetInEdgeArrayS, lineEquation); 1017 | } 1018 | 1019 | coef1 = 1 / sqrt(lineEquation[0] * lineEquation[0] + 1); 1020 | numOfOutlier = 0; 1021 | newOffsetS = offsetInEdgeArrayS; 1022 | while (offsetInEdgeArrayE > offsetInEdgeArrayS) { 1023 | pointToLineDis = fabs(lineEquation[0] * pEdgeXCors[offsetInEdgeArrayS] - pEdgeYCors[offsetInEdgeArrayS] + lineEquation[1]) * coef1; 1024 | offsetInEdgeArrayS++; 1025 | if (pointToLineDis > lineFitErrThreshold) { 1026 | numOfOutlier++; 1027 | if (numOfOutlier > 3) 1028 | break; 1029 | } else { //we count number of connective outliers. 1030 | numOfOutlier = 0; 1031 | } 1032 | } 1033 | //pop back the last few outliers from lines and return them to edge chain 1034 | offsetInEdgeArrayS -= numOfOutlier; 1035 | if (offsetInEdgeArrayS - newOffsetS > 0 && tryTimes < TryTime) { //some new pixels are added to the line 1036 | } else { 1037 | bExtended = false; //no new pixels are added. 1038 | } 1039 | } 1040 | //the line equation coefficients,for line w1x+w2y+w3 =0, we normalize it to make w1^2+w2^2 = 1. 1041 | std::array lineEqu; 1042 | lineEqu[0] = lineEquation[0] * coef1; 1043 | lineEqu[1] = -1 * coef1; 1044 | lineEqu[2] = lineEquation[1] * coef1; 1045 | if (LineValidation(pEdgeXCors, pEdgeYCors, offsetInEdgeArrayS_init, offsetInEdgeArrayS, lineEqu, direction)) { 1046 | //store the line equation coefficients // lineEquations_.push_back( lineEqu ); 1047 | Line line; 1048 | line.line_equation = lineEqu; 1049 | /*At last, compute the line endpoints and store them. 1050 | *we project the first and last pixels in the pixelChain onto the best fit line 1051 | *to get the line endpoints. 1052 | *xp= (w2^2*x0-w1*w2*y0-w3*w1)/(w1^2+w2^2) 1053 | *yp= (w1^2*y0-w1*w2*x0-w3*w2)/(w1^2+w2^2) */ 1054 | // std::array lineEndP; //line endpoints 1055 | 1056 | double a1 = lineEqu[1] * lineEqu[1]; 1057 | double a2 = lineEqu[0] * lineEqu[0]; 1058 | double a3 = lineEqu[0] * lineEqu[1]; 1059 | double a4 = lineEqu[2] * lineEqu[0]; 1060 | double a5 = lineEqu[2] * lineEqu[1]; 1061 | unsigned int Px = pEdgeXCors[offsetInEdgeArrayS_init]; //first pixel 1062 | unsigned int Py = pEdgeYCors[offsetInEdgeArrayS_init]; 1063 | float x1 = (float)(a1 * Px - a3 * Py - a4); //x 1064 | float y1 = (float)(a2 * Py - a3 * Px - a5); //y 1065 | 1066 | Px = pEdgeXCors[offsetInEdgeArrayS - 1]; //last pixel 1067 | Py = pEdgeYCors[offsetInEdgeArrayS - 1]; 1068 | float x2 = (float)(a1 * Px - a3 * Py - a4); //x 1069 | float y2 = (float)(a2 * Py - a3 * Px - a5); //y 1070 | 1071 | line.line_endpoint[0] = x1; 1072 | line.line_endpoint[1] = y1; 1073 | line.line_endpoint[2] = x2; 1074 | line.line_endpoint[3] = y2; 1075 | 1076 | line.center[0] = (x1 + x2) / 2.0; 1077 | line.center[1] = (y1 + y2) / 2.0; 1078 | 1079 | line.length = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); 1080 | { 1081 | lock.lock(); 1082 | lines->push_back(line); 1083 | lock.unlock(); 1084 | } 1085 | } 1086 | 1087 | } else { //x=ay+b, i.e. x-ay-b=0 1088 | // offsetInLineArray = offsetInEdgeArrayS; 1089 | offsetInEdgeArrayS_init = offsetInEdgeArrayS; 1090 | while (bExtended) { 1091 | tryTimes++; 1092 | if (bFirstTry) { 1093 | bFirstTry = false; 1094 | offsetInEdgeArrayS += minLineLen; 1095 | } else { //after each try, line is extended, line equation should be re-estimated 1096 | //adjust the line equation 1097 | lineFitErr = LeastSquaresLineFit(ATA, ATV, tempMatLineFit, tempVecLineFit, 1098 | pEdgeXCors, pEdgeYCors, offsetInEdgeArrayS_init, newOffsetS, offsetInEdgeArrayS, lineEquation); 1099 | } 1100 | coef1 = 1 / sqrt(1 + lineEquation[0] * lineEquation[0]); 1101 | numOfOutlier = 0; 1102 | newOffsetS = offsetInEdgeArrayS; 1103 | while (offsetInEdgeArrayE > offsetInEdgeArrayS) { 1104 | pointToLineDis = fabs(pEdgeXCors[offsetInEdgeArrayS] - lineEquation[0] * pEdgeYCors[offsetInEdgeArrayS] - lineEquation[1]) * coef1; 1105 | offsetInEdgeArrayS++; 1106 | if (pointToLineDis > lineFitErrThreshold) { 1107 | numOfOutlier++; 1108 | if (numOfOutlier > 3) 1109 | break; 1110 | } else { //we count number of connective outliers. 1111 | numOfOutlier = 0; 1112 | } 1113 | } 1114 | //pop back the last few outliers from lines and return them to edge chain 1115 | offsetInEdgeArrayS -= numOfOutlier; 1116 | if (offsetInEdgeArrayS - newOffsetS > 0 && tryTimes < TryTime) { //some new pixels are added to the line 1117 | } else { 1118 | bExtended = false; //no new pixels are added. 1119 | } 1120 | } 1121 | //the line equation coefficients,for line w1x+w2y+w3 =0, we normalize it to make w1^2+w2^2 = 1. 1122 | std::array lineEqu; 1123 | lineEqu[0] = 1 * coef1; 1124 | lineEqu[1] = -lineEquation[0] * coef1; 1125 | lineEqu[2] = -lineEquation[1] * coef1; 1126 | 1127 | if (LineValidation(pEdgeXCors, pEdgeYCors, offsetInEdgeArrayS_init, offsetInEdgeArrayS, lineEqu, direction)) { 1128 | // if (LineValidation_(pLineXCors, pLineYCors, lineSID, offsetInLineArray, lineEqu, direction)) { //check the line 1129 | //store the line equation coefficients 1130 | 1131 | Line line; 1132 | line.line_equation = lineEqu; 1133 | 1134 | /*At last, compute the line endpoints and store them. 1135 | *we project the first and last pixels in the pixelChain onto the best fit line 1136 | *to get the line endpoints. 1137 | *xp= (w2^2*x0-w1*w2*y0-w3*w1)/(w1^2+w2^2) 1138 | *yp= (w1^2*y0-w1*w2*x0-w3*w2)/(w1^2+w2^2) */ 1139 | // std::array lineEndP; //line endpoints 1140 | double a1 = lineEqu[1] * lineEqu[1]; 1141 | double a2 = lineEqu[0] * lineEqu[0]; 1142 | double a3 = lineEqu[0] * lineEqu[1]; 1143 | double a4 = lineEqu[2] * lineEqu[0]; 1144 | double a5 = lineEqu[2] * lineEqu[1]; 1145 | unsigned int Px = pEdgeXCors[offsetInEdgeArrayS_init]; //first pixel 1146 | unsigned int Py = pEdgeYCors[offsetInEdgeArrayS_init]; 1147 | float x1 = (float)(a1 * Px - a3 * Py - a4); //x 1148 | float y1 = (float)(a2 * Py - a3 * Px - a5); //y 1149 | Px = pEdgeXCors[offsetInEdgeArrayS - 1]; //last pixel 1150 | Py = pEdgeYCors[offsetInEdgeArrayS - 1]; 1151 | float x2 = (float)(a1 * Px - a3 * Py - a4); //x 1152 | float y2 = (float)(a2 * Py - a3 * Px - a5); //y 1153 | 1154 | line.line_endpoint[0] = x1; 1155 | line.line_endpoint[1] = y1; 1156 | line.line_endpoint[2] = x2; 1157 | line.line_endpoint[3] = y2; 1158 | 1159 | line.center[0] = (x1 + x2) / 2.0; 1160 | line.center[1] = (y1 + y2) / 2.0; 1161 | 1162 | line.length = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); 1163 | 1164 | { 1165 | lock.lock(); 1166 | lines->push_back(line); 1167 | lock.unlock(); 1168 | } 1169 | } 1170 | } 1171 | //Extract line segments from the remaining pixel; Current chain has been shortened already. 1172 | } 1173 | } //end for(unsigned int edgeID=0; edgeID &lines, 1178 | bool smoothed) { 1179 | //first, call EdgeDrawing function to extract edges 1180 | if (!EdgeDrawing(image, edges_, smoothed)) { 1181 | cout << "Line Detection not finished" << endl; 1182 | return -1; 1183 | } 1184 | 1185 | //detect lines 1186 | logNT_ = 2.0 * (log10((double)imageWidth) + log10((double)imageHeight)); 1187 | 1188 | lines.clear(); 1189 | lines.reserve(edges_.numOfEdges); 1190 | 1191 | cv::Mutex lock; 1192 | 1193 | EDLineDetectorParallel line_detector_parallel(this, &edges_, logNT_, lock, &lines); 1194 | 1195 | cv::parallel_for_(cv::Range(0, edges_.numOfEdges), line_detector_parallel, int(edges_.numOfEdges / 20)); 1196 | 1197 | return 1; 1198 | } -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(OpenCV REQUIRED) 2 | 3 | include_directories( 4 | ${OpenCV_INCLUDE_DIRS} 5 | ) 6 | 7 | link_directories( 8 | ${OpenCV_LIBRARY_DIRS} 9 | ) 10 | 11 | add_executable(test_edline_detector test_edline_detector.cpp) 12 | target_link_libraries(test_edline_detector EDLine ${OpenCV_LIBS}) 13 | -------------------------------------------------------------------------------- /test/test_edline_detector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "edline_detector.h" 4 | 5 | void test() { 6 | // ksize, sigma, gradientThreshold, anchorThreshold, scanIntervals, minLineLen, lineFitErrThreshold 7 | EDLineParam param = {5, 1.0, 30, 5, 2, 25, 1.8}; 8 | EDLineDetector line_detctor = EDLineDetector(param); 9 | std::vector lines; 10 | 11 | cv::Mat img = cv::imread("./../data/test.png", 0); 12 | 13 | struct timeval t1, t2; 14 | int test_times = 500; 15 | double run_time, all_run_time_sum = 0; 16 | 17 | for (int i = 0; i < test_times; i++) { 18 | gettimeofday(&t1, NULL); 19 | line_detctor.EDline(img, lines, false); 20 | gettimeofday(&t2, NULL); 21 | 22 | run_time = (t2.tv_sec - t1.tv_sec) * 1000. + (t2.tv_usec - t1.tv_usec) / 1000.; 23 | all_run_time_sum += run_time; 24 | } 25 | 26 | int line_num = (int)lines.size(); 27 | std::cout << "Edline parallel version:"<