fitellipse.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /********************************************************************************
  2. *
  3. *
  4. * This program is demonstration for ellipse fitting. Program finds
  5. * contours and approximate it by ellipses using three methods.
  6. * 1: OpenCV's original method fitEllipse which implements Fitzgibbon 1995 method.
  7. * 2: The Approximate Mean Square (AMS) method fitEllipseAMS proposed by Taubin 1991
  8. * 3: The Direct least square (Direct) method fitEllipseDirect proposed by Fitzgibbon1999.
  9. *
  10. * Trackbar specify threshold parameter.
  11. *
  12. * White lines is contours/input points and the true ellipse used to generate the data.
  13. * 1: Blue lines is fitting ellipses using openCV's original method.
  14. * 2: Green lines is fitting ellipses using the AMS method.
  15. * 3: Red lines is fitting ellipses using the Direct method.
  16. *
  17. *
  18. * Original Author: Denis Burenkov
  19. * AMS and Direct Methods Author: Jasper Shemilt
  20. *
  21. *
  22. ********************************************************************************/
  23. #include "opencv2/imgproc.hpp"
  24. #include "opencv2/imgcodecs.hpp"
  25. #include "opencv2/highgui.hpp"
  26. #include <iostream>
  27. using namespace cv;
  28. using namespace std;
  29. class canvas{
  30. public:
  31. bool setupQ;
  32. cv::Point origin;
  33. cv::Point corner;
  34. int minDims,maxDims;
  35. double scale;
  36. int rows, cols;
  37. cv::Mat img;
  38. void init(int minD, int maxD){
  39. // Initialise the canvas with minimum and maximum rows and column sizes.
  40. minDims = minD; maxDims = maxD;
  41. origin = cv::Point(0,0);
  42. corner = cv::Point(0,0);
  43. scale = 1.0;
  44. rows = 0;
  45. cols = 0;
  46. setupQ = false;
  47. }
  48. void stretch(cv::Point2f min, cv::Point2f max){
  49. // Stretch the canvas to include the points min and max.
  50. if(setupQ){
  51. if(corner.x < max.x){corner.x = (int)(max.x + 1.0);};
  52. if(corner.y < max.y){corner.y = (int)(max.y + 1.0);};
  53. if(origin.x > min.x){origin.x = (int) min.x;};
  54. if(origin.y > min.y){origin.y = (int) min.y;};
  55. } else {
  56. origin = cv::Point((int)min.x, (int)min.y);
  57. corner = cv::Point((int)(max.x + 1.0), (int)(max.y + 1.0));
  58. }
  59. int c = (int)(scale*((corner.x + 1.0) - origin.x));
  60. if(c<minDims){
  61. scale = scale * (double)minDims/(double)c;
  62. } else {
  63. if(c>maxDims){
  64. scale = scale * (double)maxDims/(double)c;
  65. }
  66. }
  67. int r = (int)(scale*((corner.y + 1.0) - origin.y));
  68. if(r<minDims){
  69. scale = scale * (double)minDims/(double)r;
  70. } else {
  71. if(r>maxDims){
  72. scale = scale * (double)maxDims/(double)r;
  73. }
  74. }
  75. cols = (int)(scale*((corner.x + 1.0) - origin.x));
  76. rows = (int)(scale*((corner.y + 1.0) - origin.y));
  77. setupQ = true;
  78. }
  79. void stretch(vector<Point2f> pts)
  80. { // Stretch the canvas so all the points pts are on the canvas.
  81. cv::Point2f min = pts[0];
  82. cv::Point2f max = pts[0];
  83. for(size_t i=1; i < pts.size(); i++){
  84. Point2f pnt = pts[i];
  85. if(max.x < pnt.x){max.x = pnt.x;};
  86. if(max.y < pnt.y){max.y = pnt.y;};
  87. if(min.x > pnt.x){min.x = pnt.x;};
  88. if(min.y > pnt.y){min.y = pnt.y;};
  89. };
  90. stretch(min, max);
  91. }
  92. void stretch(cv::RotatedRect box)
  93. { // Stretch the canvas so that the rectangle box is on the canvas.
  94. cv::Point2f min = box.center;
  95. cv::Point2f max = box.center;
  96. cv::Point2f vtx[4];
  97. box.points(vtx);
  98. for( int i = 0; i < 4; i++ ){
  99. cv::Point2f pnt = vtx[i];
  100. if(max.x < pnt.x){max.x = pnt.x;};
  101. if(max.y < pnt.y){max.y = pnt.y;};
  102. if(min.x > pnt.x){min.x = pnt.x;};
  103. if(min.y > pnt.y){min.y = pnt.y;};
  104. }
  105. stretch(min, max);
  106. }
  107. void drawEllipseWithBox(cv::RotatedRect box, cv::Scalar color, int lineThickness)
  108. {
  109. if(img.empty()){
  110. stretch(box);
  111. img = cv::Mat::zeros(rows,cols,CV_8UC3);
  112. }
  113. box.center = scale * cv::Point2f(box.center.x - origin.x, box.center.y - origin.y);
  114. box.size.width = (float)(scale * box.size.width);
  115. box.size.height = (float)(scale * box.size.height);
  116. ellipse(img, box, color, lineThickness, LINE_AA);
  117. Point2f vtx[4];
  118. box.points(vtx);
  119. for( int j = 0; j < 4; j++ ){
  120. line(img, vtx[j], vtx[(j+1)%4], color, lineThickness, LINE_AA);
  121. }
  122. }
  123. void drawPoints(vector<Point2f> pts, cv::Scalar color)
  124. {
  125. if(img.empty()){
  126. stretch(pts);
  127. img = cv::Mat::zeros(rows,cols,CV_8UC3);
  128. }
  129. for(size_t i=0; i < pts.size(); i++){
  130. Point2f pnt = scale * cv::Point2f(pts[i].x - origin.x, pts[i].y - origin.y);
  131. img.at<cv::Vec3b>(int(pnt.y), int(pnt.x))[0] = (uchar)color[0];
  132. img.at<cv::Vec3b>(int(pnt.y), int(pnt.x))[1] = (uchar)color[1];
  133. img.at<cv::Vec3b>(int(pnt.y), int(pnt.x))[2] = (uchar)color[2];
  134. };
  135. }
  136. void drawLabels( std::vector<std::string> text, std::vector<cv::Scalar> colors)
  137. {
  138. if(img.empty()){
  139. img = cv::Mat::zeros(rows,cols,CV_8UC3);
  140. }
  141. int vPos = 0;
  142. for (size_t i=0; i < text.size(); i++) {
  143. cv::Scalar color = colors[i];
  144. std::string txt = text[i];
  145. Size textsize = getTextSize(txt, FONT_HERSHEY_COMPLEX, 1, 1, 0);
  146. vPos += (int)(1.3 * textsize.height);
  147. Point org((img.cols - textsize.width), vPos);
  148. cv::putText(img, txt, org, FONT_HERSHEY_COMPLEX, 1, color, 1, LINE_8);
  149. }
  150. }
  151. };
  152. static void help(char** argv)
  153. {
  154. cout << "\nThis program is demonstration for ellipse fitting. The program finds\n"
  155. "contours and approximate it by ellipses. Three methods are used to find the \n"
  156. "elliptical fits: fitEllipse, fitEllipseAMS and fitEllipseDirect.\n"
  157. "Call:\n"
  158. << argv[0] << " [image_name -- Default ellipses.jpg]\n" << endl;
  159. }
  160. int sliderPos = 70;
  161. Mat image;
  162. bool fitEllipseQ, fitEllipseAMSQ, fitEllipseDirectQ;
  163. cv::Scalar fitEllipseColor = Scalar(255, 0, 0);
  164. cv::Scalar fitEllipseAMSColor = Scalar( 0,255, 0);
  165. cv::Scalar fitEllipseDirectColor = Scalar( 0, 0,255);
  166. cv::Scalar fitEllipseTrueColor = Scalar(255,255,255);
  167. void processImage(int, void*);
  168. int main( int argc, char** argv )
  169. {
  170. fitEllipseQ = true;
  171. fitEllipseAMSQ = true;
  172. fitEllipseDirectQ = true;
  173. cv::CommandLineParser parser(argc, argv,"{help h||}{@image|ellipses.jpg|}");
  174. if (parser.has("help"))
  175. {
  176. help(argv);
  177. return 0;
  178. }
  179. string filename = parser.get<string>("@image");
  180. image = imread(samples::findFile(filename), 0);
  181. if( image.empty() )
  182. {
  183. cout << "Couldn't open image " << filename << "\n";
  184. return 0;
  185. }
  186. imshow("source", image);
  187. namedWindow("result", WINDOW_NORMAL );
  188. // Create toolbars. HighGUI use.
  189. createTrackbar( "threshold", "result", &sliderPos, 255, processImage );
  190. processImage(0, 0);
  191. // Wait for a key stroke; the same function arranges events processing
  192. waitKey();
  193. return 0;
  194. }
  195. // Define trackbar callback function. This function finds contours,
  196. // draws them, and approximates by ellipses.
  197. void processImage(int /*h*/, void*)
  198. {
  199. RotatedRect box, boxAMS, boxDirect;
  200. vector<vector<Point> > contours;
  201. Mat bimage = image >= sliderPos;
  202. findContours(bimage, contours, RETR_LIST, CHAIN_APPROX_NONE);
  203. canvas paper;
  204. paper.init(int(0.8*MIN(bimage.rows, bimage.cols)), int(1.2*MAX(bimage.rows, bimage.cols)));
  205. paper.stretch(cv::Point2f(0.0f, 0.0f), cv::Point2f((float)(bimage.cols+2.0), (float)(bimage.rows+2.0)));
  206. std::vector<std::string> text;
  207. std::vector<cv::Scalar> color;
  208. if (fitEllipseQ) {
  209. text.push_back("OpenCV");
  210. color.push_back(fitEllipseColor);
  211. }
  212. if (fitEllipseAMSQ) {
  213. text.push_back("AMS");
  214. color.push_back(fitEllipseAMSColor);
  215. }
  216. if (fitEllipseDirectQ) {
  217. text.push_back("Direct");
  218. color.push_back(fitEllipseDirectColor);
  219. }
  220. paper.drawLabels(text, color);
  221. int margin = 2;
  222. vector< vector<Point2f> > points;
  223. for(size_t i = 0; i < contours.size(); i++)
  224. {
  225. size_t count = contours[i].size();
  226. if( count < 6 )
  227. continue;
  228. Mat pointsf;
  229. Mat(contours[i]).convertTo(pointsf, CV_32F);
  230. vector<Point2f>pts;
  231. for (int j = 0; j < pointsf.rows; j++) {
  232. Point2f pnt = Point2f(pointsf.at<float>(j,0), pointsf.at<float>(j,1));
  233. if ((pnt.x > margin && pnt.y > margin && pnt.x < bimage.cols-margin && pnt.y < bimage.rows-margin)) {
  234. if(j%20==0){
  235. pts.push_back(pnt);
  236. }
  237. }
  238. }
  239. points.push_back(pts);
  240. }
  241. for(size_t i = 0; i < points.size(); i++)
  242. {
  243. vector<Point2f> pts = points[i];
  244. if (pts.size()<=5) {
  245. continue;
  246. }
  247. if (fitEllipseQ) {
  248. box = fitEllipse(pts);
  249. if( MAX(box.size.width, box.size.height) > MIN(box.size.width, box.size.height)*30 ||
  250. MAX(box.size.width, box.size.height) <= 0 ||
  251. MIN(box.size.width, box.size.height) <= 0){continue;};
  252. }
  253. if (fitEllipseAMSQ) {
  254. boxAMS = fitEllipseAMS(pts);
  255. if( MAX(boxAMS.size.width, boxAMS.size.height) > MIN(boxAMS.size.width, boxAMS.size.height)*30 ||
  256. MAX(box.size.width, box.size.height) <= 0 ||
  257. MIN(box.size.width, box.size.height) <= 0){continue;};
  258. }
  259. if (fitEllipseDirectQ) {
  260. boxDirect = fitEllipseDirect(pts);
  261. if( MAX(boxDirect.size.width, boxDirect.size.height) > MIN(boxDirect.size.width, boxDirect.size.height)*30 ||
  262. MAX(box.size.width, box.size.height) <= 0 ||
  263. MIN(box.size.width, box.size.height) <= 0 ){continue;};
  264. }
  265. if (fitEllipseQ) {
  266. paper.drawEllipseWithBox(box, fitEllipseColor, 3);
  267. }
  268. if (fitEllipseAMSQ) {
  269. paper.drawEllipseWithBox(boxAMS, fitEllipseAMSColor, 2);
  270. }
  271. if (fitEllipseDirectQ) {
  272. paper.drawEllipseWithBox(boxDirect, fitEllipseDirectColor, 1);
  273. }
  274. paper.drawPoints(pts, cv::Scalar(255,255,255));
  275. }
  276. imshow("result", paper.img);
  277. }