├── pics ├── 13.jpg ├── 23.bmp ├── 27.jpg ├── 35.jpg ├── 43.jpg ├── 666.jpg ├── im993.jpg ├── 23_result.jpg ├── 27_result.jpg ├── 43_result.jpg ├── 666_result.jpg ├── 666_negative.jpg ├── 666_positive.jpg └── different-polarity-detection_all.jpg ├── Matlab ├── pics │ ├── 13.jpg │ ├── 23.bmp │ ├── 27.jpg │ ├── 35.jpg │ ├── 43.jpg │ ├── 666.jpg │ ├── im993.jpg │ ├── 23_result.jpg │ ├── 27_result.jpg │ ├── 43_result.jpg │ ├── 666_result.jpg │ ├── 666_negative.jpg │ ├── 666_positive.jpg │ └── different-polarity-detection_all.jpg ├── LCS_ellipse.m ├── ellipseDetectionByArcSupportLSs.m └── drawEllipses.m ├── fitEllipse_mex.cpp ├── ellipse-specific-fitting.pdf ├── generateEllipseCandidates.cpp ├── generateEllipseCandidates_mex.cpp ├── README.md ├── .gitignore ├── CMakeLists.txt ├── fit_ellipse.py └── main.cpp /pics/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/13.jpg -------------------------------------------------------------------------------- /pics/23.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/23.bmp -------------------------------------------------------------------------------- /pics/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/27.jpg -------------------------------------------------------------------------------- /pics/35.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/35.jpg -------------------------------------------------------------------------------- /pics/43.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/43.jpg -------------------------------------------------------------------------------- /pics/666.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/666.jpg -------------------------------------------------------------------------------- /pics/im993.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/im993.jpg -------------------------------------------------------------------------------- /Matlab/pics/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/13.jpg -------------------------------------------------------------------------------- /Matlab/pics/23.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/23.bmp -------------------------------------------------------------------------------- /Matlab/pics/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/27.jpg -------------------------------------------------------------------------------- /Matlab/pics/35.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/35.jpg -------------------------------------------------------------------------------- /Matlab/pics/43.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/43.jpg -------------------------------------------------------------------------------- /Matlab/pics/666.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/666.jpg -------------------------------------------------------------------------------- /fitEllipse_mex.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/fitEllipse_mex.cpp -------------------------------------------------------------------------------- /pics/23_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/23_result.jpg -------------------------------------------------------------------------------- /pics/27_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/27_result.jpg -------------------------------------------------------------------------------- /pics/43_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/43_result.jpg -------------------------------------------------------------------------------- /pics/666_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/666_result.jpg -------------------------------------------------------------------------------- /Matlab/LCS_ellipse.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/LCS_ellipse.m -------------------------------------------------------------------------------- /Matlab/pics/im993.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/im993.jpg -------------------------------------------------------------------------------- /pics/666_negative.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/666_negative.jpg -------------------------------------------------------------------------------- /pics/666_positive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/666_positive.jpg -------------------------------------------------------------------------------- /Matlab/pics/23_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/23_result.jpg -------------------------------------------------------------------------------- /Matlab/pics/27_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/27_result.jpg -------------------------------------------------------------------------------- /Matlab/pics/43_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/43_result.jpg -------------------------------------------------------------------------------- /Matlab/pics/666_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/666_result.jpg -------------------------------------------------------------------------------- /Matlab/pics/666_negative.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/666_negative.jpg -------------------------------------------------------------------------------- /Matlab/pics/666_positive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/666_positive.jpg -------------------------------------------------------------------------------- /ellipse-specific-fitting.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/ellipse-specific-fitting.pdf -------------------------------------------------------------------------------- /generateEllipseCandidates.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/generateEllipseCandidates.cpp -------------------------------------------------------------------------------- /generateEllipseCandidates_mex.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/generateEllipseCandidates_mex.cpp -------------------------------------------------------------------------------- /Matlab/ellipseDetectionByArcSupportLSs.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/ellipseDetectionByArcSupportLSs.m -------------------------------------------------------------------------------- /pics/different-polarity-detection_all.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/pics/different-polarity-detection_all.jpg -------------------------------------------------------------------------------- /Matlab/pics/different-polarity-detection_all.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Smorodov/ellipseDetector/HEAD/Matlab/pics/different-polarity-detection_all.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | This is a try to port MATLAB ellipse detector project to cpp. 3 | 4 | Project based on https://github.com/AlanLuSun/High-quality-ellipse-detection repository. 5 | 6 | Seems it works well now, but It still need to check carefully. 7 | 8 | ## Prerequisites (which I used) 9 | 10 | - OS 11 | Windows 10 12 | MSVS 2019 13 | 14 | - Packages 15 | 0. OpenCV 4.x 16 | 1. [CMake](https://cmake.org/download/) 17 | 2. Eigen 18 | 3. MATLAB (if you want matlab binding) 19 | -------------------------------------------------------------------------------- /Matlab/drawEllipses.m: -------------------------------------------------------------------------------- 1 | % Usage: plot_ellipses(Ellipses,size_im,fig_handle); 2 | % 3 | % Inputs: 4 | % Ellipses - parameters of the detected ellipses. Each coloumn contains 5 | % [ x0 - x coordinate of the center of the ellipse 6 | % y0 - y coordinate of the center of the ellipse 7 | % a - length of semimajor axis 8 | % b - length of semiminor axis 9 | % alpha - angle of orientation of semimajor axis] 10 | % size_im - size(im) where im is the gray image 11 | % fig_handle - the handle of the figure if specified, if fig_handle=[] then 12 | % a new figure is created 13 | % 14 | % This function plots the ellipses 15 | % 16 | % Copyright (c) 2012 Dilip K. Prasad 17 | % School of Computer Engineering 18 | % Nanyang Technological University, Singapore 19 | % http://www.ntu.edu.sg/ 20 | 21 | function [] = drawEllipses(ellipses_para,im) 22 | if ~isempty(im) 23 | figure; 24 | %imshow(im); %show image 25 | imshow(im,'border','tight','initialmagnification','fit'); %show image 26 | size_im = size(im); 27 | hold on; 28 | else 29 | hold on; 30 | end 31 | 32 | th=0:pi/180:2*pi; 33 | for i=1:size(ellipses_para,2) 34 | Semi_major= ellipses_para(3,i); 35 | Semi_minor= ellipses_para(4,i); 36 | x0= ellipses_para(1,i); 37 | y0= ellipses_para(2,i); 38 | Phi= ellipses_para(5,i); 39 | x=x0+Semi_major*cos(Phi)*cos(th)-Semi_minor*sin(Phi)*sin(th); 40 | y=y0+Semi_minor*cos(Phi)*sin(th)+Semi_major*sin(Phi)*cos(th); 41 | 42 | plot(x,y,'r', 'LineWidth',2); 43 | end 44 | if ~isempty(im) 45 | axis on; set(gca,'XTick',[],'YTick',[]);axis ij;axis equal;axis([0 size_im(2) 0 size_im(1)]); 46 | end 47 | 48 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ===== CMake ================================================================ 2 | 3 | CMakeLists.txt.user 4 | CMakeCache.txt 5 | CMakeFiles 6 | CMakeScripts 7 | Testing 8 | Makefile 9 | cmake_install.cmake 10 | install_manifest.txt 11 | compile_commands.json 12 | CTestTestfile.cmake 13 | _deps 14 | 15 | # ===== C++ ================================================================== 16 | 17 | *.pkl 18 | *.npz 19 | *.npy 20 | *.obj 21 | 22 | # Prerequisites 23 | *.d 24 | 25 | # Compiled Object files 26 | *.slo 27 | *.lo 28 | *.o 29 | *.obj 30 | 31 | # Precompiled Headers 32 | *.gch 33 | *.pch 34 | 35 | # Compiled Dynamic libraries 36 | #*.so 37 | #*.dylib 38 | #*.dll 39 | 40 | # Fortran module files 41 | *.mod 42 | *.smod 43 | 44 | # Compiled Static libraries 45 | #*.lai 46 | #*.la 47 | #*.a 48 | #*.lib 49 | 50 | # Executables 51 | #*.exe 52 | #*.out 53 | #*.app 54 | 55 | # ===== C ==================================================================== 56 | 57 | # Prerequisites 58 | *.d 59 | 60 | # Object files 61 | *.o 62 | *.ko 63 | *.obj 64 | *.elf 65 | 66 | # Linker output 67 | *.ilk 68 | *.map 69 | *.exp 70 | 71 | # Precompiled Headers 72 | *.gch 73 | *.pch 74 | 75 | # Libraries 76 | #*.lib 77 | #*.a 78 | #*.la 79 | #*.lo 80 | 81 | # Shared objects (inc. Windows DLLs) 82 | #*.dll 83 | #*.so 84 | #*.so.* 85 | #*.dylib 86 | 87 | # Executables 88 | #*.exe 89 | #*.out 90 | #*.app 91 | #*.i*86 92 | #*.x86_64 93 | #*.hex 94 | 95 | # Debug files 96 | *.dSYM/ 97 | *.su 98 | *.idb 99 | *.pdb 100 | 101 | 102 | 103 | # CMake 104 | cmake-build-*/ 105 | 106 | 107 | # ===== Miscellaneous ======================================================== 108 | 109 | build 110 | vs2019 111 | 112 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | set (CMAKE_CXX_STANDARD 14) 3 | project(HighQualityEllipseDetector) 4 | 5 | 6 | #matlab_add_mex(NAME mex_file_name SRC source_file.cpp) 7 | 8 | set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/build) 9 | set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) 10 | set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}) 11 | 12 | find_package(OpenCV REQUIRED) 13 | 14 | include_directories("../Eigen") 15 | include_directories(${OpenCV_INCLUDE_DIRS}) 16 | set(SOURCES 17 | main.cpp 18 | generateEllipseCandidates.cpp 19 | ) 20 | 21 | set(HEADERS 22 | ) 23 | 24 | SOURCE_GROUP("Source Files" FILES ${SOURCES}) 25 | SOURCE_GROUP("Header Files" FILES ${HEADERS}) 26 | 27 | add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) 28 | 29 | set_target_properties( ${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$") 30 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) 31 | target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS}) 32 | 33 | option(BuildMatlabMexs "Build mex functions for MATLAB scripts" OFF) 34 | 35 | if(BuildMatlabMexs) 36 | set(Matlab_ROOT_DIR "c:/Program Files (x86)/MATLAB/R2016a") 37 | find_package(Matlab) 38 | 39 | set(MEX1_SOURCES 40 | generateEllipseCandidates_mex.cpp 41 | generateEllipseCandidates.cpp 42 | ) 43 | 44 | set(MEX1_HEADERS 45 | ) 46 | SOURCE_GROUP("Mex Source Files" FILES ${MEX1_SOURCES}) 47 | SOURCE_GROUP("Mex Header Files" FILES ${MEX1_HEADERS}) 48 | matlab_add_mex(NAME generateEllipseCandidates SRC ${MEX1_SOURCES}) 49 | target_link_libraries(generateEllipseCandidates ${OpenCV_LIBS}) 50 | 51 | set(MEX2_SOURCES 52 | fitEllipse_mex.cpp 53 | ) 54 | 55 | set(MEX2_HEADERS 56 | ) 57 | SOURCE_GROUP("Mex Source Files" FILES ${MEX1_SOURCES}) 58 | SOURCE_GROUP("Mex Header Files" FILES ${MEX1_HEADERS}) 59 | matlab_add_mex(NAME fitEllipse SRC ${MEX2_SOURCES}) 60 | target_link_libraries(fitEllipse ${OpenCV_LIBS}) 61 | endif() -------------------------------------------------------------------------------- /fit_ellipse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import numpy 3 | def fitEllipse(x,y): 4 | 5 | #x=cont[:,0] 6 | #y=cont[:,1] 7 | x=x[:,None] 8 | y=y[:,None] 9 | D=numpy.hstack([x*x,x*y,y*y,x,y,numpy.ones(x.shape)]) 10 | S=numpy.matmul(D.T,D) 11 | C=numpy.zeros([6,6]) 12 | C[0,2]=C[2,0]=2 13 | C[1,1]=-1 14 | invS=numpy.linalg.inv(S) 15 | invSdotC=numpy.matmul(invS,C) 16 | E,V=numpy.linalg.eig(invSdotC) 17 | n=numpy.argmax(numpy.abs(E)) 18 | a=V[:,n] 19 | #-------------------Fit ellipse------------------- 20 | b,c,d,f,g,a=a[1]/2., a[2], a[3]/2., a[4]/2., a[5], a[0] 21 | num=b*b-a*c 22 | cx=(c*d-b*f)/num 23 | cy=(a*f-b*d)/num 24 | angle=0.5*numpy.arctan(2*b/(a-c))*180/numpy.pi 25 | up = 2*(a*f*f+c*d*d+g*b*b-2*b*d*f-a*c*g) 26 | down1=(b*b-a*c)*( (c-a)*numpy.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a)) 27 | down2=(b*b-a*c)*( (a-c)*numpy.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a)) 28 | a=numpy.sqrt(abs(up/down1)) 29 | b=numpy.sqrt(abs(up/down2)) 30 | params=[cx,cy,a,b,angle] 31 | return params 32 | 33 | x=[1229,1230,1231,1231,1232,1232,1233,1233,1234,1234,1235,1235,1236,1236,1237,1237,1238,1238,1239,1239,1240,1240,1241,1241,1242,1242,1243,1244,1244,1245,1245,1246,1246,1247,1247,1248,1248,1249,1249,1250,1250,1251,1252,1252,1253,1253,1254,1254,1255,1255,1256,1256,1257,1258,1258,1259,1259,1260,1260,1261,1261,1262,1263,1263,1264,1264,1265,1265,1266,1266,1267,1268,1268,1269,1269,1270,1270,1271,1272,1272,1273,1273,1274,1275,1275,1276,1276,1277,1277,1278,1279,1279,1280,1280,1282,1282,1282,1283,1283,1284,1285,1285,1286,1286,1287,1288,1288,1289,1289,1290,1291,1291,1292,1292,1293,1294,1294,1295,1296,1296,1297,1297,1298,1299,1299,1300,1301,1301,1302,1302,1303,1304,1304,1305,1306,1306,1307,1308,1308,1309,1309,1310,1311,1311,1312,1313,1313,1314,1315,1315,1316,1317,1318,1319,1319,1320,1321,1321,1322,1323,1323,1324,1325,1326,1327,1328,1328,1329,1330,1330,1331,1332,1333,1334,1335,1336,1337,1337,1338,1339,1340,1340,1341,1342,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381,1382,1383,1384,1384,1385,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1419,1420,1421,1422,1423,1424,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1451,1452,1453,1453,1454,1455,1455,1456,1457,1457,1458,1458,1459,1460,1460,1461,1461,1462,1463,1463,1464,1464,1465,1465,1466,1465,1465,1466,1466,1467,1468,1468,1469,1469,1470,1470,1471,1471,1472,1472,1473,1473,1474,1474,1474,1475,1475,1476,1476,1477,1477,1478,1478,1478,1479,1479,1480,1480,1480,1481,1481,1482,1482,1482,1483,1484,1485,1485,1485,1486,1486,1487,1487,1487,1487,1488,1488,1489,1489,1489,1490,1490,1490,1491,1491,1491,1491,1492,1492,1492,1492,1492,1493,1493,1493,1493,1493,1494,1494,1494,1494,1494,1494,1494,1495,1495,1491,1491,1491,1491,1492,1492,1492,1492,1493,1493,1493,1493,1493,1493,1494,1494,1494,1494,1494,1494,1494,1494,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1495,1484,1485,1485,1485,1486,1486,1486,1487,1487,1487,1488,1488,1488,1488,1489,1489,1489,1490,1490,1490,1490,1491,1461,1461,1462,1462,1463,1463,1463,1464,1464,1465,1465,1465,1466,1466,1466,1467,1467,1467,1468,1468,1468,1469,1469,1469,1470,1470,1471,1471,1472,1472,1473,1473,1474,1474,1474,1475,1475,1475,1475,1476,1476,1476,1477,1477,1478,1478,1478,1479,1479,1480,1480,1480,1481,1481,1481,1482,1482,1483,] 34 | y=[432,435,434,435,433,434,432,433,431,432,430,431,429,430,428,429,427,428,426,427,425,426,424,425,423,424,423,422,423,421,422,420,421,419,420,418,419,417,418,416,417,416,415,416,414,415,413,414,412,413,411,412,411,410,411,409,410,408,409,407,408,407,406,407,405,406,404,405,403,404,403,402,403,401,402,400,401,400,399,400,398,399,398,397,398,396,397,395,396,395,394,395,393,394,389,392,393,391,392,391,390,391,389,390,389,388,389,387,388,387,386,387,385,386,385,384,385,384,383,384,382,383,382,381,382,381,380,381,379,380,379,378,379,378,377,378,377,376,377,375,376,375,374,375,374,373,374,373,372,373,372,371,371,370,371,370,369,370,369,368,369,368,368,367,367,366,367,366,365,366,365,364,364,364,363,363,362,363,362,362,361,361,361,360,361,360,360,359,359,359,358,358,358,357,357,357,356,356,356,355,355,355,355,354,354,354,353,353,353,353,352,352,352,352,351,351,351,351,351,350,350,350,350,350,349,349,346,349,346,349,349,349,349,349,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,348,349,349,349,349,349,349,349,350,350,350,350,351,351,351,351,352,352,352,352,353,353,353,354,354,355,355,356,356,356,357,357,358,358,358,359,359,359,360,360,360,361,361,362,362,362,363,363,364,364,364,365,365,366,366,367,367,366,367,367,368,368,368,369,369,370,370,371,371,372,372,373,373,374,374,375,376,376,377,377,378,378,379,379,380,381,381,382,382,383,384,384,385,385,386,387,387,389,389,390,391,391,392,387,393,394,395,396,397,398,399,400,401,402,403,404,405,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,468,469,470,471,464,465,466,467,458,459,460,461,462,463,450,451,452,453,454,455,456,457,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,493,490,491,492,487,488,489,484,485,486,480,481,482,483,477,478,479,473,474,475,476,472,539,540,538,539,536,537,538,535,536,533,534,535,531,532,533,529,530,531,527,528,529,525,526,527,524,525,521,523,519,520,517,518,515,516,517,512,513,514,515,510,511,512,509,510,506,507,508,504,505,501,502,503,498,499,500,496,497,495,] 35 | 36 | fitEllipse(numpy.array(x),numpy.array(y) ) -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // -------------------------------------------------------- 10 | // Check if point located in ellipse ROI square 11 | // with center in ellipse center and side equals to ellipse 12 | // long axis. 13 | // -------------------------------------------------------- 14 | bool inline pointInellipseRoi(cv::Mat& ellipse, cv::Vec2d& point, double distance_tolerance) 15 | { 16 | double cx = ellipse.at(0); 17 | double cy = ellipse.at(1); 18 | double a = ellipse.at(2); 19 | if (point[0] >= (cx - a - distance_tolerance) && 20 | point[0] <= (cx + a + distance_tolerance) && 21 | point[1] >= (cy - a - distance_tolerance) && 22 | point[1] <= (cy + a + distance_tolerance)) 23 | { 24 | return true; 25 | } 26 | return false; 27 | } 28 | // -------------------------------------------------------- 29 | // 30 | // -------------------------------------------------------- 31 | void updateInlierPts(std::vector& srcPts, std::vector& inlier_idxs, std::vector& inlierPts) 32 | { 33 | std::vector tmpPts; 34 | tmpPts.assign(srcPts.begin(), srcPts.end()); 35 | inlierPts.clear(); 36 | for (auto inl : inlier_idxs) 37 | { 38 | inlierPts.push_back(tmpPts[inl]); 39 | } 40 | } 41 | // -------------------------------------------------------- 42 | // 43 | // -------------------------------------------------------- 44 | void generateEllipseCandidates( 45 | cv::Mat& I, // input grayscale 8 bit image 46 | int edge_process_select, // edge detector: 1 - sobel, 2 - canny 47 | int specified_polarity, // 1 - ellipse lighter than background, -1 - ellipse darker than background 48 | cv::Mat& candidates_out, // ellipse candidates n x 5 49 | cv::Mat& edgeimg_out, // edges image 50 | cv::Mat& gradient_vec_out); // gradients for each edge point n x 2 51 | 52 | 53 | // ---------------------------------------------------- 54 | // Point-ellipse edge distance. 55 | // Used for edge points filtering. 56 | // ---------------------------------------------------- 57 | double dRosin_square(cv::Mat& param, cv::Vec2d& point) 58 | { 59 | double dmin; 60 | double ae2 = param.at(2) * param.at(2); 61 | double be2 = param.at(3) * param.at(3); 62 | double x = point[0] - param.at(0); 63 | double y = point[1] - param.at(1); 64 | double xp = x * cos(-param.at(4)) - y * sin(-param.at(4)); 65 | double yp = x * sin(-param.at(4)) + y * cos(-param.at(4)); 66 | double fe2 = ae2 - be2; 67 | double X = xp * xp; 68 | double Y = yp * yp; 69 | double delta = (X + Y + fe2) * (X + Y + fe2) - 4 * fe2 * X; 70 | double A = (X + Y + fe2 - sqrt(delta)) / 2; 71 | double ah = sqrt(A); 72 | double bh2 = fe2 - A; 73 | double term = A * be2 + ae2 * bh2; 74 | double xi = ah * sqrt(ae2 * (be2 + bh2) / term); 75 | double yi = param.at(3) * sqrt(bh2 * (ae2 - A) / term); 76 | std::vector d(4, 0); 77 | d[0] = (xp - xi) * (xp - xi) + (yp - yi) * (yp - yi); 78 | d[1] = (xp - xi) * (xp - xi) + (yp + yi) * (yp + yi); 79 | d[2] = (xp + xi) * (xp + xi) + (yp - yi) * (yp - yi); 80 | d[3] = (xp + xi) * (xp + xi) + (yp + yi) * (yp + yi); 81 | dmin = *std::min_element(d.begin(), d.end()); 82 | return dmin; 83 | } 84 | 85 | // ---------------------------------------------------- 86 | // compute the points' normals belong to an ellipse, 87 | // the normals have been already normalized. 88 | // param: [x0 y0 a b phi] . 89 | // points : [xi yi] , n x 2 90 | // ---------------------------------------------------- 91 | void computePointAngle(cv::Mat& ellipse, 92 | std::vector& points, 93 | cv::Mat& ellipse_normals) 94 | { 95 | // convert[x0 y0 a b phi] to Ax ^ 2 + Bxy + Cy ^ 2 + Dx + Ey + F = 0 96 | double a_square = ellipse.at(2) * ellipse.at(2); 97 | double b_square = ellipse.at(3) * ellipse.at(3); 98 | double sin_phi = sin(ellipse.at(4)); 99 | double cos_phi = cos(ellipse.at(4)); 100 | double sin_square = sin_phi * sin_phi; 101 | double cos_square = cos_phi * cos_phi; 102 | double A = b_square * cos_square + a_square * sin_square; 103 | double B = (b_square - a_square) * sin_phi * cos_phi * 2; 104 | double C = b_square * sin_square + a_square * cos_square; 105 | double D = -2 * A * ellipse.at(0) - B * ellipse.at(1); 106 | double E = -2 * C * ellipse.at(1) - B * ellipse.at(0); 107 | 108 | // calculate points' normals to ellipse 109 | ellipse_normals = cv::Mat(points.size(), 2, CV_64FC1); 110 | for (int i = 0; i < points.size(); ++i) 111 | { 112 | double y = C * points[i][1] + B / 2 * points[i][0] + E / 2; 113 | double x = A * points[i][0] + B / 2 * points[i][1] + D / 2; 114 | double angles = atan2(y, x); 115 | ellipse_normals.at(i, 0) = cos(angles); 116 | ellipse_normals.at(i, 1) = sin(angles); 117 | } 118 | } 119 | 120 | // ---------------------------------------------------- 121 | // Matrix-Matrix dot product 122 | // ---------------------------------------------------- 123 | void dot(cv::Mat& A, cv::Mat& B, cv::Mat& Res) 124 | { 125 | Res = cv::Mat(A.rows, 1, CV_64FC1); 126 | for (int i = 0; i < A.rows; ++i) 127 | { 128 | double sum = 0; 129 | for (int j = 0; j < A.cols; ++j) 130 | { 131 | sum += A.at(i, j) * B.at(i, j); 132 | } 133 | Res.at(i) = sum; 134 | } 135 | } 136 | 137 | // ---------------------------------------------------- 138 | // Histogram computation 139 | // ---------------------------------------------------- 140 | void histc(std::vector& tt, size_t tbins, std::vector& h) 141 | { 142 | h = std::vector(tbins, 0); 143 | for (auto t : tt) 144 | { 145 | h[t]++; 146 | } 147 | } 148 | 149 | // ---------------------------------------------------- 150 | // compute continous arcs pieces 151 | // ---------------------------------------------------- 152 | void takeInliers(std::vector& x, cv::Vec2d& center, float tbins, std::vector& idx) 153 | { 154 | if (x.size() == 0) 155 | { 156 | idx.clear(); 157 | return; 158 | } 159 | const double tmin = -CV_PI; 160 | const double tmax = CV_PI; 161 | double divisor = (tmax - tmin) * (tbins - 1); 162 | std::vector bin_number; 163 | for (auto p : x) 164 | { 165 | // bin numbers for x[i] normalized to range [0:tbins-1] 166 | auto th = atan2(p[1] - center[1], p[0] - center[0]); 167 | bin_number.push_back(round((th - tmin) / divisor)); 168 | } 169 | // compute histogram of normal angles distribution 170 | std::vector histogram; 171 | histc(bin_number, tbins, histogram); 172 | // label of connected histogram bins group 173 | std::vector component_label(tbins, 0); 174 | // number of bins in each component 175 | std::vector compSize(tbins, 0); 176 | // number of components 177 | int nComps = 0; 178 | // compute components (continous sectors) 179 | std::vector queue(tbins, 0); 180 | const double du[2] = { -1, 1 }; 181 | size_t front = 0; 182 | size_t rear = 0; 183 | for (int i = 0; i < tbins; ++i) 184 | { 185 | if (histogram[i] > 0 && component_label[i] == 0) 186 | { 187 | nComps = nComps + 1; 188 | component_label[i] = nComps; 189 | front = 0; 190 | rear = 0; 191 | queue[front] = i; 192 | 193 | while (front <= rear) 194 | { 195 | size_t u = queue[front]; 196 | front++; 197 | for (int j = 0; j < 2; ++j) 198 | { 199 | int v = u + du[j]; 200 | if (v == -1) 201 | { 202 | v = tbins - 1; 203 | } 204 | if (v >= tbins) 205 | { 206 | v = 0; 207 | } 208 | if (component_label[v] == 0 && histogram[v] > 0) 209 | { 210 | rear++; 211 | queue[rear] = v; 212 | component_label[v] = nComps; 213 | } 214 | } 215 | } 216 | 217 | std::set idx_list; 218 | for (size_t j = 0; j < component_label.size(); ++j) 219 | { 220 | if (component_label[j] == nComps) 221 | { 222 | idx_list.insert(j); 223 | } 224 | } 225 | size_t sum = 0; 226 | for (size_t j = 0; j < bin_number.size(); ++j) 227 | { 228 | if (idx_list.find(bin_number[j]) != idx_list.end()) 229 | { 230 | sum++; 231 | } 232 | } 233 | compSize[nComps] = sum; 234 | } 235 | } 236 | 237 | // as components indexing start from 1 we need to add 1 to size 238 | compSize.resize(nComps + 1); 239 | 240 | if (nComps > 0) 241 | { 242 | std::vector validComps; 243 | for (int j = 0; j < compSize.size(); ++j) 244 | { 245 | // if component is not empty, it is valid 246 | if (compSize[j] >= 50) 247 | { 248 | validComps.push_back(j); 249 | } 250 | } 251 | std::vector validBins; 252 | for (int j = 0; j < component_label.size(); ++j) 253 | { 254 | for (int k = 0; k < validComps.size(); ++k) 255 | { 256 | if (component_label[j] == validComps[k]) 257 | { 258 | validBins.push_back(j); 259 | } 260 | } 261 | } 262 | 263 | idx.clear(); 264 | for (int k = 0; k < validBins.size(); ++k) 265 | { 266 | for (int j = 0; j < bin_number.size(); ++j) 267 | { 268 | if (bin_number[j] == validBins[k]) 269 | { 270 | idx.push_back(j); 271 | } 272 | } 273 | } 274 | } 275 | } 276 | 277 | // ---------------------------------------------------- 278 | // 279 | // ---------------------------------------------------- 280 | float calcuCompleteness(std::vector& x, cv::Vec2d& center, size_t tbins) 281 | { 282 | if (x.size() == 0) 283 | { 284 | return 0; 285 | } 286 | float completeness = 0; 287 | std::vector theta(x.size(), 0); 288 | for (int i = 0; i < x.size(); ++i) 289 | { 290 | theta[i] = atan2(x[i][1] - center[1], x[i][0] - center[0]); 291 | } 292 | const double tmin = -CV_PI; 293 | const double tmax = CV_PI; 294 | std::vector tt(theta.size(), 0); 295 | for (int i = 0; i < x.size(); ++i) 296 | { 297 | tt[i] = round((theta[i] - tmin) / (tmax - tmin) * (tbins - 1)); 298 | } 299 | std::vector h(tbins, 0); 300 | histc(tt, tbins, h); 301 | float h_greatthanzero_num = 0; 302 | for (auto v : h) 303 | { 304 | if (v > 0) 305 | { 306 | h_greatthanzero_num++; 307 | } 308 | } 309 | completeness = h_greatthanzero_num / tbins; 310 | return completeness; 311 | } 312 | 313 | struct point2d 314 | { 315 | double x, y; 316 | }; 317 | // ---------------------------------------------------- 318 | // 319 | // ---------------------------------------------------- 320 | //input : (xi,yi) 321 | //output: x0,y0,a,b,phi,ellipara 322 | //successfull, return 1; else return 0 323 | int fitEllipse(point2d* dataxy, int datanum, double* ellipara); 324 | // ---------------------------------------------------- 325 | // 326 | // ---------------------------------------------------- 327 | void fitEllipse(std::vector& points, cv::Mat& ellipse, int& info) 328 | { 329 | info = fitEllipse((point2d*)points.data(), points.size(), (double*)ellipse.data); 330 | } 331 | 332 | // ---------------------------------------------------- 333 | // 334 | // ---------------------------------------------------- 335 | void subEllipseDetection(cv::Mat& list, // N x 5 336 | std::vector& points, 337 | cv::Mat& normals, 338 | double distance_tolerance, 339 | double normal_tolerance, 340 | double Tmin, 341 | double angleCoverage, 342 | cv::Mat& E, 343 | //std::vector& angleLoop, 344 | std::vector& mylabels, 345 | std::vector& labels, 346 | cv::Mat& ellipses, 347 | std::vector& validCandidates) 348 | { 349 | labels = std::vector(points.size(), 0); 350 | mylabels = std::vector(points.size(), 0); 351 | ellipses = cv::Mat(); 352 | int ellipse_polarity = 0; 353 | 354 | // max_dis = max(points) - min(points); 355 | // double maxSemiMajor = max(max_dis); 356 | // double maxSemiMinor = min(max_dis); 357 | 358 | double distance_tolerance_square = distance_tolerance * distance_tolerance; 359 | validCandidates = std::vector(list.rows, 1); 360 | cv::Mat convergence = list.clone(); 361 | // ------------------------------------------- 362 | // the main loop over input ellipse candidates 363 | // ------------------------------------------- 364 | for (size_t i = 0; i < list.rows; ++i) 365 | { 366 | // extract ellipse parameters for convenoence 367 | cv::Vec2d ellipseCenter(list.at(i, 0), list.at(i, 1)); 368 | cv::Vec2d ellipseAxes(list.at(i, 2), list.at(i, 3));; 369 | double ellipsePhi = list.at(i, 4); 370 | 371 | //ellipse circumference is approximate pi * (1.5*sum(ellipseAxes)-sqrt(ellipseAxes(1)*ellipseAxes(2)) 372 | double tbins = std::min(180.0, floor(CV_PI * (1.5 * (ellipseAxes[0] + ellipseAxes[1]) - sqrt(ellipseAxes[0] * ellipseAxes[1])) * Tmin)); 373 | std::vector inliers; 374 | 375 | //i_dx - indices of points inside ellipse ROI 376 | std::vector i_dx; 377 | for (int j = 0; j < points.size(); ++j) 378 | { 379 | if (pointInellipseRoi(list.row(i), points[j], 1)) 380 | { 381 | i_dx.push_back(j); 382 | } 383 | } 384 | 385 | // inliers are edge points indices for points 386 | // places near current ellipse candidate edge and not labeled yet. 387 | for (int j = 0; j < i_dx.size(); ++j) 388 | { 389 | if (labels[i_dx[j]] == 0 && (dRosin_square(list.row(i), points[i_dx[j]]) <= distance_tolerance_square)) 390 | { 391 | inliers.push_back(i_dx[j]); 392 | } 393 | } 394 | 395 | // inlier edge point coordinates 396 | std::vector inlierPts; 397 | // inlier edge normals, computed by edge detector 398 | cv::Mat inlierNormals = cv::Mat::zeros(inliers.size(), 2, CV_64FC1); 399 | for (int j = 0; j < inliers.size(); ++j) 400 | { 401 | inlierPts.push_back(points[inliers[j]]); 402 | inlierNormals.at(j, 0) = normals.at(inliers[j], 0); 403 | inlierNormals.at(j, 1) = normals.at(inliers[j], 1); 404 | } 405 | // ellipse candidate normals for current set of inlier edge points. 406 | cv::Mat ellipse_normals; 407 | computePointAngle(list.row(i), inlierPts, ellipse_normals); 408 | // filter inliers by normal difference 409 | // between: 410 | // current ellipse normal for this point 411 | // image edge normal for this point 412 | cv::Mat p_dot_temp; 413 | // compute cos(delta_angle) 414 | dot(inlierNormals, ellipse_normals, p_dot_temp); 415 | // compute polarity (normal may be directed inside or outside of ellpse) 416 | size_t p_cnt = 0; 417 | for (int j = 0; j < p_dot_temp.rows; ++j) 418 | { 419 | if (p_dot_temp.at(j) > 0) 420 | { 421 | ++p_cnt; 422 | } 423 | } 424 | // search for edge points with normals collinear 425 | // to ellipse candidates normal for this point. 426 | std::vector inliers_tmp; 427 | // negative polarity 428 | if (p_cnt > inliers.size() * 0.5) 429 | { 430 | // ellipse interior darker than exterior 431 | ellipse_polarity = -1; 432 | for (int k = 0; k < inliers.size(); ++k) 433 | { 434 | if (p_dot_temp.at(k) >= 0.923879532511287) 435 | { 436 | inliers_tmp.push_back(inliers[k]); 437 | } 438 | } 439 | } 440 | else 441 | // positive polarity 442 | // ellipse interior lighter than exterior 443 | { 444 | ellipse_polarity = 1; 445 | for (int k = 0; k < inliers.size(); ++k) 446 | { 447 | if (p_dot_temp.at(k) <= -0.923879532511287) 448 | { 449 | inliers_tmp.push_back(inliers[k]); 450 | } 451 | } 452 | } 453 | std::swap(inliers_tmp, inliers); 454 | inliers_tmp.clear(); 455 | // ---------------------- 456 | std::vector inliers2 = inliers; 457 | std::vector inliers3 = inliers;; 458 | // update inlier points 459 | updateInlierPts(points, inliers, inlierPts); 460 | 461 | // search for max continous arcs 462 | std::vector idx; 463 | takeInliers(inlierPts, ellipseCenter, tbins, idx); 464 | for (auto id : idx) 465 | { 466 | inliers_tmp.push_back(inliers[id]); 467 | } 468 | std::swap(inliers_tmp, inliers); 469 | inliers_tmp.clear(); 470 | 471 | // update points and normals 472 | inlierPts.clear(); 473 | inlierNormals = cv::Mat::zeros(inliers.size(), 2, CV_64FC1); 474 | for (int k = 0; k < inliers.size(); ++k) 475 | { 476 | inlierPts.push_back(points[inliers[k]]); 477 | inlierNormals.at(k, 0) = normals.at(inliers[k], 0); 478 | inlierNormals.at(k, 1) = normals.at(inliers[k], 1); 479 | } 480 | 481 | // trying to fit ellipse for found inliers 482 | cv::Mat new_ellipse(1, 5, CV_64FC1); 483 | int new_info = 0; 484 | fitEllipse(inlierPts, new_ellipse, new_info); 485 | 486 | 487 | std::vector newinliers; 488 | // if success, check if it close to current ellipse candidate 489 | if (new_info == 1) 490 | { 491 | // if new ellipse is near the same as current one 492 | if (((pow(new_ellipse.at(0) - ellipseCenter[0], 2) + 493 | pow(new_ellipse.at(1) - ellipseCenter[1], 2)) <= 16 * distance_tolerance_square) && 494 | ((pow(new_ellipse.at(2) - ellipseAxes[0], 2) + 495 | pow(new_ellipse.at(3) - ellipseAxes[1], 2)) <= 16 * distance_tolerance_square) && 496 | (fabs(new_ellipse.at(4) - ellipsePhi) <= 0.314159265358979)) 497 | { 498 | // update ellipse normals for current set of inlisrs 499 | computePointAngle(new_ellipse, inlierPts, ellipse_normals); 500 | 501 | //i_dx - indices of edge points inside ROI for new ellipse candidate 502 | i_dx.clear(); 503 | for (int k = 0; k < points.size(); ++k) 504 | { 505 | if (pointInellipseRoi(new_ellipse, points[k], distance_tolerance)) 506 | { 507 | i_dx.push_back(k); 508 | } 509 | } 510 | // update coords,normals and labels for use below 511 | std::vector idxPts; 512 | std::vector idxLabels; 513 | cv::Mat idxNormals = cv::Mat::zeros(i_dx.size(), 2, CV_64FC1); 514 | 515 | for (int k = 0; k < i_dx.size(); ++k) 516 | { 517 | idxPts.push_back(points[i_dx[k]]); 518 | idxLabels.push_back(labels[i_dx[k]]); 519 | idxNormals.at(k, 0) = normals.at(i_dx[k], 0); 520 | idxNormals.at(k, 1) = normals.at(i_dx[k], 1); 521 | } 522 | // update ellipse candidate normals 523 | computePointAngle(new_ellipse, idxPts, ellipse_normals); 524 | 525 | // filter inliers by collinearity ellipse and edge gradient normals 526 | // and distance from edge point to ellipse edge. 527 | newinliers.clear(); 528 | for (int k = 0; k < i_dx.size(); ++k) 529 | { 530 | float cosang = idxNormals.row(k).dot(ellipse_normals.row(k)); 531 | if (idxLabels[k] == 0 && 532 | dRosin_square(new_ellipse, idxPts[k]) <= distance_tolerance_square && 533 | (cosang * (-ellipse_polarity)) >= 0.923879532511287) 534 | { 535 | newinliers.push_back(i_dx[k]); 536 | } 537 | } 538 | // updete points set for next usage 539 | std::vector newinliersPts; 540 | cv::Vec2d center; 541 | center[0] = new_ellipse.at(0); 542 | center[1] = new_ellipse.at(1); 543 | updateInlierPts(points, newinliers, newinliersPts); 544 | 545 | // get largest arcs inliers 546 | idx.clear(); 547 | takeInliers(newinliersPts, center, tbins, idx); 548 | // update inliers 549 | std::vector new_inliers_tmp; 550 | for (auto id : idx) 551 | { 552 | new_inliers_tmp.push_back(newinliers[id]); 553 | } 554 | std::swap(new_inliers_tmp, newinliers); 555 | new_inliers_tmp.clear(); 556 | // update points again 557 | updateInlierPts(points, newinliers, newinliersPts); 558 | 559 | // if newly found ellipse is better than current candidate 560 | // than take it as current candidate. 561 | if (newinliers.size() > inliers.size()) 562 | { 563 | inliers.clear(); 564 | inliers.assign(newinliers.begin(), newinliers.end()); 565 | inliers3.assign(newinliers.begin(), newinliers.end()); 566 | updateInlierPts(points, newinliers, newinliersPts); 567 | int new_new_info = 0; 568 | cv::Mat new_new_ellipse(1, 5, CV_64FC1); 569 | // take new inliers into account 570 | fitEllipse(newinliersPts, new_new_ellipse, new_new_info); 571 | if (new_new_info == 1) 572 | { 573 | new_ellipse = new_new_ellipse.clone(); 574 | } 575 | } 576 | } 577 | } 578 | // if new ellipse not better than current one 579 | // just continue with it. 580 | else 581 | { 582 | new_ellipse = list.row(i).clone(); 583 | } 584 | // check if edge contain enough pixels wrt boundary lenghth. 585 | if (inliers.size() >= floor(CV_PI * (1.5 * (new_ellipse.at(2) + new_ellipse.at(3)) - 586 | sqrt(new_ellipse.at(2) * new_ellipse.at(3))) * Tmin)) 587 | { 588 | // accept it 589 | new_ellipse.copyTo(convergence.row(i)); 590 | // check if it is close to any previously saved ellipses 591 | bool hasClone = false; 592 | for (int k = 0; k < i; ++k) 593 | { 594 | double dx = convergence.at(k, 0) - new_ellipse.at(0); 595 | double dy = convergence.at(k, 1) - new_ellipse.at(1); 596 | double da = convergence.at(k, 2) - new_ellipse.at(2); 597 | double db = convergence.at(k, 3) - new_ellipse.at(3); 598 | double dang = convergence.at(k, 4) - new_ellipse.at(4); 599 | if (sqrt(dx * dx + dy * dy) <= distance_tolerance && 600 | sqrt(da * da + db * db) <= distance_tolerance && 601 | fabs(dang) <= 0.314159265358979) 602 | { 603 | hasClone = true; 604 | break; 605 | } 606 | } 607 | // maek as not valid if true 608 | if (hasClone) 609 | { 610 | validCandidates[i] = false; 611 | } 612 | 613 | // check if edge points cover enough angle 614 | updateInlierPts(points, newinliers, inlierPts); 615 | double completeness = calcuCompleteness(inlierPts, cv::Vec2d(new_ellipse.at(0), new_ellipse.at(1)), tbins) * 360.0; 616 | bool completeOrNot = (completeness >= angleCoverage); 617 | 618 | if (new_info == 1 && completeOrNot) 619 | { 620 | hasClone = false; 621 | for (int k = 0; k < i; ++k) 622 | { 623 | double dx = convergence.at(k, 0) - new_ellipse.at(0); 624 | double dy = convergence.at(k, 1) - new_ellipse.at(1); 625 | double da = convergence.at(k, 2) - new_ellipse.at(2); 626 | double db = convergence.at(k, 3) - new_ellipse.at(3); 627 | double dang = convergence.at(k, 4) - new_ellipse.at(4); 628 | if (sqrt(dx * dx + dy * dy) <= distance_tolerance && 629 | sqrt(da * da + db * db) <= distance_tolerance && 630 | fabs(dang) <= 0.314159265358979) 631 | { 632 | hasClone = true; 633 | break; 634 | } 635 | } 636 | // if new candidate is unique 637 | if (!hasClone) 638 | { 639 | //labels(inliers) = size(ellipses, 1) + 1; 640 | for (auto inl : inliers) 641 | { 642 | // label = 0 is not labeled, so, start from 1 643 | // Assign labels to current inlier edge points set. 644 | labels[inl] = ellipses.rows+1; 645 | } 646 | 647 | bool inliersAreSame=true; 648 | if (inliers3.size() == newinliers.size()) 649 | { 650 | for (int n = 0; n < inliers3.size(); ++n) 651 | { 652 | if (inliers3[n] != newinliers[n]) 653 | { 654 | inliersAreSame = false; 655 | break; 656 | } 657 | } 658 | } 659 | else 660 | { 661 | inliersAreSame = false; 662 | } 663 | 664 | //================================================================== 665 | if (inliersAreSame) 666 | { 667 | // mylabels(inliers3) = size(ellipses, 1) + 1; 668 | for (auto inl : inliers3) 669 | { 670 | mylabels[inl] = ellipses.rows+1; 671 | } 672 | } 673 | 674 | // append newly found ellipse to ellipses list 675 | if (ellipses.empty()) 676 | { 677 | ellipses = new_ellipse.clone(); 678 | } 679 | else 680 | { 681 | std::vector v = { ellipses,new_ellipse }; 682 | cv::vconcat(v, ellipses); 683 | } 684 | validCandidates[i] = false; 685 | } 686 | } 687 | } 688 | else 689 | { 690 | validCandidates[i] = false; 691 | } 692 | } 693 | } 694 | 695 | // ---------------------------------------------------- 696 | // 697 | // ---------------------------------------------------- 698 | void ellipseDetection(cv::Mat& candidates, std::vector& points, cv::Mat& normals, float distance_tolerance, float normal_tolerance, float Tmin, float angleCoverage, cv::Mat& E, std::vector& mylabels, std::vector& labels, cv::Mat& ellipses) 699 | { 700 | labels = std::vector(points.size(), 0); 701 | mylabels = std::vector(points.size(), 0); 702 | ellipses = cv::Mat(); 703 | 704 | std::vector validElls; 705 | std::vector > ellIndGoodness; 706 | std::vector goodness; 707 | for (int i = 0; i < candidates.rows; ++i) 708 | { 709 | // ellipse circumference is approximate pi* (1.5 * sum(ellipseAxes) - sqrt(ellipseAxes(1) * ellipseAxes(2)) 710 | cv::Vec2d ellipseCenter(candidates.at(i, 0), candidates.at(i, 1)); 711 | cv::Vec2d ellipseAxes(candidates.at(i, 2), candidates.at(i, 3)); 712 | double tbins = std::min(180.0, floor(CV_PI * (1.5 * (ellipseAxes[0] + ellipseAxes[1]) - sqrt(ellipseAxes[0] * ellipseAxes[1])) * Tmin)); 713 | std::vector s_dx; 714 | 715 | for (int j = 0; j < points.size(); ++j) 716 | { 717 | if (pointInellipseRoi(candidates.row(i), points[j], 1)) 718 | { 719 | s_dx.push_back(j); 720 | } 721 | } 722 | std::vector inliers; 723 | 724 | for (int j = 0; j < s_dx.size(); ++j) 725 | { 726 | if (dRosin_square(candidates.row(i), points[s_dx[j]]) <= 1) 727 | { 728 | inliers.push_back(s_dx[j]); 729 | } 730 | } 731 | 732 | //ellipse_normals = computePointAngle(candidates(i, :), points(inliers, :)); 733 | cv::Mat ellipse_normals; 734 | std::vector inlierPts; 735 | cv::Mat inliers_normals(inliers.size(), 2, CV_64FC1); 736 | for (int k = 0; k < inliers.size(); ++k) 737 | { 738 | inlierPts.push_back(points[inliers[k]]); 739 | inliers_normals.at(k, 0) = normals.at(inliers[k], 0); 740 | inliers_normals.at(k, 1) = normals.at(inliers[k], 1); 741 | } 742 | 743 | // ------------------------------------------------------ 744 | // Measure angles between ellipse and edge points normals 745 | // ------------------------------------------------------ 746 | computePointAngle(candidates.row(i), inlierPts, ellipse_normals); 747 | /* 748 | // plot edge points normals 749 | for (int i = 0; i < inliers.size(); ++i) 750 | { 751 | cv::Point p1(inlierPts[i][0], inlierPts[i][1]); 752 | float nx = ellipse_normals.at(i, 0) * 10; 753 | float ny = ellipse_normals.at(i, 1) * 10; 754 | float nx1 = inliers_normals.at(i, 0) * 10; 755 | float ny1 = inliers_normals.at(i, 1) * 10; 756 | cv::Point p2(inlierPts[i][0] + nx, inlierPts[i][1] + ny); 757 | cv::Point p21(inlierPts[i][0] + nx1, inlierPts[i][1] + ny1); 758 | cv::line(global_dbg_img, p1, p2, cv::Scalar(255, 255, 0)); 759 | cv::line(global_dbg_img, p1, p21, cv::Scalar(255, 0, 255)); 760 | } 761 | */ 762 | cv::Mat p_dot_temp; 763 | dot(inliers_normals, ellipse_normals, p_dot_temp); 764 | size_t p_cnt = 0; 765 | for (int i = 0; i < p_dot_temp.rows; ++i) 766 | { 767 | if (p_dot_temp.at(i) > 0) 768 | { 769 | p_cnt++; 770 | } 771 | } 772 | std::vector inliers_tmp; 773 | if (p_cnt > inliers.size() * 0.5) 774 | { 775 | // ellipse_polarity = -1; 776 | for (int k = 0; k < inliers.size(); ++k) 777 | { 778 | if (p_dot_temp.at(k) >= 0.923879532511287) 779 | { 780 | inliers_tmp.push_back(inliers[k]); 781 | } 782 | } 783 | } 784 | else 785 | { 786 | // ellipse_polarity = 1; 787 | for (int k = 0; k < inliers.size(); ++k) 788 | { 789 | if (p_dot_temp.at(k) <= -0.923879532511287) 790 | { 791 | inliers_tmp.push_back(inliers[k]); 792 | } 793 | } 794 | } 795 | std::swap(inliers_tmp, inliers); 796 | // ------------------------------------------------------ 797 | // Filter inliers by continous sectors 798 | // ------------------------------------------------------ 799 | // Update inlier points and normals 800 | inliers_tmp.clear(); 801 | 802 | updateInlierPts(points, inliers, inlierPts); 803 | 804 | std::vector idx; 805 | takeInliers(inlierPts, ellipseCenter, tbins, idx); 806 | //std::cout << "inlierPts " << i << " : " << inlierPts.size() << std::endl; 807 | //std::cout << "idx " << i << " : " << idx.size() << std::endl; 808 | for (auto id : idx) 809 | { 810 | inliers_tmp.push_back(inliers[id]); 811 | } 812 | std::swap(inliers_tmp, inliers); 813 | inliers_tmp.clear(); 814 | // ------------------------------------------------------ 815 | // compute quality of detected ellipses 816 | // ------------------------------------------------------ 817 | updateInlierPts(points, inliers, inlierPts); 818 | 819 | float support_inliers_ratio = (float)inliers.size() / (CV_PI * (1.5 * (ellipseAxes[0] + ellipseAxes[1]) - sqrt(ellipseAxes[0] * ellipseAxes[1]))); 820 | float completeness_ratio = calcuCompleteness(inlierPts, ellipseCenter, tbins); 821 | float g = sqrt(support_inliers_ratio * completeness_ratio); 822 | ellIndGoodness.push_back(std::make_pair(i, g)); 823 | } 824 | // sort ellipses by quality 825 | std::sort(ellIndGoodness.begin(), ellIndGoodness.end(), [](auto& left, auto& right) 826 | { 827 | return left.second > right.second; 828 | }); 829 | 830 | ellipses = cv::Mat::zeros(ellIndGoodness.size(),5, CV_64FC1); 831 | for (int i = 0; i < ellIndGoodness.size(); ++i) 832 | { 833 | goodness.push_back(ellIndGoodness[i].second); 834 | validElls.push_back(ellIndGoodness[i].first); 835 | candidates.row(ellIndGoodness[i].first).copyTo(ellipses.row(i)); 836 | //std::cout << ellIndGoodness[i].first << " : " << ellIndGoodness[i].second << std::endl; 837 | } 838 | candidates = ellipses.clone(); 839 | ellipses = cv::Mat(); 840 | // candidates now sorted by goodness 841 | std::vector angles_init = { 300, 210, 150, 90 }; 842 | std::vector angles; 843 | // angles(angles < angleCoverage) = []; 844 | for (auto a : angles_init) 845 | { 846 | if (a >= angleCoverage) 847 | { 848 | angles.push_back(a); 849 | } 850 | } 851 | 852 | if (angles.empty() || angles[angles.size() - 1] != angleCoverage) 853 | { 854 | angles.push_back(angleCoverage); 855 | } 856 | 857 | for (size_t angleLoop = 0; angleLoop < angles.size(); ++angleLoop) 858 | { 859 | std::vector idx; 860 | for (int i = 0; i < labels.size(); ++i) 861 | { 862 | if (labels[i] == 0) 863 | { 864 | idx.push_back(i); 865 | } 866 | } 867 | if (idx.size() < 2 * CV_PI * (6 * distance_tolerance) * Tmin) 868 | { 869 | break; 870 | } 871 | 872 | std::vector idxPts; 873 | cv::Mat idx_normals = cv::Mat(idx.size(), 2, CV_64FC1); 874 | for (int k = 0; k < idx.size(); ++k) 875 | { 876 | idxPts.push_back(points[idx[k]]); 877 | idx_normals.at(k, 0) = normals.at(idx[k], 0); 878 | idx_normals.at(k, 1) = normals.at(idx[k], 1); 879 | } 880 | 881 | //std::vector anglesLoop; 882 | std::vector L2; 883 | std::vector L; 884 | cv::Mat C; // N x 5 885 | std::vector validCandidates; 886 | //[L2, L, C, validCandidates] = subEllipseDetection(candidates, points(idx, :), normals(idx, :), distance_tolerance, normal_tolerance, Tmin, angles(angleLoop), E, angleLoop); 887 | 888 | // std::cout << "coverage :" << angles[angleLoop] << std::endl; 889 | subEllipseDetection(candidates, idxPts, idx_normals, distance_tolerance, normal_tolerance, Tmin, angles[angleLoop], E, L2, L, C, validCandidates); 890 | 891 | //candidates = candidates(validCandidates, :); 892 | 893 | //std::cout << "C.size" << std::endl; 894 | //std::cout << C.rows << std::endl; 895 | 896 | // prepare candidates for nexl iteration 897 | int cnt_v = 0; 898 | std::vector validIdx; 899 | //std::cout << "validIdx" << std::endl; 900 | for (auto v : validCandidates) 901 | { 902 | if (v) 903 | { 904 | validIdx.push_back(cnt_v); 905 | //std::cout << cnt_v << std::endl; 906 | } 907 | cnt_v++; 908 | } 909 | cv::Mat tmpCandidates(validIdx.size(), 5, CV_64FC1); 910 | int ivc = 0; 911 | for (auto v : validIdx) 912 | { 913 | if (v) 914 | { 915 | candidates.row(v).copyTo(tmpCandidates.row(ivc)); 916 | ivc++; 917 | } 918 | } 919 | std::swap(candidates, tmpCandidates); 920 | tmpCandidates = cv::Mat(); 921 | 922 | 923 | if (C.rows > 0) 924 | { 925 | for (int k = 0; k < C.rows; ++k) 926 | { 927 | bool flag = false; 928 | for (int j = 0; j < ellipses.rows; ++j) 929 | { 930 | if (sqrt(pow(C.at(k, 0) - ellipses.at(j, 0), 2) + pow(C.at(k, 1) - ellipses.at(j, 2), 2)) <= distance_tolerance 931 | && sqrt(pow(C.at(k, 2) - ellipses.at(j, 2), 2) + pow(C.at(k, 3) - ellipses.at(j, 3), 2)) <= distance_tolerance 932 | && abs(C.at(k, 4) - ellipses.at(j, 4)) <= 0.314159265358979) 933 | { 934 | flag = true; 935 | //labels(idx(L == k)) = j; 936 | for (int n = 0; n < L.size(); ++n) 937 | { 938 | if (L[n] == k+1) 939 | { 940 | labels[idx[n]] = j+1; 941 | } 942 | } 943 | 944 | 945 | //= ================================================ = 946 | // mylabels(idx(L2 == k)) = j; 947 | 948 | for (int n = 0; n < L2.size(); ++n) 949 | { 950 | if (L2[n] == k+1) 951 | { 952 | labels[idx[n]] = j+1; 953 | } 954 | } 955 | //= ================================================ = 956 | break; 957 | } 958 | } 959 | if (~flag) 960 | { 961 | for (int n = 0; n < L.size(); ++n) 962 | { 963 | if (L[n] == k+1) 964 | { 965 | labels[idx[n]] = ellipses.rows+1; 966 | } 967 | } 968 | 969 | 970 | //= ================================================ = 971 | // mylabels(idx(L2 == k)) = j; 972 | 973 | for (int n = 0; n < L2.size(); ++n) 974 | { 975 | if (L2[n] == k+1) 976 | { 977 | labels[idx[n]] = ellipses.rows+1; 978 | } 979 | } 980 | 981 | //ellipses = [ellipses; C(k, :)]; 982 | if (ellipses.empty()) 983 | { 984 | ellipses = C.row(k).clone(); 985 | } 986 | else 987 | { 988 | std::vector v = { ellipses,C.row(k) }; 989 | cv::vconcat(v, ellipses); 990 | } 991 | } 992 | } 993 | } 994 | } 995 | } 996 | 997 | 998 | //function[ellipses, L, posi] = ellipseDetectionByArcSupportLSs(I, Tac, Tr, specified_polarity) 999 | //% input: 1000 | //% I : input image 1001 | //% Tac : elliptic angular coverage(completeness degree) 1002 | //% Tni : ratio of support inliers on an ellipse 1003 | //% output : 1004 | // % ellipses : N by 5. (center_x, center_y, a, b, phi) 1005 | // % reference : 1006 | // % 1¡¢von Gioi R Grompone, Jeremie Jakubowicz, Jean - 1007 | // % Michel Morel, and Gregory Randall, ¡°Lsd : a fast line 1008 | // % segment detector with a false detection control., ¡± IEEE 1009 | // % transactions on pattern analysisand machine intelligence, 1010 | // % vol. 32, no. 4, pp. 722¨C732, 2010. 1011 | 1012 | void ellipseDetectionByArcSupportLSs(cv::Mat& I,int edge_process_select, float Tac, float Tr, float specified_polarity, cv::Mat& ellipses) 1013 | { 1014 | float angleCoverage = Tac;// default 165 1015 | float Tmin = Tr;// default 0.6 1016 | float unit_dis_tolerance = 2; 1017 | float normal_tolerance = CV_PI / 9; 1018 | cv::Mat candidates; 1019 | cv::Mat edge; 1020 | cv::Mat normals; 1021 | 1022 | if (I.channels() > 1) 1023 | { 1024 | cv::cvtColor(I, I, cv::COLOR_BGR2GRAY); 1025 | } 1026 | //[candidates, edge, normals, lsimg] = generateEllipseCandidates(I, 2, specified_polarity); // 1, sobel; 2, canny 1027 | generateEllipseCandidates(I, edge_process_select, specified_polarity, candidates, edge, normals); 1028 | //cv::imshow("edge", edge); 1029 | candidates = candidates.t(); 1030 | if (candidates.at(0) == 0) 1031 | { 1032 | candidates = cv::Mat(); 1033 | } 1034 | cv::Mat posi = candidates.clone(); 1035 | normals = normals.t();//norams matrix transposition 1036 | 1037 | std::vector edgePts; 1038 | for (int j = 0; j < edge.cols; ++j) 1039 | { 1040 | for (int i = 0; i < edge.rows; ++i) 1041 | { 1042 | uchar v = edge.at(i, j); 1043 | if (v > 0) 1044 | { 1045 | edgePts.push_back(cv::Vec2d(j, i)); 1046 | } 1047 | } 1048 | } 1049 | std::vector mylabels; 1050 | std::vector labels; 1051 | 1052 | cv::Mat E = I.clone(); 1053 | ellipseDetection(candidates, edgePts, normals, unit_dis_tolerance, normal_tolerance, Tmin, angleCoverage, E, mylabels, labels, ellipses); 1054 | } 1055 | 1056 | int main(int argc, char* argv[]) 1057 | { 1058 | cv::Mat image = cv::imread("../../pics/35.jpg", 1); 1059 | if (image.empty()) { return 0; } 1060 | cv::cvtColor(image, image, cv::COLOR_BGR2GRAY); 1061 | cv::imshow("image", image); 1062 | double Tac = 165; // angular coverage 1063 | double Tr = 0.65; // ratio of support inliers on an ellipse 1064 | int polarity = 0; // 0 - both, 1 - interior lighter, -1 - interior darker 1065 | int edge_process_select = 2; // 1 - sobel, 2 - canny 1066 | cv::Mat ellipses; 1067 | std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); 1068 | ellipseDetectionByArcSupportLSs(image, edge_process_select, Tac, Tr, polarity,ellipses); 1069 | std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); 1070 | std::cout << "Detection time = " << std::chrono::duration_cast(end - begin).count() << "[ms]" << std::endl; 1071 | 1072 | 1073 | cv::Mat E= image.clone(); 1074 | // plot ellipses 1075 | if (E.channels() == 1) 1076 | { 1077 | cv::cvtColor(E, E, cv::COLOR_GRAY2BGR); 1078 | } 1079 | for (int ei = 0; ei < ellipses.rows; ei++) 1080 | { 1081 | int x = (int)ellipses.at(ei, 0); 1082 | int y = (int)ellipses.at(ei, 1); 1083 | int a1 = ellipses.at(ei, 2); 1084 | int a2 = ellipses.at(ei, 3); 1085 | double ang = ellipses.at(ei, 4) * 180 / CV_PI; 1086 | if (a1 > 0 && a2 > 0) 1087 | { 1088 | cv::ellipse(E, cv::Point(x, y), cv::Size(a1, a2), ang, 0, 360, (cv::Scalar(255, 0, 0)), 2); 1089 | } 1090 | } 1091 | cv::imshow("E", E); 1092 | cv::waitKey(); 1093 | } --------------------------------------------------------------------------------