{"id":2713,"date":"2017-10-24T16:59:41","date_gmt":"2017-10-24T20:59:41","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=2713"},"modified":"2019-11-01T17:19:12","modified_gmt":"2019-11-01T21:19:12","slug":"feret-diameters-and-antipodal-vertices","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2017\/10\/24\/feret-diameters-and-antipodal-vertices\/","title":{"rendered":"Feret Diameters and Antipodal Vertices"},"content":{"rendered":"<div class=\"content\"><p><a href=\"https:\/\/blogs.mathworks.com\/steve\/2017\/09\/29\/feret-diameter-introduction\/\">Last time<\/a>, I wrote about finding the maximum Feret diameter for an object in a binary image, ending up with this figure:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_1_04.png\" alt=\"\"> <\/p><p>I had computed the convex hull of all the pixel corners, and then I computed the pairwise distance between every pair of convex hull vertices to find the maximum distance.<\/p><p>The procedure would work fine in many cases, but the time required to find the maximum distance this way grows with the square of the number of convex hull vertices. With modern digital image resolutions, it's not hard to imagine having thousands of vertices and therefore millions of pairwise distances to compute.<\/p><p>There is a procedure for reducing the number of vertex pairs we can consider. It is based on this theorem:<\/p><p><i>The diameter of a convex figure is the greatest distance between parallel lines of support.<\/i>  [Theorem 4,18, Preparata and Shamos, <i>Computational Geometry<\/i>, 1985]<\/p><p>A <i>line of support<\/i> for a polygon is a line that contains a vertex of the polygon, with the polygon lying entirely on one side of the line.<\/p><p>Let me show you a picture using the convex hull points from last time.<\/p><pre class=\"codeinput\">hull = [\r\n    2.5000    5.5000\r\n    3.5000    4.5000\r\n    6.5000    2.5000\r\n    9.5000    1.5000\r\n   10.5000    1.5000\r\n   10.5000    3.5000\r\n    9.5000    5.5000\r\n    5.5000    7.5000\r\n    2.5000    7.5000\r\n    2.5000    5.5000  ];\r\n\r\nplot(hull(:,1),hull(:,2),<span class=\"string\">'r'<\/span>,<span class=\"string\">'LineWidth'<\/span>,2)\r\nhold <span class=\"string\">on<\/span>\r\nplot(hull(:,1),hull(:,2),<span class=\"string\">'r*'<\/span>)\r\nhold <span class=\"string\">off<\/span>\r\naxis <span class=\"string\">equal<\/span>\r\naxis <span class=\"string\">ij<\/span>\r\naxis([0 15 0 10])\r\n\r\n<span class=\"keyword\">for<\/span> theta = -55:5:-35\r\n    drawFullLine(gca,[9.5 5.5],theta,<span class=\"string\">'Color'<\/span>,[.6 .6 .6]);\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_2_01.png\" alt=\"\"> <p>The plot above shows five different lines of support drawn through the (9.5,5.5) vertex. Now I'll add some of the lines of support through the (2.5,7.5) vertex.<\/p><pre class=\"codeinput\"><span class=\"keyword\">for<\/span> theta = 10:10:80\r\n    drawFullLine(gca,[2.5 7.5],theta,<span class=\"string\">'Color'<\/span>,[.6 .6 .6]);\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_2_02.png\" alt=\"\"> <p>You can see that there is no line of support for the (2.5,7.5) vertex that is parallel to a line of support for the (9.5,5.5) vertex. That rules out this pair of vertices for computing the maximum Feret diameter.<\/p><p>Now I'll draw lines of support for a different pair of vertices.<\/p><pre class=\"codeinput\">plot(hull(:,1),hull(:,2),<span class=\"string\">'r'<\/span>,<span class=\"string\">'LineWidth'<\/span>,2)\r\nhold <span class=\"string\">on<\/span>\r\nplot(hull(:,1),hull(:,2),<span class=\"string\">'r*'<\/span>)\r\nhold <span class=\"string\">off<\/span>\r\naxis <span class=\"string\">equal<\/span>\r\naxis <span class=\"string\">ij<\/span>\r\naxis([0 15 0 10])\r\n\r\ndrawFullLine(gca,[6.5 2.5],-30,<span class=\"string\">'Color'<\/span>,[.6 .6 .6]);\r\ndrawFullLine(gca,[9.5 5.5],-30,<span class=\"string\">'Color'<\/span>,[.6 .6 .6]);\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_2_03.png\" alt=\"\"> <p>Because the vertices (6.5,2.5) and (9.5,5.5) have parallel lines of support, they are called <i>antipodal vertices<\/i>. There is an algorithm in the Preparata and Shamos book (referenced above) that finds all the antipodal vertices for a convex polygon. There's an implementation of the algorithm in a function at the bottom of this post. I'll use it to find all the antipodal pairs of the convex hull vertices.<\/p><pre class=\"codeinput\">pq = antipodalPairs(hull);\r\n<\/pre><p><b>Important note<\/b>: the algorithm in <tt>antipodalPairs<\/tt> assumes that its input is convex. Further, it assumes that the input does not contain any vertices that are on the straight line between the two adjacent vertices. To satisfy this condition, compute the convex hull using the call: <tt>k = convhull(P,'Simplify',true)<\/tt>.<\/p><p>Now let's plot the line segments joining each antipodal pair.<\/p><pre class=\"codeinput\">plot(hull(:,1),hull(:,2),<span class=\"string\">'r'<\/span>,<span class=\"string\">'LineWidth'<\/span>,2)\r\nhold <span class=\"string\">on<\/span>\r\nplot(hull(:,1),hull(:,2),<span class=\"string\">'r*'<\/span>)\r\naxis <span class=\"string\">equal<\/span>\r\naxis <span class=\"string\">ij<\/span>\r\naxis([0 15 0 10])\r\n\r\n<span class=\"keyword\">for<\/span> k = 1:size(pq,1)\r\n    x = [hull(pq(k,1),1) hull(pq(k,2),1)];\r\n    y = [hull(pq(k,1),2) hull(pq(k,2),2)];\r\n    plot(x,y,<span class=\"string\">'Color'<\/span>,[.6 .6 .6]);\r\n<span class=\"keyword\">end<\/span>\r\nhold <span class=\"string\">off<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_2_04.png\" alt=\"\"> <p>To find the maximum Feret diameter, we only have to check the distances of these 10 segments, instead of 10*9 = 90 line segments as before.<\/p><pre class=\"codeinput\">p1 = hull(pq(:,1),:);\r\np2 = hull(pq(:,2),:);\r\nv = p1 - p2;\r\nd = hypot(v(:,1),v(:,2));\r\n[d_max,idx] = max(d);\r\np1_max = p1(idx,:);\r\np2_max = p2(idx,:);\r\nhold <span class=\"string\">on<\/span>\r\nplot([p1_max(1) p2_max(1)],[p1_max(2) p2_max(2)],<span class=\"string\">'b'<\/span>,<span class=\"string\">'LineWidth'<\/span>,2)\r\nhold <span class=\"string\">off<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_2_05.png\" alt=\"\"> <pre class=\"codeinput\">d_max\r\n<\/pre><pre class=\"codeoutput\">\r\nd_max =\r\n\r\n    10\r\n\r\n<\/pre><p>And there is the maximum Feret distance.<\/p><p>I am sure that, in many cases, it would be quicker to find the maximum distance by brute force comparison of all pairs off convex hull vertices. The computation of the antipodal pairs does take some time, after all. I have not done a performance study to investigate this question further. However, the antipodal pairs computation is useful for another measurement: the minimum Feret distance.<\/p><p>I will look at that next time.<\/p><p><i>End of post<\/i><\/p><p><i>Algorithm, visualization, and utility functions used by the code above<\/i><\/p><pre class=\"codeinput\"><span class=\"keyword\">function<\/span> h = drawFullLine(ax,point,angle_degrees,varargin)\r\n<span class=\"comment\">%drawFullLine Draw a line that spans the entire plot<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%    drawFullLine(ax,point,angle_degrees) draws a line in the<\/span>\r\n<span class=\"comment\">%    specified axes that goes through the specified point at the<\/span>\r\n<span class=\"comment\">%    specified angle (in degrees). The line is drawn to span the<\/span>\r\n<span class=\"comment\">%    entire plot.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%    drawFullLine(___,Name,Value) passes name-value parameter pairs<\/span>\r\n<span class=\"comment\">%    to the line function.<\/span>\r\n\r\n<span class=\"comment\">% Steve Eddins<\/span>\r\n\r\n\r\nlimits = axis(ax);\r\nwidth = abs(limits(2) - limits(1));\r\nheight = abs(limits(4) - limits(3));\r\nd = 2*hypot(width,height);\r\nx1 = point(1) - d*cosd(angle_degrees);\r\nx2 = point(1) + d*cosd(angle_degrees);\r\ny1 = point(2) - d*sind(angle_degrees);\r\ny2 = point(2) + d*sind(angle_degrees);\r\nh = line(ax,<span class=\"string\">'XData'<\/span>,[x1 x2],<span class=\"string\">'YData'<\/span>,[y1 y2],varargin{:});\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> pq = antipodalPairs(S)\r\n<span class=\"comment\">% antipodalPairs Antipodal vertex pairs of simple, convex polygon.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   pq = antipodalPairs(S) computes the antipodal vertex pairs of a simple,<\/span>\r\n<span class=\"comment\">%   convex polygon. S is a Px2 matrix of (x,y) vertex coordinates for the<\/span>\r\n<span class=\"comment\">%   polygon. S must be simple and convex without repeated vertices. It is<\/span>\r\n<span class=\"comment\">%   not checked for satisfying these conditions. S can either be closed or<\/span>\r\n<span class=\"comment\">%   not. The output, pq, is an Mx2 matrix representing pairs of vertices in<\/span>\r\n<span class=\"comment\">%   S. The coordinates of the k-th antipodal pair are S(pq(k,1),:) and<\/span>\r\n<span class=\"comment\">%   S(pq(k,2),:).<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   TERMINOLOGY<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   For a convex polygon, an antipodal pair of vertices is one where you<\/span>\r\n<span class=\"comment\">%   can draw distinct lines of support through each vertex such that the<\/span>\r\n<span class=\"comment\">%   lines of support are parallel.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   A line of support is a line that goes through a polygon vertex such<\/span>\r\n<span class=\"comment\">%   that the interior of the polygon lies entirely on one side of the line.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   EXAMPLE<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%     Compute antipodal vertices of a polygon and plot the corresponding<\/span>\r\n<span class=\"comment\">%     line segments.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%       x = [0 0 1 3 5 4 0];<\/span>\r\n<span class=\"comment\">%       y = [0 1 4 5 4 1 0];<\/span>\r\n<span class=\"comment\">%       S = [x' y'];<\/span>\r\n<span class=\"comment\">%       pq = antipodalPairs(S);<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%       plot(S(:,1),S(:,2))<\/span>\r\n<span class=\"comment\">%       hold on<\/span>\r\n<span class=\"comment\">%       for k = 1:size(pq,1)<\/span>\r\n<span class=\"comment\">%           xk = [S(pq(k,1),1) S(pq(k,2),1)];<\/span>\r\n<span class=\"comment\">%           yk = [S(pq(k,1),2) S(pq(k,2),2)];<\/span>\r\n<span class=\"comment\">%           plot(xk,yk,'LineStyle','--','Marker','o','Color',[0.7 0.7 0.7])<\/span>\r\n<span class=\"comment\">%       end<\/span>\r\n<span class=\"comment\">%       hold off<\/span>\r\n<span class=\"comment\">%       axis equal<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   ALGORITHM NOTES<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   This function uses the \"ANTIPODAL PAIRS\" algorithm, Preparata and<\/span>\r\n<span class=\"comment\">%   Shamos, Computational Geometry: An Introduction, Springer-Verlag, 1985,<\/span>\r\n<span class=\"comment\">%   p. 174.<\/span>\r\n\r\n<span class=\"comment\">%   Steve Eddins<\/span>\r\n\r\n\r\nn = size(S,1);\r\n\r\n<span class=\"keyword\">if<\/span> isequal(S(1,:),S(n,:))\r\n    <span class=\"comment\">% The input polygon is closed. Remove the duplicate vertex from the<\/span>\r\n    <span class=\"comment\">% end.<\/span>\r\n    S(n,:) = [];\r\n    n = n - 1;\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"comment\">% The algorithm assumes the input vertices are in counterclockwise order.<\/span>\r\n<span class=\"comment\">% If the vertices are in clockwise order, reverse the vertices.<\/span>\r\nclockwise = simplePolygonOrientation(S) &lt; 0;\r\n<span class=\"keyword\">if<\/span> clockwise\r\n    S = flipud(S);\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"comment\">% The following variables, including the two anonymous functions, are set<\/span>\r\n<span class=\"comment\">% up to follow the notation in the pseudocode on page 174 of Preparata and<\/span>\r\n<span class=\"comment\">% Shamos. p and q are indices (1-based) that identify vertices of S. p0 and<\/span>\r\n<span class=\"comment\">% q0 identify starting vertices for the algorithm. area(i,j,k) is the area<\/span>\r\n<span class=\"comment\">% of the triangle with the corresponding vertices from S: S(i,:), S(j,:),<\/span>\r\n<span class=\"comment\">% and S(k,:). next(p) returns the index of the next vertex of S.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">% The initialization of p0 is missing from the Preparata and Shamos text.<\/span>\r\narea = @(i,j,k) signedTriangleArea(S(i,:),S(j,:),S(k,:));\r\nnext = @(i) mod(i,n) + 1; <span class=\"comment\">% mod((i-1) + 1,n) + 1<\/span>\r\np = n;\r\np0 = next(p);\r\nq = next(p);\r\n\r\n<span class=\"comment\">% The list of antipodal vertices will be built up in the vectors pp and qq.<\/span>\r\npp = zeros(0,1);\r\nqq = zeros(0,1);\r\n\r\n<span class=\"comment\">% ANTIPODAL PAIRS step 3.<\/span>\r\n<span class=\"keyword\">while<\/span> (area(p,next(p),next(q)) &gt; area(p,next(p),q))\r\n    q = next(q);\r\n<span class=\"keyword\">end<\/span>\r\nq0 = q;    <span class=\"comment\">% Step 4.<\/span>\r\n\r\n<span class=\"keyword\">while<\/span> (q ~= p0)    <span class=\"comment\">% Step 5.<\/span>\r\n    p = next(p);   <span class=\"comment\">% Step 6.<\/span>\r\n    <span class=\"comment\">% Step 7. (p,q) is an antipodal pair.<\/span>\r\n    pp = [pp ; p];\r\n    qq = [qq ; q];\r\n\r\n    <span class=\"comment\">% Step 8.<\/span>\r\n    <span class=\"keyword\">while<\/span> (area(p,next(p),next(q)) &gt; area(p,next(p),q))\r\n        q = next(q);    <span class=\"comment\">% Step 9.<\/span>\r\n        <span class=\"keyword\">if<\/span> ~isequal([p q],[q0,p0])\r\n            <span class=\"comment\">% Step 10.<\/span>\r\n            pp = [pp ; p];\r\n            qq = [qq ; q];\r\n        <span class=\"keyword\">else<\/span>\r\n            <span class=\"comment\">% This loop break is omitted from the Preparata and Shamos<\/span>\r\n            <span class=\"comment\">% text.<\/span>\r\n            <span class=\"keyword\">break<\/span>\r\n        <span class=\"keyword\">end<\/span>\r\n    <span class=\"keyword\">end<\/span>\r\n\r\n    <span class=\"comment\">% Step 11. Check for parallel edges.<\/span>\r\n    <span class=\"keyword\">if<\/span> (area(p,next(p),next(q)) == area(p,next(p),q))\r\n        <span class=\"keyword\">if<\/span> ~isequal([p q],[q0 n])\r\n            <span class=\"comment\">% Step 12. (p,next(q)) is an antipodal pair.<\/span>\r\n            pp = [pp ; p];\r\n            qq = [qq ; next(q)];\r\n        <span class=\"keyword\">else<\/span>\r\n            <span class=\"comment\">% This loop break is omitted from the Preparata and Shamos<\/span>\r\n            <span class=\"comment\">% text.<\/span>\r\n            <span class=\"keyword\">break<\/span>\r\n        <span class=\"keyword\">end<\/span>\r\n    <span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">if<\/span> clockwise\r\n    <span class=\"comment\">% Compensate for the flipping of the polygon vertices.<\/span>\r\n    pp = n + 1 - pp;\r\n    qq = n + 1 - qq;\r\n<span class=\"keyword\">end<\/span>\r\n\r\npq = [pp qq];\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> s = vertexOrientation(P0,P1,P2)\r\n<span class=\"comment\">% vertexOrientation  Orientation of a vertex with respect to line segment.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   s = vertexOrientation(P0,P1,P2) returns a positive number if P2 is to<\/span>\r\n<span class=\"comment\">%   the left of the line through P0 to P1. It returns 0 if P2 is on the<\/span>\r\n<span class=\"comment\">%   line. It returns a negative number if P2 is to the right of the line.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   Stating it another way, a positive output corresponds to a<\/span>\r\n<span class=\"comment\">%   counterclockwise traversal from P0 to P1 to P2.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   P0, P1, and P2 are two-element vectors containing (x,y) coordinates.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   Reference: http:\/\/geomalgorithms.com\/a01-_area.html, function isLeft()<\/span>\r\n\r\n<span class=\"comment\">% Steve Eddins<\/span>\r\n\r\n\r\ns = (P1(1) - P0(1)) * (P2(2) - P0(2)) - <span class=\"keyword\">...<\/span>\r\n    (P2(1) - P0(1)) * (P1(2) - P0(2));\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> s = simplePolygonOrientation(V)\r\n<span class=\"comment\">% simplePolygonOrientation  Determine vertex order for simple polygon.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   s = simplePolygonOrientation(V) returns a positive number if the simple<\/span>\r\n<span class=\"comment\">%   polygon V is counterclockwise. It returns a negative number of the<\/span>\r\n<span class=\"comment\">%   polygon is clockwise. It returns 0 for degenerate cases. V is a Px2<\/span>\r\n<span class=\"comment\">%   matrix of (x,y) vertex coordinates.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   Reference: http:\/\/geomalgorithms.com\/a01-_area.html, function<\/span>\r\n<span class=\"comment\">%   orientation2D_Polygon()<\/span>\r\n\r\n<span class=\"comment\">% Steve Eddins<\/span>\r\n\r\n\r\nn = size(V,1);\r\n\r\n<span class=\"keyword\">if<\/span> n &lt; 3\r\n    s = 0;\r\n    <span class=\"keyword\">return<\/span>\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"comment\">% Find rightmost lowest vertext of the polygon.<\/span>\r\n\r\nx = V(:,1);\r\ny = V(:,2);\r\nymin = min(y,[],1);\r\ny_idx = find(y == ymin);\r\n<span class=\"keyword\">if<\/span> isscalar(y_idx)\r\n    idx = y_idx;\r\n<span class=\"keyword\">else<\/span>\r\n    [~,x_idx] = max(x(y_idx),[],1);\r\n    idx = y_idx(x_idx(1));\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"comment\">% The polygon is counterclockwise if the edge leaving V(idx,:) is left of<\/span>\r\n<span class=\"comment\">% the entering edge.<\/span>\r\n\r\n<span class=\"keyword\">if<\/span> idx == 1\r\n    s = vertexOrientation(V(n,:), V(1,:), V(2,:));\r\n<span class=\"keyword\">elseif<\/span> idx == n\r\n    s = vertexOrientation(V(n-1,:), V(n,:), V(1,:));\r\n<span class=\"keyword\">else<\/span>\r\n    s = vertexOrientation(V(idx-1,:), V(idx,:), V(idx+1,:));\r\n<span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><script language=\"JavaScript\"> <!-- \r\n    function grabCode_ef7ae35817354026aaa3ecaaa1e75cb9() {\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='ef7ae35817354026aaa3ecaaa1e75cb9 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' ef7ae35817354026aaa3ecaaa1e75cb9';\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 2017 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\">Copyright 2017 The MathWorks, Inc.<br><a href=\"javascript:grabCode_ef7ae35817354026aaa3ecaaa1e75cb9()\"><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; R2017b<br><\/p><\/div><!--\r\nef7ae35817354026aaa3ecaaa1e75cb9 ##### SOURCE BEGIN #####\r\n%%\r\n% <https:\/\/blogs.mathworks.com\/steve\/2017\/09\/29\/feret-diameter-introduction\/ \r\n% Last time>, I wrote about finding the maximum Feret diameter for an\r\n% object in a binary image, ending up with this figure:\r\n%\r\n% <<https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_1_04.png>>\r\n%\r\n% I had computed the convex hull of all the pixel corners, and then\r\n% I computed the pairwise distance between every pair of convex hull\r\n% vertices to find the maximum distance.\r\n%\r\n% The procedure would work fine in many cases, but the time required\r\n% to find the maximum distance this way grows with the square of the\r\n% number of convex hull vertices. With modern digital image\r\n% resolutions, it's not hard to imagine having thousands of vertices\r\n% and therefore millions of pairwise distances to compute.\r\n%\r\n% There is a procedure for reducing the number of vertex pairs we\r\n% can consider. It is based on this theorem:\r\n%\r\n% _The diameter of a convex figure is the greatest distance between\r\n% parallel lines of support._  [Theorem 4,18, Preparata and Shamos,\r\n% _Computational Geometry_, 1985]\r\n%\r\n% A _line of support_ for a polygon is a line that contains a vertex\r\n% of the polygon, with the polygon lying entirely on one side of the\r\n% line.\r\n%\r\n% Let me show you a picture using the convex hull points from last\r\n% time.\r\n\r\nhull = [    \r\n    2.5000    5.5000\r\n    3.5000    4.5000\r\n    6.5000    2.5000\r\n    9.5000    1.5000\r\n   10.5000    1.5000\r\n   10.5000    3.5000\r\n    9.5000    5.5000\r\n    5.5000    7.5000\r\n    2.5000    7.5000\r\n    2.5000    5.5000  ];\r\n\r\nplot(hull(:,1),hull(:,2),'r','LineWidth',2)\r\nhold on\r\nplot(hull(:,1),hull(:,2),'r*')\r\nhold off\r\naxis equal\r\naxis ij\r\naxis([0 15 0 10])\r\n\r\nfor theta = -55:5:-35\r\n    drawFullLine(gca,[9.5 5.5],theta,'Color',[.6 .6 .6]);\r\nend\r\n\r\n%%\r\n% The plot above shows five different lines of support drawn through\r\n% the (9.5,5.5) vertex. Now I'll add some of the lines of support\r\n% through the (2.5,7.5) vertex.\r\n\r\nfor theta = 10:10:80\r\n    drawFullLine(gca,[2.5 7.5],theta,'Color',[.6 .6 .6]);\r\nend\r\n\r\n%%\r\n% You can see that there is no line of support for the (2.5,7.5)\r\n% vertex that is parallel to a line of support for the (9.5,5.5)\r\n% vertex. That rules out this pair of vertices for computing the\r\n% maximum Feret diameter.\r\n\r\n%%\r\n% Now I'll draw lines of support for a different pair of vertices.\r\n\r\nplot(hull(:,1),hull(:,2),'r','LineWidth',2)\r\nhold on\r\nplot(hull(:,1),hull(:,2),'r*')\r\nhold off\r\naxis equal\r\naxis ij\r\naxis([0 15 0 10])\r\n\r\ndrawFullLine(gca,[6.5 2.5],-30,'Color',[.6 .6 .6]);\r\ndrawFullLine(gca,[9.5 5.5],-30,'Color',[.6 .6 .6]);\r\n\r\n%%\r\n% Because the vertices (6.5,2.5) and (9.5,5.5) have parallel lines of\r\n% support, they are called _antipodal vertices_. There is an algorithm in\r\n% the Preparata and Shamos book (referenced above) that finds all the\r\n% antipodal vertices for a convex polygon. There's an implementation of the\r\n% algorithm in a function at the bottom of this post. I'll use it to find\r\n% all the antipodal pairs of the convex hull vertices.\r\n\r\npq = antipodalPairs(hull);\r\n\r\n%%\r\n% *Important note*: the algorithm in |antipodalPairs| assumes that its\r\n% input is convex. Further, it assumes that the input does not contain any\r\n% vertices that are on the straight line between the two adjacent vertices.\r\n% To satisfy this condition, compute the convex hull using the call: |k =\r\n% convhull(P,'Simplify',true)|.\r\n\r\n%%\r\n% Now let's plot the line segments joining each antipodal pair.\r\nplot(hull(:,1),hull(:,2),'r','LineWidth',2)\r\nhold on\r\nplot(hull(:,1),hull(:,2),'r*')\r\naxis equal\r\naxis ij\r\naxis([0 15 0 10])\r\n\r\nfor k = 1:size(pq,1)\r\n    x = [hull(pq(k,1),1) hull(pq(k,2),1)];\r\n    y = [hull(pq(k,1),2) hull(pq(k,2),2)];\r\n    plot(x,y,'Color',[.6 .6 .6]);\r\nend\r\nhold off\r\n\r\n%%\r\n% To find the maximum Feret diameter, we only have to check the\r\n% distances of these 10 segments, instead of 10*9 = 90 line\r\n% segments as before.\r\n\r\np1 = hull(pq(:,1),:);\r\np2 = hull(pq(:,2),:);\r\nv = p1 - p2;\r\nd = hypot(v(:,1),v(:,2));\r\n[d_max,idx] = max(d);\r\np1_max = p1(idx,:);\r\np2_max = p2(idx,:);\r\nhold on\r\nplot([p1_max(1) p2_max(1)],[p1_max(2) p2_max(2)],'b','LineWidth',2)\r\nhold off\r\n\r\n%%\r\nd_max\r\n\r\n%%\r\n% And there is the maximum Feret distance.\r\n%\r\n% I am sure that, in many cases, it would be quicker to find the maximum\r\n% distance by brute force comparison of all pairs off convex hull vertices.\r\n% The computation of the antipodal pairs does take some time, after all. I\r\n% have not done a performance study to investigate this question further.\r\n% However, the antipodal pairs computation is useful for another\r\n% measurement: the minimum Feret distance.\r\n%\r\n% I will look at that next time.\r\n\r\n%%\r\n% _End of post_\r\n%\r\n% _Algorithm, visualization, and utility functions used by the\r\n% code above_\r\n%\r\nfunction h = drawFullLine(ax,point,angle_degrees,varargin)\r\n%drawFullLine Draw a line that spans the entire plot\r\n%\r\n%    drawFullLine(ax,point,angle_degrees) draws a line in the\r\n%    specified axes that goes through the specified point at the\r\n%    specified angle (in degrees). The line is drawn to span the\r\n%    entire plot.\r\n%\r\n%    drawFullLine(___,Name,Value) passes name-value parameter pairs\r\n%    to the line function.\r\n\r\n% Steve Eddins\r\n% Copyright 2017 The MathWorks, Inc.\r\n\r\nlimits = axis(ax);\r\nwidth = abs(limits(2) - limits(1));\r\nheight = abs(limits(4) - limits(3));\r\nd = 2*hypot(width,height);\r\nx1 = point(1) - d*cosd(angle_degrees);\r\nx2 = point(1) + d*cosd(angle_degrees);\r\ny1 = point(2) - d*sind(angle_degrees);\r\ny2 = point(2) + d*sind(angle_degrees);\r\nh = line(ax,'XData',[x1 x2],'YData',[y1 y2],varargin{:});\r\nend\r\n\r\nfunction pq = antipodalPairs(S)\r\n% antipodalPairs Antipodal vertex pairs of simple, convex polygon.\r\n%\r\n%   pq = antipodalPairs(S) computes the antipodal vertex pairs of a simple,\r\n%   convex polygon. S is a Px2 matrix of (x,y) vertex coordinates for the\r\n%   polygon. S must be simple and convex without repeated vertices. It is\r\n%   not checked for satisfying these conditions. S can either be closed or\r\n%   not. The output, pq, is an Mx2 matrix representing pairs of vertices in\r\n%   S. The coordinates of the k-th antipodal pair are S(pq(k,1),:) and\r\n%   S(pq(k,2),:).\r\n%\r\n%   TERMINOLOGY\r\n%\r\n%   For a convex polygon, an antipodal pair of vertices is one where you\r\n%   can draw distinct lines of support through each vertex such that the \r\n%   lines of support are parallel.\r\n%\r\n%   A line of support is a line that goes through a polygon vertex such\r\n%   that the interior of the polygon lies entirely on one side of the line.\r\n%\r\n%   EXAMPLE\r\n%\r\n%     Compute antipodal vertices of a polygon and plot the corresponding\r\n%     line segments.\r\n%\r\n%       x = [0 0 1 3 5 4 0];\r\n%       y = [0 1 4 5 4 1 0];\r\n%       S = [x' y'];\r\n%       pq = antipodalPairs(S);\r\n%\r\n%       plot(S(:,1),S(:,2))\r\n%       hold on\r\n%       for k = 1:size(pq,1)\r\n%           xk = [S(pq(k,1),1) S(pq(k,2),1)];\r\n%           yk = [S(pq(k,1),2) S(pq(k,2),2)];\r\n%           plot(xk,yk,'LineStyle','REPLACE_WITH_DASH_DASH','Marker','o','Color',[0.7 0.7 0.7])\r\n%       end\r\n%       hold off\r\n%       axis equal\r\n%\r\n%   ALGORITHM NOTES\r\n%\r\n%   This function uses the \"ANTIPODAL PAIRS\" algorithm, Preparata and\r\n%   Shamos, Computational Geometry: An Introduction, Springer-Verlag, 1985,\r\n%   p. 174.\r\n\r\n%   Steve Eddins\r\n%   Copyright 2017 The MathWorks, Inc.\r\n\r\nn = size(S,1);\r\n\r\nif isequal(S(1,:),S(n,:))\r\n    % The input polygon is closed. Remove the duplicate vertex from the\r\n    % end.\r\n    S(n,:) = [];\r\n    n = n - 1;\r\nend\r\n\r\n% The algorithm assumes the input vertices are in counterclockwise order.\r\n% If the vertices are in clockwise order, reverse the vertices.\r\nclockwise = simplePolygonOrientation(S) < 0;\r\nif clockwise\r\n    S = flipud(S);\r\nend    \r\n\r\n% The following variables, including the two anonymous functions, are set\r\n% up to follow the notation in the pseudocode on page 174 of Preparata and\r\n% Shamos. p and q are indices (1-based) that identify vertices of S. p0 and\r\n% q0 identify starting vertices for the algorithm. area(i,j,k) is the area\r\n% of the triangle with the corresponding vertices from S: S(i,:), S(j,:),\r\n% and S(k,:). next(p) returns the index of the next vertex of S.\r\n%\r\n% The initialization of p0 is missing from the Preparata and Shamos text.\r\narea = @(i,j,k) signedTriangleArea(S(i,:),S(j,:),S(k,:));\r\nnext = @(i) mod(i,n) + 1; % mod((i-1) + 1,n) + 1\r\np = n;\r\np0 = next(p);\r\nq = next(p);\r\n\r\n% The list of antipodal vertices will be built up in the vectors pp and qq.\r\npp = zeros(0,1);\r\nqq = zeros(0,1);\r\n\r\n% ANTIPODAL PAIRS step 3.\r\nwhile (area(p,next(p),next(q)) > area(p,next(p),q))\r\n    q = next(q);\r\nend\r\nq0 = q;    % Step 4.\r\n\r\nwhile (q ~= p0)    % Step 5.\r\n    p = next(p);   % Step 6.\r\n    % Step 7. (p,q) is an antipodal pair.\r\n    pp = [pp ; p];\r\n    qq = [qq ; q];\r\n    \r\n    % Step 8.\r\n    while (area(p,next(p),next(q)) > area(p,next(p),q))\r\n        q = next(q);    % Step 9.\r\n        if ~isequal([p q],[q0,p0])\r\n            % Step 10.\r\n            pp = [pp ; p];\r\n            qq = [qq ; q];\r\n        else\r\n            % This loop break is omitted from the Preparata and Shamos\r\n            % text. \r\n            break\r\n        end\r\n    end\r\n    \r\n    % Step 11. Check for parallel edges.\r\n    if (area(p,next(p),next(q)) == area(p,next(p),q))\r\n        if ~isequal([p q],[q0 n])\r\n            % Step 12. (p,next(q)) is an antipodal pair.\r\n            pp = [pp ; p];\r\n            qq = [qq ; next(q)];\r\n        else\r\n            % This loop break is omitted from the Preparata and Shamos\r\n            % text.\r\n            break\r\n        end\r\n    end\r\nend\r\n\r\nif clockwise\r\n    % Compensate for the flipping of the polygon vertices.\r\n    pp = n + 1 - pp;\r\n    qq = n + 1 - qq;\r\nend\r\n\r\npq = [pp qq];\r\nend\r\n\r\nfunction s = vertexOrientation(P0,P1,P2)\r\n% vertexOrientation  Orientation of a vertex with respect to line segment.\r\n%\r\n%   s = vertexOrientation(P0,P1,P2) returns a positive number if P2 is to\r\n%   the left of the line through P0 to P1. It returns 0 if P2 is on the\r\n%   line. It returns a negative number if P2 is to the right of the line.\r\n%\r\n%   Stating it another way, a positive output corresponds to a\r\n%   counterclockwise traversal from P0 to P1 to P2.\r\n%\r\n%   P0, P1, and P2 are two-element vectors containing (x,y) coordinates.\r\n%\r\n%   Reference: http:\/\/geomalgorithms.com\/a01-_area.html, function isLeft()\r\n\r\n% Steve Eddins\r\n% Copyright 2017 The MathWorks, Inc.\r\n\r\ns = (P1(1) - P0(1)) * (P2(2) - P0(2)) - ...\r\n    (P2(1) - P0(1)) * (P1(2) - P0(2));\r\nend\r\n\r\nfunction s = simplePolygonOrientation(V)\r\n% simplePolygonOrientation  Determine vertex order for simple polygon.\r\n%\r\n%   s = simplePolygonOrientation(V) returns a positive number if the simple\r\n%   polygon V is counterclockwise. It returns a negative number of the\r\n%   polygon is clockwise. It returns 0 for degenerate cases. V is a Px2\r\n%   matrix of (x,y) vertex coordinates.\r\n%\r\n%   Reference: http:\/\/geomalgorithms.com\/a01-_area.html, function\r\n%   orientation2D_Polygon()\r\n\r\n% Steve Eddins\r\n% Copyright 2017 The MathWorks, Inc.\r\n\r\nn = size(V,1);\r\n\r\nif n < 3\r\n    s = 0;\r\n    return\r\nend\r\n\r\n% Find rightmost lowest vertext of the polygon.\r\n\r\nx = V(:,1);\r\ny = V(:,2);\r\nymin = min(y,[],1);\r\ny_idx = find(y == ymin);\r\nif isscalar(y_idx)\r\n    idx = y_idx;\r\nelse\r\n    [~,x_idx] = max(x(y_idx),[],1);\r\n    idx = y_idx(x_idx(1));\r\nend\r\n\r\n% The polygon is counterclockwise if the edge leaving V(idx,:) is left of\r\n% the entering edge.\r\n\r\nif idx == 1\r\n    s = vertexOrientation(V(n,:), V(1,:), V(2,:));\r\nelseif idx == n\r\n    s = vertexOrientation(V(n-1,:), V(n,:), V(1,:));\r\nelse\r\n    s = vertexOrientation(V(idx-1,:), V(idx,:), V(idx+1,:));\r\nend\r\nend\r\n\r\n##### SOURCE END ##### ef7ae35817354026aaa3ecaaa1e75cb9\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/feret_diameter_2_02.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>Last time, I wrote about finding the maximum Feret diameter for an object in a binary image, ending up with this figure: I had computed the convex hull of all the pixel corners, and then I computed... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2017\/10\/24\/feret-diameters-and-antipodal-vertices\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":2716,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[208,50,1207,348,745,80,90,334,346,581,747,122,380,388,68,190,130],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2713"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/comments?post=2713"}],"version-history":[{"count":3,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2713\/revisions"}],"predecessor-version":[{"id":2721,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2713\/revisions\/2721"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/2716"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=2713"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=2713"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=2713"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}