{"id":997,"date":"2014-03-27T15:55:06","date_gmt":"2014-03-27T19:55:06","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=997"},"modified":"2019-11-01T11:01:34","modified_gmt":"2019-11-01T15:01:34","slug":"comparing-the-geometries-of-bwboundaries-and-poly2mask","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2014\/03\/27\/comparing-the-geometries-of-bwboundaries-and-poly2mask\/","title":{"rendered":"Comparing the geometries of bwboundaries and poly2mask"},"content":{"rendered":"\r\n<div class=\"content\"><p>MATLAB user <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/answers\/contributors\/178590-meshooo\">Meshooo<\/a> asked a <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/answers\/122981-serious-problem-with-createmask-function\">question on MATLAB Answers<\/a> about a problem with the <tt>createMask<\/tt> function associated with <tt>impoly<\/tt>. Meshooo observed a discrepancy between the output of <tt>bwboundaries<\/tt> and the mask created by <tt>createMask<\/tt>.<\/p><p>I want to describe the issue in more general terms here as a conflict between the geometry of the <tt>bwboundaries<\/tt> function and the geometry of the <tt>poly2mask<\/tt> function (which is used by <tt>createMask<\/tt>).<\/p><p>Here's a simple example that illustrates the discrepancy. Start by creating a small binary image.<\/p><pre class=\"codeinput\">BW = [ <span class=\"keyword\">...<\/span>\r\n    0 0 0 0 0 0 0 0 0\r\n    0 0 0 0 0 0 0 0 0\r\n    0 0 0 0 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 0 0 0 0 0\r\n    0 0 0 0 0 0 0 0 0\r\n    0 0 0 0 0 0 0 0 0 ];\r\n<\/pre><p>Call <tt>bwboundaries<\/tt>, which traces the perimeter pixels of all the objects (and holes) in the image.<\/p><pre class=\"codeinput\">B = bwboundaries(BW);\r\n<\/pre><p>There's only one boundary in this case. Extract and plot it.<\/p><pre class=\"codeinput\">B = B{1};\r\nBx = B(:,2);\r\nBy = B(:,1);\r\nplot(Bx,By)\r\naxis <span class=\"string\">ij<\/span>\r\naxis <span class=\"string\">equal<\/span>\r\naxis([.5 9.5 .5 10.5])\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2014\/bwboundaries_by_3_01.png\" alt=\"\"> <p>If we now pass <tt>Bx<\/tt> and <tt>By<\/tt> to <tt>poly2mask<\/tt>, we don't get exactly the same binary mask image that we started with.<\/p><pre class=\"codeinput\">BW2 = poly2mask(Bx,By,10,9)\r\n<\/pre><pre class=\"codeoutput\">\r\nBW2 =\r\n\r\n     0     0     0     0     0     0     0     0     0\r\n     0     0     0     0     0     0     0     0     0\r\n     0     0     0     0     0     0     0     0     0\r\n     0     0     0     0     1     1     1     0     0\r\n     0     0     0     1     1     1     1     0     0\r\n     0     0     0     1     1     1     1     0     0\r\n     0     0     0     1     1     1     1     0     0\r\n     0     0     0     1     0     0     0     0     0\r\n     0     0     0     0     0     0     0     0     0\r\n     0     0     0     0     0     0     0     0     0\r\n\r\n<\/pre><p>To understand the reason for the discrepancy, it helps to understand that <tt>bwboundaries<\/tt> treats the foreground pixels of the input image as points in space. Here's a plot to illustrate:<\/p><pre class=\"codeinput\">plot(Bx,By)\r\naxis <span class=\"string\">ij<\/span>\r\naxis <span class=\"string\">equal<\/span>\r\naxis([.5 9.5 .5 10.5])\r\nhold <span class=\"string\">on<\/span>\r\n[yy,xx] = find(BW);\r\nplot(xx,yy,<span class=\"string\">'*'<\/span>)\r\nhold <span class=\"string\">off<\/span>\r\nlegend(<span class=\"string\">'Boundary polygon'<\/span>,<span class=\"string\">'Foreground pixels'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2014\/bwboundaries_by_3_02.png\" alt=\"\"> <p>Now let's do a plot that shows the pixels as squares of unit area. I'll include some code to overlay the pixel edges as gray lines.<\/p><pre class=\"codeinput\">imshow(BW,<span class=\"string\">'InitialMagnification'<\/span>,<span class=\"string\">'fit'<\/span>)\r\nhold <span class=\"string\">on<\/span>\r\n\r\nx = [.5 9.5];\r\n<span class=\"keyword\">for<\/span> k = .5:10.5\r\n    y = [k k];\r\n    plot(x,y,<span class=\"string\">'Color'<\/span>,[.7 .7 .7]);\r\n<span class=\"keyword\">end<\/span>\r\n\r\ny = [.5 10.5];\r\n<span class=\"keyword\">for<\/span> k = .5:9.5\r\n    x = [k k];\r\n    plot(x,y,<span class=\"string\">'Color'<\/span>,[.7 .7 .7]);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nplot(Bx,By,<span class=\"string\">'r'<\/span>)\r\n\r\nplot(xx,yy,<span class=\"string\">'*'<\/span>)\r\nhold <span class=\"string\">off<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2014\/bwboundaries_by_3_03.png\" alt=\"\"> <p>Now you can see that the polygon produced by <tt>bwboundaries<\/tt> does not completely contain the pixels along the border of the object. In fact, most of those pixels are only half inside the polygon (or less).<\/p><p>That's the clue needed to explain the discrepancy with <tt>poly2mask<\/tt>. That function treats images pixels not as points, but as squares having unit area. Its algorithm is carefully designed to treat partially covered pixels in a geometrically consistent way. I wrote several blog posts (POLY2MASK and ROIPOLY <a href=\"https:\/\/blogs.mathworks.com\/steve\/2006\/12\/05\/poly2mask-and-roipoly-part-1\/\">Part 1<\/a>, <a href=\"https:\/\/blogs.mathworks.com\/steve\/2006\/12\/13\/poly2mask-and-roipoly-part-2\/\">Part 2<\/a>, and <a href=\"https:\/\/blogs.mathworks.com\/steve\/2006\/12\/22\/poly2mask-and-roipoly-part-3\/\">Part 3<\/a>) about this back in 2006. Here's a diagram from Part 3 that illustrates a bit of the algorithm for handling partially covered pixels.<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/108\/roipoly3_05.png\" alt=\"\"> <\/p><p>It turns out that there is a way to get boundary polygons from <tt>bwboundaries<\/tt> that are consistent with the <tt>poly2mask<\/tt> geometry. The idea is to upsample the binary image so that the polygon produced by <tt>bwboundaries<\/tt> is outside the pixel centers instead of running directly through the centers.<\/p><pre class=\"codeinput\">BW3 = imresize(BW,3,<span class=\"string\">'nearest'<\/span>);\r\nB3 = bwboundaries(BW3);\r\nB3 = B3{1};\r\n<\/pre><p>Now shift and scale the polygon coordinates back into the coordinate system of the original image.<\/p><pre class=\"codeinput\">Bx = (B3(:,2) + 1)\/3;\r\nBy = (B3(:,1) + 1)\/3;\r\n\r\nimshow(BW,<span class=\"string\">'InitialMagnification'<\/span>,<span class=\"string\">'fit'<\/span>)\r\nhold <span class=\"string\">on<\/span>\r\n\r\nx = [.5 9.5];\r\n<span class=\"keyword\">for<\/span> k = .5:10.5\r\n    y = [k k];\r\n    plot(x,y,<span class=\"string\">'Color'<\/span>,[.7 .7 .7]);\r\n<span class=\"keyword\">end<\/span>\r\n\r\ny = [.5 10.5];\r\n<span class=\"keyword\">for<\/span> k = .5:9.5\r\n    x = [k k];\r\n    plot(x,y,<span class=\"string\">'Color'<\/span>,[.7 .7 .7]);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nplot(Bx,By,<span class=\"string\">'r'<\/span>)\r\n\r\nplot(xx,yy,<span class=\"string\">'*'<\/span>)\r\nhold <span class=\"string\">off<\/span>\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2014\/bwboundaries_by_3_04.png\" alt=\"\"> <p>You can see that the pixel centers are now clearly inside the modified polygon (Bx,By). That means that when we try <tt>poly2mask<\/tt> again, we'll get the same mask as the original image.<\/p><pre class=\"codeinput\">BWout = poly2mask(Bx,By,10,9)\r\n<\/pre><pre class=\"codeoutput\">\r\nBWout =\r\n\r\n     0     0     0     0     0     0     0     0     0\r\n     0     0     0     0     0     0     0     0     0\r\n     0     0     0     0     1     1     1     0     0\r\n     0     0     1     1     1     1     1     0     0\r\n     0     0     1     1     1     1     1     0     0\r\n     0     0     1     1     1     1     1     0     0\r\n     0     0     1     1     1     1     1     0     0\r\n     0     0     1     1     0     0     0     0     0\r\n     0     0     0     0     0     0     0     0     0\r\n     0     0     0     0     0     0     0     0     0\r\n\r\n<\/pre><pre class=\"codeinput\">isequal(BW,BWout)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n     1\r\n\r\n<\/pre><p>To everyone in the Northern Hemisphere: Happy Spring!<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_8cefa12d82864408baf3fc2bbbc43200() {\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='8cefa12d82864408baf3fc2bbbc43200 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 8cefa12d82864408baf3fc2bbbc43200';\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 2014 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_8cefa12d82864408baf3fc2bbbc43200()\"><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; R2014a<br><\/p><\/div><!--\r\n8cefa12d82864408baf3fc2bbbc43200 ##### SOURCE BEGIN #####\r\n%%\r\n% MATLAB user\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/answers\/contributors\/178590-meshooo\r\n% Meshooo> asked a\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/answers\/122981-serious-problem-with-createmask-function\r\n% question on MATLAB Answers> about a problem with the |createMask|\r\n% function associated with |impoly|. Meshooo observed a discrepancy between\r\n% the output of |bwboundaries| and the mask created by |createMask|.\r\n%\r\n% I want to describe the issue in more general terms here as a conflict\r\n% between the geometry of the |bwboundaries| function and the geometry of\r\n% the |poly2mask| function (which is used by |createMask|).\r\n%\r\n% Here's a simple example that illustrates the discrepancy. Start by\r\n% creating a small binary image.\r\n\r\nBW = [ ...\r\n    0 0 0 0 0 0 0 0 0\r\n    0 0 0 0 0 0 0 0 0\r\n    0 0 0 0 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 1 1 1 0 0\r\n    0 0 1 1 0 0 0 0 0\r\n    0 0 0 0 0 0 0 0 0\r\n    0 0 0 0 0 0 0 0 0 ];\r\n\r\n%%\r\n% Call |bwboundaries|, which traces the perimeter pixels of all the objects\r\n% (and holes) in the image.\r\n\r\nB = bwboundaries(BW);\r\n\r\n%%\r\n% There's only one boundary in this case. Extract and plot it.\r\nB = B{1};\r\nBx = B(:,2);\r\nBy = B(:,1);\r\nplot(Bx,By)\r\naxis ij \r\naxis equal\r\naxis([.5 9.5 .5 10.5])\r\n\r\n%%\r\n% If we now pass |Bx| and |By| to |poly2mask|, we don't get exactly the\r\n% same binary mask image that we started with.\r\n\r\nBW2 = poly2mask(Bx,By,10,9)\r\n    \r\n%%\r\n% To understand the reason for the discrepancy, it helps to understand that\r\n% |bwboundaries| treats the foreground pixels of the input image as points\r\n% in space. Here's a plot to illustrate:\r\n\r\nplot(Bx,By)\r\naxis ij\r\naxis equal\r\naxis([.5 9.5 .5 10.5])\r\nhold on\r\n[yy,xx] = find(BW);\r\nplot(xx,yy,'*')\r\nhold off\r\nlegend('Boundary polygon','Foreground pixels')\r\n\r\n%%\r\n% Now let's do a plot that shows the pixels as squares of unit area. I'll\r\n% include some code to overlay the pixel edges as gray lines.\r\nimshow(BW,'InitialMagnification','fit')\r\nhold on\r\n\r\nx = [.5 9.5];\r\nfor k = .5:10.5\r\n    y = [k k];\r\n    plot(x,y,'Color',[.7 .7 .7]);\r\nend\r\n\r\ny = [.5 10.5];\r\nfor k = .5:9.5\r\n    x = [k k];\r\n    plot(x,y,'Color',[.7 .7 .7]);\r\nend\r\n\r\nplot(Bx,By,'r')\r\n\r\nplot(xx,yy,'*')\r\nhold off\r\n\r\n%%\r\n% Now you can see that the polygon produced by |bwboundaries| does not\r\n% completely contain the pixels along the border of the object. In fact,\r\n% most of those pixels are only half inside the polygon (or less).\r\n%\r\n% That's the clue needed to explain the discrepancy with |poly2mask|. That\r\n% function treats images pixels not as points, but as squares having unit\r\n% area. Its algorithm is carefully designed to treat partially covered\r\n% pixels in a geometrically consistent way. I wrote several blog posts\r\n% (POLY2MASK and ROIPOLY\r\n% <https:\/\/blogs.mathworks.com\/steve\/2006\/12\/05\/poly2mask-and-roipoly-part-1\/\r\n% Part 1>,\r\n% <https:\/\/blogs.mathworks.com\/steve\/2006\/12\/13\/poly2mask-and-roipoly-part-2\/\r\n% Part 2>, and\r\n% <https:\/\/blogs.mathworks.com\/steve\/2006\/12\/22\/poly2mask-and-roipoly-part-3\/\r\n% Part 3>) about this back in 2006. Here's a diagram from Part 3 that\r\n% illustrates a bit of the algorithm for handling partially covered pixels.\r\n%\r\n% <<https:\/\/blogs.mathworks.com\/images\/steve\/108\/roipoly3_05.png>>\r\n%\r\n% It turns out that there is a way to get boundary polygons from\r\n% |bwboundaries| that are consistent with the |poly2mask| geometry. The\r\n% idea is to upsample the binary image so that the polygon produced by\r\n% |bwboundaries| is outside the pixel centers instead of running directly\r\n% through the centers.\r\n\r\nBW3 = imresize(BW,3,'nearest');\r\nB3 = bwboundaries(BW3);\r\nB3 = B3{1};\r\n\r\n%%\r\n% Now shift and scale the polygon coordinates back into the coordinate\r\n% system of the original image.\r\n\r\nBx = (B3(:,2) + 1)\/3;\r\nBy = (B3(:,1) + 1)\/3;\r\n\r\nimshow(BW,'InitialMagnification','fit')\r\nhold on\r\n\r\nx = [.5 9.5];\r\nfor k = .5:10.5\r\n    y = [k k];\r\n    plot(x,y,'Color',[.7 .7 .7]);\r\nend\r\n\r\ny = [.5 10.5];\r\nfor k = .5:9.5\r\n    x = [k k];\r\n    plot(x,y,'Color',[.7 .7 .7]);\r\nend\r\n\r\nplot(Bx,By,'r')\r\n\r\nplot(xx,yy,'*')\r\nhold off\r\n\r\n%%\r\n% You can see that the pixel centers are now clearly inside the modified\r\n% polygon (Bx,By). That means that when we try |poly2mask| again, we'll get\r\n% the same mask as the original image.\r\n\r\nBWout = poly2mask(Bx,By,10,9)\r\n\r\n%%\r\nisequal(BW,BWout)\r\n\r\n%%\r\n% To everyone in the Northern Hemisphere: Happy Spring!\r\n##### SOURCE END ##### 8cefa12d82864408baf3fc2bbbc43200\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img decoding=\"async\"  class=\"img-responsive\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2014\/bwboundaries_by_3_04.png\" onError=\"this.style.display ='none';\" \/><\/div><p>\r\nMATLAB user Meshooo asked a question on MATLAB Answers about a problem with the createMask function associated with impoly. Meshooo observed a discrepancy between the output of bwboundaries and the... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2014\/03\/27\/comparing-the-geometries-of-bwboundaries-and-poly2mask\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[50,88,348,90,156,36,346,92,68,296],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/997"}],"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=997"}],"version-history":[{"count":7,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/997\/revisions"}],"predecessor-version":[{"id":1004,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/997\/revisions\/1004"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=997"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=997"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=997"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}