{"id":11201,"date":"2019-12-13T09:00:25","date_gmt":"2019-12-13T14:00:25","guid":{"rendered":"https:\/\/blogs.mathworks.com\/pick\/?p=11201"},"modified":"2020-06-25T09:23:58","modified_gmt":"2020-06-25T13:23:58","slug":"minimally-bounded-rectangles-and-more","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/pick\/2019\/12\/13\/minimally-bounded-rectangles-and-more\/","title":{"rendered":"Minimally Bounded Rectangles, and more!"},"content":{"rendered":"<div class=\"content\"><!--introduction--><\/p>\n<p><a href=\"http:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/authors\/911\">Brett<\/a>&#8216;s Pick this week is <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/34767-a-suite-of-minimal-bounding-objects\"><tt>A suite of minimal bounding objects<\/tt><\/a>, by <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/869215-john-d-errico\">John D&#8217;Errico<\/a>. I also give a shout-out to <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/1978578-julien-diener\">julien diener<\/a>, for his <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/31126-2d-minimal-bounding-box?s_tid=prof_contriblnk\">2D minimal bounding box<\/a> function.<\/p>\n<p><!--\/introduction--><\/p>\n<p>During the course of developing some code to automatically track objects in videos, I found that I needed to create a &#8220;minimal bounding rectangle&#8221; to enclose detected points. There is a <a href=\"https:\/\/www.mathworks.com\/help\/vision\/examples\/face-detection-and-tracking-using-the-klt-algorithm.html\">very nice demo<\/a> in the Computer Vision Toolbox in which points used to track a moving face are delineated by a <i>polygon<\/i> created by the <a href=\"https:\/\/www.mathworks.com\/help\/matlab\/ref\/polyshape.html\"><tt>polyshape<\/tt><\/a> command. All well and good; it works nicely, and is very fast. But for other reasons, I preferred to work with an &#8216;images.roi.Rectangle&#8217; object, as created by the <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/drawrectangle.html\"><tt>drawrectangle<\/tt><\/a> command (which is itself a convenience function for <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/images.roi.rectangle.html\"><tt>rectangle()<\/tt><\/a>.)<\/p>\n<p>A modified version of the aforementioned example looks like this:<\/p>\n<pre class=\"codeinput\">vidObj = VideoReader(<span class=\"string\">'tilted_face.avi'<\/span>);\r\nvideoFrame = readFrame(vidObj);\r\ntogglefig(<span class=\"string\">'Face Tracker'<\/span>)\r\nimgH = imshow(videoFrame);\r\nfaceDetector = vision.CascadeObjectDetector();\r\nbbox = faceDetector(videoFrame);\r\nbboxPoints = bbox2points(bbox(1, :));\r\nthisPoly = polyshape(bboxPoints);\r\nhold <span class=\"string\">on<\/span>\r\npolyH = plot(thisPoly, <span class=\"string\">'FaceAlpha'<\/span>, 0, <span class=\"string\">'EdgeColor'<\/span>, <span class=\"string\">'y'<\/span>, <span class=\"string\">'LineWidth'<\/span>, 2);\r\npoints = detectMinEigenFeatures(rgb2gray(videoFrame), <span class=\"string\">'ROI'<\/span>, bbox);\r\npointTracker = vision.PointTracker(<span class=\"string\">'MaxBidirectionalError'<\/span>, 2);\r\npoints = points.Location;\r\ninitialize(pointTracker, points, videoFrame);\r\noldPoints = points;\r\ntic;\r\n<span class=\"keyword\">while<\/span> hasFrame(vidObj)\r\n    <span class=\"comment\">% Get the next frame, update the visualization:<\/span>\r\n    videoFrame = readFrame(vidObj);\r\n    imgH.CData = videoFrame;\r\n    <span class=\"comment\">% Track the points. Note that some points may be lost.<\/span>\r\n    [points, isFound] = pointTracker(videoFrame);\r\n    visiblePoints = points(isFound, :);\r\n    oldInliers = oldPoints(isFound, :);\r\n    <span class=\"keyword\">if<\/span> size(visiblePoints, 1) &gt;= 2 <span class=\"comment\">% need at least 2 points<\/span>\r\n        [xform, oldInliers, visiblePoints] = estimateGeometricTransform(<span class=\"keyword\">...<\/span>\r\n            oldInliers, visiblePoints, <span class=\"string\">'similarity'<\/span>, <span class=\"string\">'MaxDistance'<\/span>, 4);\r\n        bboxPoints = transformPointsForward(xform, bboxPoints);\r\n        thisPoly = polyshape(bboxPoints);\r\n        polyH.Shape = thisPoly;\r\n        oldPoints = visiblePoints;\r\n        setPoints(pointTracker, oldPoints);\r\n    <span class=\"keyword\">end<\/span>\r\n    imgH.CData = videoFrame;\r\n    drawnow\r\n<span class=\"keyword\">end<\/span>\r\nt = toc;\r\n<span class=\"comment\">% Clean up<\/span>\r\nrelease(pointTracker);\r\n\r\n<span class=\"comment\">% (Processing the while loop takes about 6.5 seconds on my laptop.)<\/span>\r\n <p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/FaceTracking.png\" alt=\"\"> <\/p><p>But how would one replace the polygon with a rotated rectangle? Fortunately for us, there's the File Exchange, where a search for \"minimal bounding rectangle\" quickly turned up John's \"suite\" of functions for minimally bounding circles, spheres, parallelograms, quadrilaterals, semicircles, triangles, and rectangles.<\/p><p>As we've come to expect, John's functions are well written and commented. It was a simple matter to replace the polygon with a minimally bounded rectangle, which not surprisingly computes the convex hull of the input points. One note: John's function returned the <i>x-<\/i> and <i>y-<\/i> coordinates of the target rectangle, and the area and perimeter of the same:<\/p><pre class=\"language-matlab\">[rectx,recty,area,perimeter] = minboundrect(x,y,metric)\r\n<\/pre>\n<p>(In fact, one can elect, using the 'metric' parameter, to minimize by area or perimeter, with area being the default.) I needed the <i>rotation angle<\/i> of the triangle, to use as an input in <tt>drawrectangle<\/tt>. It took a moment to modify <tt>minboundrect<\/tt> to return as an additional output the <i>edgeangles<\/i> parameter, which is computed internally. In moments, we had:<\/p>\n<pre class=\"codeinput\">vidObj.CurrentTime = 0; <span class=\"comment\">% Reset and reuse the VideoReader object<\/span>\r\nvideoFrame = readFrame(vidObj);\r\n<span class=\"comment\">%togglefig('Face Tracker', true) %Clear and reuse figure<\/span>\r\n<span class=\"comment\">%imgH = imshow(videoFrame);<\/span>\r\nbbox = faceDetector(videoFrame);\r\n<span class=\"comment\">% hRect = drawrectangle('Position', bbox, ...<\/span>\r\n<span class=\"comment\">%     'FaceAlpha', 0, 'Color', 'y', 'LineWidth', 2);<\/span>\r\nbboxPoints = bbox2points(bbox(1, :));\r\npoints = detectMinEigenFeatures(rgb2gray(videoFrame), <span class=\"string\">'ROI'<\/span>, bbox);\r\npointTracker = vision.PointTracker(<span class=\"string\">'MaxBidirectionalError'<\/span>, 2);\r\npoints = points.Location;\r\ninitialize(pointTracker, points, videoFrame);\r\noldPoints = points;\r\ntic;\r\n<span class=\"keyword\">while<\/span> hasFrame(vidObj)\r\n    <span class=\"comment\">% Get the next frame, update the visualization:<\/span>\r\n    videoFrame = readFrame(vidObj);\r\n<span class=\"comment\">%     imgH.CData = videoFrame;<\/span>\r\n    <span class=\"comment\">% Track the points. Note that some points may be lost.<\/span>\r\n    [points, isFound] = pointTracker(videoFrame);\r\n    visiblePoints = points(isFound, :);\r\n    oldInliers = oldPoints(isFound, :);\r\n    <span class=\"keyword\">if<\/span> size(visiblePoints, 1) &gt;= 2 <span class=\"comment\">% need at least 2 points<\/span>\r\n        [xform, oldInliers, visiblePoints] = estimateGeometricTransform(<span class=\"keyword\">...<\/span>\r\n            oldInliers, visiblePoints, <span class=\"string\">'similarity'<\/span>, <span class=\"string\">'MaxDistance'<\/span>, 4);\r\n        bboxPoints = double(transformPointsForward(xform, bboxPoints));\r\n        <span class=\"comment\">% |vertToPos| is a helper function I wrote; it is the inverter for<\/span>\r\n        <span class=\"comment\">% &lt;https:\/\/www.mathworks.com\/help\/vision\/ref\/bbox2points.html |bbox2points|&gt;.<\/span>\r\n        test = <span class=\"string\">'john'<\/span>;\r\n        <span class=\"keyword\">switch<\/span> test\r\n            <span class=\"keyword\">case<\/span> <span class=\"string\">'john'<\/span>\r\n                [rectx, recty, theta] = <span class=\"keyword\">...<\/span>\r\n                    minboundrect(bboxPoints(:,1), bboxPoints(:,2));\r\n                rectPos = vertToPos([rectx, recty]);\r\n            <span class=\"keyword\">case<\/span> <span class=\"string\">'julien'<\/span>\r\n                [rect, theta] = minBoundingBox(bboxPoints');\r\n                rectPos = vertToPos(rect');\r\n        <span class=\"keyword\">end<\/span>\r\n        theta = 360-rad2deg(theta(1));\r\n        hRect.Position = rectPos;\r\n        hRect.RotationAngle = theta;\r\n        oldPoints = visiblePoints;\r\n        setPoints(pointTracker, oldPoints);\r\n    <span class=\"keyword\">end<\/span>\r\n<span class=\"comment\">%     imgH.CData = videoFrame;<\/span>\r\n    drawnow\r\n<span class=\"keyword\">end<\/span>\r\nt = toc;\r\n<span class=\"comment\">% Clean up<\/span>\r\nrelease(pointTracker);\r\n<\/pre>\n<p>Looping over all the video frames took 8.3 seconds; <tt>minboundrect<\/tt> added about 4 msec per frame overhead. (Not bad!).<\/p>\n<p>As a side note, I also found my way to (and tested--see the switch-case in the code above) julien's '2D minimal bounding box' contribution. julien (who apparently eschews capitalization) wrote that his code is \"fully vectorized,\" and thus \"is better for big set of points.\" I didn't test the difference on a large dataset, but for my use case, I could detect no performance difference. Very nice, gentlemen--thank you both!<\/p>\n<p>As always, I welcome your <a href=\"http:\/\/blogs.mathworks.com\/pick\/?p=11201#respond\">thoughts and comments<\/a>.<\/p>\n<p><script language=\"JavaScript\"> <!-- \n    function grabCode_36c6418b22fc4315ade02fb81935013d() {\n        \/\/ Remember the title so we can use it in the new page\n        title = document.title;\n\n        \/\/ Break up these strings so that their presence\n        \/\/ in the Javascript doesn't mess up the search for\n        \/\/ the MATLAB code.\n        t1='36c6418b22fc4315ade02fb81935013d ' + '##### ' + 'SOURCE BEGIN' + ' #####';\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 36c6418b22fc4315ade02fb81935013d';\n    \n        b=document.getElementsByTagName('body')[0];\n        i1=b.innerHTML.indexOf(t1)+t1.length;\n        i2=b.innerHTML.indexOf(t2);\n \n        code_string = b.innerHTML.substring(i1, i2);\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\n\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \n        \/\/ in the XML parser.\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\n        \/\/ doesn't go ahead and substitute the less-than character. \n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\n\n        copyright = 'Copyright 2020 The MathWorks, Inc.';\n\n        w = window.open();\n        d = w.document;\n        d.write('\n\n<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\n\\n');\n\n        d.title = title + ' (MATLAB code)';\n        d.close();\n    }   \n     --> <\/script><\/p>\n<p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><a href=\"javascript:grabCode_36c6418b22fc4315ade02fb81935013d()\"><span style=\"font-size: x-small;        font-style: italic;\">Get<br \/>\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><\/p>\n<p>      Published with MATLAB&reg; R2019b<\/p>\n<\/div>\n<p><!--\n36c6418b22fc4315ade02fb81935013d ##### SOURCE BEGIN #####\n%% Minimally Bounded Rectangles, and more!\n%\n% <http:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/authors\/911 Brett>'s\n% Pick this week is\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/34767-a-suite-of-minimal-bounding-objects |A suite of minimal bounding objects|>,\n% by\n% <https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/869215-john-d-errico\n% John D'Errico>. I also give a shout-out to\n% <https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/1978578-julien-diener\n% julien diener>, for his\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/31126-2d-minimal-bounding-box?s_tid=prof_contriblnk\n% 2D minimal bounding box> function.\n\n%%\n% During the course of developing some code to automatically track objects\n% in videos, I found that I needed to create a \"minimal bounding rectangle\"\n% to enclose detected points. There is a <https:\/\/www.mathworks.com\/help\/vision\/examples\/face-detection-and-tracking-using-the-klt-algorithm.html very nice demo> in the Computer\n% Vision Toolbox in which points used to track a moving face are delineated\n% by a _polygon_ created by the <https:\/\/www.mathworks.com\/help\/matlab\/ref\/polyshape.html |polyshape|> command. All well and good; it works nicely, and is very fast. But for other reasons, I preferred to\n% work with an 'images.roi.Rectangle' object, as created by the\n% <https:\/\/www.mathworks.com\/help\/images\/ref\/drawrectangle.html |drawrectangle|> command (which is itself a convenience function for\n% <https:\/\/www.mathworks.com\/help\/images\/ref\/images.roi.rectangle.html |rectangle()|>.)\n%\n% A modified version of the aforementioned example looks like this:\n\n%%\nvidObj = VideoReader('tilted_face.avi');\nvideoFrame = readFrame(vidObj);\ntogglefig('Face Tracker')\nimgH = imshow(videoFrame);\nfaceDetector = vision.CascadeObjectDetector();\nbbox = faceDetector(videoFrame);\nbboxPoints = bbox2points(bbox(1, :));\nthisPoly = polyshape(bboxPoints);\nhold on\npolyH = plot(thisPoly, 'FaceAlpha', 0, 'EdgeColor', 'y', 'LineWidth', 2);\npoints = detectMinEigenFeatures(rgb2gray(videoFrame), 'ROI', bbox);\npointTracker = vision.PointTracker('MaxBidirectionalError', 2);\npoints = points.Location;\ninitialize(pointTracker, points, videoFrame);\noldPoints = points;\ntic;\nwhile hasFrame(vidObj)\n    % Get the next frame, update the visualization:\n    videoFrame = readFrame(vidObj);\n    imgH.CData = videoFrame;\n    % Track the points. Note that some points may be lost.\n    [points, isFound] = pointTracker(videoFrame);\n    visiblePoints = points(isFound, :);\n    oldInliers = oldPoints(isFound, :);\n    if size(visiblePoints, 1) >= 2 % need at least 2 points\n        [xform, oldInliers, visiblePoints] = estimateGeometricTransform(...\n            oldInliers, visiblePoints, 'similarity', 'MaxDistance', 4);\n        bboxPoints = transformPointsForward(xform, bboxPoints);\n        thisPoly = polyshape(bboxPoints);\n        polyH.Shape = thisPoly;\n        oldPoints = visiblePoints;\n        setPoints(pointTracker, oldPoints);\n    end\n    imgH.CData = videoFrame;\n    drawnow\nend\nt = toc;\n% Clean up\nrelease(pointTracker);\n\n% (Processing the while loop takes about 6.5 seconds on my laptop.)\n%%\n% \n% <<https:\/\/blogs.mathworks.com\/pick\/files\/FaceTracking.png>>\n% \n% But how would one replace the polygon with a rotated rectangle?\n% Fortunately for us, there's the File Exchange, where a search for\n% \"minimal bounding rectangle\" quickly turned up John's \"suite\" of\n% functions for minimally bounding circles, spheres, parallelograms,\n% quadrilaterals, semicircles, triangles, and rectangles.\n%\n% As we've come to expect, John's functions are well written and commented.\n% It was a simple matter to replace the polygon with a minimally bounded\n% rectangle, which not surprisingly computes the convex hull of the input\n% points. One note: John's function returned the _x-_ and _y-_ coordinates\n% of the target rectangle, and the area and perimeter of the same:\n\n%%\n% \n%   [rectx,recty,area,perimeter] = minboundrect(x,y,metric)\n% \n\n%%\n% (In fact, one can elect, using the 'metric' parameter, to minimize by\n% area or perimeter, with area being the default.) I needed the _rotation angle_ of\n% the triangle, to use as an input in |drawrectangle|. It took a moment to\n% modify |minboundrect| to return as an additional output the\n% _edgeangles_ parameter, which is computed internally. In moments, we had:\n\n%%\nvidObj.CurrentTime = 0; % Reset and reuse the VideoReader object\nvideoFrame = readFrame(vidObj);\n%togglefig('Face Tracker', true) %Clear and reuse figure\n%imgH = imshow(videoFrame);\nbbox = faceDetector(videoFrame);\n% hRect = drawrectangle('Position', bbox, ...\n%     'FaceAlpha', 0, 'Color', 'y', 'LineWidth', 2);\nbboxPoints = bbox2points(bbox(1, :));\npoints = detectMinEigenFeatures(rgb2gray(videoFrame), 'ROI', bbox);\npointTracker = vision.PointTracker('MaxBidirectionalError', 2);\npoints = points.Location;\ninitialize(pointTracker, points, videoFrame);\noldPoints = points;\ntic;\nwhile hasFrame(vidObj)\n    % Get the next frame, update the visualization:\n    videoFrame = readFrame(vidObj);\n%     imgH.CData = videoFrame;\n    % Track the points. Note that some points may be lost.\n    [points, isFound] = pointTracker(videoFrame);\n    visiblePoints = points(isFound, :);\n    oldInliers = oldPoints(isFound, :);\n    if size(visiblePoints, 1) >= 2 % need at least 2 points\n        [xform, oldInliers, visiblePoints] = estimateGeometricTransform(...\n            oldInliers, visiblePoints, 'similarity', 'MaxDistance', 4);\n        bboxPoints = double(transformPointsForward(xform, bboxPoints));\n        % |vertToPos| is a helper function I wrote; it is the inverter for\n        % <https:\/\/www.mathworks.com\/help\/vision\/ref\/bbox2points.html |bbox2points|>.\n        test = 'john';\n        switch test\n            case 'john'\n                [rectx, recty, theta] = ...\n                    minboundrect(bboxPoints(:,1), bboxPoints(:,2));\n                rectPos = vertToPos([rectx, recty]);\n            case 'julien'\n                [rect, theta] = minBoundingBox(bboxPoints');\n                rectPos = vertToPos(rect');\n        end\n        theta = 360-rad2deg(theta(1));\n        hRect.Position = rectPos;\n        hRect.RotationAngle = theta;\n        oldPoints = visiblePoints;\n        setPoints(pointTracker, oldPoints);\n    end\n%     imgH.CData = videoFrame;\n    drawnow\nend\nt = toc;\n% Clean up\nrelease(pointTracker);\n\n%%\n% Looping over all the video frames took 8.3 seconds; |minboundrect| added\n% about 4 msec per frame overhead. (Not bad!).\n% \n% As a side note, I also found my way to (and testedREPLACE_WITH_DASH_DASHsee the switch-case\n% in the code above) julien's '2D minimal bounding box' contribution.\n% julien (who apparently eschews capitalization) wrote that his code is \"fully vectorized,\" and thus \"is better for\n% big set of points.\" I didn't test the difference on a large dataset, but\n% for my use case, I could detect no performance difference. Very nice, gentlemenREPLACE_WITH_DASH_DASHthank you both!\n%%\n% As always, I welcome your\n% <http:\/\/blogs.mathworks.com\/pick\/?p=11201#respond thoughts and comments>.\n##### SOURCE END ##### 36c6418b22fc4315ade02fb81935013d\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img decoding=\"async\"  class=\"img-responsive\" src=\"https:\/\/blogs.mathworks.com\/pick\/files\/FaceTracking.png\" onError=\"this.style.display ='none';\" \/><\/div>\n<p>\nBrett&#8216;s Pick this week is A suite of minimal bounding objects, by John D&#8217;Errico. I also give a shout-out to julien diener, for his 2D minimal bounding box function.<\/p>\n<p>During the course of&#8230; <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/pick\/2019\/12\/13\/minimally-bounded-rectangles-and-more\/\">read more >><\/a><\/p>\n","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\/11201"}],"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=11201"}],"version-history":[{"count":7,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/11201\/revisions"}],"predecessor-version":[{"id":11407,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/posts\/11201\/revisions\/11407"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/media?parent=11201"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/categories?post=11201"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/pick\/wp-json\/wp\/v2\/tags?post=11201"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}