{"id":15862,"date":"2022-04-04T18:13:23","date_gmt":"2022-04-04T22:13:23","guid":{"rendered":"https:\/\/blogs.mathworks.com\/pick\/?p=15862"},"modified":"2022-07-06T09:30:29","modified_gmt":"2022-07-06T13:30:29","slug":"detecting-ellipses-in-images","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/pick\/2022\/04\/04\/detecting-ellipses-in-images\/","title":{"rendered":"Detecting Ellipses in Images"},"content":{"rendered":"<div class=\"content\"><!--introduction--><p><a href=\"http:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/authors\/911\">Brett<\/a>'s Pick this week is <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/33970-ellipse-detection-using-1d-hough-transform?s_tid=srchtitle\"><tt>Ellipse Detection Using 1D Hough Transform<\/tt><\/a>, by <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/2741135\">Martin Simonovsky<\/a>.<\/p><!--\/introduction--><h3>Contents<\/h3><div><ul><li><a href=\"#e840f3d5-3379-41d5-a762-3cd218cd49ef\">Introduction<\/a><\/li><li><a href=\"#2aaddd1b-3d85-4d13-a1e5-7c27d89e9c35\">The Source<\/a><\/li><li><a href=\"#332fb788-151e-4592-94ec-697a4305bf20\">The Implementation<\/a><\/li><li><a href=\"#31e2570f-ed4f-488d-96bc-9d4d35e1f08b\">A Test Drive<\/a><\/li><li><a href=\"#4caa616c-5330-4244-8588-5dcc3db2d60e\">Segmentation<\/a><\/li><li><a href=\"#196bcd59-d82e-4132-b90c-e0af72f9eb86\">Detecting the Ellipses<\/a><\/li><li><a href=\"#a485cf07-f41f-4a9b-99c3-97bba2763804\">Improving the Results<\/a><\/li><li><a href=\"#cf205488-0b8e-4a6e-aabf-c2a46f86b592\">Paring Results<\/a><\/li><li><a href=\"#368eac9e-9e5d-48d8-b104-0f40b75dd320\">A Note on Visualizing the Results<\/a><\/li><li><a href=\"#eb64c753-1e08-4e31-954b-5ed2739e1e47\">Notes<\/a><\/li><li><a href=\"#24bed6e5-9c2a-429c-8aa8-2729e4b0460b\">A Couple of Suggestions<\/a><\/li><\/ul><\/div><h4>Introduction<a name=\"e840f3d5-3379-41d5-a762-3cd218cd49ef\"><\/a><\/h4><p>I've written several times in the past about detecting circles in images (<a href=\"https:\/\/blogs.mathworks.com\/pick\/2008\/05\/23\/detecting-circles-in-an-image\/?s_tid=srchtitle_circle%20detection_1\">here<\/a>, <a href=\"https:\/\/blogs.mathworks.com\/pick\/2017\/05\/12\/detecting-circles-revisited\/?s_tid=srchtitle_detecting%20circles_2\">here<\/a>, and <a href=\"https:\/\/blogs.mathworks.com\/pick\/2014\/06\/06\/detecting-circles-in-images-revisited-2\/?s_tid=srchtitle_detecting%2520circles_3\">here<\/a>). I've written, too, on <a href=\"https:\/\/blogs.mathworks.com\/pick\/2011\/10\/21\/drawing-ellipses\/?s_tid=srchtitle_ellipse_2\">drawing ellipses<\/a>. Today, I want to write about <i>detecting ellipses<\/i>.<\/p><p>Since ellipses are described by more parameters than are lines or circles, detecting ellipses is more challenging than is detecting lines or circles. And while we have nice functionality for detecting the simpler shapes (<a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/houghlines.html\">houghlines<\/a>, <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/imfindcircles.html\">imfindcircles<\/a>), we do not (currently) have a function to detect ellipses. Enter Martin's ellipse-detection function.<\/p><h4>The Source<a name=\"2aaddd1b-3d85-4d13-a1e5-7c27d89e9c35\"><\/a><\/h4><p>Fortunately, and appropriately, Martin cited the source for his algorithm; he used a paper (Y. Xie, Q. Ji. \"A New Efficient Ellipse Detection Method.\" 1051-4651\/02 IEEE, 2002) that I have had on my desk for quite some time, thinking that I would one day sit down to implement it in MATLAB code. I love that Martin did that work for me--it makes me appreciate the File Exchange all the more.<\/p><h4>The Implementation<a name=\"332fb788-151e-4592-94ec-697a4305bf20\"><\/a><\/h4><p>After playing around with Martin's <tt>ellipseDetection()<\/tt> for most of an afternoon, I have some thoughts to share. First, recognize that ellipse detection is an expensive memory hog; the computation scales with the square of the number of nonzero pixels, <i>N<\/i>, in your search image. You'll almost certainly want to operate on an \"edge image\" rather than a simple binary mask of your regions of interest, and to play with the function's nine input parameters to limit the search. Among these parameters, you can specify the range of major axes lengths to consider, and the minimum aspect ratio. You may also specify some parameters for limiting the angles of the ellipses you seek; this could be very useful if you know the ellipse orientations <i>a priori<\/i>. Even after such limits are used, an exhaustive search examines <i>N<\/i> x <i>N<\/i> candidates for major axes. Martin's function provides a parameter that allows you to trade off between speed and accuracy. (The \"randomize\" parameter is not a Boolean variable, as the name suggests; rather it reduces the search space from <i>N<\/i> x <i>N<\/i> to <i>N<\/i> x randomize. If randomize is 0, then the search is exhaustive--increased accuracy at a computational cost.)<\/p><h4>A Test Drive<a name=\"31e2570f-ed4f-488d-96bc-9d4d35e1f08b\"><\/a><\/h4><p>So let's try it out on a sample image of our creation. We can start with an image containing circles, and warp it to generate ellipses:<\/p><pre class=\"language-matlab\">inputImage = imread(<span class=\"string\">'coloredChips.png'<\/span>);\r\ntform = affine2d([1 0 0; 0.75 1 0; 0 0 1]);\r\ninputImage = imwarp(inputImage, tform);\r\nimshow(inputImage)\r\ntitle(<span class=\"string\">\"Test Image\"<\/span>)\r\n<\/pre><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge5.png\" alt=\"\"> <\/p><h4>Segmentation<a name=\"4caa616c-5330-4244-8588-5dcc3db2d60e\"><\/a><\/h4><p>I <i>segmented<\/i> the image by:<\/p><div><ol><li>Using the <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/colorthresholder-app.html\">colorThresholder<\/a> to create a mask of the background<\/li><li>Splitting the color image into R, G, and B components (<a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/imsplit.html\"><tt>imsplit<\/tt><\/a>)<\/li><li>Masking the R, G, and B planes individually (<tt>R(backgroundMask) = 0<\/tt>, ...)<\/li><li>Re-compositing the masked planes into an RGB image (<a href=\"https:\/\/www.mathworks.com\/help\/matlab\/ref\/double.cat.html\"><tt>cat<\/tt><\/a>)<\/li><li>Converting the masked RGB image to grayscale (<a href=\"https:\/\/www.mathworks.com\/help\/matlab\/ref\/im2gray.html\"><tt>im2gray<\/tt><\/a>)<\/li><li>Calculating (with carefully selected input parameters) the edges of the grayscale image (<a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/edge.html\"><tt>edge<\/tt><\/a>)<\/li><li>Filtering the results to select the desired Major Axes Lengths and Areas (<a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/imageregionanalyzer-app.html\"><tt>imageRegionAnalyzer<\/tt><\/a>, <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/bwpropfilt.html\"><tt>bwpropfilt<\/tt><\/a>)<\/li><\/ol><\/div><p>In just a few minutes, I had an edge image in which to detect those ellipses:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge.png\" alt=\"\"> <\/p><h4>Detecting the Ellipses<a name=\"196bcd59-d82e-4132-b90c-e0af72f9eb86\"><\/a><\/h4><p>With this binary edge mask in hand, I was ready to search for ellipses--first, naively:<\/p><pre class=\"language-matlab\">bestFits = ellipseDetection(edgeMask);\r\n<\/pre><p>The operation completed in (just) less than a second, but the results were underwhelming:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge2.png\" alt=\"\"> <\/p><p>(By the way, calling <tt>ellipseDetection()<\/tt> on the binary mask of the warped chips <i>without<\/i> first calculating the edges took upwards of 20 minutes, and the results were even worse!)<\/p><h4>Improving the Results<a name=\"a485cf07-f41f-4a9b-99c3-97bba2763804\"><\/a><\/h4><p>To improve the performance, I judiciously selected input parameters to reduce the computational cost. First, I used <tt>imdistline<\/tt> to measure the major and minor axes lengths:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge3.png\" alt=\"\"> <\/p><p>Then I used <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/82650-protractor-measure-angles-in-image-or-non-image-axes?s_tid=srchtitle\"><tt>protractor<\/tt><\/a> to get a sense of the orientations of the ellipses:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge4.png\" alt=\"\"> <\/p><p>After a few minutes, I found my way to:<\/p><pre class=\"language-matlab\">params.minMajorAxis = 55;\r\nparams.maxMajorAxis = 75;\r\nparams.minAspectRatio = 0.4; <span class=\"comment\">%1 = circle; 0 = line;<\/span>\r\nparams.rotation = 35;\r\nparams.rotationSpan = 10;\r\nparams.randomize = 0; <span class=\"comment\">%Exhaustive search<\/span>\r\nparams.numBest = 30; <span class=\"comment\">%(Number of chips = 26)<\/span>\r\n<\/pre><pre class=\"language-matlab\">bestFits = ellipseDetection(edgeMask, params);\r\n<\/pre><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge5.png\" alt=\"\"> <\/p><h4>Paring Results<a name=\"cf205488-0b8e-4a6e-aabf-c2a46f86b592\"><\/a><\/h4><p>Wait...what? <tt>bestFits<\/tt> contains paramaters for 30 detections (a manual count informs me that there are 26 ellipses wholly contained in the image), but when visualizing the results, it appears that there are far fewer. What's going on?<\/p><p>It turns out that the algorithm is susceptible to reporting the same ellipse multiple times. (If I drag those cyan ellipses, there are other coincident ellipses underneath!)<\/p><p>Experimenting, I found it quite useful to dramatically <i>overspecify<\/i> the number of ellipses I wanted to detect, then to pare the results in post-processing. Consider:<\/p><pre class=\"language-matlab\">params.smoothStddev = 0.5;\r\nparams.numBest = 1000; <span class=\"comment\">%(Number of chips = 26)<\/span>\r\nbestFits = ellipseDetection(edgeMask, params);\r\nminCenterDistance = 10;\r\nminScore = 30;\r\nmaxAspectRatio = 0.6;\r\nbestFits = pareEllipseFits(bestFits, <span class=\"keyword\">...<\/span>\r\n    minCenterDistance, minScore, maxAspectRatio);\r\n<\/pre><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge6.png\" alt=\"\"> <\/p><p>I wrote <tt>pareResults<\/tt> to allow filtering by minimum center distance (to disallow overlapping detections), minimum score, and maximum aspect ratio. Using that paring approach, I can request far more detections (params.numBest = 1000) than I really want, then discard \"bad\" results. I'm pretty pleased with the way it's working!<\/p><h4>A Note on Visualizing the Results<a name=\"368eac9e-9e5d-48d8-b104-0f40b75dd320\"><\/a><\/h4><p>Finally, I also wrote <tt>visualizeEllipses<\/tt> to call <tt>drawellipse<\/tt> directly on the output of <tt>ellipseDetection<\/tt>.<\/p><h4>Notes<a name=\"eb64c753-1e08-4e31-954b-5ed2739e1e47\"><\/a><\/h4><p>In the interest of making this post a bit shorter (I know...too late!), I didn't post all of the code. If anyone would like to see it, I'm happy to share. Just drop me an email at:<\/p><p><tt>char(cumsum([98 17 -11 7 -10 7 7 -4 -47 45 -12 19 -12 15 -8 3 -7 8 -69 53 12 -2]))<\/tt><\/p><p>Also, there are a few other files on the Exchange that purport to facilitate ellipse detection. Please leave me a comment if you'd like to see those considered those in a subsequent post!<\/p><h4>A Couple of Suggestions<a name=\"24bed6e5-9c2a-429c-8aa8-2729e4b0460b\"><\/a><\/h4><p>Martin's implementation uses Gaussian filtering via <tt>fspecial<\/tt>. That syntax is no longer recommended; the newer <tt>imgaussfilt<\/tt> is more efficient. Also, the call to <tt>fspecial<\/tt> requires an <i>integer<\/i> argument for the second (<tt>'hsize'<\/tt>) parameter. (I added a call to <tt>round()<\/tt> in my version.) Finally, since getting good results requires tuning a number of parameters, this function is just <i>begging<\/i> for a code-generating app to front it; that's perhaps a project for another day!<\/p><p>Thank you, Martin...finding this function, and figuring out how to use it, gives me a valuable tool in my image processing arsenal.<\/p><p>As always, I welcome your <a href=\"http:\/\/blogs.mathworks.com\/pick\/?p=15862#respond\">thoughts and comments<\/a>.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_8fba6e32e51c42289f2c0a7f220c8d37() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='8fba6e32e51c42289f2c0a7f220c8d37 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 8fba6e32e51c42289f2c0a7f220c8d37';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2022 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_8fba6e32e51c42289f2c0a7f220c8d37()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2022a<br><\/p><\/div><!--\r\n8fba6e32e51c42289f2c0a7f220c8d37 ##### SOURCE BEGIN #####\r\n%% Ellipse Detection\r\n%\r\n% <http:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/authors\/911 Brett>'s\r\n% Pick this week is\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/33970-ellipse-detection-using-1d-hough-transform?s_tid=srchtitle |Ellipse Detection Using 1D Hough Transform|>,\r\n% by <https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/2741135 Martin Simonovsky>.\r\n\r\n%% Introduction\r\n% I've written several times in the past about detecting circles in images\r\n% (<https:\/\/blogs.mathworks.com\/pick\/2008\/05\/23\/detecting-circles-in-an-image\/?s_tid=srchtitle_circle%20detection_1\r\n% here>,\r\n% <https:\/\/blogs.mathworks.com\/pick\/2017\/05\/12\/detecting-circles-revisited\/?s_tid=srchtitle_detecting%20circles_2\r\n% here>, and\r\n% <https:\/\/blogs.mathworks.com\/pick\/2014\/06\/06\/detecting-circles-in-images-revisited-2\/?s_tid=srchtitle_detecting%2520circles_3\r\n% here>). I've written, too, on\r\n% <https:\/\/blogs.mathworks.com\/pick\/2011\/10\/21\/drawing-ellipses\/?s_tid=srchtitle_ellipse_2\r\n% drawing ellipses>. Today, I want to write about _detecting ellipses_.\r\n\r\n%%\r\n% Since ellipses are described by more parameters than are lines or\r\n% circles, detecting ellipses is more challenging than is\r\n% detecting lines or circles. And while we have nice functionality for\r\n% detecting the simpler shapes \r\n% (<https:\/\/www.mathworks.com\/help\/images\/ref\/houghlines.html houghlines>, \r\n% <https:\/\/www.mathworks.com\/help\/images\/ref\/imfindcircles.html imfindcircles>),\r\n% we do not (currently) have a function to\r\n% detect ellipses. Enter Martin's ellipse-detection function.\r\n\r\n%% The Source\r\n% Fortunately, and appropriately, Martin cited the source for his\r\n% algorithm; he used a paper (Y. Xie, Q. Ji. \"A New Efficient Ellipse\r\n% Detection Method.\" 1051-4651\/02 IEEE, 2002) that I have had on my desk\r\n% for quite some time, thinking that I would one day sit down to implement\r\n% it in MATLAB code. I love that Martin did that work for meREPLACE_WITH_DASH_DASHit makes me\r\n% appreciate the File Exchange all the more.\r\n\r\n%% The Implementation\r\n% After playing around with Martin's |ellipseDetection()| for most of an\r\n% afternoon, I have some thoughts to share. First, recognize that ellipse\r\n% detection is an expensive memory hog; the computation scales with the\r\n% square of the number of nonzero pixels, _N_, in your search image. You'll almost\r\n% certainly want to operate on an \"edge image\" rather than a simple binary\r\n% mask of your regions of interest, and to play with the function's nine\r\n% input parameters to limit the search. Among these parameters, you can\r\n% specify the range of major axes lengths to consider, and the minimum\r\n% aspect ratio. You may also specify some parameters for limiting the\r\n% angles of the ellipses you seek; this could be very useful if you know\r\n% the ellipse orientations _a priori_. Even after such limits are used, an\r\n% exhaustive search examines _N_ x _N_ candidates for major axes. Martin's\r\n% function provides a parameter that allows you to trade off between speed\r\n% and accuracy. (The \"randomize\" parameter is not a Boolean variable, as\r\n% the name suggests; rather it reduces the search space from _N_ x _N_ to\r\n% _N_ x randomize. If randomize is 0, then the search is\r\n% exhaustiveREPLACE_WITH_DASH_DASHincreased accuracy at a computational cost.)\r\n\r\n%% A Test Drive\r\n% So let's try it out on a sample image of our creation. We can start with\r\n% an image containing circles, and warp it to generate ellipses:\r\n\r\n%%\r\n% \r\n%   inputImage = imread('coloredChips.png');\r\n%   inputImage = imread('coloredChips.png');\r\n%   tform = affine2d([1 0 0; 0.75 1 0; 0 0 1]);\r\n%   inputImage = imwarp(inputImage, tform);\r\n%   imshow(inputImage)\r\n%   title(\"Test Image\")\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge5.png>>\r\n% \r\n\r\n%% Segmentation\r\n% I _segmented_ the image by:\r\n%%\r\n% \r\n% # Using the <https:\/\/www.mathworks.com\/help\/images\/ref\/colorthresholder-app.html colorThresholder>\r\n% to create a mask of the background\r\n% # Splitting the color image into R, G, and B components\r\n% (<https:\/\/www.mathworks.com\/help\/images\/ref\/imsplit.html |imsplit|>)\r\n% # Masking the R, G, and B planes individually (|R(backgroundMask) = 0|, ...)\r\n% # Re-compositing the masked planes into an RGB image (<https:\/\/www.mathworks.com\/help\/matlab\/ref\/double.cat.html |cat|>)\r\n% # Converting the masked RGB image to grayscale (<https:\/\/www.mathworks.com\/help\/matlab\/ref\/im2gray.html |im2gray|>)\r\n% # Calculating (with carefully selected input parameters) the edges of the grayscale image (<https:\/\/www.mathworks.com\/help\/matlab\/ref\/edge.html |edge|>)\r\n% # Filtering the results to select the desired Major Axes Lengths and\r\n% Areas (<https:\/\/www.mathworks.com\/help\/images\/ref\/imageregionanalyzer-app.html |imageRegionAnalyzer|>, <https:\/\/www.mathworks.com\/help\/images\/ref\/bwpropfilt.html |bwpropfilt|>)\r\n\r\n%% \r\n% In just a few minutes, I had an edge image in which to detect those\r\n% ellipses:\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge.png>>\r\n% \r\n\r\n%% Detecting the Ellipses\r\n% With this binary edge mask in hand, I was ready to search for\r\n% ellipsesREPLACE_WITH_DASH_DASHfirst, naively:\r\n\r\n%%\r\n% \r\n%   bestFits = ellipseDetection(edgeMask);\r\n% \r\n\r\n%%\r\n% The operation completed in (just) less than a second, but the results\r\n% were underwhelming:\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge2.png>>\r\n% \r\n\r\n%%\r\n% (By the way, calling |ellipseDetection()| on the binary mask of the\r\n% warped chips _without_ first calculating the edges took upwards of 20\r\n% minutes, and the results were even worse!)\r\n\r\n%% Improving the Results\r\n% To improve the performance, I judiciously selected input parameters to\r\n% reduce the computational cost. First, I used |imdistline| to measure the\r\n% major and minor axes lengths:\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge3.png>>\r\n% \r\n\r\n%%\r\n% Then I used <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/82650-protractor-measure-angles-in-image-or-non-image-axes?s_tid=srchtitle |protractor|> to get a sense of the orientations of the ellipses:\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge4.png>>\r\n% \r\n\r\n%%\r\n% After a few minutes, I found my way to:\r\n\r\n%%\r\n% \r\n%   params.minMajorAxis = 55;\r\n%   params.maxMajorAxis = 75;\r\n%   params.minAspectRatio = 0.4; %1 = circle; 0 = line;\r\n%   params.rotation = 35;\r\n%   params.rotationSpan = 10;\r\n%   params.randomize = 0; %Exhaustive search\r\n%   params.numBest = 30; %(Number of chips = 26)\r\n%   \r\n%   bestFits = ellipseDetection(edgeMask, params);\r\n\r\n%%\r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge5.png>>\r\n% \r\n\r\n%% Paring Results\r\n% Wait...what? |bestFits| contains paramaters for 30 detections (a manual\r\n% count informs me that there are 26 ellipses wholly contained in the\r\n% image), but when visualizing the results, it appears that there are far\r\n% fewer. What's going on?\r\n%\r\n%%\r\n% It turns out that the algorithm is susceptible to reporting the same\r\n% ellipse multiple times. (If I drag those cyan ellipses, there are other\r\n% coincident ellipses underneath!)\r\n% \r\n% Experimenting, I found it quite useful to dramatically _overspecify_ the\r\n% number of ellipses I wanted to detect, then to pare the results in\r\n% post-processing. Consider:\r\n\r\n%%\r\n%   params.smoothStddev = 0.5;\r\n%   params.numBest = 1000; %(Number of chips = 26)\r\n%   bestFits = ellipseDetection(edgeMask, params);\r\n%   minCenterDistance = 10;\r\n%   minScore = 30;\r\n%   maxAspectRatio = 0.6;\r\n%   bestFits = pareEllipseFits(bestFits, ...\r\n%       minCenterDistance, minScore, maxAspectRatio);\r\n\r\n%% \r\n% \r\n% <<https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge6.png>>\r\n% \r\n\r\n%%\r\n% I wrote |pareResults| to allow filtering by minimum center distance (to\r\n% disallow overlapping detections), minimum score, and maximum aspect\r\n% ratio. Using that paring approach, I can request far more detections\r\n% (params.numBest = 1000) than I really want, then discard \"bad\" results.\r\n% I'm pretty pleased with the way it's working!\r\n\r\n%% A Note on Visualizing the Results\r\n% Finally, I also wrote |visualizeEllipses| to call |drawellipse| directly\r\n% on the output of |ellipseDetection|.\r\n\r\n%% Notes\r\n% In the interest of making this post a bit shorter (I know...too late!), I\r\n% didn't post all of the code. If anyone would like to see it, I'm happy to\r\n% share. Just drop me an email at:\r\n\r\n%%\r\n% |char(cumsum([98 17 -11 7 -10 7 7 -4 -47 45 -12 19 -12 15 -8 3 -7 8 -69 53 12 -2]))|\r\n\r\n%%\r\n% Also, there are a few other files on the Exchange that purport to\r\n% facilitate ellipse detection. Please leave me a comment if you'd like to\r\n% see those considered those in a subsequent post!\r\n\r\n%% A Couple of Suggestions\r\n% Martin's implementation uses Gaussian filtering via |fspecial|. That\r\n% syntax is no longer recommended; the newer |imgaussfilt| is more\r\n% efficient. Also, the call to |fspecial| requires an _integer_ argument\r\n% for the second (|'hsize'|) parameter. (I added a call to |round()| in my\r\n% version.) Finally, since getting good results requires tuning a number of\r\n% parameters, this function is just _begging_ for a code-generating app to\r\n% front it; that's perhaps a project for another day!\r\n\r\n%%\r\n% Thank you, Martin...finding this function, and figuring out how to use\r\n% it, gives me a valuable tool in my image processing arsenal.\r\n\r\n%%\r\n% As always, I welcome your\r\n% <http:\/\/blogs.mathworks.com\/pick\/?p=15883#respond thoughts and comments>.\r\n##### SOURCE END ##### 8fba6e32e51c42289f2c0a7f220c8d37\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img decoding=\"async\"  class=\"img-responsive\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/ellipseTestImageEdge5.png\" onError=\"this.style.display ='none';\" \/><\/div><p>Brett's Pick this week is Ellipse Detection Using 1D Hough Transform, by Martin Simonovsky.ContentsIntroductionThe SourceThe ImplementationA Test DriveSegmentationDetecting the EllipsesImproving the... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/pick\/2022\/04\/04\/detecting-ellipses-in-images\/\">read more >><\/a><\/p>","protected":false},"author":34,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[16],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/15862"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/users\/34"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/comments?post=15862"}],"version-history":[{"count":9,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/15862\/revisions"}],"predecessor-version":[{"id":15898,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/15862\/revisions\/15898"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/media?parent=15862"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/categories?post=15862"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/tags?post=15862"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}