{"id":2537,"date":"2017-04-06T15:35:08","date_gmt":"2017-04-06T19:35:08","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=2537"},"modified":"2019-11-01T17:08:12","modified_gmt":"2019-11-01T21:08:12","slug":"variation-on-connected-objects-using-graphs","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2017\/04\/06\/variation-on-connected-objects-using-graphs\/","title":{"rendered":"Variation on Connected Objects &#8211; Using Graphs"},"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><a href=\"https:\/\/blogs.mathworks.com\/steve\/2017\/03\/31\/variations-on-connected-objects-using-imfill\/\">Last week<\/a> I showed how to tackle this problem using <a href=\"https:\/\/www.mathworks.com\/help\/images\/ref\/imfill.html\"><tt>imfill<\/tt><\/a>. Today I'm going to use a <tt>graph<\/tt>.<\/p><p>In December 2015 I wrote about how to create graphs based on the connectivity of image pixels, and I <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/53-shift\">submitted some functions to the File Exchange<\/a> to make it easy.<\/p><p>One of those functions is <tt>binaryImageGraph<\/tt>. Let's try it using one of the same images from my last post.<\/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_graph_01.png\" alt=\"\"> <pre class=\"codeinput\">g = binaryImageGraph(bw1)\r\n<\/pre><pre class=\"codeoutput\">\r\ng = \r\n\r\n  graph with properties:\r\n\r\n    Edges: [180444&times;2 table]\r\n    Nodes: [45880&times;3 table]\r\n\r\n<\/pre><p>Each foreground pixel in the binary image corresponds to a graph node, so the total number of graph nodes is the same as the number of foreground pixels.<\/p><pre class=\"codeinput\">nnz(bw1)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n       45880\r\n\r\n<\/pre><p>The function <tt>plotImageGraph<\/tt> (from the same <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/53-shift\">File Exchange submission<\/a>) can be used to visualize the image graph.<\/p><pre class=\"codeinput\">plotImageGraph(g)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_graph_02.png\" alt=\"\"> <p>Zoom in to see the graph details in a small portion of the image.<\/p><pre class=\"codeinput\">axis([275 300 125 140])\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_graph_03.png\" alt=\"\"> <p>Now let's figure out whether there are any graph paths connecting a row-100 pixels to a row-300 pixel. Note that <tt>binaryImageGraph<\/tt> stores the original foreground pixel locations in the graph's node table for purposes just like this. Here's what the node table looks like.<\/p><pre class=\"codeinput\">head(g.Nodes)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  8&times;3 table\r\n\r\n     x     y     PixelIndex\r\n    ___    __    __________\r\n\r\n    121    76    57676     \r\n    121    77    57677     \r\n    121    78    57678     \r\n    121    79    57679     \r\n    121    80    57680     \r\n    121    81    57681     \r\n    121    82    57682     \r\n    121    83    57683     \r\n\r\n<\/pre><pre class=\"codeinput\">row_100_nodes = find(g.Nodes.y == 100);\r\nrow_300_nodes = find(g.Nodes.y == 300);\r\n<\/pre><p>Next, use <tt>conncomp<\/tt> to find the connected components of the graph.<\/p><pre class=\"codeinput\">bins = conncomp(g);\r\n<\/pre><p>For one of the graph nodes, <tt>bins(node)<\/tt> tells you which connected component that node belongs to.<\/p><pre class=\"codeinput\">unique(bins(row_100_nodes))\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n     1\r\n\r\n<\/pre><p>So the foreground pixels on row 100 all belong to the same connected component.<\/p><pre class=\"codeinput\">unique(bins(row_300_nodes))\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n     1     2\r\n\r\n<\/pre><p>The foreground pixels on row 300 belong to two different connected components. Since one of those the components is the same as for the row 100 pixels, that means there is path from row 100 to row 300.<\/p><p>Just like <tt>imfill<\/tt>, <tt>binaryImageGraph<\/tt> can take a custom connectivity matrix. We can use that to constrain the allowed paths. Let's try that with one of the other images I used last time.<\/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<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_graph_04.png\" alt=\"\"> <pre class=\"codeinput\">conn = [\r\n    1 0 0\r\n    0 1 0\r\n    0 0 1 ];\r\ng = binaryImageGraph(bw3,conn);\r\nplotImageGraph(g)\r\naxis([275 300 125 140])\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_graph_05.png\" alt=\"\"> <p>Is there a constrained path from row 180 to row 250?<\/p><pre class=\"codeinput\">row_180_nodes = find(g.Nodes.y == 180);\r\nrow_250_nodes = find(g.Nodes.y == 250);\r\nbins = conncomp(g);\r\n~isempty(intersect(bins(row_180_nodes),bins(row_250_nodes)))\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  logical\r\n\r\n   0\r\n\r\n<\/pre><p>No.<\/p><p>But what if we reverse the path constraint?<\/p><pre class=\"codeinput\">conn = [\r\n    0 0 1\r\n    0 1 0\r\n    1 0 0 ];\r\ng = binaryImageGraph(bw3,conn);\r\nplotImageGraph(g)\r\naxis([275 300 125 140])\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_graph_06.png\" alt=\"\"> <pre class=\"codeinput\">row_180_nodes = find(g.Nodes.y == 180);\r\nrow_250_nodes = find(g.Nodes.y == 250);\r\nbins = conncomp(g);\r\n~isempty(intersect(bins(row_180_nodes),bins(row_250_nodes)))\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  logical\r\n\r\n   1\r\n\r\n<\/pre><p>Yes, rows 180 and 250 are connected via a paths constrained to lie along the upper-right to lower-left diagonal.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_3fb39edd0ee44d8bbc65ff98f003c70c() {\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='3fb39edd0ee44d8bbc65ff98f003c70c ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 3fb39edd0ee44d8bbc65ff98f003c70c';\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_3fb39edd0ee44d8bbc65ff98f003c70c()\"><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\n3fb39edd0ee44d8bbc65ff98f003c70c ##### 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% <https:\/\/blogs.mathworks.com\/steve\/2017\/03\/31\/variations-on-connected-objects-using-imfill\/\r\n% Last week> I showed how to tackle this problem using\r\n% <https:\/\/www.mathworks.com\/help\/images\/ref\/imfill.html |imfill|>. Today\r\n% I'm going to use a |graph|.\r\n%\r\n% In December 2015 I wrote about how to create graphs based on the\r\n% connectivity of image pixels, and I <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/53-shift \r\n% submitted some functions to the File Exchange> to make it easy.\r\n%\r\n% One of those functions is |binaryImageGraph|. Let's try it using one of\r\n% the same images from my last post.\r\n\r\nbw1 = imread('https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-1.png');\r\nimshow(bw1)\r\n\r\n%%\r\ng = binaryImageGraph(bw1)\r\n\r\n%%\r\n% Each foreground pixel in the binary image corresponds to a graph node, so\r\n% the total number of graph nodes is the same as the number of foreground\r\n% pixels.\r\n\r\nnnz(bw1)\r\n\r\n%%\r\n% The function |plotImageGraph| (from the same\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/fileexchange\/53-shift\r\n% File Exchange submission>) can be used to visualize the image graph.\r\n\r\nplotImageGraph(g)\r\n\r\n%%\r\n% Zoom in to see the graph details in a small portion of the image.\r\naxis([275 300 125 140])\r\n\r\n%%\r\n% Now let's figure out whether there are any graph paths connecting a\r\n% row-100 pixels to a row-300 pixel. Note that |binaryImageGraph| stores\r\n% the original foreground pixel locations in the graph's node table for\r\n% purposes just like this. Here's what the node table looks like.\r\n\r\nhead(g.Nodes)\r\n\r\n%%\r\nrow_100_nodes = find(g.Nodes.y == 100);\r\nrow_300_nodes = find(g.Nodes.y == 300);\r\n\r\n%%\r\n% Next, use |conncomp| to find the connected components of the graph.\r\n\r\nbins = conncomp(g);\r\n\r\n%%\r\n% For one of the graph nodes, |bins(node)| tells you which connected\r\n% component that node belongs to.\r\n\r\nunique(bins(row_100_nodes))\r\n\r\n%%\r\n% So the foreground pixels on row 100 all belong to the same connected\r\n% component.\r\n\r\nunique(bins(row_300_nodes))\r\n\r\n%%\r\n% The foreground pixels on row 300 belong to two different connected\r\n% components. Since one of those the components is the same as for the row\r\n% 100 pixels, that means there is path from row 100 to row 300.\r\n%\r\n% Just like |imfill|, |binaryImageGraph| can take a custom connectivity\r\n% matrix. We can use that to constrain the allowed paths. Let's try that\r\n% with one of the other images I used last time.\r\n\r\nbw3 = imread('https:\/\/blogs.mathworks.com\/steve\/files\/path-shapes-3.png');\r\nimshow(bw3)\r\n\r\n%%\r\nconn = [ \r\n    1 0 0\r\n    0 1 0\r\n    0 0 1 ];\r\ng = binaryImageGraph(bw3,conn);\r\nplotImageGraph(g)\r\naxis([275 300 125 140])\r\n\r\n%%\r\n% Is there a constrained path from row 180 to row 250?\r\nrow_180_nodes = find(g.Nodes.y == 180);\r\nrow_250_nodes = find(g.Nodes.y == 250);\r\nbins = conncomp(g);\r\n~isempty(intersect(bins(row_180_nodes),bins(row_250_nodes)))\r\n\r\n%%\r\n% No.\r\n%\r\n% But what if we reverse the path constraint?\r\n\r\nconn = [\r\n    0 0 1\r\n    0 1 0\r\n    1 0 0 ];\r\ng = binaryImageGraph(bw3,conn);\r\nplotImageGraph(g)\r\naxis([275 300 125 140])\r\n\r\n%%\r\nrow_180_nodes = find(g.Nodes.y == 180);\r\nrow_250_nodes = find(g.Nodes.y == 250);\r\nbins = conncomp(g);\r\n~isempty(intersect(bins(row_180_nodes),bins(row_250_nodes)))\r\n\r\n%%\r\n% Yes, rows 180 and 250 are connected via a paths constrained to lie along\r\n% the upper-right to lower-left diagonal.\r\n##### SOURCE END ##### 3fb39edd0ee44d8bbc65ff98f003c70c\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/shortest_path_variation_graph_03.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\/04\/06\/variation-on-connected-objects-using-graphs\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":2541,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[50,1187,1185,348,1183,136,76,36,1189,472,707,350],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2537"}],"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=2537"}],"version-history":[{"count":1,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2537\/revisions"}],"predecessor-version":[{"id":2538,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/2537\/revisions\/2538"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/2541"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=2537"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=2537"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=2537"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}