{"id":2535,"date":"2017-03-31T14:41:19","date_gmt":"2017-03-31T18:41:19","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=2535"},"modified":"2019-11-01T17:07:01","modified_gmt":"2019-11-01T21:07:01","slug":"variations-on-connected-objects-using-imfill","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2017\/03\/31\/variations-on-connected-objects-using-imfill\/","title":{"rendered":"Variations on Connected Objects &#8211; Using imfill"},"content":{"rendered":"<div class=\"content\"><p>Reader Uri Pe'er found an <a href=\"https:\/\/blogs.mathworks.com\/steve\/2011\/11\/01\/exploring-shortest-paths-part-1\/\">old post from 2011<\/a> about shortest paths in binary images, and he asked this follow-up question (paraphrased):<\/p><p><i>How can I determine if there is a path from one row to another in a binary image? And can I constrain the direction of movement along the path?<\/i><\/p><p>That sounds fun. Let's give it a try.<\/p><p>I can think of several different approaches. Today I'm going to tackle it using <tt>imfill<\/tt>.<\/p><p>Here's a sample image I just drew up.<\/p><pre class=\"codeinput\">bw1 = imread(<span class=\"string\">'https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-1.png'<\/span>);\r\nimshow(bw1)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_01.png\" alt=\"\"> <p>Let's use <tt>imfill<\/tt> to see if there is a path from any of the foreground pixels on row 100 to any of the foreground pixels on row 300. First, find the foreground pixels on those rows.<\/p><pre class=\"codeinput\">fg_cols_row_100 = find(bw1(100,:));\r\nfg_cols_row_300 = find(bw1(300,:));\r\nhold <span class=\"string\">on<\/span>\r\nplot(fg_cols_row_100,100,<span class=\"string\">'b*'<\/span>)\r\nplot(fg_cols_row_300,300,<span class=\"string\">'b*'<\/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\/shortest_path_variation_02.png\" alt=\"\"> <p>Now let's perform an <tt>imfill<\/tt> operation starting from all the foreground pixels on row 100. Because <tt>imfill<\/tt> fills in background pixels, we need to complement the image first.<\/p><pre class=\"codeinput\">bw1a = ~bw1;\r\nimshow(bw1a)\r\ntitle(<span class=\"string\">'Complemented image'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_03.png\" alt=\"\"> <p>Now perform the fill in the complemented image.<\/p><pre class=\"codeinput\">seed_pixels = [100*ones(numel(fg_cols_row_100),1) fg_cols_row_100'];\r\nbw1b = imfill(bw1a,seed_pixels);\r\nimshow(bw1b)\r\ntitle(<span class=\"string\">'Filled image'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_04.png\" alt=\"\"> <p>Which pixels changed as a result of the fill?<\/p><pre class=\"codeinput\">bw1c = bw1b &amp; ~bw1a;\r\nimshow(bw1c)\r\ntitle(<span class=\"string\">'Pixels changed by the fill operation'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_05.png\" alt=\"\"> <p>If there are any foreground pixels on row 300 of <tt>bw1c<\/tt>, then that means row 100 is connected to row 300.<\/p><pre class=\"codeinput\">connected = any(bw1c(300,:))\r\n<\/pre><pre class=\"codeoutput\">\r\nconnected =\r\n\r\n  logical\r\n\r\n   1\r\n\r\n<\/pre><p>Let's try the steps above on another image.<\/p><pre class=\"codeinput\">bw2 = imread(<span class=\"string\">'https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-2.png'<\/span>);\r\nimshow(bw2)\r\ntitle(<span class=\"string\">'Image 2'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_06.png\" alt=\"\"> <pre class=\"codeinput\">fg_cols_row_100 = find(bw1(100,:));\r\nfg_cols_row_300 = find(bw1(300,:));\r\nbw2a = ~bw2;\r\nseed_pixels = [100*ones(numel(fg_cols_row_100),1) fg_cols_row_100'];\r\nbw2b = imfill(bw2a,seed_pixels);\r\nbw2c = bw2b &amp; ~bw2a;\r\nconnected = any(bw2c(300,:))\r\n<\/pre><pre class=\"codeoutput\">\r\nconnected =\r\n\r\n  logical\r\n\r\n   0\r\n\r\n<\/pre><p>So we have computed that there is no path connecting rows 100 and 300 in the second image.<\/p><p>OK, what about the second part of Uri's question? How do we constrain the path? For example, how do we constrain the problem to allow movement from the upper-left-to-lower-right diagonal?<\/p><p>We can do that by specifying a <i>custom connectivity<\/i> when we call <tt>imfill<\/tt>. Here's another sample image to test the concept.<\/p><pre class=\"codeinput\">bw3 = imread(<span class=\"string\">'https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-3.png'<\/span>);\r\nimshow(bw3)\r\n\r\nfg_cols_row_100 = find(bw3(100,:));\r\nfg_cols_row_300 = find(bw3(300,:));\r\nhold <span class=\"string\">on<\/span>\r\nplot(fg_cols_row_100,100,<span class=\"string\">'b*'<\/span>)\r\nplot(fg_cols_row_300,300,<span class=\"string\">'b*'<\/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\/shortest_path_variation_07.png\" alt=\"\"> <p>You can specify a custom connectivity using a 3x3 matrix. Here is one that only allows movement along the upper-left-to-lower-right diagonal.<\/p><pre class=\"codeinput\">conn = [\r\n    1 0 0\r\n    0 1 0\r\n    0 0 1 ];\r\n<\/pre><p>Now use that connectivity matrix with <tt>imfill<\/tt>.<\/p><pre class=\"codeinput\">bw3a = ~bw3;\r\nseed_pixels = [100*ones(numel(fg_cols_row_100),1) fg_cols_row_100'];\r\nbw3b = imfill(bw3a,seed_pixels,conn);\r\nbw3c = bw3b &amp; ~bw3a;\r\nimshow(bw3c)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_08.png\" alt=\"\"> <p>The odd-looking shape above is the set of pixels reachable from foreground pixels on row 100 traveling only along the specified diagonal. With this constrained path, rows 100 and 300 are not connected.<\/p><pre class=\"codeinput\">connected = any(bw3c(300,:))\r\n<\/pre><pre class=\"codeoutput\">\r\nconnected =\r\n\r\n  logical\r\n\r\n   0\r\n\r\n<\/pre><p>But what if we use the opposite diagonal for our path constraint?<\/p><pre class=\"codeinput\">conn = [\r\n    0 0 1\r\n    0 1 0\r\n    1 0 0 ];\r\nbw3b = imfill(bw3a,seed_pixels,conn);\r\nbw3c = bw3b &amp; ~bw3a;\r\nimshow(bw3c)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_09.png\" alt=\"\"> <p>You can see that rows 100 and 300 are still not connected. Let's try rows 180 and 250.<\/p><pre class=\"codeinput\">imshow(bw3)\r\n\r\nfg_cols_row_180 = find(bw3(180,:));\r\nfg_cols_row_250 = find(bw3(250,:));\r\nhold <span class=\"string\">on<\/span>\r\nplot(fg_cols_row_180,180,<span class=\"string\">'b*'<\/span>)\r\nplot(fg_cols_row_250,250,<span class=\"string\">'b*'<\/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\/shortest_path_variation_10.png\" alt=\"\"> <pre class=\"codeinput\">seed_pixels = [180*ones(numel(fg_cols_row_180),1) fg_cols_row_180'];\r\nbw3b = imfill(bw3a,seed_pixels,conn);\r\nbw3c = bw3b &amp; ~bw3a;\r\nimshow(bw3c)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_11.png\" alt=\"\"> <p>And rows 180 and 250 are connected.<\/p><pre class=\"codeinput\">connected = any(bw3c(250,:))\r\n<\/pre><pre class=\"codeoutput\">\r\nconnected =\r\n\r\n  logical\r\n\r\n   1\r\n\r\n<\/pre><p>Next time I plan to take another look at this problem using MATLAB <tt>graph<\/tt> operations.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_e5b92f70a47a4b55aeb3134cd0c05804() {\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='e5b92f70a47a4b55aeb3134cd0c05804 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' e5b92f70a47a4b55aeb3134cd0c05804';\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\"><br><a href=\"javascript:grabCode_e5b92f70a47a4b55aeb3134cd0c05804()\"><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; R2017a<br><\/p><\/div><!--\r\ne5b92f70a47a4b55aeb3134cd0c05804 ##### SOURCE BEGIN #####\r\n%%\r\n% Reader Uri Pe'er found an <https:\/\/blogs.mathworks.com\/steve\/2011\/11\/01\/exploring-shortest-paths-part-1\/ \r\n% old post from 2011> about shortest paths in\r\n% binary images, and he asked this follow-up question (paraphrased): \r\n%\r\n% _How can I determine\r\n% if there is a path from one row to another in a binary image? And can I\r\n% constrain the direction of movement along the path?_\r\n%\r\n% That sounds fun. Let's give it a try.\r\n%\r\n% I can think of several different approaches. Today I'm going to tackle it using |imfill|. \r\n%\r\n% Here's a sample image I just drew up.\r\n\r\nbw1 = imread('https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-1.png');\r\nimshow(bw1)\r\n\r\n%%\r\n% Let's use |imfill| to see if there is a path from any of the foreground\r\n% pixels on row 100 to any of the foreground pixels on row 300. First, find\r\n% the foreground pixels on those rows.\r\n\r\nfg_cols_row_100 = find(bw1(100,:));\r\nfg_cols_row_300 = find(bw1(300,:));\r\nhold on\r\nplot(fg_cols_row_100,100,'b*')\r\nplot(fg_cols_row_300,300,'b*')\r\nhold off\r\n\r\n%%\r\n% Now let's perform an |imfill| operation starting from all the foreground\r\n% pixels on row 100. Because |imfill| fills in background pixels, we need\r\n% to complement the image first.\r\n\r\nbw1a = ~bw1;\r\nimshow(bw1a)\r\ntitle('Complemented image')\r\n\r\n%%\r\n% Now perform the fill in the complemented image.\r\n\r\nseed_pixels = [100*ones(numel(fg_cols_row_100),1) fg_cols_row_100'];\r\nbw1b = imfill(bw1a,seed_pixels);\r\nimshow(bw1b)\r\ntitle('Filled image')\r\n\r\n%%\r\n% Which pixels changed as a result of the fill?\r\n\r\nbw1c = bw1b & ~bw1a;\r\nimshow(bw1c)\r\ntitle('Pixels changed by the fill operation')\r\n\r\n%%\r\n% If there are any foreground pixels on row 300 of |bw1c|, then that means\r\n% row 100 is connected to row 300.\r\n\r\nconnected = any(bw1c(300,:))\r\n\r\n%%\r\n% Let's try the steps above on another image.\r\n\r\nbw2 = imread('https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-2.png');\r\nimshow(bw2)\r\ntitle('Image 2')\r\n\r\n%%\r\nfg_cols_row_100 = find(bw1(100,:));\r\nfg_cols_row_300 = find(bw1(300,:));\r\nbw2a = ~bw2;\r\nseed_pixels = [100*ones(numel(fg_cols_row_100),1) fg_cols_row_100'];\r\nbw2b = imfill(bw2a,seed_pixels);\r\nbw2c = bw2b & ~bw2a;\r\nconnected = any(bw2c(300,:))\r\n\r\n%%\r\n% So we have computed that there is no path connecting rows 100 and 300 in\r\n% the second image.\r\n%\r\n% OK, what about the second part of Uri's question? How do we constrain the\r\n% path? For example, how do we constrain the problem to allow movement from\r\n% the upper-left-to-lower-right diagonal?\r\n%\r\n% We can do that by specifying a _custom connectivity_ when we call\r\n% |imfill|. Here's another sample image to test the concept.\r\n\r\nbw3 = imread('https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-3.png');\r\nimshow(bw3)\r\n\r\nfg_cols_row_100 = find(bw3(100,:));\r\nfg_cols_row_300 = find(bw3(300,:));\r\nhold on\r\nplot(fg_cols_row_100,100,'b*')\r\nplot(fg_cols_row_300,300,'b*')\r\nhold off\r\n\r\n%%\r\n% You can specify a custom connectivity using a 3x3 matrix. Here is one\r\n% that only allows movement along the upper-left-to-lower-right diagonal.\r\n\r\nconn = [\r\n    1 0 0\r\n    0 1 0\r\n    0 0 1 ];\r\n\r\n%%\r\n% Now use that connectivity matrix with |imfill|.\r\nbw3a = ~bw3;\r\nseed_pixels = [100*ones(numel(fg_cols_row_100),1) fg_cols_row_100'];\r\nbw3b = imfill(bw3a,seed_pixels,conn);\r\nbw3c = bw3b & ~bw3a;\r\nimshow(bw3c)\r\n\r\n%%\r\n% The odd-looking shape above is the set of pixels reachable from\r\n% foreground pixels on row 100 traveling only along the specified diagonal.\r\n% With this constrained path, rows 100 and 300 are not connected.\r\n\r\nconnected = any(bw3c(300,:))\r\n\r\n%%\r\n% But what if we use the opposite diagonal for our path constraint?\r\n\r\nconn = [\r\n    0 0 1\r\n    0 1 0\r\n    1 0 0 ];\r\nbw3b = imfill(bw3a,seed_pixels,conn);\r\nbw3c = bw3b & ~bw3a;\r\nimshow(bw3c)\r\n\r\n%%\r\n% You can see that rows 100 and 300 are still not connected. Let's try rows\r\n% 180 and 250.\r\n\r\nimshow(bw3)\r\n\r\nfg_cols_row_180 = find(bw3(180,:));\r\nfg_cols_row_250 = find(bw3(250,:));\r\nhold on\r\nplot(fg_cols_row_180,180,'b*')\r\nplot(fg_cols_row_250,250,'b*')\r\nhold off\r\n\r\n%%\r\nseed_pixels = [180*ones(numel(fg_cols_row_180),1) fg_cols_row_180'];\r\nbw3b = imfill(bw3a,seed_pixels,conn);\r\nbw3c = bw3b & ~bw3a;\r\nimshow(bw3c)\r\n\r\n%%\r\n% And rows 180 and 250 are connected.\r\n\r\nconnected = any(bw3c(250,:))\r\n\r\n%%\r\n% Next time I plan to take another look at this problem using MATLAB\r\n% |graph| operations.\r\n##### SOURCE END ##### e5b92f70a47a4b55aeb3134cd0c05804\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_02.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>Reader Uri Pe'er found an old post from 2011 about shortest paths in binary images, and he asked this follow-up question (paraphrased):How can I determine if there is a path from one row to another... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2017\/03\/31\/variations-on-connected-objects-using-imfill\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":2525,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[907,348,90,136,76,36,162,288,68,52],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2535"}],"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=2535"}],"version-history":[{"count":1,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2535\/revisions"}],"predecessor-version":[{"id":2536,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2535\/revisions\/2536"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/2525"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=2535"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=2535"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=2535"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}