├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── images ├── test1.jpg ├── test10.jpg ├── test10_result.jpg ├── test11.jpg ├── test11_result.jpg ├── test12.jpg ├── test12_result.jpg ├── test1_result.jpg ├── test2.jpg ├── test2_result.jpg ├── test3.jpg ├── test3_result.jpg ├── test4.jpg ├── test4_result.jpg ├── test5.jpg ├── test5_result.jpg ├── test6.jpg ├── test6_result.jpg ├── test7.jpg ├── test7_result.jpg ├── test8.jpg ├── test8_result.jpg ├── test9.jpg └── test9_result.jpg ├── include ├── compute.h ├── cvcannyapi.h ├── defines.h ├── detect.h ├── types.hpp └── unitily.h ├── src ├── compute.cpp ├── cvcannyapi.cpp ├── detect.cpp ├── lsd.cpp └── unitily.cpp └── test ├── CMakeLists.txt ├── testdetect.cpp ├── testgroup.cpp ├── testinital.cpp ├── testlsd.cpp └── testtype.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | build 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ## CMake configuration file of Ellipsedetcet project 2 | 3 | cmake_minimum_required (VERSION 2.4) 4 | 5 | if (POLICY CMP0048) 6 | cmake_policy (SET CMP0048 NEW) 7 | endif () 8 | 9 | 10 | if (COMMAND cmake_policy) 11 | cmake_policy (SET CMP0003 NEW) 12 | endif (COMMAND cmake_policy) 13 | 14 | 15 | set (PACKAGE_NAME "ellipse_detection") 16 | set (PACKAGE_VERSION "1.0") 17 | 18 | 19 | project (${PACKAGE_NAME} VERSION ${PACKAGE_VERSION} LANGUAGES CXX) 20 | 21 | set (OpenCV_FOUND 1) 22 | find_package (OpenCV REQUIRED) 23 | 24 | # Manual set open library 25 | #include_directories(/xxx/opencv_tag_v3.2.0) 26 | #include_directories (/xxx/opencv_tag_v3.2.0/include) 27 | #link_directories(/xxx/opencv_tag_v3.2.0/lib) 28 | set (OTHER_LIBS -llapack -lblas -lgfortran) 29 | 30 | set (CMAKE_CXX_STANDARD 11) 31 | set (CMAKE_CXX_STANDARD_REQUIRE ON) 32 | 33 | set (EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}/bin") 34 | set (LIBRARY_OUTPUT_PATH "${PROJECT_BINARY_DIR}/lib") 35 | 36 | include_directories ("${PROJECT_SOURCE_DIR}/include") 37 | 38 | add_definitions ("-Wall") 39 | 40 | set (DEPEND_FILE 41 | "src/unitily.cpp" 42 | "src/detect.cpp" 43 | "src/compute.cpp" 44 | "src/lsd.cpp" 45 | "src/cvcannyapi.cpp" 46 | ) 47 | 48 | set (Opencv_LIBS 49 | -lopencv_core 50 | -lopencv_imgproc 51 | -lopencv_highgui 52 | ) 53 | 54 | add_library (${PACKAGE_NAME} ${DEPEND_FILE}) 55 | target_link_libraries(${PACKAGE_NAME} ${Opencv_LIBS} ${OTHER_LIBS}) 56 | 57 | set (PUBLIC_HDRS 58 | include/types.hpp 59 | include/unitily.h 60 | include/defines.h 61 | include/detect.h 62 | include/compute.h 63 | ) 64 | 65 | set (INCLUDE_INSTALL_DIR "include/${PACKAGE_NAME}") 66 | set (RUNTIME_INSTALL_DIR "bin") 67 | set (LIBRARY_INSTALL_DIR "lib") 68 | 69 | install (FILES ${PUBLIC_HDRS} DESTINATION ${INCLUDE_INSTALL_DIR}/${GFLAGS_INCLUDE_DIR}) 70 | 71 | install (TARGETS ${PACKAGE_NAME} 72 | RUNTIME DESTINATION ${RUNTIME_INSTALL_DIR} 73 | LIBRARY DESTINATION ${LIBRARY_INSTALL_DIR} 74 | ARCHIVE DESTINATION ${LIBRARY_INSTALL_DIR} 75 | ) 76 | 77 | if (BUILD_TESTING) 78 | add_subdirectory (test) 79 | endif () 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zhengaohong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 本库提供了一个可以在工业中稳定使用的椭圆检测方法。对于图像中的标准、明显、完整、大小在 100x100 像素以上的椭圆的检测效果非常好,速度也很快。 3 | 这个库的实现参考了论文 https://arxiv.org/pdf/1810.03243v4.pdf。 4 | 5 | 微信小程序搜索"椭圆识别"可以体验效果(小程序首次使用需要启动服务,第一张图片可能会失败,多试几次)。 6 | 7 | ![image](https://user-images.githubusercontent.com/15645203/226512866-71bbaab5-6e43-41ef-bac7-23b61733269d.png) 8 | 9 | 也可以线上体验:http://43.154.37.202 10 | 11 | 12 | 13 | 14 | # 效果展示 15 | 16 | 17 | ![图1](https://github.com/memory-overflow/standard-ellipse-detection/blob/master/images/test12_result.jpg) 18 | 19 | ![图2](https://github.com/memory-overflow/standard-ellipse-detection/blob/master/images/test6_result.jpg) 20 | 21 | ![图3](https://github.com/memory-overflow/standard-ellipse-detection/blob/master/images/test9_result.jpg) 22 | 23 | 24 | 25 | # 安装 26 | ## Ubuntu 27 | ### Install opencv 28 | opencv 需要通过源码安装,ubuntu opencv 的安装可以参考博客 https://blog.csdn.net/Arthur_Holmes/article/details/100146463, 注意本项目中一定要安装 opencv3 的版本,否则可能会有兼容性问题。服务器版本无需安装图像化相关的库。 29 | 30 | ### Install lapack 31 | lapack 是一个矩阵运算的库,也是需要源码安装。 32 | 先下载[lapack源码](https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v3.9.1.tar.gz),lapack 是 gfortran 写的,要先`sudo apt-get install gfortran`安装gfortran 才能正常编译。 33 | 34 | 执行下面的操作完成安装 35 | ``` 36 | tar -xzvf lapack-3.9.1.tar.gz && cd lapack-3.9.1 37 | mkdir build && cd build 38 | cmake .. 39 | make -j7 40 | sudo make install 41 | sudo ldconfig 42 | cd .. 43 | sudo cp LAPACKE/include/*.h /usr/local/include/ 44 | ``` 45 | 46 | ### Install ellipse-detection 47 | 执行下面的操作完成安装 48 | ``` 49 | git clone https://github.com/memory-overflow/standard-ellipse-detection.git 50 | cd standard-ellipse-detection 51 | mkdir build && cd build 52 | cmake .. 53 | make 54 | sudo make install 55 | ``` 56 | 57 | ## Centos 58 | ### Install opencv 59 | opencv 需要通过源码安装,centos 的 opencv 的安装可以参考博客 https://www.jianshu.com/p/1cb1ca235eb3, 注意本项目中一定要安装 opencv3 的版本,否则可能会有兼容性问题。服务器版本无需安装图像化相关的库。 60 | 61 | 62 | ### Install lapack 63 | lapack 是一个矩阵运算的库,也是需要源码安装。 64 | 先下载[lapack源码](https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v3.9.1.tar.gz),lapack 是 gfortran 写的,要先`yum install gcc-gfortran`安装 gfortran 才能正常编译。 65 | 66 | 由于 lapack 的编译需要 cmake3, 先安装 cmake3, `yum install cmake3` 67 | 68 | 执行下面的操作完成 lapack 的安装 69 | ``` 70 | tar -xzvf lapack-3.9.1.tar.gz && cd lapack-3.9.1 71 | mkdir build && cd build 72 | cmake3 .. 73 | make -j7 74 | sudo make install 75 | sudo ldconfig 76 | cd .. 77 | sudo cp LAPACKE/include/*.h /usr/local/include/ 78 | ``` 79 | 80 | ### Install ellipse-detection 81 | 执行下面的操作完成安装 82 | ``` 83 | git clone https://github.com/memory-overflow/standard-ellipse-detection.git 84 | cd standard-ellipse-detection 85 | mkdir build && cd build 86 | cmake3 .. 87 | make 88 | sudo make install 89 | ``` 90 | 提供一个 centos server 的镜像,可以作为基础镜像开发,里面装好的依赖和库。 91 | ``` 92 | docker pull jisuanke/standard-ellipse-detection-centos7 93 | ``` 94 | 95 | 96 | ## Other system 97 | 不建议在 mac 和 windows 上使用此库。mac 的兼容性比较差,我尝试在 mac 上编译,最后因为 gfortran 链接库不兼容放弃。 98 | windows 上的编译和使用的IDE有关,如需要使用,按照 opencv,lapack, ellipsedetect 的顺序依次编译。 99 | 100 | 其他类unix的操作系统比如安卓可以自行参考 ubuntu 和 centos 的流程编译安装。 101 | 102 | # 接口和使用方法 103 | 代码中引用头文件`#include "ellipse_detection/detect.h"`,然后引入namespace zgh。 104 | 105 | 接口说明 106 | ``` 107 | bool detectEllipse(const uint8_t *image, int height, int width, 108 | std::vector > &ells, 109 | int polarity = 0, double line_width = 2.0); 110 | ``` 111 | - 输入参数: 112 | - image 图像原始数据,灰度图,彩色图需要先转换成灰度图,并且转换成一维数组输入 113 | - height 图像高度 114 | - width 图像宽度 115 | - polarity 表示椭圆极性,-1、0、1, 默认为 0,检测所有极性。 116 | - line_width 椭圆线宽,单位像素,推荐使用默认值 117 | - 输出 118 | - ells 检测到的椭圆列表 119 | 120 | 关于 Ellipse 结构的说明 121 | ``` 122 | Pointd o; // 椭圆中心点坐标 123 | double a, b; // 短半轴长度,长半轴长度 124 | double phi; // 椭圆偏角,单位为弧度 125 | int polarity; // 椭圆极性 126 | double goodness; // 椭圆评分 127 | double coverangle; // 椭圆角度完整程度 128 | std::vector inliers; // 构成的像素点 129 | ``` 130 | 输出的图像坐标系 131 | ![image](https://github.com/memory-overflow/standard-ellipse-detection/assets/15645203/a6f388df-3a51-4dac-8d27-a106d496362a) 132 | 133 | 134 | 135 | # 测试 136 | 提供了1个测试工具,可以查看算法效果。需要桌面版的操作系统才能显示图片,如果是服务器版本的操作系统,需要注释掉 imshow 部分。 137 | ``` 138 | cmake3 .. -DBUILD_TESTING=ON 139 | make 140 | ./bin/testdetect [image_dir1] [image_dir2] [image_dir3] ... 141 | ``` 142 | 143 | 144 | 145 | 146 | 有问题欢迎联系 zhengaohong@gmail.com, wechat: islands___ 147 | -------------------------------------------------------------------------------- /images/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test1.jpg -------------------------------------------------------------------------------- /images/test10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test10.jpg -------------------------------------------------------------------------------- /images/test10_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test10_result.jpg -------------------------------------------------------------------------------- /images/test11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test11.jpg -------------------------------------------------------------------------------- /images/test11_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test11_result.jpg -------------------------------------------------------------------------------- /images/test12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test12.jpg -------------------------------------------------------------------------------- /images/test12_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test12_result.jpg -------------------------------------------------------------------------------- /images/test1_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test1_result.jpg -------------------------------------------------------------------------------- /images/test2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test2.jpg -------------------------------------------------------------------------------- /images/test2_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test2_result.jpg -------------------------------------------------------------------------------- /images/test3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test3.jpg -------------------------------------------------------------------------------- /images/test3_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test3_result.jpg -------------------------------------------------------------------------------- /images/test4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test4.jpg -------------------------------------------------------------------------------- /images/test4_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test4_result.jpg -------------------------------------------------------------------------------- /images/test5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test5.jpg -------------------------------------------------------------------------------- /images/test5_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test5_result.jpg -------------------------------------------------------------------------------- /images/test6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test6.jpg -------------------------------------------------------------------------------- /images/test6_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test6_result.jpg -------------------------------------------------------------------------------- /images/test7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test7.jpg -------------------------------------------------------------------------------- /images/test7_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test7_result.jpg -------------------------------------------------------------------------------- /images/test8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test8.jpg -------------------------------------------------------------------------------- /images/test8_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test8_result.jpg -------------------------------------------------------------------------------- /images/test9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test9.jpg -------------------------------------------------------------------------------- /images/test9_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memory-overflow/standard-ellipse-detection/2ebc16c20cf035e4fa6a4f74306ef21a3d5aae18/images/test9_result.jpg -------------------------------------------------------------------------------- /include/compute.h: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | 9 | #ifndef _INCLUDE_COMPUTE_H_ 10 | #define _INCLUDE_COMPUTE_H_ 11 | 12 | #include "types.hpp" 13 | 14 | namespace zgh { 15 | 16 | bool calculateGradient(const uint8_t *image, int row, int col, double *angles); 17 | 18 | std::shared_ptr calcElliseParam(const std::shared_ptr &arc1, 19 | const std::shared_ptr &arc2, 20 | const double *angles, 21 | int row, 22 | int col); 23 | 24 | bool regionLimitation(const std::shared_ptr &arc1, const std::shared_ptr &arc2); 25 | 26 | bool gaussianSampler(const uint8_t *ori_data, int ori_row, int ori_col, 27 | double *data, int row, int col, 28 | double scale, double sigma_scale); 29 | 30 | std::shared_ptr fitEllipse(const std::vector &points); 31 | 32 | } // namespace zgh 33 | 34 | #endif // _INCLUDE_COMPUTE_H_ 35 | -------------------------------------------------------------------------------- /include/cvcannyapi.h: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-27 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | 9 | #ifndef _INCLUDE_CVCANNYAPI_H_ 10 | #define _INCLUDE_CVCANNYAPI_H_ 11 | #include 12 | 13 | namespace zgh { 14 | 15 | bool calculateGradient3(const uint8_t *image, int row, int col, double * angles); 16 | 17 | } // namespace zgh 18 | 19 | 20 | #endif // _INCLUDE_CVCANNYAPI_H_ 21 | -------------------------------------------------------------------------------- /include/defines.h: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | 9 | #ifndef _INCLUDE_DEFINES_H_ 10 | #define _INCLUDE_DEFINES_H_ 11 | 12 | #include 13 | 14 | namespace zgh { 15 | 16 | #ifndef M_LN10 17 | #define M_LN10 2.30258509299404568402 // ln10 18 | #endif /* !M_LN10 */ 19 | 20 | 21 | #define ANGLE_NOT_DEF -1024.0 22 | 23 | #define NOTUSED 0 24 | 25 | #define USED 1 26 | 27 | #define RELATIVE_ERROR_FACTOR 100.0 28 | 29 | #define NONE_POL 0 30 | 31 | #define SAME_POL 1 32 | 33 | #define OPP_POL -1 34 | 35 | #define TABSIZE 100000 36 | 37 | #define log_gamma(x) ((x) > 15.0? log_gamma_windschitl(x) : log_gamma_lanczos(x)) 38 | 39 | #define PI 3.14159265358979323846 40 | 41 | #define PI_2 1.57079632679489661923 42 | 43 | #define PI_4 0.78539816339744830962 44 | 45 | #define PI_8 0.392699081 46 | 47 | const double DEPS = 1e-8; 48 | 49 | const double EPS = DEPS; 50 | 51 | const float FEPS = 1e-4; 52 | 53 | const double ANGLE_TH = 22.5; 54 | 55 | const double GRAD_THRESHOLD = 2.0 / sin(PI * ANGLE_TH / 180.0); 56 | 57 | const double MIN_ELLIPSE_THRESHOLD_LENGTH = 2.0; 58 | 59 | const double REGION_LIMITATION_DIS_TOLERACE = -3.0 * MIN_ELLIPSE_THRESHOLD_LENGTH; 60 | 61 | } // namespace zgh 62 | 63 | #endif // _INCLUDE_DEFINES_H_ 64 | -------------------------------------------------------------------------------- /include/detect.h: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-22 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #ifndef _INCLUDE_DETECT_H_ 9 | #define _INCLUDE_DETECT_H_ 10 | 11 | #include "types.hpp" 12 | 13 | 14 | namespace zgh { 15 | 16 | bool lineSegmentDetection(const double *image, int row, int col, 17 | std::vector > &lines); 18 | 19 | bool lsdgroups(const double *image, int row, int col, double scale, 20 | std::vector >& arcs); 21 | 22 | bool getValidInitialEllipseSet(const uint8_t *image, 23 | const double *angles, 24 | int row, int col, 25 | std::vector > &ells, 26 | int polarity = 0); 27 | 28 | 29 | bool generateEllipseCandidates(const uint8_t *image, 30 | const double *angles, 31 | int row, int col, 32 | std::vector > &ells, int polarity); 33 | 34 | 35 | bool detectEllipse(const uint8_t *image, int row, int col, 36 | std::vector > &ells, 37 | int polarity = 0, double width = 2.0); 38 | 39 | } 40 | //namespace zgh 41 | 42 | 43 | #endif // _INCLUDE_DETECT_H_ 44 | -------------------------------------------------------------------------------- /include/types.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | 9 | #ifndef _INCLUDE_TYPES_H_ 10 | #define _INCLUDE_TYPES_H_ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "defines.h" 21 | #include "unitily.h" 22 | 23 | namespace zgh { 24 | 25 | 26 | template 27 | class Point_ { 28 | public: 29 | T x, y; 30 | Point_(); 31 | Point_(T _x, T _y); 32 | ~Point_(); 33 | Point_(const Point_& pt); 34 | T dot(const Point_& pt) const; 35 | T cross(const Point_& pt) const; 36 | double length() const; 37 | double angle(const Point_& pt) const; 38 | Point_ rotate(double rad) const; 39 | bool operator < (const Point_& pt) const; 40 | bool operator == (const Point_& pt) const; 41 | Point_ operator + (const Point_& pt) const; 42 | Point_ operator - (const Point_& pt) const; 43 | Point_ operator * (T k) const; 44 | Point_ operator / (T k) const; 45 | void rotation(double rad); 46 | void normal(); 47 | Point_& operator = (const Point_& pt); 48 | Point_& operator += (const Point_& pt); 49 | Point_& operator -= (const Point_& pt); 50 | Point_& operator *= (T k); 51 | Point_& operator /= (T k); 52 | }; 53 | 54 | template 55 | using Vector_ = Point_; 56 | 57 | using Pointi = Point_; 58 | using Pointf = Point_; 59 | using Pointd = Point_; 60 | using Vectori = Vector_; 61 | using Vectorf = Vector_; 62 | using Vectord = Vector_; 63 | using Pixel = Point_; 64 | 65 | 66 | 67 | template 68 | std::istream& operator >> (std::istream& in, Point_& pt); 69 | 70 | template 71 | std::istream& operator >> (std::istream& in, Point_&& pt); 72 | 73 | template 74 | std::ostream& operator << (std::ostream& out, const Point_& pt); 75 | 76 | template 77 | std::ostream& operator << (std::ostream& out, const Point_&& pt); 78 | 79 | 80 | 81 | template 82 | class Line_ { 83 | public: 84 | Point_ sp, ep; 85 | Point_ center; 86 | double length; 87 | int polarity; 88 | double width; 89 | Vector_ dir; 90 | Line_(); 91 | Line_(const Point_ _sp, const Point_ _ep, int _pol = 0); 92 | Line_(T _x1, T _y1, T _x2, T _y2, int _pol); 93 | Line_(const Line_& lt); 94 | Line_(Line_&& lt); 95 | ~Line_(); 96 | double angle(const Line_& lt) const; 97 | const std::vector& getregs() const; 98 | void addpixel(const Pixel& pix); 99 | void setregs(const std::vector& pv); 100 | void setregs(std::vector&& pv); 101 | private: 102 | std::vector regions; 103 | }; 104 | 105 | using Linei = Line_; 106 | using Linef = Line_; 107 | using Lined = Line_; 108 | 109 | 110 | class Arc { 111 | public: 112 | double coverages; 113 | int polarity; 114 | double fitMat[6][6]; 115 | std::vector > lines; 116 | Arc(std::shared_ptr& lptr); 117 | Arc(std::shared_ptr&& lptr); 118 | Arc(const Arc& arc); 119 | ~Arc(); 120 | const std::vector& getregs() const; 121 | void setregs(const std::vector& pv); 122 | void setregs(std::vector&& pv); 123 | void merge(std::shared_ptr& lptr); 124 | void merge(std::shared_ptr&& lptr); 125 | void updateMatrix(const std::shared_ptr& lptr); 126 | private: 127 | std::vector regions; 128 | }; 129 | 130 | class Ellipse { 131 | public: 132 | Pointd o; // center point 133 | double a, b; // short, long axis length 134 | double phi; 135 | int polarity; 136 | double coefficients[6]; 137 | double goodness; 138 | double coverangle; 139 | std::vector inliers; 140 | Ellipse(); 141 | Ellipse(const Ellipse &e); 142 | Ellipse(Pointd center, double a, double b, double phi); 143 | Ellipse(double a, double b, double c, double d, double e, double f); 144 | bool isCircle(); 145 | bool isLegal(); 146 | bool equal(const std::shared_ptr &eptr, 147 | double centers_distabce__threshold = MIN_ELLIPSE_THRESHOLD_LENGTH, 148 | double semimajor_errorratio = 0.1, 149 | double semiminor_errorratio = 0.1, 150 | double angle_errorratio = 0.1, 151 | double iscircle_ratio = 0.9); 152 | double distopoint(Pointd p); 153 | Vectord getTangent(Pointd p); 154 | Vectord getTangent(Pointi p); 155 | private: 156 | 157 | }; 158 | 159 | class RectIter { 160 | public: 161 | RectIter(const std::shared_ptr &line); 162 | Pointd polys[4]; 163 | Pixel np; 164 | RectIter operator ++ (); 165 | RectIter operator ++ (int); 166 | bool isEnd(); 167 | private: 168 | int xmin, xmax; 169 | int ymin, ymax; 170 | double ang; 171 | void doinc(); 172 | void calcYAxisRange(int x); 173 | }; 174 | 175 | class EllipseIter { 176 | public: 177 | EllipseIter(const std::shared_ptr &ell_t, double distance_tolerance); 178 | Pixel np; 179 | EllipseIter operator ++ (); 180 | EllipseIter operator ++ (int); 181 | bool isEnd(); 182 | private: 183 | double a, b, c, d, e, f; 184 | const std::shared_ptr ℓ 185 | double distance_tolerance; 186 | int dir; 187 | int xmin, xmax; 188 | std::stack points; 189 | bool isend; 190 | void doinc(); 191 | void calcYAxis(int x); 192 | }; 193 | 194 | 195 | template 196 | class TemplateSameType { 197 | public: 198 | operator bool () { 199 | return false; 200 | } 201 | }; 202 | 203 | template 204 | class TemplateSameType { 205 | public: 206 | operator bool () { 207 | return true; 208 | } 209 | }; 210 | 211 | template 212 | class FuncTimerDecorator { 213 | public: 214 | static double times; 215 | FuncTimerDecorator(std::string funcname) : funcname(funcname) { 216 | 217 | } 218 | template 219 | return_type operator () (T func, Args&&... args){ 220 | clock_t start = clock(); 221 | return_type res = func(args...); 222 | clock_t finish = clock(); 223 | double millis = (double)(finish - start) / CLOCKS_PER_SEC * 1000; 224 | std::cerr << "Using " << millis << " ms to call function " << funcname << std::endl; 225 | return res; 226 | } 227 | private: 228 | std::string funcname; 229 | }; 230 | 231 | 232 | } // namespace zgh 233 | 234 | 235 | namespace zgh { 236 | 237 | /*--------------------------------------------------------------*/ 238 | 239 | template 240 | T min(const T& x, const T& y) { 241 | return x < y ? x : y; 242 | } 243 | 244 | template 245 | T max(const T& x, const T& y) { 246 | return x > y ? x : y; 247 | } 248 | 249 | template 250 | Point_::Point_(): x(0), y(0) { 251 | 252 | } 253 | 254 | template 255 | Point_::Point_(T _x, T _y): x(_x), y(_y) { 256 | 257 | } 258 | 259 | template 260 | Point_::~Point_() { 261 | 262 | } 263 | 264 | template 265 | Point_::Point_(const Point_& pt): x(pt.x), y(pt.y) { 266 | 267 | } 268 | 269 | template 270 | T Point_::dot(const Point_& pt) const { 271 | return x * pt.x + y * pt.y; 272 | } 273 | 274 | template 275 | T Point_::cross(const Point_& pt) const { 276 | return x * pt.y - y * pt.x; 277 | } 278 | 279 | template 280 | double Point_::length() const { 281 | return sqrt(max(decltype(dot(*this))(0), dot(*this))); 282 | } 283 | 284 | 285 | template 286 | double Point_::angle(const Point_& pt) const { 287 | double temp = 1.0 * dot(pt) / length() / pt.length(); 288 | return acos(min(max(0.0, temp), 1.0)); 289 | } 290 | 291 | template 292 | Point_ Point_::rotate(double rad) const { 293 | return Point_(x * cos(rad) - y * sin(rad), x * sin(rad) + y * cos(rad)); 294 | } 295 | 296 | template 297 | bool Point_::operator < (const Point_& pt) const { 298 | if (equal(x, pt.x)) { 299 | return y < pt.y; 300 | } 301 | return x < pt.x; 302 | } 303 | 304 | template 305 | bool Point_::operator == (const Point_& pt) const { 306 | return equal(x, pt.x) && equal(y, pt.y); 307 | } 308 | 309 | template 310 | Point_ Point_::operator + (const Point_& pt) const { 311 | return Point_(x + pt.x, y + pt.y); 312 | } 313 | 314 | template 315 | Point_ Point_::operator - (const Point_& pt) const { 316 | return Point_(x - pt.x, y - pt.y); 317 | } 318 | 319 | template 320 | Point_ Point_::operator * (T k) const { 321 | return Point_(x * k, y * k); 322 | } 323 | 324 | template 325 | Point_ Point_::operator / (T k) const { 326 | if (equal(k, 0 * k)) { 327 | return Point_(0, 0); 328 | } 329 | return Point_(x / k, y / k); 330 | } 331 | template 332 | void Point_::rotation(double rad) { 333 | T xx = x, yy = y; 334 | x = xx * cos(rad) - yy * sin(rad); 335 | y = xx * sin(rad) + yy * cos(rad); 336 | } 337 | 338 | template 339 | void Point_::normal() { 340 | double len = length(); 341 | if (equal(len, 0.0)) { 342 | x = y = 0; 343 | } 344 | x /= len; 345 | y /= len; 346 | } 347 | 348 | template 349 | Point_& Point_::operator = (const Point_ &pt) { 350 | x = pt.x; 351 | y = pt.y; 352 | return *this; 353 | } 354 | 355 | template 356 | Point_& Point_::operator += (const Point_ &pt) { 357 | x += pt.x; 358 | y += pt.y; 359 | return *this; 360 | } 361 | 362 | template 363 | Point_& Point_::operator -= (const Point_ &pt) { 364 | x -= pt.x; 365 | y -= pt.y; 366 | return *this; 367 | } 368 | 369 | template 370 | Point_& Point_::operator *= (T k) { 371 | x *= k; 372 | y *= k; 373 | return *this; 374 | } 375 | 376 | template 377 | Point_& Point_::operator /= (T k) { 378 | if (equal(k, 0 * k)) { 379 | x = y = 0; 380 | return *this; 381 | } 382 | x /= k; 383 | y /= k; 384 | return *this; 385 | } 386 | 387 | 388 | /*--------------------------------------------------------------*/ 389 | 390 | template 391 | Line_::Line_() { 392 | width = 2.0; 393 | } 394 | 395 | template 396 | Line_::Line_(const Point_ _sp, const Point_ _ep, int _pol): sp(_sp), ep(_ep), polarity(_pol) { 397 | dir = ep - sp; 398 | length = dir.length(); 399 | if (!equal(length, 0.0)) { 400 | dir.normal(); 401 | } 402 | width = 2.0; 403 | } 404 | 405 | template 406 | Line_::Line_(T _x1, T _y1, T _x2, T _y2, int _pol): sp(_x1, _y1), ep(_x2, _y2), polarity(_pol) { 407 | dir = ep - sp; 408 | length = dir.length(); 409 | if (!equal(length, 0.0)) { 410 | dir.normal(); 411 | } 412 | width = 2.0; 413 | } 414 | 415 | template 416 | Line_::Line_(const Line_& lt): sp(lt.sp), ep(lt.ep), length(lt.length), polarity(lt.polarity), 417 | dir(lt.dir), regions(lt.regions) { 418 | width = 2.0; 419 | } 420 | 421 | template 422 | Line_::Line_(Line_&& lt): sp(lt.sp), ep(lt.ep), length(lt.length), polarity(lt.polarity), 423 | dir(lt.dir), regions(std::forward >(lt.regions)) { 424 | width = 2.0; 425 | } 426 | 427 | template 428 | Line_::~Line_() { 429 | std::vector().swap(regions); 430 | } 431 | 432 | template 433 | double Line_::angle(const Line_& lt) const { 434 | if (!equal(length, 0.0) && !equal(lt.length, 0.0)) { 435 | return dir.angle(lt.dir); 436 | } else { 437 | return NAN; 438 | } 439 | } 440 | 441 | template 442 | const std::vector& Line_::getregs() const { 443 | return regions; 444 | } 445 | 446 | template 447 | void Line_::addpixel(const Pixel& pix) { 448 | regions.push_back(pix); 449 | } 450 | 451 | template 452 | void Line_::setregs(const std::vector& pv) { 453 | std::vector().swap(regions); 454 | regions = pv; 455 | } 456 | 457 | template 458 | void Line_::setregs(std::vector&& pv) { 459 | std::vector().swap(regions); 460 | regions = std::forward >(pv); 461 | } 462 | 463 | 464 | 465 | /*--------------------------------------------------------------*/ 466 | 467 | inline Arc::Arc(std::shared_ptr& lptr): coverages(0), polarity(lptr->polarity) { 468 | memset(fitMat, 0, sizeof(double) * 6 * 6); 469 | updateMatrix(lptr); 470 | lines.push_back(lptr); 471 | } 472 | 473 | inline Arc::Arc(std::shared_ptr&& lptr): coverages(0), polarity(lptr->polarity) { 474 | memset(fitMat, 0, sizeof(double) * 6 * 6); 475 | updateMatrix(lptr); 476 | lines.push_back(std::forward >(lptr)); 477 | } 478 | 479 | inline Arc::Arc(const Arc& arc): coverages(arc.coverages), polarity(arc.polarity), lines(arc.lines) { 480 | memcpy(fitMat, arc.fitMat, sizeof(double) * 6 * 6); 481 | } 482 | 483 | inline Arc::~Arc() { 484 | 485 | } 486 | 487 | inline void Arc::updateMatrix(const std::shared_ptr& lptr) { 488 | 489 | double D[2][6]; 490 | // start point 491 | D[0][0] = sqr(lptr->sp.x); 492 | D[0][1] = lptr->sp.x * lptr->sp.y; 493 | D[0][2] = sqr(lptr->sp.y); 494 | D[0][3] = lptr->sp.x; 495 | D[0][4] = lptr->sp.y; 496 | D[0][5] = 1.0; 497 | 498 | 499 | // end point 500 | D[1][0] = sqr(lptr->ep.x); 501 | D[1][1] = lptr->ep.x * lptr->ep.y; 502 | D[1][2] = sqr(lptr->ep.y); 503 | D[1][3] = lptr->ep.x; 504 | D[1][4] = lptr->ep.y; 505 | D[1][5] = 1.0; 506 | 507 | for (int i = 0; i < 6; ++i) { 508 | for (int j = 0; j < 6; ++j) { 509 | fitMat[i][j] += D[0][i] * D[0][j]; 510 | fitMat[i][j] += D[1][i] * D[1][j]; 511 | } 512 | } 513 | 514 | } 515 | inline void Arc::merge(std::shared_ptr& lptr) { 516 | if (lptr->polarity != polarity) { 517 | std::cerr << "Different polarity segment cannot be merge!" << std::endl; 518 | return; 519 | } 520 | updateMatrix(lptr); 521 | lines.push_back(lptr); 522 | double start_angle = atan2(lines[lines.size() - 2]->dir.y, lines[lines.size() - 2]->dir.x); 523 | double end_angle = atan2(lptr->dir.y, lptr->dir.x); 524 | coverages += rotateAngle(start_angle, end_angle, polarity); 525 | } 526 | 527 | inline void Arc::merge(std::shared_ptr&& lptr) { 528 | if (lptr->polarity != polarity) { 529 | std::cerr << "Different polarity segment cannot be merge!" << std::endl; 530 | return; 531 | } 532 | updateMatrix(lptr); 533 | double start_angle = atan2(lines[lines.size() - 1]->dir.y, lines[lines.size() - 1]->dir.x); 534 | double end_angle = atan2(lptr->dir.y, lptr->dir.x); 535 | coverages += rotateAngle(start_angle, end_angle, polarity); 536 | lines.push_back(std::forward >(lptr)); 537 | } 538 | 539 | 540 | 541 | /*--------------------------------------------------------------*/ 542 | 543 | inline Ellipse::Ellipse() { 544 | a = b = phi = 0; 545 | } 546 | 547 | inline Ellipse::Ellipse(Pointd center, double a, double b, double phi) { 548 | this->o = center; 549 | this->a = a; 550 | this->b = b; 551 | this->phi = phi; 552 | coefficients[0] = sqr(b * cos(phi)) + sqr(a * sin(phi)); 553 | coefficients[1] = 2.0 * (b * b - a * a) * sin(phi) * cos(phi); 554 | coefficients[2] = sqr(b * sin(phi)) + sqr(a * cos(phi)); 555 | 556 | coefficients[3] = -2.0 * (sqr(b * cos(phi)) + sqr(a * sin(phi))) * o.x + 557 | 2.0 * (a * a - b * b) * sin(phi) * cos(phi) * o.y; 558 | 559 | coefficients[4] = -2.0 * (sqr(b * sin(phi)) + sqr(a * cos(phi))) * o.y + 560 | 2.0 * (a * a - b * b) * sin(phi) * cos(phi) * o.x; 561 | 562 | coefficients[5] = (sqr(b * cos(phi)) + sqr(a * sin(phi))) * sqr(o.x) + 563 | (sqr(b * sin(phi)) + sqr(a * cos(phi))) * sqr(o.y) + 564 | 2.0 * (b * b - a * a) * o.x * o.y * sin(phi) * cos(phi) - 565 | sqr(a * b); 566 | 567 | } 568 | 569 | inline Ellipse::Ellipse(double a, double b, double c, double d, double e, double f) { 570 | 571 | // ax^2 + bxy + cy^2 + dx + ey + f = 0; 572 | coefficients[0] = a; 573 | coefficients[1] = b; 574 | coefficients[2] = c; 575 | coefficients[3] = d; 576 | coefficients[4] = e; 577 | coefficients[5] = f; 578 | 579 | double thetarad = 0.5 * atan2(b, a - c); 580 | double cost = cos(thetarad); 581 | double sint = sin(thetarad); 582 | double sin_squared = sqr(sint); 583 | double cos_squared = sqr(cost); 584 | double cos_sin = sint * cost; 585 | double Ao = f; 586 | double Au = d * cost + e * sint; 587 | double Av = -d * sint + e * cost; 588 | double Auu = a * cos_squared + c * sin_squared + b * cos_sin; 589 | double Avv = a * sin_squared + c * cos_squared - b * cos_sin; 590 | if (zgh::equal(0.0, Auu) || zgh::equal(0.0, Avv)) { 591 | this->a = this->b = this->phi = 0; 592 | } else { 593 | double tuCentre = -Au / (2.0 * Auu); 594 | double tvCentre = -Av / (2.0 * Avv); 595 | double wCentre = Ao - Auu * tuCentre * tuCentre - Avv * tvCentre * tvCentre; 596 | double uCentre = tuCentre * cost - tvCentre * sint; 597 | double vCentre = tuCentre * sint + tvCentre * cost; 598 | double Ru = -wCentre / Auu; 599 | double Rv = -wCentre / Avv; 600 | if (Ru <= 0 || Rv <= 0) { 601 | this->a = this->b = this->phi = 0; 602 | return; 603 | } 604 | Ru = sqrt(Ru); 605 | Rv = sqrt(Rv); 606 | this->o = Pointd(uCentre, vCentre); 607 | this->a = Ru; 608 | this->b = Rv; 609 | this->phi = thetarad; 610 | if (Ru < Rv) { 611 | std::swap(this->a, this->b); 612 | if (this->phi < 0) { 613 | this->phi += PI_2; 614 | } else { 615 | this->phi -= PI_2; 616 | } 617 | if (this->phi < -PI_2) { 618 | this->phi += PI; 619 | } 620 | if (this->phi > PI_2) { 621 | this->phi -= PI; 622 | } 623 | } 624 | } 625 | } 626 | 627 | inline Ellipse::Ellipse(const Ellipse &e): o(e.o), a(e.a), b(e.b), polarity(e.polarity) { 628 | memcpy(coefficients, e.coefficients, sizeof(double) * 6); 629 | } 630 | 631 | inline bool Ellipse::isLegal() { 632 | if (min(a, b) <= 3 * MIN_ELLIPSE_THRESHOLD_LENGTH) { 633 | return false; 634 | } 635 | return true; 636 | } 637 | inline bool Ellipse::isCircle() { 638 | return isLegal() && std::fabs(a - b) <= FEPS; 639 | } 640 | 641 | inline bool Ellipse::equal(const std::shared_ptr &eptr, 642 | double centers_distabce_threshold, 643 | double semimajor_errorratio, 644 | double semiminor_errorratio, 645 | double angle_errorratio, 646 | double iscircle_ratio) { 647 | 648 | if (!eptr) { 649 | return false; 650 | } 651 | bool con1 = std::fabs(this->o.x - eptr->o.x) < centers_distabce_threshold 652 | && std::fabs(this->o.y - eptr->o.y) < centers_distabce_threshold 653 | && std::fabs(this->a - eptr->a) / max(this->a, eptr->a) < semimajor_errorratio 654 | && std::fabs(this->b - eptr->b) / min(this->b, eptr->b) < semiminor_errorratio; 655 | 656 | double temp = angle_diff(this->phi, eptr->phi); 657 | double phi_diff = min(PI - temp, temp); 658 | bool con2 = (this->b / this->a) >= iscircle_ratio; 659 | bool con3 = (eptr->b / eptr->a) >= iscircle_ratio; 660 | bool con4 = ((con2 && con3) || (!con2 && !con3 && phi_diff <= angle_errorratio * PI)); 661 | 662 | return con1 && con4; 663 | } 664 | 665 | inline double Ellipse::distopoint(Pointd p) { 666 | 667 | Vectord pt = p - o; 668 | pt.rotation(-phi); 669 | double ae2 = a * a; 670 | double be2 = b * b; 671 | double fe2 = a * a - b * b; 672 | if (isCircle()) { 673 | return std::fabs((p - o).length() - a); 674 | } 675 | 676 | double X = sqr(pt.x); 677 | double Y = sqr(pt.y); 678 | 679 | double delta = sqr(X + Y + fe2) - 4 * fe2 * X; 680 | double A = (X + Y + fe2 - sqrt(delta)) / 2.0; 681 | double ah = sqrt(A); 682 | double bh2 = fe2 - A; 683 | double term = A * be2 + ae2 * bh2; 684 | double xi = ah * sqrt(ae2 * (be2 + bh2) / term); 685 | double yi = b * sqrt(bh2 * (ae2 - A) / term); 686 | double d = sqr(pt.x - xi) + sqr(pt.y - yi); 687 | d = min(d, sqr(pt.x + xi) + sqr(pt.y - yi)); 688 | d = min(d, sqr(pt.x - xi) + sqr(pt.y + yi)); 689 | d = min(d, sqr(pt.x + xi) + sqr(pt.y + yi)); 690 | return sqrt(d); 691 | } 692 | 693 | 694 | inline Vectord Ellipse::getTangent(Pointd p) { 695 | double dx = coefficients[1] * p.x + 2.0 * p.y * coefficients[2] + coefficients[4]; 696 | double dy = -1.0 * (coefficients[3] + 2.0 * coefficients[0] * p.x + coefficients[1] * p.y); 697 | Vectord tan(dx, dy); 698 | tan.rotation(-PI_2); 699 | tan.normal(); 700 | return tan; 701 | } 702 | 703 | inline Vectord Ellipse::getTangent(Pointi p) { 704 | return getTangent(Pointd(1.0 * p.x, 1.0 * p.y)); 705 | } 706 | 707 | 708 | 709 | /*--------------------------------------------------------------*/ 710 | 711 | inline RectIter::RectIter(const std::shared_ptr &line) { 712 | Pointd sp = line->sp, ep = line->ep; 713 | Vectord dir = line->ep - line->sp; 714 | if (dir.y < 0) { 715 | dir.x = -dir.x; 716 | dir.y = -dir.y; 717 | std::swap(sp, ep); 718 | } 719 | dir.normal(); 720 | ang = atan2(dir.x, dir.y); 721 | if (ang < 0) { 722 | /* 723 | --------------------- -> y 724 | | | 725 | | / | 726 | | / | 727 | | / | 728 | | / | 729 | | | 730 | --------------------- 731 | | 732 | v 733 | x 734 | */ 735 | 736 | // Clockwise 737 | polys[0] = sp + dir.rotate(-PI / 2) * line->width / 2.0; 738 | polys[1] = sp + dir.rotate(PI / 2) * line->width / 2.0; 739 | polys[2] = ep + dir.rotate(PI / 2) * line->width / 2.0; 740 | polys[3] = ep + dir.rotate(-PI / 2) * line->width / 2.0; 741 | } else { 742 | /* 743 | --------------------- -> y 744 | | | 745 | | \ | 746 | | \ | 747 | | \ | 748 | | \ | 749 | | | 750 | --------------------- 751 | | 752 | v 753 | x 754 | */ 755 | 756 | // Counterclockwise 757 | polys[0] = ep + dir.rotate(-PI / 2) * line->width / 2.0; 758 | polys[1] = ep + dir.rotate(PI / 2) * line->width / 2.0; 759 | polys[2] = sp + dir.rotate(PI / 2) * line->width / 2.0; 760 | polys[3] = sp + dir.rotate(-PI / 2) * line->width / 2.0; 761 | } 762 | xmin = (int)ceil(polys[2].x); 763 | xmax = (int)floor(polys[0].x); 764 | np.x = xmin; 765 | while (np.x <= xmax) { 766 | calcYAxisRange(np.x); 767 | if (ymin <= ymax) { 768 | break; 769 | } 770 | ++np.x; 771 | } 772 | np.y = ymin; 773 | } 774 | 775 | 776 | inline bool RectIter::isEnd() { 777 | return np.x > xmax; 778 | } 779 | 780 | 781 | inline RectIter RectIter::operator ++ () { 782 | doinc(); 783 | return *this; 784 | } 785 | 786 | inline RectIter RectIter::operator ++ (int) { 787 | auto res = *this; 788 | doinc(); 789 | return res; 790 | } 791 | 792 | inline void RectIter::doinc() { 793 | ++np.y; 794 | if (np.y > (double) ymax) { 795 | ++np.x; 796 | calcYAxisRange(np.x); 797 | np.y = ymin; 798 | } 799 | } 800 | 801 | inline void RectIter::calcYAxisRange(int x) { 802 | if (std::fabs(ang) <= FEPS || std::fabs(std::fabs(ang) - PI_2) <= FEPS) { 803 | ymin = min(min(polys[0].y, polys[1].y), polys[2].y); 804 | ymax = max(max(polys[0].y, polys[1].y), polys[2].y); 805 | return; 806 | } 807 | if (ang < 0) { 808 | // calculate ymin 809 | if ((double)x <= polys[1].x) { 810 | double k = ((double)x - polys[2].x) / (polys[1].x - polys[2].x); 811 | Pointd p = polys[2] + (polys[1] - polys[2]) * k; 812 | ymin = (int)ceil(p.y); 813 | } else { 814 | double k = ((double)x - polys[1].x) / (polys[0].x - polys[1].x); 815 | Pointd p = polys[1] + (polys[0] - polys[1]) * k; 816 | ymin = (int)ceil(p.y); 817 | } 818 | // calculate ymax 819 | if ((double)x <= polys[3].x) { 820 | double k = ((double)x - polys[2].x) / (polys[3].x - polys[2].x); 821 | Pointd p = polys[2] + (polys[3] - polys[2]) * k; 822 | ymax = (int)floor(p.y); 823 | } else { 824 | double k = ((double)x - polys[3].x) / (polys[0].x - polys[3].x); 825 | Pointd p = polys[3] + (polys[0] - polys[3]) * k; 826 | ymax = (int)floor(p.y); 827 | } 828 | } else { 829 | // calculate ymin 830 | if ((double)x <= polys[3].x) { 831 | double k = ((double)x - polys[2].x) / (polys[3].x - polys[2].x); 832 | Pointd p = polys[2] + (polys[3] - polys[2]) * k; 833 | ymin = (int)ceil(p.y); 834 | } else { 835 | double k = ((double)x - polys[3].x) / (polys[0].x - polys[3].x); 836 | Pointd p = polys[3] + (polys[0] - polys[3]) * k; 837 | ymin = (int)ceil(p.y); 838 | } 839 | // calculate ymax 840 | if ((double)x <= polys[1].x) { 841 | double k = ((double)x - polys[2].x) / (polys[1].x - polys[2].x); 842 | Pointd p = polys[2] + (polys[1] - polys[2]) * k; 843 | ymax = (int)floor(p.y); 844 | } else { 845 | double k = ((double)x - polys[1].x) / (polys[0].x - polys[1].x); 846 | Pointd p = polys[1] + (polys[0] - polys[1]) * k; 847 | ymax = (int)floor(p.y); 848 | } 849 | } 850 | } 851 | 852 | /*--------------------------------------------------------------*/ 853 | 854 | inline EllipseIter::EllipseIter(const std::shared_ptr &ell_t, 855 | double _distance_tolerance): ell(ell_t) { 856 | distance_tolerance = _distance_tolerance; 857 | a = ell->coefficients[0]; 858 | b = ell->coefficients[1]; 859 | c = ell->coefficients[2]; 860 | d = ell->coefficients[3]; 861 | e = ell->coefficients[4]; 862 | f = ell->coefficients[5]; 863 | 864 | if (b * b - 4 * a * c >= 0) { 865 | std::cerr << "error" << std::endl; 866 | isend = true; 867 | return; 868 | } 869 | double B = (2 * b * e - 4 * d * c) / (b * b - 4 * a * c); 870 | double C = (e * e - 4 * f * c) / (b * b - 4 * a * c); 871 | double delta = B * B - 4 * C; 872 | if (delta < 0) { 873 | isend = true; 874 | return; 875 | } 876 | xmin = (int)ceil((-B - sqrt(delta)) / 2.0); 877 | xmax = (int)floor((-B + sqrt(delta)) / 2.0); 878 | np.x = xmin; 879 | dir = 1; 880 | isend = false; 881 | calcYAxis(np.x); 882 | while (np.x < xmax && points.empty()) { 883 | calcYAxis(++np.x); 884 | } 885 | if (!points.empty()) { 886 | np = points.top(); 887 | points.pop(); 888 | } 889 | } 890 | 891 | inline bool EllipseIter::isEnd() { 892 | return isend; 893 | } 894 | 895 | inline EllipseIter EllipseIter::operator ++ () { 896 | doinc(); 897 | return *this; 898 | } 899 | 900 | inline EllipseIter EllipseIter::operator ++ (int) { 901 | auto res = *this; 902 | doinc(); 903 | return res; 904 | } 905 | 906 | inline void EllipseIter::doinc() { 907 | if (points.empty()) { 908 | if (dir == 1) { 909 | while (np.x < xmax && points.empty()) { 910 | calcYAxis(++np.x); 911 | } 912 | if (points.empty()) { 913 | np.x = xmax + 1; 914 | dir = -1; 915 | } 916 | } 917 | 918 | if (dir == -1) { 919 | while (np.x > xmin && points.empty()) { 920 | calcYAxis(--np.x); 921 | } 922 | if (points.empty()) { 923 | isend = true; 924 | } 925 | } 926 | } 927 | 928 | if (!isend) { 929 | np = points.top(); 930 | points.pop(); 931 | } 932 | } 933 | 934 | inline void EllipseIter::calcYAxis(int x) { 935 | double A = c; 936 | double B = (b * x + e); 937 | double C = f + d * x + a * x * x; 938 | double delta = B * B - 4 * A * C; 939 | if (delta < 0) { 940 | return; 941 | } 942 | double ya = (-B + sqrt(delta)) / (2.0 * A); 943 | double yb = (-B - sqrt(delta)) / (2.0 * A); 944 | double y; 945 | if (dir == 1) { 946 | y = max(ya, yb); 947 | } else { 948 | y = min(ya, yb); 949 | } 950 | int ymid = (int)floor((ya + yb) / 2.0); 951 | int yy = (int)y; 952 | for (int idy = yy + 1; dir == 1 || idy < ymid; ++idy) { 953 | if (ell->distopoint(Pointd(1.0 * x, 1.0 * idy)) <= distance_tolerance) { 954 | points.push(Pointi(x, idy)); 955 | } else { 956 | break; 957 | } 958 | } 959 | for (int idy = yy; dir == -1 || idy >= ymid; --idy) { 960 | if (ell->distopoint(Pointd(1.0 * x, 1.0 * idy)) <= distance_tolerance) { 961 | points.push(Pointi(x, idy)); 962 | } else { 963 | break; 964 | } 965 | } 966 | if ((dir == 1 && x == xmax) || (dir == -1 && x == xmin)) { 967 | for (int id = 1; id <= (int)ceil(distance_tolerance); ++id) { 968 | for (int idy = yy + 1; ; ++idy) { 969 | if (ell->distopoint(Pointd(1.0 * (x + id * dir), 1.0 * idy)) <= distance_tolerance) { 970 | points.push(Pointi(x + id * dir, idy)); 971 | } else { 972 | break; 973 | } 974 | } 975 | 976 | for (int idy = yy; ; --idy) { 977 | if (ell->distopoint(Pointd(1.0 * (x + id * dir), 1.0 * idy)) <= distance_tolerance) { 978 | points.push(Pointi(x + id * dir, idy)); 979 | } else { 980 | break; 981 | } 982 | } 983 | } 984 | } 985 | 986 | } 987 | 988 | /*--------------------------------------------------------------*/ 989 | 990 | template 991 | std::istream& operator >> (std::istream& in, Point_& pt) { 992 | in >> pt.x >> pt.y; 993 | return in; 994 | } 995 | 996 | template 997 | std::ostream& operator << (std::ostream& out, const Point_& pt) { 998 | out << std::fixed << std::setprecision(5) << "[" << pt.x << ", " << pt.y << "]"; 999 | return out; 1000 | } 1001 | 1002 | template 1003 | std::istream& operator >> (std::istream& in, Point_&& pt) { 1004 | in >> pt.x >> pt.y; 1005 | return in; 1006 | } 1007 | 1008 | template 1009 | std::ostream& operator << (std::ostream& out, const Point_&& pt) { 1010 | out << std::fixed << std::setprecision(5) << "[" << pt.x << ", " << pt.y << "]"; 1011 | return out; 1012 | } 1013 | 1014 | } // namespace zgh 1015 | 1016 | 1017 | 1018 | #endif // _INCLUDE_TYPES_H_ 1019 | -------------------------------------------------------------------------------- /include/unitily.h: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | 9 | #ifndef _INCLUDE_UNITILT_H_ 10 | #define _INCLUDE_UNITILT_H_ 11 | 12 | namespace zgh { 13 | 14 | bool equal(int x, int y); 15 | 16 | bool euqal(float x, float y); 17 | 18 | bool equal(double x, double y); 19 | 20 | double angle2rad(double angle); 21 | 22 | double rad2angle(double rad); 23 | 24 | double angle_diff(double a, double b); 25 | 26 | double angle_diff_signed(double a, double b); 27 | 28 | bool inRect(int h, int w, int x, int y); 29 | 30 | double rotateAngle(double start_angle, double end_angle, int polarity); 31 | 32 | double sqr(double a); 33 | 34 | int sqr(int a); 35 | 36 | double log_gamma_windschitl(double x); 37 | 38 | double log_gamma_lanczos(double x); 39 | 40 | double nfa(int n, int k, double p, double logNT); 41 | 42 | } // namespace zgh 43 | 44 | 45 | #endif // _INCLUDE_UNITILT_H_ 46 | -------------------------------------------------------------------------------- /src/compute.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | 10 | 11 | #include "compute.h" 12 | #include "defines.h" 13 | 14 | 15 | namespace zgh { 16 | 17 | bool calculateGradient(const uint8_t *image, int row, int col, double *angles) { 18 | double *mod = new double[row * col]; 19 | double normsum = 0.0; 20 | for (int idx = 0; idx < row; ++idx) { 21 | angles[idx * col] = ANGLE_NOT_DEF; 22 | angles[idx * col + col - 1] = ANGLE_NOT_DEF; 23 | mod[idx * col] = ANGLE_NOT_DEF; 24 | mod[idx * col + col - 1] = ANGLE_NOT_DEF; 25 | } 26 | for (int idy = 0; idy < col; ++idy) { 27 | angles[idy] = ANGLE_NOT_DEF; 28 | angles[(row - 1) * col + idy] = ANGLE_NOT_DEF; 29 | mod[idy] = ANGLE_NOT_DEF; 30 | mod[(row - 1) * col + idy] = ANGLE_NOT_DEF; 31 | } 32 | for (int idy = 1; idy < col - 1; ++idy) { 33 | for (int idx = 1; idx < row - 1; ++idx) { 34 | int addr = idx * col + idy; 35 | /* 36 | Norm 2 computation using 3x3 pixel window: 37 | A B C 38 | D E F 39 | G H I 40 | and 41 | com1 = C - G, com2 = I - A 42 | Then 43 | gx = C + 2F + I - (A + 2D + G) = com1 + com2 + 2(F - D) horizontal difference 44 | gy = G + 2H + I - (A + 2B + C) = -com1 + com2 + 2(H - B) vertical difference 45 | com1 and com2 are just to avoid 2 additions. 46 | */ 47 | double com1, com2; 48 | double dx, dy; 49 | com1 = (double)image[addr - col + 1] - (double)image[addr + col - 1]; 50 | com2 = (double)image[addr + col + 1] - (double)image[addr - col - 1]; 51 | dx = com1 + com2 + 2.0 * ((double)image[addr + 1] - (double)image[addr - 1]); 52 | dy = -com1 + com2 + 2.0 * ((double)image[addr + col] - (double)image[addr - col]); 53 | dx /= (8.0 * 255); 54 | dy /= (8.0 * 255); 55 | double norm_square = dx * dx + dy * dy; 56 | normsum += norm_square; 57 | double norm = sqrt(norm_square); 58 | mod[addr] = norm; 59 | angles[addr] = atan2(dy, dx); 60 | } 61 | } 62 | 63 | double threshold = sqrt(normsum / (row * col)) / 3.0; // auto threshold 64 | for (int idx = 1; idx < row - 1; ++idx) { 65 | for (int idy = 1; idy < col - 1; ++idy) { 66 | int addr = idx * col + idy; 67 | if (mod[addr] < threshold) { 68 | angles[addr] = ANGLE_NOT_DEF; 69 | continue; 70 | } 71 | double val = angles[addr]; 72 | if ((val > -PI_8 && val <= PI_8) || val <= -7.0 * PI_8 || val > 7.0 * PI_8) { 73 | if (mod[addr] <= mod[addr + 1] || mod[addr] <= mod[addr - 1]) { 74 | angles[addr] = ANGLE_NOT_DEF; 75 | } 76 | } else if ((val > PI_8 && val <= 3.0 * PI_8) || (val > -7.0 * PI_8 && val <= -5.0 * PI_8)) { 77 | if (mod[addr] <= mod[addr - col - 1] || mod[addr] <= mod[addr + col + 1]) { 78 | angles[addr] = ANGLE_NOT_DEF; 79 | } 80 | } else if ((val > 3.0 * PI_8 && val <= 5.0 * PI_8) || (val > -5.0 * PI_8 && val <= -3.0 * PI_8)) { 81 | if (mod[addr] <= mod[addr - col] || mod[addr] <= mod[addr + col]) { 82 | angles[addr] = ANGLE_NOT_DEF; 83 | } 84 | } else { 85 | if (mod[addr] <= mod[addr - col + 1] || mod[addr] < mod[addr + col - 1]) { 86 | angles[addr] = ANGLE_NOT_DEF; 87 | } 88 | } 89 | } 90 | } 91 | 92 | delete [] mod; 93 | return true; 94 | } 95 | 96 | 97 | static std::shared_ptr fitEllipse(double *S) { 98 | double C[36]; 99 | memset(C, 0, sizeof(double) * 36); 100 | C[0 * 6 + 2] = 2; 101 | C[1 * 6 + 1] = -1; 102 | C[2 * 6 + 0] = 2; 103 | 104 | 105 | double alphar[6], alphai[6], beta[6]; 106 | double vl[36] = {0}; 107 | double vr[36] = {0}; 108 | char JOBVL = 'N'; 109 | char JOBVR = 'V'; 110 | int fitN = 6; 111 | double fitWork[64]; 112 | int workLen = 64; 113 | int info; 114 | dggev_(&JOBVL, &JOBVR, &fitN, S, &fitN, C, &fitN, alphar, alphai, 115 | beta, vl, &fitN, vr, &fitN, fitWork, &workLen, &info); 116 | if (info == 0) { 117 | int index = -1; 118 | for (int i = 0; i < 6; ++i) { 119 | if (alphar[i] >= -2.2204460492503131e-014 && alphai[i] == 0 && beta[i] != 0) { 120 | index = i; 121 | } 122 | } 123 | if (index == -1) { 124 | double temp = -0.005; 125 | for (int i = 0; i < 6; ++i) { 126 | if (alphar[i] >= temp && alphai[i] == 0 && beta[i] != 0) { 127 | temp = alphar[i]; 128 | index = i; // vr[:,i], vr 第 i 列对应的特征向量则为拟合参数 129 | } 130 | } 131 | } 132 | if (index != -1) { 133 | if(vr[6 * index + 0] < 0) { 134 | return std::make_shared(-vr[6 * index + 0], -vr[6 * index + 1], -vr[6 * index + 2], 135 | -vr[6 * index + 3], -vr[6 * index + 4], -vr[6 * index + 5]); 136 | } else { 137 | return std::make_shared(vr[6 * index + 0], vr[6 * index + 1], vr[6 * index + 2], 138 | vr[6 * index + 3], vr[6 * index + 4], vr[6 * index + 5]); 139 | } 140 | } 141 | } 142 | return nullptr; 143 | } 144 | 145 | std::shared_ptr fitEllipse(const std::vector &points) { 146 | int pnum = points.size(); 147 | double *D = new double[6 * pnum]; 148 | for (int id = 0; id < pnum; ++id) { 149 | D[6 * id + 0] = sqr(points[id].x); 150 | D[6 * id + 1] = points[id].x * points[id].y; 151 | D[6 * id + 2] = sqr(points[id].y); 152 | D[6 * id + 3] = points[id].x; 153 | D[6 * id + 4] = points[id].y; 154 | D[6 * id + 5] = 1; 155 | } 156 | double S[36]; 157 | for (int idx = 0; idx < 6; ++idx) { 158 | for (int idy = 0; idy < 6; ++idy) { 159 | S[idx * 6 + idy] = 0; 160 | for (int k = 0; k < pnum; ++k) { 161 | S[idx * 6 + idy] += D[k * 6 + idx] * D[k * 6 + idy]; 162 | } 163 | } 164 | } 165 | delete [] D; 166 | return fitEllipse(S); 167 | } 168 | 169 | static std::shared_ptr fitEllipse(const std::vector &points1, const std::vector &points2) { 170 | int pnum = points1.size() + points2.size(); 171 | double *D = new double[6 * pnum]; 172 | for (int id = 0; id < (int)points1.size(); ++id) { 173 | D[6 * id + 0] = sqr(points1[id].x); 174 | D[6 * id + 1] = points1[id].x * points1[id].y; 175 | D[6 * id + 2] = sqr(points1[id].y); 176 | D[6 * id + 3] = points1[id].x; 177 | D[6 * id + 4] = points1[id].y; 178 | D[6 * id + 5] = 1; 179 | } 180 | 181 | int offset = (int)points1.size(); 182 | for (int id = offset; id < pnum; ++id) { 183 | D[6 * id + 0] = sqr(points2[id - offset].x); 184 | D[6 * id + 1] = points2[id - offset].x * points2[id - offset].y; 185 | D[6 * id + 2] = sqr(points2[id - offset].y); 186 | D[6 * id + 3] = points2[id - offset].x; 187 | D[6 * id + 4] = points2[id - offset].y; 188 | D[6 * id + 5] = 1; 189 | } 190 | 191 | double S[36]; 192 | for (int idx = 0; idx < 6; ++idx) { 193 | for (int idy = 0; idy < 6; ++idy) { 194 | S[idx * 6 + idy] = 0; 195 | for (int k = 0; k < pnum; ++k) { 196 | S[idx * 6 + idy] += D[k * 6 + idx] * D[k * 6 + idy]; 197 | } 198 | } 199 | } 200 | delete [] D; 201 | return fitEllipse(S); 202 | } 203 | 204 | std::shared_ptr calcElliseParam(const std::shared_ptr &arc1, const std::shared_ptr &arc2, 205 | const double *angles, int row, int col) { 206 | 207 | double S[36]; 208 | if (!arc2) { 209 | for (int idx = 0; idx < 6; ++idx) { 210 | for (int idy = 0; idy < 6; ++idy) { 211 | S[idx * 6 + idy] = arc1->fitMat[idx][idy]; 212 | } 213 | } 214 | } else { 215 | for (int idx = 0; idx < 6; ++idx) { 216 | for (int idy = 0; idy < 6; ++idy) { 217 | S[idx * 6 + idy] = arc1->fitMat[idx][idy] + arc2->fitMat[idx][idy]; 218 | } 219 | } 220 | } 221 | std::shared_ptr ell = fitEllipse(S); 222 | if (!ell || !ell->isLegal()) { 223 | return nullptr; 224 | } 225 | 226 | if (!inRect(row, col, ell->o.x, ell->o.y) || max(ell->a, ell->b) > min(row, col)) { 227 | return nullptr; 228 | } 229 | 230 | int support_cnt, inlier_cnt; 231 | // verification first arc1 232 | bool significant1 = true; 233 | std::vector support_regs1; 234 | for (auto &line : arc1->lines) { 235 | support_cnt = inlier_cnt = 0; 236 | line->width = 3 * MIN_ELLIPSE_THRESHOLD_LENGTH; 237 | for (RectIter iter(line); !iter.isEnd(); ++iter) { 238 | auto pix = iter.np; 239 | if (inRect(row, col, pix.x, pix.y)) { 240 | double temp = angles[pix.x * col + pix.y]; 241 | if (!equal(temp, ANGLE_NOT_DEF)) { 242 | double point_normalx = ell->coefficients[0] * pix.x + 243 | (ell->coefficients[1] * pix.y + ell->coefficients[3]) / 2.0; 244 | double point_normaly = ell->coefficients[2] * pix.y + 245 | (ell->coefficients[1] * pix.x + ell->coefficients[4]) / 2.0; 246 | double point_normal; 247 | if (line->polarity == SAME_POL) { 248 | point_normal = atan2(-point_normalx, -point_normaly); 249 | } else { 250 | point_normal = atan2(point_normalx, point_normaly); 251 | } 252 | ++inlier_cnt; 253 | if (angle_diff(point_normal, temp) <= PI / 10.0) { 254 | ++support_cnt; 255 | support_regs1.push_back(pix); 256 | } 257 | } 258 | } 259 | } 260 | if (support_cnt == 0 261 | || (1.0 * support_cnt <= 0.7 * line->length 262 | && 1.0 * support_cnt / inlier_cnt <= 0.6)) { 263 | significant1 = false; 264 | break; 265 | } 266 | } 267 | 268 | // end verification first arc1 269 | 270 | if (!arc2) { 271 | if (significant1) { 272 | // 再次拟合提高质量 273 | std::shared_ptr ell_t = fitEllipse(support_regs1); 274 | if (ell_t && ell_t->equal(ell, 3 * MIN_ELLIPSE_THRESHOLD_LENGTH, 0.1, 0.1, 0.1, 0.9)) { 275 | ell = ell_t; 276 | } 277 | } 278 | ell->polarity = arc1->polarity; 279 | return ell; 280 | } 281 | 282 | if (!significant1) { 283 | return nullptr; 284 | } 285 | 286 | 287 | // verification second arc2 288 | bool significant2 = true; 289 | std::vector support_regs2; 290 | for (auto &line : arc2->lines) { 291 | support_cnt = inlier_cnt = 0; 292 | line->width = 3 * MIN_ELLIPSE_THRESHOLD_LENGTH; 293 | for (RectIter iter(line); !iter.isEnd(); ++iter) { 294 | auto pix = iter.np; 295 | if (inRect(row, col, pix.x, pix.y)) { 296 | double temp = angles[pix.x * col + pix.y]; 297 | if (!equal(temp, ANGLE_NOT_DEF)) { 298 | double point_normalx = ell->coefficients[0] * pix.x + 299 | (ell->coefficients[1] * pix.y + ell->coefficients[3]) / 2.0; 300 | double point_normaly = ell->coefficients[2] * pix.y + 301 | (ell->coefficients[1] * pix.x + ell->coefficients[4]) / 2.0; 302 | double point_normal; 303 | if (line->polarity == 1) { 304 | point_normal = atan2(-point_normalx, -point_normaly); 305 | } else { 306 | point_normal = atan2(point_normalx, point_normaly); 307 | } 308 | ++inlier_cnt; 309 | if (angle_diff(point_normal, temp) <= PI / 10.0) { 310 | ++support_cnt; 311 | support_regs2.push_back(pix); 312 | } 313 | } 314 | } 315 | } 316 | if (support_cnt == 0 317 | || (1.0 * support_cnt <= 0.7 * line->length 318 | && 1.0 * support_cnt / inlier_cnt <= 0.6)) { 319 | significant2 = false; 320 | break; 321 | } 322 | } 323 | 324 | // end verification first arc2 325 | 326 | if (significant1 && significant2) { 327 | std::shared_ptr ell_t = fitEllipse(support_regs1, support_regs2); 328 | if (!ell_t) { 329 | return nullptr; 330 | } 331 | double semimajor_errorratio, semiminor_errorratio, iscircle_ratio; 332 | if (ell->a <= 50) { 333 | semimajor_errorratio = 0.25; 334 | } else if (ell->a <= 100) { 335 | semimajor_errorratio = 0.15; 336 | } else { 337 | semimajor_errorratio = 0.1; 338 | } 339 | 340 | if (ell->b <= 50) { 341 | semiminor_errorratio = 0.25; 342 | } else if (ell->b <= 100) { 343 | semiminor_errorratio = 0.15; 344 | } else { 345 | semiminor_errorratio = 0.1; 346 | } 347 | 348 | if (ell->a <= 50 && ell->b <= 50) { 349 | iscircle_ratio = 0.75; 350 | } else if (50 <= ell->a && ell->a <= 100 && 50 <= ell->b && ell->b <= 100) { 351 | iscircle_ratio = 0.85; 352 | } else { 353 | iscircle_ratio = 0.9; 354 | } 355 | if (ell_t && ell_t->equal(ell, 3 * MIN_ELLIPSE_THRESHOLD_LENGTH, 356 | semimajor_errorratio, semiminor_errorratio, 0.1, iscircle_ratio)) { 357 | ell = ell_t; 358 | ell->polarity = arc1->polarity; 359 | return ell; 360 | } 361 | } 362 | return nullptr; 363 | } 364 | 365 | static bool regionLimitation_reverse(const std::shared_ptr &arc1, const std::shared_ptr &arc2) { 366 | if (!arc1 || !arc2) { 367 | return false; 368 | } 369 | Lined line1((*arc1->lines.begin())->sp, (*arc1->lines.rbegin())->ep, 0); 370 | Lined line2((*arc2->lines.begin())->sp, (*arc2->lines.rbegin())->ep, 0); 371 | Vectord arc_dir_s1, arc_dir_e1, arc_dir_m1; 372 | Vectord arc_dir_s2, arc_dir_e2, arc_dir_m2; 373 | if (arc1->polarity == SAME_POL) { 374 | arc_dir_s1 = (*arc1->lines.begin())->dir.rotate(PI_2); 375 | arc_dir_e1 = (*arc1->lines.rbegin())->dir.rotate(PI_2); 376 | arc_dir_m1 = line1.dir.rotate(PI_2); 377 | arc_dir_s2 = (*arc2->lines.rbegin())->dir.rotate(-PI_2); 378 | arc_dir_e2 = (*arc2->lines.begin())->dir.rotate(-PI_2); 379 | arc_dir_m2 = line2.dir.rotate(-PI_2); 380 | } else { 381 | arc_dir_s1 = (*arc1->lines.begin())->dir.rotate(-PI_2); 382 | arc_dir_e1 = (*arc1->lines.rbegin())->dir.rotate(-PI_2); 383 | arc_dir_m1 = line1.dir.rotate(-PI_2); 384 | arc_dir_s2 = (*arc2->lines.rbegin())->dir.rotate(PI_2); 385 | arc_dir_e2 = (*arc2->lines.begin())->dir.rotate(PI_2); 386 | arc_dir_m2 = line2.dir.rotate(PI_2); 387 | } 388 | Vectord test_vec1 = (*arc2->lines.begin())->sp - (*arc1->lines.begin())->sp; 389 | Vectord test_vec2 = (*arc2->lines.rbegin())->ep - (*arc1->lines.rbegin())->ep; 390 | Vectord test_vec3 = (test_vec1 + test_vec2) / 2.0; 391 | 392 | double t1, t2, t3, t4, t5, t6; 393 | t1 = arc_dir_s1.dot(test_vec1); 394 | t2 = arc_dir_e1.dot(test_vec2); 395 | t3 = arc_dir_m1.dot(test_vec3); 396 | t4 = -arc_dir_e2.dot(test_vec1); 397 | t5 = -arc_dir_s2.dot(test_vec2); 398 | t6 = -arc_dir_m2.dot(test_vec3); 399 | return t1 >= REGION_LIMITATION_DIS_TOLERACE && 400 | t2 >= REGION_LIMITATION_DIS_TOLERACE && 401 | t3 >= REGION_LIMITATION_DIS_TOLERACE && 402 | t4 >= REGION_LIMITATION_DIS_TOLERACE && 403 | t5 >= REGION_LIMITATION_DIS_TOLERACE && 404 | t6 >= REGION_LIMITATION_DIS_TOLERACE; 405 | } 406 | 407 | 408 | bool regionLimitation(const std::shared_ptr &arc1, const std::shared_ptr &arc2) { 409 | if (!arc1 || !arc2) { 410 | return false; 411 | } 412 | Lined line1((*arc1->lines.begin())->sp, (*arc1->lines.rbegin())->ep, 0); 413 | Lined line2((*arc2->lines.begin())->sp, (*arc2->lines.rbegin())->ep, 0); 414 | Vectord arc_dir_s1, arc_dir_e1, arc_dir_m1; 415 | Vectord arc_dir_s2, arc_dir_e2, arc_dir_m2; 416 | if (arc1->polarity == SAME_POL) { 417 | arc_dir_s1 = (*arc1->lines.begin())->dir.rotate(PI_2); 418 | arc_dir_e1 = (*arc1->lines.rbegin())->dir.rotate(PI_2); 419 | arc_dir_m1 = line1.dir.rotate(PI_2); 420 | arc_dir_s2 = (*arc2->lines.begin())->dir.rotate(PI_2); 421 | arc_dir_e2 = (*arc2->lines.rbegin())->dir.rotate(PI_2); 422 | arc_dir_m2 = line2.dir.rotate(PI_2); 423 | } else { 424 | arc_dir_s1 = (*arc1->lines.begin())->dir.rotate(-PI_2); 425 | arc_dir_e1 = (*arc1->lines.rbegin())->dir.rotate(-PI_2); 426 | arc_dir_m1 = line1.dir.rotate(-PI_2); 427 | arc_dir_s2 = (*arc2->lines.begin())->dir.rotate(-PI_2); 428 | arc_dir_e2 = (*arc2->lines.rbegin())->dir.rotate(-PI_2); 429 | arc_dir_m2 = line2.dir.rotate(-PI_2); 430 | } 431 | Vectord test_vec1 = (*arc2->lines.rbegin())->ep - (*arc1->lines.begin())->sp; 432 | Vectord test_vec2 = (*arc2->lines.begin())->sp - (*arc1->lines.rbegin())->ep; 433 | Vectord test_vec3 = (test_vec1 + test_vec2) / 2.0; 434 | 435 | double t1, t2, t3, t4, t5, t6; 436 | t1 = arc_dir_s1.dot(test_vec1); 437 | t2 = arc_dir_e1.dot(test_vec2); 438 | t3 = arc_dir_m1.dot(test_vec3) * arc1->polarity * arc2->polarity; 439 | t4 = -arc_dir_e2.dot(test_vec1); 440 | t5 = -arc_dir_s2.dot(test_vec2); 441 | t6 = -arc_dir_m2.dot(test_vec3) * arc1->polarity * arc2->polarity; 442 | 443 | return (t1 >= REGION_LIMITATION_DIS_TOLERACE && 444 | t2 >= REGION_LIMITATION_DIS_TOLERACE && 445 | t3 >= REGION_LIMITATION_DIS_TOLERACE && 446 | t4 >= REGION_LIMITATION_DIS_TOLERACE && 447 | t5 >= REGION_LIMITATION_DIS_TOLERACE && 448 | t6 >= REGION_LIMITATION_DIS_TOLERACE) || 449 | regionLimitation_reverse(arc1, arc2); 450 | } 451 | 452 | static void gaussianKernel(std::vector &kernel, double sigma, double mean) { 453 | double sum = 0.0; 454 | for (int i = 0; i < (int)kernel.size(); ++i) { 455 | double val = ((double)i - mean) / sigma; 456 | kernel[i] = exp(-0.5 * val * val); 457 | sum += kernel[i]; 458 | } 459 | if (sum >= 0.0) { 460 | for (int i = 0; i < (int)kernel.size(); ++i) { 461 | kernel[i] /= sum; 462 | } 463 | } 464 | } 465 | 466 | bool gaussianSampler(const uint8_t *ori_data, int ori_row, int ori_col, 467 | double *data, int row, int col, 468 | double scale, double sigma_scale) { 469 | 470 | double *aux = new double[ori_row * col]; 471 | double sigma = scale < 1.0 ? sigma_scale / scale : sigma_scale; 472 | double prec = 3.0; 473 | int h = (int)ceil(sigma * sqrt(2.0 * prec * log(10.0))); 474 | int n = 1 + 2 * h; 475 | std::vector kernel(n); 476 | 477 | for (int idy = 0; idy < col; ++idy) { 478 | double yy = (double)idy / scale; 479 | int yc = (int)floor(yy + 0.5); 480 | gaussianKernel(kernel, sigma, (double)h + yy - (double)yc); 481 | for (int idx = 0; idx < ori_row; ++idx) { 482 | double sum = 0.0; 483 | for (int dim = 0; dim < n; ++dim) { 484 | int j = yc - h + dim; 485 | while (j < 0) { 486 | j += 2 * ori_col; 487 | } 488 | while (j >= 2 * ori_col) { 489 | j -= 2 * ori_col; 490 | } 491 | if (j >= ori_col) { 492 | j = 2 * ori_col - 1 - j; 493 | } 494 | sum += (double)ori_data[idx * ori_col + j] * kernel[dim]; 495 | } 496 | aux[idx * col + idy] = sum; 497 | } 498 | } 499 | 500 | for (int idx = 0; idx < row; ++idx) { 501 | double xx = (double)idx / scale; 502 | int xc = (int)floor(xx + 0.5); 503 | gaussianKernel(kernel, sigma, (double)h + xx - (double)xc); 504 | for (int idy = 0; idy < col; ++idy) { 505 | double sum = 0.0; 506 | for (int dim = 0; dim < n; ++dim) { 507 | int j = xc - h + dim; 508 | while (j < 0) { 509 | j += 2 * ori_row; 510 | } 511 | while (j >= 2 * ori_row) { 512 | j -= 2 * ori_row; 513 | } 514 | if (j >= ori_row) { 515 | j = 2 * ori_row - 1 - j; 516 | } 517 | sum += aux[j * col + idy] * kernel[dim]; 518 | } 519 | data[idx * col + idy] = sum; 520 | } 521 | } 522 | delete [] aux; 523 | return true; 524 | } 525 | 526 | 527 | } // zgh -------------------------------------------------------------------------------- /src/cvcannyapi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-6-27 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "cvcannyapi.h" 14 | #include "defines.h" 15 | 16 | namespace zgh { 17 | 18 | using namespace cv; 19 | 20 | static void cvCanny3(const void* srcarr, void* dstarr, void* dxarr, 21 | void* dyarr, int aperture_size) { 22 | // cv::Ptr dx, dy; 23 | cv::AutoBuffer buffer; 24 | std::vector stack; 25 | uchar **stack_top = 0, **stack_bottom = 0; 26 | 27 | CvMat srcstub, *src = cvGetMat(srcarr, &srcstub); 28 | CvMat dststub, *dst = cvGetMat(dstarr, &dststub); 29 | 30 | CvMat dxstub, *dx = cvGetMat(dxarr, &dxstub); 31 | CvMat dystub, *dy = cvGetMat(dyarr, &dystub); 32 | 33 | CvSize size; 34 | int flags = aperture_size; 35 | int low, high; 36 | int* mag_buf[3]; 37 | uchar* map; 38 | int mapstep; 39 | int maxsize; 40 | int i, j; 41 | CvMat mag_row; 42 | 43 | if (CV_MAT_TYPE(src->type) != CV_8UC1 || 44 | CV_MAT_TYPE(dst->type) != CV_8UC1 || 45 | CV_MAT_TYPE(dx->type) != CV_16SC1 || CV_MAT_TYPE(dy->type) != CV_16SC1) 46 | CV_Error(CV_StsUnsupportedFormat, ""); 47 | 48 | if (!CV_ARE_SIZES_EQ(src, dst)) CV_Error(CV_StsUnmatchedSizes, ""); 49 | 50 | aperture_size &= INT_MAX; 51 | if ((aperture_size & 1) == 0 || aperture_size < 3 || aperture_size > 7) 52 | CV_Error(CV_StsBadFlag, ""); 53 | 54 | size.width = src->cols; 55 | size.height = src->rows; 56 | 57 | // aperture_size = -1; //SCHARR 58 | cvSobel(src, dx, 1, 0, aperture_size); 59 | cvSobel(src, dy, 0, 1, aperture_size); 60 | 61 | Mat1f magGrad(size.height, size.width, 0.f); 62 | float maxGrad(0); 63 | float val(0); 64 | for (i = 0; i < size.height; ++i) { 65 | float* _pmag = magGrad.ptr(i); 66 | const short* _dx = (short*)(dx->data.ptr + dx->step * i); 67 | const short* _dy = (short*)(dy->data.ptr + dy->step * i); 68 | for (j = 0; j < size.width; ++j) { 69 | val = float(abs(_dx[j]) + abs(_dy[j])); 70 | _pmag[j] = val; 71 | maxGrad = (val > maxGrad) ? val : maxGrad; 72 | } 73 | } 74 | 75 | //% Normalize for threshold selection 76 | // normalize(magGrad, magGrad, 0.0, 1.0, NORM_MINMAX); 77 | 78 | //% Determine Hysteresis Thresholds 79 | 80 | // set magic numbers 81 | const int NUM_BINS = 64; 82 | const double percent_of_pixels_not_edges = 0.65; 83 | const double threshold_ratio = 0.3; 84 | 85 | // compute histogram 86 | int bin_size = cvFloor(maxGrad / float(NUM_BINS) + 0.5f) + 1; 87 | if (bin_size < 1) bin_size = 1; 88 | int bins[NUM_BINS] = {0}; 89 | for (i = 0; i < size.height; ++i) { 90 | float* _pmag = magGrad.ptr(i); 91 | for (j = 0; j < size.width; ++j) { 92 | int hgf = int(_pmag[j]); 93 | bins[hgf / bin_size]++; 94 | } 95 | } 96 | 97 | //% Select the thresholds 98 | float total(0.f); 99 | float target = 100 | float(size.height * size.width * percent_of_pixels_not_edges); 101 | int low_thresh, high_thresh(0); 102 | 103 | while (total < target) { 104 | total += bins[high_thresh]; 105 | high_thresh++; 106 | } 107 | high_thresh *= bin_size; 108 | low_thresh = cvFloor(threshold_ratio * float(high_thresh)); 109 | 110 | if (flags & CV_CANNY_L2_GRADIENT) { 111 | Cv32suf ul, uh; 112 | ul.f = (float)low_thresh; 113 | uh.f = (float)high_thresh; 114 | 115 | low = ul.i; 116 | high = uh.i; 117 | } else { 118 | low = cvFloor(low_thresh); 119 | high = cvFloor(high_thresh); 120 | } 121 | 122 | buffer.allocate((size.width + 2) * (size.height + 2) + 123 | (size.width + 2) * 3 * sizeof(int)); 124 | mag_buf[0] = (int*)(char*)buffer; 125 | mag_buf[1] = mag_buf[0] + size.width + 2; 126 | mag_buf[2] = mag_buf[1] + size.width + 2; 127 | map = (uchar*)(mag_buf[2] + size.width + 2); 128 | mapstep = size.width + 2; 129 | 130 | maxsize = MAX(1 << 10, size.width * size.height / 10); 131 | stack.resize(maxsize); 132 | stack_top = stack_bottom = &stack[0]; 133 | 134 | memset(mag_buf[0], 0, (size.width + 2) * sizeof(int)); 135 | memset(map, 1, mapstep); 136 | memset(map + mapstep * (size.height + 1), 1, mapstep); 137 | 138 | /* sector numbers 139 | (Top-Left Origin) 140 | 141 | 1 2 3 142 | * * * 143 | * * * 144 | 0*******0 145 | * * * 146 | * * * 147 | 3 2 1 148 | */ 149 | 150 | #define CANNY_PUSH(d) *(d) = (uchar)2, *stack_top++ = (d) 151 | #define CANNY_POP(d) (d) = *--stack_top 152 | 153 | mag_row = cvMat(1, size.width, CV_32F); 154 | 155 | // calculate magnitude and angle of gradient, perform non-maxima supression. 156 | // fill the map with one of the following values: 157 | // 0 - the pixel might belong to an edge 158 | // 1 - the pixel can not belong to an edge 159 | // 2 - the pixel does belong to an edge 160 | for (i = 0; i <= size.height; i++) { 161 | int* _mag = mag_buf[(i > 0) + 1] + 1; 162 | float* _magf = (float*)_mag; 163 | const short* _dx = (short*)(dx->data.ptr + dx->step * i); 164 | const short* _dy = (short*)(dy->data.ptr + dy->step * i); 165 | uchar* _map; 166 | int x, y; 167 | int magstep1, magstep2; 168 | int prev_flag = 0; 169 | 170 | if (i < size.height) { 171 | _mag[-1] = _mag[size.width] = 0; 172 | 173 | if (!(flags & CV_CANNY_L2_GRADIENT)) 174 | for (j = 0; j < size.width; j++) _mag[j] = abs(_dx[j]) + abs(_dy[j]); 175 | 176 | else { 177 | for (j = 0; j < size.width; j++) { 178 | x = _dx[j]; 179 | y = _dy[j]; 180 | _magf[j] = (float)std::sqrt((double)x * x + (double)y * y); 181 | } 182 | } 183 | } else 184 | memset(_mag - 1, 0, (size.width + 2) * sizeof(int)); 185 | 186 | // at the very beginning we do not have a complete ring 187 | // buffer of 3 magnitude rows for non-maxima suppression 188 | if (i == 0) continue; 189 | 190 | _map = map + mapstep * i + 1; 191 | _map[-1] = _map[size.width] = 1; 192 | 193 | _mag = mag_buf[1] + 1; // take the central row 194 | _dx = (short*)(dx->data.ptr + dx->step * (i - 1)); 195 | _dy = (short*)(dy->data.ptr + dy->step * (i - 1)); 196 | 197 | magstep1 = mag_buf[2] - mag_buf[1]; 198 | magstep2 = mag_buf[0] - mag_buf[1]; 199 | 200 | if ((stack_top - stack_bottom) + size.width > maxsize) { 201 | int sz = (int)(stack_top - stack_bottom); 202 | maxsize = MAX(maxsize * 3 / 2, maxsize + 8); 203 | stack.resize(maxsize); 204 | stack_bottom = &stack[0]; 205 | stack_top = stack_bottom + sz; 206 | } 207 | 208 | for (j = 0; j < size.width; j++) { 209 | #define CANNY_SHIFT 15 210 | #define TG22 (int)(0.4142135623730950488016887242097 * (1 << CANNY_SHIFT) + 0.5) 211 | 212 | x = _dx[j]; 213 | y = _dy[j]; 214 | int s = x ^ y; 215 | int m = _mag[j]; 216 | 217 | x = abs(x); 218 | y = abs(y); 219 | if (m > low) { 220 | int tg22x = x * TG22; 221 | int tg67x = tg22x + ((x + x) << CANNY_SHIFT); 222 | 223 | y <<= CANNY_SHIFT; 224 | 225 | if (y < tg22x) { 226 | if (m > _mag[j - 1] && m >= _mag[j + 1]) { 227 | if (m > high && !prev_flag && _map[j - mapstep] != 2) { 228 | CANNY_PUSH(_map + j); 229 | prev_flag = 1; 230 | } else 231 | _map[j] = (uchar)0; 232 | continue; 233 | } 234 | } else if (y > tg67x) { 235 | if (m > _mag[j + magstep2] && m >= _mag[j + magstep1]) { 236 | if (m > high && !prev_flag && _map[j - mapstep] != 2) { 237 | CANNY_PUSH(_map + j); 238 | prev_flag = 1; 239 | } else 240 | _map[j] = (uchar)0; 241 | continue; 242 | } 243 | } else { 244 | s = s < 0 ? -1 : 1; 245 | if (m > _mag[j + magstep2 - s] && m > _mag[j + magstep1 + s]) { 246 | if (m > high && !prev_flag && _map[j - mapstep] != 2) { 247 | CANNY_PUSH(_map + j); 248 | prev_flag = 1; 249 | } else 250 | _map[j] = (uchar)0; 251 | continue; 252 | } 253 | } 254 | } 255 | prev_flag = 0; 256 | _map[j] = (uchar)1; 257 | } 258 | 259 | // scroll the ring buffer 260 | _mag = mag_buf[0]; 261 | mag_buf[0] = mag_buf[1]; 262 | mag_buf[1] = mag_buf[2]; 263 | mag_buf[2] = _mag; 264 | } 265 | 266 | // now track the edges (hysteresis thresholding) 267 | while (stack_top > stack_bottom) { 268 | uchar* m; 269 | if ((stack_top - stack_bottom) + 8 > maxsize) { 270 | int sz = (int)(stack_top - stack_bottom); 271 | maxsize = MAX(maxsize * 3 / 2, maxsize + 8); 272 | stack.resize(maxsize); 273 | stack_bottom = &stack[0]; 274 | stack_top = stack_bottom + sz; 275 | } 276 | 277 | CANNY_POP(m); 278 | 279 | if (!m[-1]) CANNY_PUSH(m - 1); 280 | if (!m[1]) CANNY_PUSH(m + 1); 281 | if (!m[-mapstep - 1]) CANNY_PUSH(m - mapstep - 1); 282 | if (!m[-mapstep]) CANNY_PUSH(m - mapstep); 283 | if (!m[-mapstep + 1]) CANNY_PUSH(m - mapstep + 1); 284 | if (!m[mapstep - 1]) CANNY_PUSH(m + mapstep - 1); 285 | if (!m[mapstep]) CANNY_PUSH(m + mapstep); 286 | if (!m[mapstep + 1]) CANNY_PUSH(m + mapstep + 1); 287 | } 288 | 289 | // the final pass, form the final image 290 | for (i = 0; i < size.height; i++) { 291 | const uchar* _map = map + mapstep * (i + 1) + 1; 292 | uchar* _dst = dst->data.ptr + dst->step * i; 293 | 294 | for (j = 0; j < size.width; j++) { 295 | _dst[j] = (uchar) - (_map[j] >> 1); 296 | } 297 | } 298 | } 299 | 300 | static void Canny3(InputArray image, OutputArray _edges, OutputArray _sobel_x, 301 | OutputArray _sobel_y, int apertureSize, bool L2gradient) { 302 | Mat src = image.getMat(); 303 | _edges.create(src.size(), CV_8U); 304 | _sobel_x.create(src.size(), CV_16S); 305 | _sobel_y.create(src.size(), CV_16S); 306 | 307 | CvMat c_src = src, c_dst = _edges.getMat(); 308 | CvMat c_dx = _sobel_x.getMat(); 309 | CvMat c_dy = _sobel_y.getMat(); 310 | 311 | cvCanny3(&c_src, &c_dst, &c_dx, &c_dy, 312 | apertureSize + (L2gradient ? CV_CANNY_L2_GRADIENT : 0)); 313 | } 314 | 315 | 316 | bool calculateGradient3(const uint8_t* data, int row, int col, double* angles) { 317 | cv::Mat1b edge; 318 | cv::Mat1s DX, DY; 319 | cv::Mat1b gray = cv::Mat::zeros(row, col, CV_8UC1); 320 | 321 | // copy to gray image 322 | memcpy(gray.data, data, sizeof(uint8_t) * row * col); 323 | 324 | // canny 325 | Canny3(gray, edge, DX, DY, 3, false); 326 | 327 | for (int idx = 0; idx < row; ++idx) { 328 | short* _dx = DX.ptr(idx); 329 | short* _dy = DY.ptr(idx); 330 | uchar* _e = edge.ptr(idx); 331 | for (int idy = 0; idy < col; ++idy) { 332 | if (_e[idy] > 0) { 333 | angles[idx * col + idy] = 334 | atan2((double)_dy[idy], (double)_dx[idy]); // calculate gradient 335 | } else { 336 | angles[idx * col + idy] = ANGLE_NOT_DEF; 337 | } 338 | } 339 | } 340 | 341 | edge.release(); 342 | DX.release(); 343 | DY.release(); 344 | gray.release(); 345 | return true; 346 | } 347 | 348 | } // namespace zgh -------------------------------------------------------------------------------- /src/detect.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "defines.h" 18 | #include "detect.h" 19 | #include "unitily.h" 20 | #include "compute.h" 21 | #include "cvcannyapi.h" 22 | 23 | 24 | namespace zgh { 25 | 26 | bool lsdgroups(const double *image, int row, int col, double scale, std::vector >& arcs) { 27 | std::vector > lines; 28 | lineSegmentDetection(image, row, col, lines); 29 | 30 | int *pixlabel = new int[row * col]; 31 | bool *used = new bool[(int)lines.size()]; 32 | memset(used, 0, lines.size() * sizeof(bool)); 33 | memset(pixlabel, -1, sizeof(int) * row * col); 34 | for (size_t idx = 0; idx < lines.size(); ++idx) { 35 | for (auto &pix : lines[idx]->getregs()) { 36 | pixlabel[pix.x * col + pix.y] = idx; 37 | } 38 | } 39 | 40 | for (size_t begin_line_id = 0; begin_line_id < lines.size(); ++begin_line_id) { 41 | if (!used[begin_line_id]) { 42 | // 4 X 4 邻域进行搜索 43 | int xsize = 4, ysize = 4; 44 | // 向后搜索能组合成弧的线段 45 | std::vector > group_back; 46 | group_back.push_back(lines[begin_line_id]); 47 | size_t current_id = begin_line_id; 48 | bool isFound = true; 49 | while (isFound) { 50 | used[current_id] = true; 51 | auto updir = lines[current_id]->dir; 52 | auto downdir = updir; 53 | if (lines[current_id]->polarity == SAME_POL) { 54 | downdir.rotation(PI_4); 55 | } else { 56 | downdir.rotation(-PI_4); 57 | } 58 | std::unordered_map vote; 59 | int max_vote = 0, max_idx = -1; 60 | 61 | for (int idx = 1; idx <= xsize; ++idx) { 62 | for (int idy = 1; idy <= ysize; ++idy) { 63 | int x = (int)(lines[current_id]->ep.x + updir.x * idx + downdir.x * idy); 64 | int y = (int)(lines[current_id]->ep.y + updir.y * idx + downdir.y * idy); 65 | 66 | if (inRect(row, col, x, y) && pixlabel[x * col + y] != -1) { 67 | int line_id = pixlabel[x * col + y]; 68 | pixlabel[x * col + y] = -1; 69 | int cnt = ++vote[line_id]; 70 | if (max_idx == -1) { 71 | max_idx = line_id; 72 | max_vote = 1; 73 | } else if (cnt > max_vote){ 74 | max_idx = line_id; 75 | max_vote = cnt; 76 | } 77 | } 78 | } 79 | } 80 | 81 | if (max_vote >= 5 && !used[max_idx] && lines[begin_line_id]->polarity == lines[max_idx]->polarity) { 82 | double start_angle = atan2(lines[current_id]->dir.y, lines[current_id]->dir.x); 83 | double end_angle = atan2(lines[max_idx]->dir.y, lines[max_idx]->dir.x); 84 | double angle_delta = rotateAngle(start_angle, end_angle, lines[begin_line_id]->polarity);; 85 | if (angle_delta <= 3.0 * PI_8) { 86 | group_back.push_back(lines[max_idx]); 87 | current_id = max_idx; 88 | } else { 89 | isFound = false; 90 | } 91 | } else { 92 | isFound = false; 93 | } 94 | } 95 | 96 | // 向前搜索能组合成弧的线段 97 | 98 | std::vector > group_front; 99 | group_front.push_back(lines[begin_line_id]); 100 | current_id = begin_line_id; 101 | isFound = true; 102 | while (isFound) { 103 | used[current_id] = true; 104 | auto updir = Pointd(0, 0) - lines[current_id]->dir; 105 | auto downdir = updir; 106 | if (lines[current_id]->polarity == SAME_POL) { 107 | downdir.rotation(-PI_4); 108 | } else { 109 | downdir.rotation(PI_4); 110 | } 111 | 112 | 113 | std::unordered_map vote; 114 | int max_vote = 0, max_idx = -1; 115 | 116 | for (int idx = 1; idx <= xsize; ++idx) { 117 | for (int idy = 1; idy <= ysize; ++idy) { 118 | int x = (int)(lines[current_id]->sp.x + updir.x * idx + downdir.x * idy); 119 | int y = (int)(lines[current_id]->sp.y + updir.y * idx + downdir.y * idy); 120 | 121 | if (inRect(row, col, x, y) && pixlabel[x * col + y] != -1) { 122 | int line_id = pixlabel[x * col + y]; 123 | pixlabel[x * col + y] = -1; 124 | int cnt = ++vote[line_id]; 125 | if (max_idx == -1) { 126 | max_idx = line_id; 127 | max_vote = 1; 128 | } else if (cnt > max_vote){ 129 | max_idx = line_id; 130 | max_vote = cnt; 131 | } 132 | } 133 | } 134 | } 135 | if (max_vote >= 5 && !used[max_idx] && lines[begin_line_id]->polarity == lines[max_idx]->polarity) { 136 | double start_angle = atan2(lines[current_id]->dir.y, lines[current_id]->dir.x); 137 | double end_angle = atan2(lines[max_idx]->dir.y, lines[max_idx]->dir.x); 138 | // 注意此时需要调换一下,因为是从尾部开始搜索 139 | double angle_delta = rotateAngle(end_angle, start_angle, lines[begin_line_id]->polarity); 140 | if (angle_delta <= 3.0 * PI_8) { 141 | group_front.push_back(lines[max_idx]); 142 | current_id = max_idx; 143 | } else { 144 | isFound = false; 145 | } 146 | } else { 147 | isFound = false; 148 | } 149 | } 150 | 151 | std::shared_ptr arc(nullptr); 152 | for (int idx = (int)group_front.size() - 1; idx >= 0; --idx) { 153 | group_front[idx]->sp /= scale; 154 | group_front[idx]->ep /= scale; 155 | group_front[idx]->length /= scale; 156 | if (!arc) { 157 | arc = std::make_shared(std::move(group_front[idx])); 158 | } else { 159 | arc->merge(std::move(group_front[idx])); 160 | } 161 | } 162 | 163 | for (size_t idx = 1; idx < group_back.size(); ++idx) { 164 | group_back[idx]->sp /= scale; 165 | group_back[idx]->ep /= scale; 166 | group_back[idx]->length /= scale; 167 | arc->merge(std::move(group_back[idx])); 168 | } 169 | arcs.push_back(arc); 170 | } 171 | } 172 | 173 | delete [] pixlabel; 174 | delete [] used; 175 | return true; 176 | } 177 | 178 | bool getValidInitialEllipseSet(const uint8_t *image, 179 | const double *angles, 180 | int row, int col, 181 | std::vector > &ells, 182 | int polarity) { 183 | 184 | double scale = 0.8; 185 | double sigma_scale = 0.6; 186 | int scale_row = (int)ceil(1.0 * row * scale); 187 | int scale_col = (int)ceil(1.0 * col * scale); 188 | double *scale_data = new double[scale_row * scale_col]; 189 | gaussianSampler(image, row, col, scale_data, scale_row, scale_col, scale, sigma_scale); 190 | std::vector > arcs; 191 | 192 | lsdgroups(scale_data, scale_row, scale_col, scale, arcs); 193 | delete [] scale_data; 194 | 195 | int groupsNum = (int)arcs.size(); 196 | 197 | for (int id = 0; id < groupsNum; ++id) { 198 | if (polarity == 0 || arcs[id]->polarity == polarity) { 199 | if (arcs[id]->coverages >= 4.0 * PI / 9.0) { 200 | auto ell = calcElliseParam(arcs[id], nullptr, angles, row, col); 201 | if (ell) { 202 | ells.push_back(std::move(ell)); 203 | } 204 | } 205 | } 206 | } 207 | 208 | for (int i = 0; i < groupsNum - 1; ++i) { 209 | for (int j = i + 1; j < groupsNum; ++j) { 210 | if ((arcs[i]->polarity == polarity || polarity == NONE_POL) && 211 | (arcs[j]->polarity == polarity || polarity == NONE_POL) && 212 | (int)arcs[i]->lines.size() + (int)arcs[j]->lines.size() >= 3 && 213 | arcs[i]->coverages + arcs[j]->coverages >= 3.0 * PI / 9.0 214 | ) { 215 | if (regionLimitation(arcs[i], arcs[j])) { 216 | auto ell = calcElliseParam(arcs[i], arcs[j], angles, row, col); 217 | if (ell) { 218 | ells.push_back(std::move(ell)); 219 | } 220 | } 221 | } 222 | } 223 | } 224 | return true; 225 | } 226 | 227 | 228 | 229 | static bool meanShift(const std::vector &data, std::vector &init_data, int dims, 230 | double sigma, double windos_size, double accuracy_tolerance, int iter_times) { 231 | 232 | double temparr[8]; 233 | int nquerrues = (int)init_data.size() / dims; 234 | int data_num = (int)data.size() / dims; 235 | double sigma2 = sigma * sigma; 236 | double radius2 = windos_size * windos_size; 237 | double tolerance = accuracy_tolerance; 238 | int maxiters = iter_times; 239 | 240 | std::vector dis(data_num); 241 | for (int loop = 0; loop < nquerrues; ++loop) { 242 | int iters = 0; 243 | while (iters < maxiters) { 244 | bool flag = false; 245 | double denominator = 0.0; 246 | for (int i = 0; i < data_num; ++i) { 247 | double temp = 0; 248 | for (int d = 0; d < dims; ++d) { 249 | temp += sqr(data[dims * i + d] - init_data[loop * dims + d]); 250 | } 251 | dis[i] = temp; 252 | if (dis[i] <= radius2) { 253 | flag = true; 254 | denominator += exp(-dis[i] / sigma2); 255 | } 256 | } 257 | if (!flag) { 258 | break; 259 | } 260 | for (int d = 0; d < dims; ++d) { 261 | temparr[d] = init_data[loop * dims + d]; 262 | init_data[loop * dims + d] = 0; 263 | } 264 | for (int i = 0; i < data_num; ++i) { 265 | if (dis[i] <= radius2) { 266 | for (int d = 0; d < dims; ++d) { 267 | init_data[loop * dims + d] += exp(-dis[i] / sigma2) * data[i * dims + d]; 268 | } 269 | } 270 | } 271 | double temp = 0; 272 | for (int d = 0; d < dims; ++d) { 273 | init_data[loop * dims + d] /= denominator; 274 | temp += sqr(init_data[loop * dims + d] - temparr[d]); 275 | } 276 | 277 | if (sqrt(temp) < tolerance) { 278 | break; 279 | } 280 | ++iters; 281 | } 282 | } 283 | return true; 284 | } 285 | 286 | static bool clusterByDistance(std::vector &data, int dims, double distance_threshold, 287 | double number_control) { 288 | 289 | double threshold2 = distance_threshold * distance_threshold; 290 | int npoints = (int)data.size() / dims; 291 | if (npoints == 1) { 292 | return true; 293 | } 294 | int nout = 0; 295 | std::vector data_out; 296 | std::vector counts; 297 | std::vector labeled(npoints, false); 298 | for (int idx = 0; idx < npoints; ++idx) { 299 | if (!labeled[idx]) { 300 | ++nout; 301 | labeled[idx] = true; 302 | for (int d = 0; d < dims; ++d) { 303 | data_out.push_back(data[idx * dims + d]); 304 | } 305 | counts.push_back(1); 306 | for (int idy = idx + 1; idy < npoints; ++idy) { 307 | if (!labeled[idy]) { 308 | double dis = 0; 309 | for (int d = 0; d < dims; ++d) { 310 | dis += sqr(data_out[(nout - 1) * dims + d] / counts[nout - 1] - data[idy * dims + d]); 311 | } 312 | if (dis <= threshold2) { 313 | ++counts[nout - 1]; 314 | labeled[idy] = true; 315 | for (int d = 0; d < dims; ++d) { 316 | data_out[(nout - 1) * dims + d] += data[idy * dims + d]; 317 | } 318 | if (counts[nout - 1] >= number_control) { 319 | // 聚类数量控制,防止均值中心漂的太远 圆心聚类时 20 半径聚类时 10 320 | break; 321 | } 322 | } 323 | } 324 | } 325 | } 326 | } 327 | for (int id = 0; id < nout; ++id) { 328 | for (int d = 0; d < dims; ++d) { 329 | data_out[id * dims + d] /= counts[id]; 330 | } 331 | } 332 | data.clear(); 333 | data = std::move(data_out); 334 | return true; 335 | } 336 | 337 | static bool cluster2DPoints(const std::vector > &ells, 338 | std::vector &cluster_center, 339 | double distance_tolerance, 340 | int data_type) { 341 | 342 | int nbinx, nbiny; 343 | double xmax, ymax, xmin, ymin; 344 | xmax = ymax = 0; 345 | xmin = ymin = DBL_MAX; 346 | std::vector data; 347 | int npoints = (int)ells.size(); 348 | for (auto &ell : ells) { 349 | if (data_type == 0) { 350 | data.push_back(ell->o.x); 351 | data.push_back(ell->o.y); 352 | xmax = max(xmax, ell->o.x); 353 | xmin = min(xmin, ell->o.x); 354 | ymax = max(ymax, ell->o.y); 355 | ymin = min(ymin, ell->o.y); 356 | } else { 357 | data.push_back(ell->a); 358 | data.push_back(ell->b); 359 | xmax = max(xmax, ell->a); 360 | xmin = min(xmin, ell->a); 361 | ymax = max(ymax, ell->b); 362 | ymin = min(ymin, ell->b); 363 | } 364 | } 365 | xmax += xmax * 0.02; 366 | xmin -= xmin * 0.02; 367 | ymax += ymax * 0.02; 368 | ymin -= ymin * 0.02; 369 | double xdelta = xmax - xmin; 370 | double ydelta = ymax - ymin; 371 | nbinx = (int)ceil(xdelta / distance_tolerance); 372 | nbiny = (int)ceil(ydelta / distance_tolerance); 373 | if (nbinx <= 0) { 374 | nbinx = 1; 375 | } 376 | if (nbiny <= 0) { 377 | nbiny = 1; 378 | } 379 | std::map > bindata; 380 | for (int id = 0; id < npoints; ++id) { 381 | int x = (int)floor((data[id * 2] - xmin) / xdelta * nbinx + 0.5); 382 | int y = (int)floor((data[id * 2 + 1] - ymin) / ydelta * nbiny + 0.5); 383 | if (x >= nbinx) { 384 | x = nbinx - 1; 385 | } 386 | if (y >= nbiny - 1) { 387 | y = nbiny - 1; 388 | } 389 | Pointi cell(x, y); 390 | if (!bindata.count(cell)) { 391 | bindata.insert(std::make_pair(cell, std::make_pair(Pointd(data[id * 2], data[id * 2 + 1]), 1))); 392 | } else { 393 | bindata[cell].first += Pointd(data[id * 2], data[id * 2 + 1]); 394 | ++bindata[cell].second; 395 | } 396 | } 397 | std::vector init_data; 398 | for (auto &bin : bindata) { 399 | init_data.push_back(bin.second.first.x / bin.second.second); 400 | init_data.push_back(bin.second.first.y / bin.second.second); 401 | } 402 | 403 | meanShift(data, init_data, 2, 1, distance_tolerance, 1e-6, 50); 404 | 405 | clusterByDistance(init_data, 2, distance_tolerance / 2, 40); 406 | 407 | for (int id = 0; id < (int)init_data.size(); id += 2) { 408 | cluster_center.push_back(Pointd(init_data[id], init_data[id + 1])); 409 | } 410 | return true; 411 | } 412 | 413 | static bool cluster1DDatas(const std::vector > &ells, 414 | std::vector &cluster_center, 415 | double distance_tolerance) { 416 | 417 | double val_max = 0; 418 | double val_min = DBL_MAX; 419 | std::vector data; 420 | for (auto &ell : ells) { 421 | val_max = max(val_max, ell->phi); 422 | val_min = min(val_min, ell->phi); 423 | data.push_back(ell->phi); 424 | } 425 | val_max += val_min * 0.02; // avoid rmax - rmin = 0 426 | val_min -= val_min * 0.02; 427 | 428 | double val_delta = val_max - val_min; 429 | int nbins = (int)ceil(val_delta / distance_tolerance); 430 | if (nbins <= 0) { 431 | nbins = 1; 432 | } 433 | // first sum, second vote; 434 | std::vector > bindata(nbins, std::make_pair(0.0, 0)); 435 | for (auto &ell : ells) { 436 | int r = (int)floor((ell->phi - val_min) / val_delta * nbins + 0.5); 437 | if (r >= nbins) { 438 | r = nbins - 1; 439 | } 440 | bindata[r].first += ell->phi; 441 | ++bindata[r].second; 442 | } 443 | auto pend = std::remove_if(bindata.begin(), bindata.end(), [](std::pair &data) { 444 | if (data.second == 0) { 445 | return true; 446 | } 447 | return false; 448 | }); 449 | cluster_center.clear(); 450 | for (auto iter = bindata.begin(); iter != pend; ++iter) { 451 | cluster_center.push_back(iter->first / iter->second); 452 | } 453 | bindata.clear(); 454 | 455 | 456 | // 均值漂移 457 | meanShift(data, cluster_center, 1, 1, distance_tolerance, 1e-6, 20); 458 | 459 | // 按照距离阈值聚类 460 | clusterByDistance(cluster_center, 1, distance_tolerance / 2, 40); 461 | 462 | return true; 463 | } 464 | 465 | bool generateEllipseCandidates(const uint8_t *image, const double *angles, 466 | int row, int col, 467 | std::vector > &ells, int polarity) { 468 | 469 | std::vector > ells_init; 470 | 471 | getValidInitialEllipseSet(image, angles, row, col, ells_init, polarity); 472 | 473 | int init_size = (int)ells_init.size(); 474 | if (init_size == 0) { 475 | return true; 476 | } 477 | 478 | 479 | // 最外层椭圆中心聚类 第二层椭圆 phi 聚类 第三层椭圆长短轴聚类 480 | 481 | std::vector cluster_center; 482 | cluster2DPoints(ells_init, cluster_center, MIN_ELLIPSE_THRESHOLD_LENGTH, 0); 483 | 484 | int center_num = (int)cluster_center.size(); 485 | 486 | std::vector > > ells_center(center_num); 487 | 488 | // TODO(using KD tree optimization if necessary) 489 | for (auto &ell : ells_init) { 490 | double dis_min = DBL_MAX; 491 | int idx = -1; 492 | for (int centerid = 0; centerid < center_num; ++centerid) { 493 | double temp_dis = (ell->o - cluster_center[centerid]).length(); 494 | if (temp_dis < dis_min) { 495 | dis_min = temp_dis; 496 | idx = centerid; 497 | } 498 | } 499 | ells_center[idx].push_back(ell); 500 | } 501 | 502 | for (int center_id = 0; center_id < center_num; ++center_id) { 503 | auto &ells_c = ells_center[center_id]; 504 | if ((int)ells_c.size() == 0) { 505 | continue; 506 | } 507 | 508 | // phi 聚类 509 | std::vector cluster_phi; 510 | cluster1DDatas(ells_c, cluster_phi, 0.0873); 511 | int phi_num = (int)cluster_phi.size(); 512 | int ells_cnum = (int)ells_c.size(); 513 | 514 | std::sort(cluster_phi.begin(), cluster_phi.end()); 515 | std::sort(ells_c.begin(), ells_c.end(), 516 | [](const std::shared_ptr &ea, const std::shared_ptr &eb) { 517 | return ea->phi < eb->phi; 518 | }); 519 | int p = 0; 520 | for (int phi_id = 0; phi_id < phi_num; ++phi_id) { 521 | std::vector > ells_p; 522 | while (p < ells_cnum && (phi_id == phi_num - 1 || 523 | fabs(ells_c[p]->phi - cluster_phi[phi_id]) 524 | < fabs(ells_c[p]->phi - cluster_phi[phi_id + 1]))) { 525 | ells_p.push_back(ells_c[p]); 526 | ++p; 527 | } 528 | 529 | // 椭圆长短轴聚类 530 | int ells_pnum = (int)ells_p.size(); 531 | if (ells_pnum == 0) { 532 | continue; 533 | } 534 | 535 | std::vector cluster_axis; 536 | cluster2DPoints(ells_p, cluster_axis, MIN_ELLIPSE_THRESHOLD_LENGTH, 1); 537 | for (auto &p : cluster_axis) { 538 | ells.push_back(std::make_shared(cluster_center[center_id], p.x, p.y, cluster_phi[phi_id])); 539 | } 540 | } 541 | } 542 | return true; 543 | } 544 | 545 | 546 | static int improveInliers(std::vector &inliers, Pointd ell_center, int tbins) { 547 | 548 | // 基于联通性分析提升内点质量 549 | double tmin = -PI, tmax = PI; 550 | std::vector votes(tbins); 551 | std::vector tids; 552 | for (auto &pix : inliers) { 553 | double theta = atan2(1.0 * pix.y - ell_center.y, 1.0 * pix.x - ell_center.x); 554 | double tid = (int)floor((theta - tmin) / (tmax - tmin) * tbins + 0.5); 555 | if (tid >= tbins) { 556 | tid = tbins - 1; 557 | } 558 | tids.push_back(tid); 559 | ++votes[tid]; 560 | } 561 | 562 | // bfs 563 | std::vector labels(tbins, -1); 564 | std::vector comp_lengths; 565 | int npoints = (int)inliers.size(); 566 | int ncomp = 0; 567 | int head = 0, tail = tbins - 1; 568 | int lens = 0; 569 | if (votes[0]) { 570 | while (head <= tail && votes[head]) { 571 | labels[head] = ncomp; 572 | ++head; 573 | ++lens; 574 | } 575 | while (head <= tail && votes[tail]) { 576 | labels[tail] = ncomp; 577 | --tail; 578 | ++lens; 579 | } 580 | comp_lengths.push_back(lens); 581 | ++ncomp; 582 | } 583 | 584 | for (int id = head; id <= tail; ++id) { 585 | if (votes[id]) { 586 | lens = 0; 587 | while (id <= tail && votes[id]) { 588 | labels[id] = ncomp; 589 | ++lens; 590 | ++id; 591 | } 592 | comp_lengths.push_back(lens); 593 | ++ncomp; 594 | } 595 | } 596 | votes.clear(); 597 | 598 | int max_len = 0; 599 | for (auto &len : comp_lengths) { 600 | max_len = max(max_len, len); 601 | } 602 | std::vector inliers_out; 603 | for (int id = 0; id < npoints; ++id) { 604 | int &label = labels[tids[id]]; 605 | if (label != -1 && 10 * comp_lengths[label] >= max_len && comp_lengths[label] > 10) { 606 | inliers_out.push_back(inliers[id]); 607 | } else { 608 | label = -1; 609 | } 610 | } 611 | inliers.clear(); 612 | inliers = std::move(inliers_out); 613 | int bin_count = 0; 614 | for (int id = 0; id < tbins; ++id) { 615 | if (labels[id] != -1) { 616 | ++bin_count; 617 | } 618 | } 619 | return bin_count; 620 | } 621 | 622 | static bool subdetect(double *angles, int row, int col, double min_cover_angle, 623 | double distance_tolerance, double normal_tolerance, double tr, 624 | std::vector > &ells) { 625 | 626 | std::vector > ells_out; 627 | for (int id = 0; id < (int)ells.size(); ++id) { 628 | auto &ell = ells[id]; 629 | bool issame = false; 630 | for (int idcompare = 0; idcompare < id; ++idcompare) { 631 | if (ell->equal(ells[idcompare])) { 632 | issame = true; 633 | break; 634 | } 635 | } 636 | if (issame) { 637 | continue; 638 | } 639 | 640 | double beta = PI * (1.5 * (ell->a + ell->b) - sqrt(ell->a * ell->b)); 641 | int tbins = min(180, (int)floor(beta * tr)); 642 | std::vector inliers_t; 643 | 644 | for (auto &pix : ell->inliers) { 645 | int addr = pix.x * col + pix.y; 646 | if (!equal(angles[addr], ANGLE_NOT_DEF)) { 647 | inliers_t.push_back(pix); 648 | } 649 | } 650 | ell->inliers.clear(); 651 | ell->inliers = std::move(inliers_t); 652 | std::shared_ptr ell_t = fitEllipse(ell->inliers); 653 | if (ell_t) { 654 | ell_t->polarity = ell->polarity; 655 | if (ell_t->equal(ell, 3 * distance_tolerance, 0.1, 0.1, 0.1, 0.9)) { 656 | 657 | // 重新计算内点 658 | beta = PI * (1.5 * (ell_t->a + ell_t->b) - sqrt(ell_t->a * ell_t->b)); 659 | tbins = min(180, (int)floor(beta * tr)); 660 | 661 | std::vector new_inliers; 662 | for (EllipseIter iter(ell_t, distance_tolerance); !iter.isEnd(); ++iter) { 663 | if (inRect(row, col, iter.np.x, iter.np.y)) { 664 | int addr = iter.np.x * col + iter.np.y; 665 | if (!equal(angles[addr], ANGLE_NOT_DEF)) { 666 | Vectord tanline = ell_t->getTangent(iter.np); 667 | Vectord grad_normal(sin(angles[addr]), cos(angles[addr])); 668 | if (tanline.dot(grad_normal) >= normal_tolerance && 669 | (ell->polarity == NONE_POL || ell->polarity == SAME_POL)) { 670 | new_inliers.push_back(iter.np); 671 | } 672 | if (tanline.dot(grad_normal) <= -normal_tolerance && 673 | (ell->polarity == NONE_POL || ell->polarity == OPP_POL)) { 674 | new_inliers.push_back(iter.np); 675 | } 676 | } 677 | } 678 | } 679 | int bin_count = improveInliers(new_inliers, ell_t->o, tbins); 680 | if ((int)new_inliers.size() - (int)ell->inliers.size() >= -10) { 681 | std::shared_ptr ell_tt = fitEllipse(new_inliers); 682 | if (ell_tt) { 683 | ell_tt->polarity = ell_t->polarity; 684 | ell_tt->inliers = std::move(new_inliers); 685 | double support_inliers_ratio = 1.0 * ell_tt->inliers.size() / distance_tolerance / beta; 686 | double completeness_ratio = 1.0 * bin_count / tbins; 687 | ell_tt->coverangle = completeness_ratio * 360.0; 688 | ell_tt->goodness = sqrt(support_inliers_ratio * completeness_ratio); 689 | ell_t = ell_tt; 690 | } else { 691 | ell_t->inliers = std::move(ell->inliers); 692 | ell_t->goodness = ell->goodness; 693 | ell_t->coverangle = ell->coverangle; 694 | } 695 | } else { 696 | ell_t->inliers = std::move(ell->inliers); 697 | ell_t->goodness = ell->goodness; 698 | ell_t->coverangle = ell->coverangle; 699 | } 700 | } else { 701 | ell_t->inliers = std::move(ell->inliers); 702 | ell_t->goodness = ell->goodness; 703 | ell_t->coverangle = ell->coverangle; 704 | } 705 | } else { 706 | ell_t = ell; 707 | } 708 | 709 | 710 | if ((double)ell_t->inliers.size() / distance_tolerance >= beta * tr / 2.0) { 711 | bool isComplete = (ell_t->coverangle >= min_cover_angle && ell_t->goodness >= 0.4); 712 | if (isComplete) { 713 | bool isUnique = true; 714 | for (auto &new_ell : ells_out) { 715 | if (new_ell->equal(ell, distance_tolerance)) { 716 | isUnique = false; 717 | break; 718 | } 719 | } 720 | if (isUnique) { 721 | ells_out.push_back(ell_t); 722 | for (auto &pix : ell_t->inliers) { 723 | int addr = pix.x * col + pix.y; 724 | angles[addr] = ANGLE_NOT_DEF; 725 | } 726 | } 727 | } 728 | } 729 | } 730 | 731 | ells.clear(); 732 | ells = std::move(ells_out); 733 | return true; 734 | } 735 | 736 | bool detectEllipse(const uint8_t *image, int row, int col, 737 | std::vector > &ells, int polarity, double width) { 738 | 739 | // calc the gradient 740 | double *angles = new double[row * col]; 741 | 742 | calculateGradient3(image, row, col, angles); 743 | 744 | generateEllipseCandidates(image, angles, row, col, ells, polarity); 745 | 746 | 747 | // control parameter 748 | double min_angle_coverage = 240; 749 | double tr = 0.6; 750 | double distance_tolerance = MIN_ELLIPSE_THRESHOLD_LENGTH; 751 | double normal_tolerance = cos(PI / 12.0); 752 | 753 | 754 | 755 | std::vector inliers_positive, inliers_negative, inliers_all; 756 | std::vector > ells_temp; 757 | for (auto &ell : ells) { 758 | ell->inliers.clear(); 759 | inliers_positive.clear(); 760 | inliers_negative.clear(); 761 | inliers_all.clear(); 762 | double beta = PI * (1.5 * (ell->a + ell->b) - sqrt(ell->a * ell->b)); 763 | int tbins = min(180, (int)floor(beta * tr)); 764 | 765 | for (EllipseIter iter(ell, distance_tolerance); !iter.isEnd(); ++iter) { 766 | if (inRect(row, col, iter.np.x, iter.np.y)) { 767 | int addr = iter.np.x * col + iter.np.y; 768 | if (!equal(angles[addr], ANGLE_NOT_DEF)) { 769 | Vectord tanline = ell->getTangent(iter.np); 770 | Vectord grad_normal(sin(angles[addr]), cos(angles[addr])); 771 | double dv = tanline.dot(grad_normal); 772 | if (dv >= normal_tolerance) { 773 | inliers_positive.push_back(iter.np); 774 | inliers_all.push_back(iter.np); 775 | } else if (dv <= -normal_tolerance){ 776 | inliers_negative.push_back(iter.np); 777 | inliers_all.push_back(iter.np); 778 | } 779 | } 780 | } 781 | } 782 | 783 | if ((int)inliers_positive.size() >= 10 * (int)inliers_negative.size()) { 784 | ell->polarity = SAME_POL; 785 | ell->inliers.clear(); 786 | ell->inliers = std::move(inliers_positive); 787 | } else if ((int)inliers_negative.size() >= 10 * (int)inliers_positive.size()) { 788 | ell->polarity = OPP_POL; 789 | ell->inliers.clear(); 790 | ell->inliers = std::move(inliers_negative); 791 | } else { 792 | ell->polarity = NONE_POL; 793 | ell->inliers.clear(); 794 | ell->inliers = std::move(inliers_all); 795 | } 796 | if (polarity != NONE_POL && ell->polarity != polarity) { 797 | continue; 798 | } 799 | 800 | int bin_count = improveInliers(ell->inliers, ell->o, tbins); 801 | double support_inliers_ratio = 1.0 * ell->inliers.size() / distance_tolerance / beta; 802 | double completeness_ratio = 1.0 * bin_count / tbins; 803 | ell->coverangle = completeness_ratio * 360.0; 804 | ell->goodness = sqrt(support_inliers_ratio * completeness_ratio); 805 | if (ell->goodness >= 0.3) { 806 | ells_temp.push_back(ell); 807 | } 808 | } 809 | ells.clear(); 810 | ells = std::move(ells_temp); 811 | std::sort(ells.begin(), ells.end(), 812 | [](const std::shared_ptr &ea, const std::shared_ptr &eb) { 813 | return ea->goodness > eb->goodness; 814 | }); 815 | 816 | subdetect(angles, row, col, min_angle_coverage, width, normal_tolerance, tr, ells); 817 | 818 | delete [] angles; 819 | return true; 820 | } 821 | 822 | } // namespace zgh 823 | -------------------------------------------------------------------------------- /src/lsd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-24 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | #include 10 | #include "types.hpp" 11 | #include "detect.h" 12 | #include "unitily.h" 13 | 14 | namespace zgh { 15 | 16 | 17 | static bool ll_angle(const double *data, double *angles, double *mod, std::vector &sort_points, 18 | int nbins, int row, int col) { 19 | double max_grad = 0.0; 20 | for (int idx = 0; idx < row; ++idx) { 21 | angles[idx * col + col - 1] = ANGLE_NOT_DEF; 22 | } 23 | for (int idy = 0; idy < col; ++idy) { 24 | angles[(row - 1) * col + idy] = ANGLE_NOT_DEF; 25 | } 26 | 27 | for (int idy = 0; idy < col - 1; ++idy) { 28 | for (int idx = 0; idx < row - 1; ++idx) { 29 | int adr = idx * col + idy; 30 | /* 31 | * Norm 2 computation using 2X2 pixel window: 32 | * A B 33 | * C D 34 | * and 35 | * com1 = D - A, com2 = B - C. 36 | * Then 37 | * gx = B + D - (A + C) horizontal difference 38 | * gy = C + D - (A + B) vertical difference 39 | * com1 and com2 are just to avoid 2 additions 40 | */ 41 | double com1, com2; 42 | com1 = data[adr + col + 1] - data[adr]; 43 | com2 = data[adr + 1] - data[adr + col]; 44 | double dx = com1 + com2, dy = com1 - com2; 45 | double norm = sqrt(1.0 * (dx * dx + dy * dy) / 4.0); 46 | mod[adr] = norm; 47 | if (norm <= GRAD_THRESHOLD) { 48 | angles[adr] = ANGLE_NOT_DEF; 49 | } else { 50 | angles[adr] = atan2(dx, -dy); 51 | sort_points.push_back(Pixel(idx, idy)); 52 | if (norm > max_grad) { 53 | max_grad = norm; 54 | } 55 | } 56 | } 57 | } 58 | 59 | std::vector bucket_ranks(nbins, 0); 60 | for (auto &pix : sort_points) { 61 | int adr = pix.x * col + pix.y; 62 | int bucket_id = (int)floor(mod[adr] * (double)nbins / max_grad); 63 | if (bucket_id >= nbins) { 64 | bucket_id = nbins - 1; 65 | } 66 | ++bucket_ranks[bucket_id]; 67 | } 68 | for (int bucket_id = nbins - 2; bucket_id >= 0; --bucket_id) { 69 | bucket_ranks[bucket_id] += bucket_ranks[bucket_id + 1]; 70 | } 71 | 72 | std::vector temp_points(sort_points.size()); 73 | for (int id = (int)sort_points.size() - 1; id >= 0; --id) { 74 | auto &pix = sort_points[id]; 75 | int adr = pix.x * col + pix.y; 76 | int bucket_id = (int)floor(mod[adr] * (double)nbins / max_grad); 77 | if (bucket_id >= nbins) { 78 | bucket_id = nbins - 1; 79 | } 80 | temp_points[--bucket_ranks[bucket_id]] = std::move(pix); 81 | } 82 | sort_points.clear(); 83 | sort_points = std::move(temp_points); 84 | return true; 85 | } 86 | 87 | static bool isaligned(double angle, double line_angle, double prec) { 88 | if (equal(angle, ANGLE_NOT_DEF)) { 89 | return false; 90 | } 91 | line_angle -= angle; 92 | if (line_angle < 0) { 93 | line_angle = -line_angle; 94 | } 95 | if (line_angle > 3.0 * PI_2) { 96 | line_angle = 2.0 * PI - line_angle; 97 | if(line_angle < 0) { 98 | line_angle = -line_angle; 99 | } 100 | } 101 | return line_angle <= prec; 102 | } 103 | 104 | static std::shared_ptr regionGrow(Pixel sp, const double *angles, bool *used, 105 | double prec, int row, int col) { 106 | if (!inRect(row, col, sp.x, sp.y)) { 107 | return nullptr; 108 | } 109 | std::shared_ptr line = std::make_shared(); 110 | line->addpixel(sp); 111 | int addr = sp.x * col + sp.y; 112 | double line_angle = angles[addr]; 113 | used[addr] = USED; 114 | double sumdx = sin(line_angle); 115 | double sumdy = cos(line_angle); 116 | 117 | // bfs search 118 | auto ®s = line->getregs(); 119 | for (int i = 0; i < (int)regs.size(); ++i) { 120 | for (int dy = -1; dy <= 1; ++dy) { 121 | for (int dx = -1; dx <= 1; ++dx) { 122 | int x = regs[i].x + dx; 123 | int y = regs[i].y + dy; 124 | int addr = x * col + y; 125 | if (inRect(row, col, x, y) && used[addr] == NOTUSED && 126 | isaligned(angles[addr], line_angle, prec)) { 127 | used[addr] = USED; 128 | line->addpixel(Pixel(x, y)); 129 | sumdx += sin(angles[addr]); 130 | sumdy += cos(angles[addr]); 131 | line_angle = atan2(sumdx, sumdy); 132 | } 133 | } 134 | } 135 | } 136 | line->dir = Pointd(sin(line_angle), cos(line_angle)); 137 | return line; 138 | } 139 | 140 | static double getTheta(const std::vector ®s, double x, double y, const double *mod, 141 | double line_angle, double prec, int col) { 142 | 143 | double Ixx = 0.0; 144 | double Iyy = 0.0; 145 | double Ixy = 0.0; 146 | for (auto &pix : regs) { 147 | int addr = pix.x * col + pix.y; 148 | Ixx += sqr((double)pix.x - x) * mod[addr]; 149 | Iyy += sqr((double)pix.y - y) * mod[addr]; 150 | Ixy -= ((double)pix.x - x) * ((double)pix.y - y) * mod[addr]; 151 | } 152 | double lambda = 0.5 * (Ixx + Iyy - sqrt(sqr(Ixx - Iyy) + 4.0 * sqr(Ixy))); 153 | double theta = fabs(Ixx) > fabs(Iyy) ? atan2(lambda - Ixx, Ixy) : atan2(Ixy, lambda - Iyy); 154 | double temp1 = angle_diff(theta, line_angle); 155 | if (temp1 > prec) { 156 | double temp2 = angle_diff(theta + PI, line_angle); 157 | if (temp2 < prec) { 158 | theta += PI; 159 | if (theta > PI) { 160 | theta -= 2.0 * PI; 161 | } 162 | } else { 163 | theta = (temp2 < temp1) ? (theta + PI) : theta; 164 | while (theta <= -PI) { 165 | theta += 2.0 * PI; 166 | } 167 | while (theta > PI) { 168 | theta -= 2.0 * PI; 169 | } 170 | } 171 | } 172 | return theta; 173 | } 174 | 175 | static void region2rect(std::shared_ptr &line, const double *mod, int col) { 176 | double x = 0.0, y = 0.0, sum = 0.0; 177 | for (auto &pix : line->getregs()) { 178 | int addr = pix.x * col + pix.y; 179 | x += (double)pix.x * mod[addr]; 180 | y += (double)pix.y * mod[addr]; 181 | sum += mod[addr]; 182 | } 183 | x /= sum; 184 | y /= sum; 185 | double line_angle = atan2(line->dir.x, line->dir.y); 186 | double theta = getTheta(line->getregs(), x, y, mod, line_angle, angle2rad(ANGLE_TH), col); 187 | double dx = sin(theta); 188 | double dy = cos(theta); 189 | double lmin = 0.0; 190 | double lmax = 0.0; 191 | double wmin = 0.0; 192 | double wmax = 0.0; 193 | for (auto &pix : line->getregs()) { 194 | double l = ((double)pix.x - x) * dx + ((double)pix.y - y) * dy; 195 | double w = -((double)pix.x - x) * dy + ((double)pix.y - y) * dx; 196 | lmax = max(lmax, l); 197 | lmin = min(lmin, l); 198 | wmax = max(wmax, w); 199 | wmin = min(wmin, w); 200 | } 201 | line->sp.x = x + lmin * dx; 202 | line->sp.y = y + lmin * dy; 203 | line->ep.x = x + lmax * dx; 204 | line->ep.y = y + lmax * dy; 205 | line->length = (line->ep - line->sp).length(); 206 | line->width = wmax - wmin; 207 | if (line->width < 1.0) { 208 | line->width = 1.0; 209 | } 210 | line->center.x = x; 211 | line->center.y = y; 212 | line->dir.x = dx; 213 | line->dir.y = dy; 214 | 215 | } 216 | 217 | 218 | static bool isArcSegment(std::shared_ptr &line, const double *angles, int8_t *pol, int col) { 219 | 220 | int same_pol_cnt = 0, opp_pol_cnt = 0; 221 | for (auto &pix : line->getregs()) { 222 | int addr = pix.x * col + pix.y; 223 | if (pol[addr] == (uint8_t)SAME_POL) { 224 | ++same_pol_cnt; 225 | } else if (pol[addr] == (uint8_t)OPP_POL) { 226 | ++opp_pol_cnt; 227 | } 228 | } 229 | if ((same_pol_cnt + opp_pol_cnt) > (int)line->getregs().size() / 2) { 230 | if (same_pol_cnt > opp_pol_cnt) { 231 | line->polarity = SAME_POL; 232 | } else { 233 | line->polarity = OPP_POL; 234 | } 235 | return true; 236 | } 237 | 238 | double angle_up = 0.0, angle_down = 0.0, angle_main; 239 | double reg_up_sin = 0.0, reg_up_cos = 0.0; 240 | double reg_down_sin = 0.0, reg_down_cos = 0.0; 241 | 242 | for (auto &pix : line->getregs()) { 243 | int addr = pix.x * col + pix.y; 244 | Pointd p(pix.x, pix.y); 245 | if (line->dir.dot(p - line->center) >= 0) { 246 | reg_up_sin += sin(angles[addr]); 247 | reg_up_cos += cos(angles[addr]); 248 | } else { 249 | reg_down_sin += sin(angles[addr]); 250 | reg_down_cos += cos(angles[addr]); 251 | } 252 | } 253 | angle_up = atan2(reg_up_sin, reg_up_cos); 254 | angle_down = atan2(reg_down_sin, reg_down_cos); 255 | angle_main = atan2(reg_up_sin + reg_down_sin, reg_up_cos + reg_down_cos); 256 | 257 | double temp1 = angle_diff_signed(angle_up, angle_main); 258 | double temp2 = angle_diff_signed(angle_down, angle_main); 259 | // 实验结果最好的阈值 260 | if (temp1 >= PI_8 / 10.0 && temp2 <= -PI_8 / 10.0) { 261 | line->polarity = OPP_POL; 262 | for (auto &pix : line->getregs()) { 263 | pol[pix.x * col + pix.y] = (uint8_t)OPP_POL; 264 | } 265 | } else if (temp1 <= -PI_8 / 10.0 && temp2 >= PI_8 / 10.0) { 266 | line->polarity = SAME_POL; 267 | for (auto &pix : line->getregs()) { 268 | pol[pix.x * col + pix.y] = (uint8_t)SAME_POL; 269 | } 270 | } else { 271 | // not a Arc support segment 272 | return false; 273 | } 274 | return true; 275 | } 276 | 277 | static bool reduce_region_radius(std::shared_ptr &line, const double *mod, bool *used, 278 | double density_th, int col) { 279 | 280 | double density = (double)line->getregs().size() / (line->length * line->width); 281 | if (density >= density_th) { 282 | return true; 283 | } 284 | Pointd p(line->getregs()[0].x, line->getregs()[0].y); 285 | double rad1 = (line->sp - p).length(); 286 | double rad2 = (line->ep - p).length(); 287 | double rad = max(rad1, rad2); 288 | while (density < density_th) { 289 | auto ®s = line->getregs(); 290 | int cnt = regs.size(); 291 | rad *= 0.75; 292 | std::vector temppix; 293 | for (int id = 0; id < cnt; ++id) { 294 | auto pix = regs[id]; 295 | if ((pix - regs[0]).length() > rad) { 296 | used[pix.x * col + pix.y] = NOTUSED; 297 | } else { 298 | temppix.push_back(pix); 299 | } 300 | } 301 | if ((int)temppix.size() < 2) { 302 | return false; 303 | } 304 | line->setregs(std::move(temppix)); 305 | region2rect(line, mod, col); 306 | density = (double)line->getregs().size() / (line->length * line->width); 307 | } 308 | return true; 309 | } 310 | 311 | static bool refine(std::shared_ptr &line, const double *angles, const double *mod, 312 | bool *used, double prec, double density_th, int row, int col) { 313 | double density = (double)line->getregs().size() / (line->length * line->width); 314 | if (density >= density_th) { 315 | return true; 316 | } 317 | auto ®s = line->getregs(); 318 | int xc = regs[0].x; 319 | int yc = regs[0].y; 320 | double sum = 0.0; 321 | double s_sum = 0.0; 322 | int n = 0; 323 | for (auto &pix : regs) { 324 | int addr = pix.x * col + pix.y; 325 | used[addr] = NOTUSED; 326 | if ((regs[0] - pix).length() < line->width) { 327 | double ang_d = angle_diff_signed(angles[addr], angles[xc * col + yc]); 328 | sum += ang_d; 329 | s_sum += ang_d * ang_d; 330 | ++n; 331 | } 332 | } 333 | 334 | double mean_angle = sum / (double)n; 335 | // 以 2 倍标准差作为新的角度容忍度,最开始为 22.5° * pi / 180 336 | double tau = 2.0 * sqrt((s_sum - 2.0 * mean_angle * sum) / (double)n + mean_angle * mean_angle); 337 | int pol = line->polarity; 338 | line = regionGrow(regs[0], angles, used, tau, row, col); 339 | line->polarity = pol; 340 | if (!line || line->getregs().size() < 2) { 341 | return false; 342 | } 343 | region2rect(line, mod, col); 344 | density = (double)line->getregs().size() / (line->length * line->width); 345 | 346 | if (density < density_th) { 347 | if (reduce_region_radius(line, mod, used, density_th, col)) { 348 | return true; 349 | } else { 350 | return false; 351 | } 352 | } else { 353 | return true; 354 | } 355 | return true; 356 | } 357 | 358 | static double rect_nfa(const std::shared_ptr &line, const double *angles, double logNT, 359 | double prec, int row, int col) { 360 | int pts = 0; 361 | int alg = 0; 362 | double line_angle = atan2(line->dir.x, line->dir.y); 363 | for (RectIter iter(line); !iter.isEnd(); ++iter) { 364 | if (inRect(row, col, iter.np.x, iter.np.y)) { 365 | ++pts; 366 | if (isaligned(angles[iter.np.x * col + iter.np.y], line_angle, prec)) { 367 | ++alg; 368 | } 369 | } 370 | } 371 | return nfa(pts, alg, prec / PI, logNT); 372 | } 373 | 374 | static double rect_improve(std::shared_ptr &line, const double *angles, double logNT, 375 | double log_eps, int row, int col) { 376 | double delta = 0.5; 377 | double delta_2 = delta / 2.0; 378 | double prec = angle2rad(ANGLE_TH); 379 | double log_nfa = rect_nfa(line, angles, logNT, prec, row, col); 380 | 381 | if (log_nfa > log_eps) { 382 | return log_nfa; 383 | } 384 | 385 | for (int i = 0; i < 5; ++i) { 386 | prec /= 2.0; 387 | double log_nfa_new = rect_nfa(line, angles, logNT, prec, row, col); 388 | if (log_nfa_new > log_nfa) { 389 | log_nfa = log_nfa_new; 390 | } 391 | } 392 | if (log_nfa > log_eps) { 393 | return log_nfa; 394 | } 395 | 396 | double min_width = line->width; 397 | for (int i = 0; i < 5; ++i) { 398 | if (line->width - delta >= 0.5) { 399 | line->width -= delta; 400 | double log_nfa_new = rect_nfa(line, angles, logNT, prec, row, col); 401 | if(log_nfa_new > log_nfa) { 402 | log_nfa = log_nfa_new; 403 | min_width = line->width; 404 | } 405 | } 406 | } 407 | line->width = min_width; 408 | if (log_nfa > log_eps) { 409 | return log_nfa; 410 | } 411 | 412 | 413 | int idx = 0; 414 | for (int i = 1; i <= 5; ++i) { 415 | if (line->width - i * delta >= 0.5) { 416 | line->sp.x += -(double)i * line->dir.y * delta_2; 417 | line->sp.y += (double)i * line->dir.x * delta_2; 418 | line->ep.x += -(double)i * line->dir.y * delta_2; 419 | line->ep.y += (double)i * line->dir.x * delta_2; 420 | line->width -= (double)i * delta; 421 | double log_nfa_new = rect_nfa(line, angles, logNT, prec, row, col); 422 | if(log_nfa_new > log_nfa) { 423 | log_nfa = log_nfa_new; 424 | idx = i; 425 | } 426 | line->sp.x -= -(double)i * line->dir.y * delta_2; 427 | line->sp.y -= (double)i * line->dir.x * delta_2; 428 | line->ep.x -= -(double)i * line->dir.y * delta_2; 429 | line->ep.y -= (double)i * line->dir.x * delta_2; 430 | line->width += (double)i * delta;; 431 | } 432 | } 433 | line->sp.x += -(double)idx * line->dir.y * delta_2; 434 | line->sp.y += (double)idx * line->dir.x * delta_2; 435 | line->ep.x += -(double)idx * line->dir.y * delta_2; 436 | line->ep.y += (double)idx * line->dir.x * delta_2; 437 | line->width -= (double)idx * delta; 438 | if (log_nfa > log_eps) { 439 | return log_nfa; 440 | } 441 | 442 | 443 | idx = 0; 444 | for (int i = 1; i <= 5; ++i) { 445 | if (line->width - i * delta >= 0.5) { 446 | line->sp.x -= -(double)i * line->dir.y * delta_2; 447 | line->sp.y -= (double)i * line->dir.x * delta_2; 448 | line->ep.x -= -(double)i * line->dir.y * delta_2; 449 | line->ep.y -= (double)i * line->dir.x * delta_2; 450 | line->width -= (double)i * delta; 451 | double log_nfa_new = rect_nfa(line, angles, logNT, prec, row, col); 452 | if(log_nfa_new > log_nfa) { 453 | log_nfa = log_nfa_new; 454 | idx = i; 455 | } 456 | line->sp.x += -(double)i * line->dir.y * delta_2; 457 | line->sp.y += (double)i * line->dir.x * delta_2; 458 | line->ep.x += -(double)i * line->dir.y * delta_2; 459 | line->ep.y += (double)i * line->dir.x * delta_2; 460 | line->width += (double)i * delta;; 461 | } 462 | } 463 | line->sp.x -= -(double)idx * line->dir.y * delta_2; 464 | line->sp.y -= (double)idx * line->dir.x * delta_2; 465 | line->ep.x -= -(double)idx * line->dir.y * delta_2; 466 | line->ep.y -= (double)idx * line->dir.x * delta_2; 467 | line->width -= (double)idx * delta; 468 | if (log_nfa > log_eps) { 469 | return log_nfa; 470 | } 471 | 472 | for (int i = 0; i < 5; ++i) { 473 | prec /= 2.0; 474 | double log_nfa_new = rect_nfa(line, angles, logNT, prec, row, col); 475 | if (log_nfa_new > log_nfa) { 476 | log_nfa = log_nfa_new; 477 | } 478 | } 479 | return log_nfa; 480 | } 481 | 482 | bool lineSegmentDetection(const double *image, int row, int col, std::vector > &lines) { 483 | int nbins = 1024; 484 | double logNT = 5.0 * (log10((double)row) + log10((double)col)) / 2.0 + log10(11.0); 485 | double min_reg_size = (int)(-logNT / log10(ANGLE_TH / 180.0)); // 每个矩形区域内 align point 最小数量 486 | 487 | double *angles = new double[row * col]; 488 | double *mod = new double[row * col]; 489 | bool *used = new bool[row * col]; 490 | int8_t *pol = new int8_t[row * col]; 491 | std::vector sort_points; 492 | 493 | ll_angle(image, angles, mod, sort_points, nbins, row, col); 494 | memset(used, NOTUSED, sizeof(bool) * row * col); 495 | memset(pol, 0, sizeof(int8_t) * row * col); 496 | 497 | for (auto &pix : sort_points) { 498 | int addr = pix.x * col + pix.y; 499 | if (used[addr] == NOTUSED && !equal(angles[addr], ANGLE_NOT_DEF)) { 500 | std::shared_ptr line = regionGrow(pix, angles, used, angle2rad(ANGLE_TH), row, col); 501 | 502 | if (!line || line->getregs().size() < min_reg_size) { 503 | continue; 504 | } 505 | region2rect(line, mod, col); 506 | 507 | if (!isArcSegment(line, angles, pol, col)) { 508 | continue; 509 | } 510 | 511 | // 提纯,通过重新生长区域来达到期望的密度阈值 512 | if (!refine(line, angles, mod, used, angle2rad(ANGLE_TH), 0.7, row, col)) { 513 | continue; 514 | } 515 | 516 | double log_eps = 0.0; 517 | double log_nfa = rect_improve(line, angles, logNT, log_eps, row, col); 518 | if (log_nfa <= log_eps) { 519 | continue; 520 | } 521 | 522 | line->sp += Pointd(0.5, 0.5); 523 | line->ep += Pointd(0.5, 0.5); 524 | lines.push_back(std::move(line)); 525 | } 526 | } 527 | delete [] angles; 528 | delete [] mod; 529 | delete [] used; 530 | delete [] pol; 531 | return true; 532 | } 533 | 534 | } // namespace zgh -------------------------------------------------------------------------------- /src/unitily.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | #include 10 | #include "unitily.h" 11 | #include "defines.h" 12 | 13 | namespace zgh { 14 | 15 | 16 | bool equal(int x, int y) { 17 | return x == y; 18 | } 19 | 20 | bool equal(double x, double y) { 21 | if (x == y) { 22 | return true; 23 | } 24 | double abs_diff = fabs(x - y); 25 | x = fabs(x); 26 | y = fabs(y); 27 | double abs_max = x > y ? x : y; 28 | if (abs_max < DBL_MIN) { 29 | abs_max = DBL_MIN; 30 | } 31 | return (abs_diff / abs_max) <= (RELATIVE_ERROR_FACTOR * DBL_EPSILON); 32 | } 33 | 34 | bool equal(float x, float y) { 35 | if (x == y) { 36 | return true; 37 | } 38 | float abs_diff = fabs(x - y); 39 | x = fabs(x); 40 | y = fabs(y); 41 | float abs_max = x > y ? x : y; 42 | if (abs_max < FLT_MIN) { 43 | abs_max = FLT_MIN; 44 | } 45 | return (abs_diff / abs_max) <= (RELATIVE_ERROR_FACTOR * FLT_EPSILON); 46 | } 47 | 48 | double angle2rad(double angle) { 49 | return angle / 180.0 * PI; 50 | } 51 | 52 | double rad2angle(double rad) { 53 | return rad / PI * 180.0; 54 | } 55 | 56 | double angle_diff(double a, double b) { 57 | a -= b; 58 | while (a <= -PI) { 59 | a += 2.0 * PI; 60 | } 61 | while (a >= PI) { 62 | a -= 2.0 * PI; 63 | } 64 | if (a < 0.0) { 65 | a = -a; 66 | } 67 | return a; 68 | } 69 | 70 | double angle_diff_signed(double a, double b) { 71 | a -= b; 72 | while (a <= -PI) { 73 | a += 2.0 * PI; 74 | } 75 | while (a > PI) { 76 | a -= 2.0 * PI; 77 | } 78 | return a; 79 | } 80 | 81 | bool inRect(int h, int w, int x, int y) { 82 | return 0 <= x && x < h && 0 <= y && y < w; 83 | } 84 | 85 | 86 | double rotateAngle(double start_angle, double end_angle, int polarity) { 87 | double coverage; 88 | if (polarity == SAME_POL) { 89 | coverage = end_angle - start_angle; 90 | } else { 91 | coverage = start_angle - end_angle; 92 | } 93 | if (coverage < 0) { 94 | coverage += 2 * PI; 95 | } 96 | return coverage; 97 | } 98 | 99 | double sqr(double x) { 100 | return x * x; 101 | } 102 | 103 | int sqr(int x) { 104 | return x * x; 105 | } 106 | 107 | /*----------------------------------------------------------------------------*/ 108 | /** Computes the natural logarithm of the absolute value of 109 | the gamma function of x using Windschitl method. 110 | See http://www.rskey.org/gamma.htm 111 | 112 | The formula used is 113 | @f[ 114 | \Gamma(x) = \sqrt{\frac{2\pi}{x}} \left( \frac{x}{e} 115 | \sqrt{ x\sinh(1/x) + \frac{1}{810x^6} } \right)^x 116 | @f] 117 | so 118 | @f[ 119 | \log\Gamma(x) = 0.5\log(2\pi) + (x-0.5)\log(x) - x 120 | + 0.5x\log\left( x\sinh(1/x) + \frac{1}{810x^6} \right). 121 | @f] 122 | This formula is a good approximation when x > 15. 123 | */ 124 | 125 | double log_gamma_windschitl(double x) { 126 | return 0.918938533204673 + (x - 0.5) * log(x) - x 127 | + 0.5 * x * log(x * sinh(1.0 / x) + 1.0 / (810.0 * pow(x, 6.0))); 128 | } 129 | 130 | /*----------------------------------------------------------------------------*/ 131 | /*----------------------------- NFA computation ------------------------------*/ 132 | /*----------------------------------------------------------------------------*/ 133 | 134 | /*----------------------------------------------------------------------------*/ 135 | /** Computes the natural logarithm of the absolute value of 136 | the gamma function of x using the Lanczos approximation. 137 | See http://www.rskey.org/gamma.htm 138 | 139 | The formula used is 140 | @f[ 141 | \Gamma(x) = \frac{ \sum_{n=0}^{N} q_n x^n }{ \Pi_{n=0}^{N} (x+n) } 142 | (x+5.5)^{x+0.5} e^{-(x+5.5)} 143 | @f] 144 | so 145 | @f[ 146 | \log\Gamma(x) = \log\left( \sum_{n=0}^{N} q_n x^n \right) 147 | + (x+0.5) \log(x+5.5) - (x+5.5) - \sum_{n=0}^{N} \log(x+n) 148 | @f] 149 | and 150 | q0 = 75122.6331530, 151 | q1 = 80916.6278952, 152 | q2 = 36308.2951477, 153 | q3 = 8687.24529705, 154 | q4 = 1168.92649479, 155 | q5 = 83.8676043424, 156 | q6 = 2.50662827511. 157 | */ 158 | 159 | double log_gamma_lanczos(double x) { 160 | static double q[7] = {75122.6331530, 80916.6278952, 36308.2951477, 161 | 8687.24529705, 1168.92649479, 83.8676043424, 162 | 2.50662827511}; 163 | double a = (x + 0.5) * log(x + 5.5) - (x + 5.5); 164 | double b = 0.0; 165 | for (int n = 0; n < 7; ++n) { 166 | a -= log(x + (double)n); 167 | b += q[n] * pow(x, (double)n); 168 | } 169 | return a + log(b); 170 | } 171 | 172 | double nfa(int n, int k, double p, double logNT) { 173 | static double inv[TABSIZE]; // table to keep computed inverse values 174 | double tolerance = 0.1; // an error of 10% in the result is accepted 175 | double log1term, term, bin_term, mult_term, bin_tail, err, p_term; 176 | 177 | /* trivial cases */ 178 | if (n == 0 || k == 0) { 179 | return -logNT; 180 | } 181 | if (n == k) { 182 | return -logNT - (double)n * log10(p); 183 | } 184 | 185 | /* probability term */ 186 | p_term = p / (1.0 - p); 187 | 188 | log1term = log_gamma((double)n + 1.0) - log_gamma((double)k + 1.0) 189 | - log_gamma((double)(n - k) + 1.0) 190 | + (double)k * log(p) + (double)(n - k) * log(1.0 - p); 191 | term = exp(log1term); 192 | 193 | /* in some cases no more computations are needed */ 194 | if (equal(term, 0.0)) { /* the first term is almost zero */ 195 | if((double)k > (double)n * p) { /* at begin or end of the tail? */ 196 | return -log1term / M_LN10 - logNT; /* end: use just the first term */ 197 | } else { 198 | return -logNT; /* begin: the tail is roughly 1 */ 199 | } 200 | } 201 | 202 | /* compute more terms if needed */ 203 | bin_tail = term; 204 | for (int i = k + 1; i <= n; ++i) { 205 | bin_term = (double)(n - i + 1) * (i < TABSIZE ? 206 | (inv[i] != 0.0 ? inv[i] : (inv[i] = 1.0 / (double)i)) : 207 | 1.0 / (double)i); 208 | 209 | mult_term = bin_term * p_term; 210 | term *= mult_term; 211 | bin_tail += term; 212 | if (bin_term < 1.0) { 213 | err = term * ((1.0 - pow(mult_term, (double)(n - i + 1))) / 214 | (1.0 - mult_term) - 1.0); 215 | 216 | if (err < tolerance * fabs(-log10(bin_tail) - logNT) * bin_tail) { 217 | break; 218 | } 219 | } 220 | } 221 | double nfavalue = -log10(bin_tail) - logNT; 222 | return nfavalue; 223 | } 224 | 225 | } // namespace zgh 226 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.4) 2 | 3 | if (COMMAND cmake_policy) 4 | cmake_policy (SET CMP0003 NEW) 5 | endif (COMMAND cmake_policy) 6 | 7 | set (OpenCV_FOUND 1) 8 | find_package (OpenCV REQUIRED) 9 | # Manual set open library 10 | #include_directories (/xxx/opencv_tag_v3.2.0) 11 | #include_directories (/xxx/opencv_tag_v3.2.0/include) 12 | #link_directories (/xxx/opencv_tag_v3.2.0/lib) 13 | 14 | set (Opencv_LIBS 15 | -lopencv_core 16 | -lopencv_imgproc 17 | -lopencv_highgui 18 | ) 19 | 20 | project (tests) 21 | 22 | 23 | add_executable (testtype testtype.cpp) 24 | target_link_libraries (testtype ellipse_detection ${OpenCV_LIBS}) 25 | 26 | add_executable (testlsd testlsd.cpp) 27 | target_link_libraries (testlsd ellipse_detection ${OpenCV_LIBS}) 28 | 29 | add_executable (testgroup testgroup.cpp) 30 | target_link_libraries (testgroup ellipse_detection ${OpenCV_LIBS}) 31 | 32 | add_executable (testinital testinital.cpp) 33 | target_link_libraries (testinital ellipse_detection ${OpenCV_LIBS}) 34 | 35 | add_executable (testdetect testdetect.cpp) 36 | target_link_libraries (testdetect ellipse_detection ${OpenCV_LIBS}) 37 | -------------------------------------------------------------------------------- /test/testdetect.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-6-31 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "opencv2/imgcodecs.hpp" 13 | #include "opencv2/highgui/highgui.hpp" 14 | #include "opencv2/imgproc/imgproc.hpp" 15 | #include "ellipse_detection/types.hpp" 16 | #include "ellipse_detection/detect.h" 17 | 18 | using namespace std; 19 | using namespace zgh; 20 | 21 | int main(int argc, char* argv[]) { 22 | if (argc <= 1) { 23 | std::cout << "[Usage]: testdetect [image_dir1] [image_dir2] [image_dir3] ..." << std::endl; 24 | } 25 | #if 0 26 | VideoCapture cap(argv[1]); 27 | Mat board; 28 | // int cnt = 0; 29 | while (1) { 30 | cap >> board; 31 | // resize(board, board, Size(board.cols / 3, board.rows / 3)); 32 | if (board.empty()) { 33 | break; 34 | } 35 | Mat image; 36 | cvtColor(board, image, CV_RGB2GRAY); 37 | // cv::imwrite("test" + to_string(cnt++) + ".jpg", image); 38 | vector > ells; 39 | int row = image.rows; 40 | int col = image.cols; 41 | double width = 2.0; 42 | FuncTimerDecorator("detectEllipse")(detectEllipse, image.data, row, col, ells, NONE_POL, width); 43 | cout << "Find " << ells.size() << " ellipse" << endl; 44 | for (int i = 0; i < (int)ells.size(); ++i) { 45 | auto ell = ells[i]; 46 | std::cout << "coverangle : " << ell->coverangle << ",\tgoodness : " << ell->goodness << ",\tpolarity : " << ell->polarity << endl; 47 | ellipse(board, 48 | Point(ell->o.y, ell->o.x), 49 | Size(ell->a, ell->b), 50 | rad2angle(PI_2 - ell->phi), 51 | 0, 52 | 360, 53 | Scalar(0, 255, 0), 54 | width, 55 | 8, 56 | 0); 57 | 58 | } 59 | imshow("origin", image); 60 | imshow("detect", board); 61 | waitKey(0); 62 | 63 | } 64 | #else 65 | for (int i = 1; i < argc; ++i) { 66 | cv::Mat board = cv::imread(argv[i]); 67 | cv::Mat image = cv::imread(argv[i],cv::IMREAD_GRAYSCALE); 68 | // cv::resize(image, image, cv::Size(image.cols / 2, image.rows / 2)); 69 | // cv::resize(board, board, cv::Size(board.cols / 2, board.rows / 2)); 70 | cv::imshow("origin image", image); 71 | vector > ells; 72 | int row = image.rows; 73 | int col = image.cols; 74 | double width = 2.0; 75 | FuncTimerDecorator("detectEllipse")(detectEllipse, image.data, row, col, ells, NONE_POL, width); 76 | cout << "Find " << ells.size() << " ellipse(s)" << endl; 77 | for (int i = 0; i < (int)ells.size(); ++i) { 78 | auto ell = ells[i]; 79 | std::cout << "coverangle : " << ell->coverangle 80 | << ",\tgoodness : " << ell->goodness 81 | << ",\tpolarity : " 82 | << ell->polarity 83 | << endl; 84 | cv::ellipse(board, 85 | cv::Point(ell->o.y, ell->o.x), 86 | cv::Size(ell->a, ell->b), 87 | rad2angle(PI_2 - ell->phi), 88 | 0, 89 | 360, 90 | cv::Scalar(0, 255, 0), 91 | width, 92 | 8, 93 | 0); 94 | 95 | } 96 | cv::imshow("detected result", board); 97 | cv::waitKey(0); 98 | } 99 | #endif 100 | return 0; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /test/testgroup.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-6-20 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | #include 10 | #include "opencv2/imgcodecs.hpp" 11 | #include "opencv2/highgui/highgui.hpp" 12 | #include "opencv2/imgproc/imgproc.hpp" 13 | #include "ellipse_detection/types.hpp" 14 | #include "ellipse_detection/detect.h" 15 | #include "ellipse_detection/compute.h" 16 | 17 | using namespace std; 18 | using namespace cv; 19 | using namespace zgh; 20 | 21 | 22 | 23 | int main(int argc, char* argv[]) { 24 | Mat image = imread(argv[1], IMREAD_GRAYSCALE); 25 | imshow("car", image); 26 | vector > arcs; 27 | double scale = 0.8; 28 | double sigma_scale = 0.6; 29 | int row = (int)ceil(1.0 * image.rows * scale); 30 | int col = (int)ceil(1.0 * image.cols * scale); 31 | double *data = new double[row * col]; 32 | gaussianSampler(image.data, image.rows, image.cols, data, row, col, scale, sigma_scale); 33 | 34 | lsdgroups(data, row, col, scale, arcs); 35 | cout << "Detect " << arcs.size() << " arcs" << endl; 36 | Mat boarda(image.rows, image.cols, CV_8UC3, Scalar(255, 255, 255)); 37 | Mat boardb(row, col, CV_8UC3, Scalar(255, 255, 255)); 38 | for (auto& arc : arcs) { 39 | // std::cout << "line numbers : " << arc->lines.size() << std::endl; 40 | Scalar color(0, rand() % 255, rand() % 255); 41 | for (auto& li : arc->lines) { 42 | line(boarda, Point2f(li->sp.y, li->sp.x), Point2f(li->ep.y, li->ep.x), color, 2, 8, 0); 43 | for (auto& pix : li->getregs()) { 44 | boardb.at(pix.x, pix.y)[0] = color[0]; 45 | boardb.at(pix.x, pix.y)[1] = color[1]; 46 | boardb.at(pix.x, pix.y)[2] = color[2]; 47 | } 48 | } 49 | } 50 | imshow("line", boarda); 51 | imshow("Arcs", boardb); 52 | waitKey(); 53 | 54 | return 0; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /test/testinital.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-6-24 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | #include 10 | #include "opencv2/imgcodecs.hpp" 11 | #include "opencv2/highgui/highgui.hpp" 12 | #include "opencv2/imgproc/imgproc.hpp" 13 | #include "ellipse_detection/types.hpp" 14 | #include "ellipse_detection/detect.h" 15 | #include "ellipse_detection/compute.h" 16 | #include "cvcannyapi.h" 17 | 18 | using namespace std; 19 | using namespace cv; 20 | using namespace zgh; 21 | 22 | 23 | int main(int argc, char* argv[]) { 24 | Mat board = imread(argv[1]); 25 | Mat image = imread(argv[1], IMREAD_GRAYSCALE); 26 | imshow("car", image); 27 | // calc the gradient 28 | int row = image.rows; 29 | int col = image.cols; 30 | double *angles = new double[row * col]; 31 | calculateGradient3(image.data, row, col, angles); 32 | vector > ells; 33 | getValidInitialEllipseSet(image.data, angles, row, col, ells); 34 | cout << "Find " << ells.size() << " initial ellipse" << endl; 35 | for (int i = 0; i < (int)ells.size(); ++i) { 36 | auto ell = ells[i]; 37 | // std::cout << ell->o << " " << ell->a << " " << ell->b << " " << ell->phi << " " << endl; 38 | ellipse(board, 39 | Point(ell->o.y, ell->o.x), 40 | Size(ell->a, ell->b), 41 | rad2angle(PI_2 - ell->phi), 42 | 0, 43 | 360, 44 | Scalar(0, 255, 0), 45 | 1, 46 | 8, 47 | 0); 48 | } 49 | imshow("initial ellpise", board); 50 | waitKey(0); 51 | 52 | return 0; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /test/testlsd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | *Copyright: Copyright (c) 2019 3 | *Created on 2019-5-21 4 | *Author:zhengaohong@zgheye.cc 5 | *Version 1.0.1 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include "opencv2/imgcodecs.hpp" 12 | #include "opencv2/highgui/highgui.hpp" 13 | #include "opencv2/imgproc/imgproc.hpp" 14 | #include "ellipse_detection/types.hpp" 15 | #include "ellipse_detection/detect.h" 16 | #include "ellipse_detection/compute.h" 17 | 18 | using namespace std; 19 | using namespace cv; 20 | using namespace zgh; 21 | 22 | 23 | 24 | int main(int argc, char* argv[]) { 25 | Mat image = imread(argv[1], IMREAD_GRAYSCALE); 26 | imshow("origin", image); 27 | vector > lines; 28 | double scale = 0.8; 29 | double sigma_scale = 0.6; 30 | int row = (int)ceil(1.0 * image.rows * scale); 31 | int col = (int)ceil(1.0 * image.cols * scale); 32 | double *data = new double[row * col]; 33 | gaussianSampler(image.data, image.rows, image.cols, data, row, col, scale, sigma_scale); 34 | image.release(); 35 | 36 | lineSegmentDetection(data, row, col, lines); 37 | 38 | std::cout << "Detect " << lines.size() << " lines" << endl; 39 | 40 | Mat boarda(row, col, CV_8UC3, Scalar(255, 255, 255)); 41 | 42 | Mat boardb(row, col, CV_8UC3, Scalar(255, 255, 255)); 43 | 44 | for (auto& li : lines) { 45 | Scalar color(0, rand() % 256, rand() % 256); 46 | line(boarda, Point2f(li->sp.y, li->sp.x), Point2f(li->ep.y, li->ep.x), color, 2, 8, 0); 47 | for (auto& pix : li->getregs()) { 48 | boardb.at(pix.x, pix.y)[0] = color[0]; 49 | boardb.at(pix.x, pix.y)[1] = color[1]; 50 | boardb.at(pix.x, pix.y)[2] = color[2]; 51 | } 52 | } 53 | imshow("line", boarda); 54 | imshow("region", boardb); 55 | waitKey(0); 56 | delete [] data; 57 | return 0; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /test/testtype.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ellipse_detection/types.hpp" 3 | 4 | 5 | #include "opencv2/core.hpp" 6 | #include "opencv2/imgcodecs.hpp" 7 | #include "opencv2/highgui/highgui.hpp" 8 | #include "opencv2/imgproc/imgproc.hpp" 9 | 10 | using namespace std; 11 | using namespace zgh; 12 | using namespace cv; 13 | 14 | 15 | int main() { 16 | // test unitily.h 17 | assert(equal(1, 3) == false); 18 | 19 | assert(equal(1, 1) == true); 20 | 21 | assert(equal(-0, 0) == true); 22 | 23 | assert(equal(1.3, 1.300000000000001) == true); 24 | 25 | assert(equal(-1.56, 5.546) == false); 26 | 27 | assert(equal(1.0, 1.34) == false); 28 | 29 | assert(equal(angle2rad(45.0), PI_4) == true); 30 | 31 | assert(equal(rad2angle(PI_4), 45.0) == true); 32 | 33 | 34 | // test types.hpp 35 | Pointi a(1, 2), b(3, 4); 36 | 37 | assert(Pointi(a) == a); 38 | 39 | assert(a.dot(b) == b.dot(a)); 40 | 41 | assert(a.dot(b) == 11); 42 | 43 | assert(a.cross(b) == -2); 44 | 45 | assert(a.cross(b) == -b.cross(a)); 46 | 47 | assert(equal(a.length(), sqrt(5.0)) == true); 48 | 49 | Pointd c(1.0, sqrt(3.0)), d(1.0, 0); 50 | 51 | assert(equal(rad2angle(c.angle(d)), 60.0) == true); 52 | 53 | assert(c.rotate(angle2rad(-30.0)) == Pointd(sqrt(3.0), 1.0)); 54 | 55 | c.rotation(angle2rad(-30.0)); 56 | 57 | 58 | assert(c == Pointd(sqrt(3.0), 1.0)); 59 | 60 | assert(a + b == Pointi(4, 6)); 61 | 62 | assert(a - b == Pointi(-2, -2)); 63 | 64 | 65 | assert(a / 1 == a); 66 | 67 | assert(a * 2 == Pointi(2, 4)); 68 | 69 | (a += b) *= 2; 70 | 71 | assert(a == Pointi(8, 12)); 72 | 73 | auto e = Pointd(-4.0, 4.0); 74 | 75 | e.normal(); 76 | 77 | assert(e == Pointd(-sqrt(0.5), sqrt(0.5))); 78 | 79 | assert(fabs(nfa(193, 145, 0.125, 13.6128) - 74.2627) <= 1e-3); 80 | 81 | cout << "All test passed" << endl; 82 | 83 | 84 | // test RectIter 85 | 86 | Mat boardb(720, 1280, CV_8UC3, Scalar(255, 255, 255)); 87 | shared_ptr line = make_shared(200, 200, 200, 500, 1); 88 | line->width = 4; 89 | for (RectIter iter(line); !iter.isEnd(); ++iter) { 90 | Scalar color(0, 0, 255); 91 | auto pix = iter.np; 92 | boardb.at(pix.x, pix.y)[0] = color[0]; 93 | boardb.at(pix.x, pix.y)[1] = color[1]; 94 | boardb.at(pix.x, pix.y)[2] = color[2]; 95 | } 96 | cv::line(boardb, Point2f(line->sp.y, line->sp.x), Point2f(line->ep.y, line->ep.x), Scalar(0, 0, 0), 2, 8, 0); 97 | 98 | line = make_shared(200, 200, 500, 200, 1); 99 | line->width = 4; 100 | for (RectIter iter(line); !iter.isEnd(); ++iter) { 101 | Scalar color(0, 255, 0); 102 | auto pix = iter.np; 103 | boardb.at(pix.x, pix.y)[0] = color[0]; 104 | boardb.at(pix.x, pix.y)[1] = color[1]; 105 | boardb.at(pix.x, pix.y)[2] = color[2]; 106 | } 107 | cv::line(boardb, Point2f(line->sp.y, line->sp.x), Point2f(line->ep.y, line->ep.x), Scalar(0, 0, 0), 2, 8, 0); 108 | 109 | 110 | imshow("test", boardb); 111 | waitKey(); 112 | 113 | std::shared_ptr ell = make_shared(Pointd(200, 200), 100, 50, 0.344); 114 | ellipse(boardb, 115 | Point(ell->o.y, ell->o.x), 116 | Size(ell->a, ell->b), 117 | rad2angle(PI_2 - ell->phi), 118 | 0, 119 | 360, 120 | Scalar(0, 255, 0), 121 | 1, 122 | 8, 123 | 0); 124 | Pointd p(ell->o); 125 | assert(equal(ell->distopoint(Pointd(ell->o.x, ell->o.y)), ell->b)); 126 | 127 | // test EllipseIter 128 | for (EllipseIter iter(ell, 1.0); !iter.isEnd(); ++iter) { 129 | Scalar color(255, 0, 0); 130 | auto pix = iter.np; 131 | if (inRect(720, 1280, pix.x, pix.y)) { 132 | boardb.at(pix.x, pix.y)[0] = color[0]; 133 | boardb.at(pix.x, pix.y)[1] = color[1]; 134 | boardb.at(pix.x, pix.y)[2] = color[2]; 135 | } 136 | } 137 | 138 | for (EllipseIter iter(ell, 1.0); !iter.isEnd(); ++iter) { 139 | Scalar color(255, 0, 0); 140 | auto pix = iter.np; 141 | if (inRect(720, 1280, pix.x, pix.y)) { 142 | cv::Mat temp = boardb.clone(); 143 | Vectord tan = ell->getTangent(pix); 144 | Point2f st(pix.y, pix.x); 145 | Point2f en(pix.y + tan.y * 50, pix.x + tan.x * 50); 146 | cv::line(temp, st, en, color, 2, 8, 0); 147 | 148 | Vectord tan1 = tan.rotate(PI_2); 149 | Point2f st1(pix.y - tan1.y * 50, pix.x - tan1.x * 50); 150 | Point2f en1(pix.y + tan1.y * 50, pix.x + tan1.x * 50); 151 | cv::line(temp, st1, en1, color, 2, 8, 0); 152 | 153 | imshow("test", temp); 154 | waitKey(30); 155 | } 156 | } 157 | 158 | 159 | 160 | return 0; 161 | } 162 | --------------------------------------------------------------------------------