{"id":196,"date":"2008-02-25T10:52:41","date_gmt":"2008-02-25T15:52:41","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/2008\/02\/25\/neighbor-indexing-2\/"},"modified":"2019-10-24T13:57:08","modified_gmt":"2019-10-24T17:57:08","slug":"neighbor-indexing-2","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2008\/02\/25\/neighbor-indexing-2\/","title":{"rendered":"Neighbor indexing"},"content":{"rendered":"<div xmlns:mwsh=\"https:\/\/www.mathworks.com\/namespace\/mcode\/v1\/syntaxhighlight.dtd\" class=\"content\">\r\n   <p>Earlier this year I wrote about <a href=\"https:\/\/blogs.mathworks.com\/steve\/2008\/01\/28\/logical-indexing\/\">logical indexing<\/a> and <a href=\"https:\/\/blogs.mathworks.com\/steve\/2008\/02\/08\/linear-indexing\/\">linear indexing<\/a>.\r\n   <\/p>\r\n   <p>Now I want to finish this little indexing series with an example of a technique I call <i>neighbor indexing<\/i>.  I've written about neighbor indexing <a href=\"https:\/\/blogs.mathworks.com\/steve\/2007\/03\/28\/neighbor-indexing\/\">before<\/a>, but in this post I'll show a more complete example of its application to image processing.\r\n   <\/p>\r\n   <p>Many image processing operations can be classified as <i>recursive neighborhood operations<\/i>. (Thanks to <a href=\"https:\/\/blogs.mathworks.com\/steve\/2007\/11\/19\/classification-of-operations\/#comment-16149\">Chris<\/a> for suggesting this term to me.) You do something to each neighbor of a pixel, then you do something to each neighbor of\r\n      those neighbors, and so on. <i>Flood filling<\/i> is a good example, and neighbor indexing is a good way to implement this type of operation in MATLAB.  If you implement your\r\n      own image processing algorithms in MATLAB, you'll probably find neighbor indexing useful.\r\n   <\/p>\r\n   <p>I'll start by reviewing the basics of neighbor indexing, and then I'll show how to use it to implement flood filling.<\/p>\r\n   <p>Let's make a sample 5-by-5 matrix and consider three <i>seed locations<\/i>, or starting locations.\r\n   <\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">A = magic(5)<\/pre><pre style=\"font-style:oblique\">\r\nA =\r\n\r\n    17    24     1     8    15\r\n    23     5     7    14    16\r\n     4     6    13    20    22\r\n    10    12    19    21     3\r\n    11    18    25     2     9\r\n\r\n<\/pre><p>Starting locations at (2,2), (1,4), (3,4):<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">rows = [2 1 3];\r\ncols = [2 4 4];<\/pre><p>As you'll see, the neighbor indexing technique is based on linear indexing, so let's convert the starting locations to linear\r\n      indices:\r\n   <\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">idx = sub2ind(size(A), rows, cols)<\/pre><pre style=\"font-style:oblique\">\r\nidx =\r\n\r\n     7    16    18\r\n\r\n<\/pre><p>The linear indices can be used to extract the pixel values of each of the three seed locations:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">A(idx)<\/pre><pre style=\"font-style:oblique\">\r\nans =\r\n\r\n     5     8    20\r\n\r\n<\/pre><p>How do you find the linear indices of the eastern neighbor of each of the starting locations?<\/p>\r\n   <p><i>Answer<\/i>: Add the number of rows\r\n   <\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">M = size(A, 1);\r\neast_neighbors = idx + M<\/pre><pre style=\"font-style:oblique\">\r\neast_neighbors =\r\n\r\n    12    21    23\r\n\r\n<\/pre><p>M is called the <i>linear offset<\/i> for the eastern direction.\r\n   <\/p>\r\n   <p>A(east_neighbors) gives you the pixel values of the eastern neighbors:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">A(east_neighbors)<\/pre><pre style=\"font-style:oblique\">\r\nans =\r\n\r\n     7    15    22\r\n\r\n<\/pre><p>What is the linear offset for the northern direction?<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">northern_offset = -1;\r\nnorthern_neighbors = idx + northern_offset<\/pre><pre style=\"font-style:oblique\">\r\nnorthern_neighbors =\r\n\r\n     6    15    17\r\n\r\n<\/pre><p>OOPS! The (1,4) pixel doesn't have a northern neighbor! Subtracting 1 from the linear indexing of a pixel on the top row will\r\n      just give you the pixel on the bottom row of the previous column.\r\n   <\/p>\r\n   <p>The neighbor indexing technique DOES NOT WORK FOR PIXELS ON THE BORDERS!<\/p>\r\n   <p>Therefore some form of padding is usually necessary.<\/p>\r\n   <p>Here are the offsets corresponding to all eight neighbor directions:<\/p><pre> East         M\r\n Southeast    M + 1\r\n South        1\r\n Southwest   -M + 1\r\n West        -M\r\n Northwest   -M - 1\r\n North       -1\r\n Northeast    M - 1<\/pre><p>The function bsxfun gives you a way to compute all the neighbors of a set of pixels in a single, vectorized expression.<\/p>\r\n   <p>For example, suppose the linear indices of our seed pixels are:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">idx = [275 2348 8100]<\/pre><pre style=\"font-style:oblique\">\r\nidx =\r\n\r\n         275        2348        8100\r\n\r\n<\/pre><p>And we are interested in finding the north, east, south, and west neighbors of all these pixels:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\"><span style=\"color: #228B22\">% Assume a 100-by-100 image for this example.<\/span>\r\nM = 100;\r\nneighbor_offsets = [-1, M, 1, -M]';<\/pre><p>Add every neighbor_offset to every linear index:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">neighbors = bsxfun(@plus, idx, neighbor_offsets)<\/pre><pre style=\"font-style:oblique\">\r\nneighbors =\r\n\r\n         274        2347        8099\r\n         375        2448        8200\r\n         276        2349        8101\r\n         175        2248        8000\r\n\r\n<\/pre><p>Now we have enough indexing tools to implement a flood-fill algorithm.  Let's try it with this image and starting location:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">bw = bwperim(imread(<span style=\"color: #A020F0\">'circles.png'<\/span>));\r\nimshow(bw)\r\n\r\nr = 100;\r\nc = 100;\r\nhold <span style=\"color: #A020F0\">on<\/span>\r\nplot(c, r, <span style=\"color: #A020F0\">'g*'<\/span>)\r\nhold <span style=\"color: #A020F0\">off<\/span><\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2008\/neighbor_indexing_example_01.png\"> <p>Pad the image with ones.  This step will prevent the flood filling operation from reaching the border of the image.<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">bw = padarray(bw, [1 1], 1);<\/pre><p>Initialize a linear index array containing the \"active pixels.\" This is the set of pixels to be changed from black to white.\r\n      Remember that we have to add 1 to the original row and column coordinates because of the padding step above.\r\n   <\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">active_pixels = sub2ind(size(bw), r+1, c+1);\r\nbw_out = bw;\r\nM = size(bw, 1);\r\nneighbor_offsets = [-1, M, 1, -M];<\/pre><p>Our main processing loop will iterate until there are no more active pixels.  In each loop iteration, we set the active pixels\r\n      to 1 in the output image.  Then we get all the neighbors of all the active pixels, and we make those neighbors the new active\r\n      pixels.\r\n   <\/p>\r\n   <p>That's the basic logic, but there are two special steps you should keep in mind if you use this technique yourself.  First,\r\n      we need to remove from the active pixel list the pixels that are already 1.  If we don't do this, the flood fill operation\r\n      will not stop at the white boundaries.\r\n   <\/p>\r\n   <p>Second, we need to \"dedup\" the active pixel list at each iteration.  That is, when you find all the neighbors of all the active\r\n      pixels, there will be duplicates in the list.  If you don't prune out the duplicates at each iteration, the number of duplicates\r\n      can quickly grow to be quite large, causing the algorithm to run slowly and to use a lot of memory.\r\n   <\/p>\r\n   <p>Here's the main processing loop:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\"><span style=\"color: #0000FF\">while<\/span> ~isempty(active_pixels)\r\n    <span style=\"color: #228B22\">% Set the active pixels to 1.<\/span>\r\n    bw_out(active_pixels) = 1;\r\n\r\n    <span style=\"color: #228B22\">% The new active pixels list is the set of neighbors of the<\/span>\r\n    <span style=\"color: #228B22\">% current list.<\/span>\r\n    active_pixels = bsxfun(@plus, active_pixels, neighbor_offsets);\r\n    active_pixels = active_pixels(:);\r\n\r\n    <span style=\"color: #228B22\">% Remove from the active_pixels list pixels that are already<\/span>\r\n    <span style=\"color: #228B22\">% 1.  This is how we keep the flood fill operation \"inside\"<\/span>\r\n    <span style=\"color: #228B22\">% the white boundaries.<\/span>\r\n    active_pixels(bw_out(active_pixels)) = [];\r\n\r\n    <span style=\"color: #228B22\">% Remove duplicates from the list.<\/span>\r\n    active_pixels = unique(active_pixels);\r\n<span style=\"color: #0000FF\">end<\/span><\/pre><p>Remember to remove the extra padding:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">bw_out = bw_out(2:end-1, 2:end-1);<\/pre><p>And here's the final result:<\/p><pre style=\"background: #F9F7F3; padding: 10px; border: 1px solid rgb(200,200,200)\">imshow(bw_out)<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/images\/steve\/2008\/neighbor_indexing_example_02.png\"> <script language=\"JavaScript\">\r\n<!--\r\n\r\n    function grabCode_bc1794d2a791482cb51ad1cc2e16d8b6() {\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='bc1794d2a791482cb51ad1cc2e16d8b6 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' bc1794d2a791482cb51ad1cc2e16d8b6';\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        author = 'Steve Eddins';\r\n        copyright = 'Copyright 2008 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 author and copyright lines at the bottom if specified.\r\n        if ((author.length > 0) || (copyright.length > 0)) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (author.length > 0) {\r\n                d.writeln('% _' + author + '_');\r\n            }\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      \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_bc1794d2a791482cb51ad1cc2e16d8b6()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n            the MATLAB code \r\n            <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; 7.5<br><\/p>\r\n<\/div>\r\n<!--\r\nbc1794d2a791482cb51ad1cc2e16d8b6 ##### SOURCE BEGIN #####\r\n%% Neighbor indexing\r\n% Earlier this year I wrote about \r\n% <https:\/\/blogs.mathworks.com\/steve\/2008\/01\/28\/logical-indexing\/ \r\n% logical indexing> and \r\n% <https:\/\/blogs.mathworks.com\/steve\/2008\/02\/08\/linear-indexing\/ \r\n% linear indexing>.\r\n%\r\n% Now I want to finish this little indexing series with an\r\n% example of a technique I call _neighbor indexing_.  I've\r\n% written about neighbor indexing \r\n% <https:\/\/blogs.mathworks.com\/steve\/2007\/03\/28\/neighbor-indexing\/ \r\n% before>, but in this post I'll show a more complete example of \r\n% its application to image processing.\r\n% \r\n% Many image processing operations can be classified as\r\n% _recursive neighborhood operations_. (Thanks to \r\n% <https:\/\/blogs.mathworks.com\/steve\/2007\/11\/19\/classification-of-operations\/#comment-16149 \r\n% Chris> for suggesting this term to me.)\r\n% You do something to each neighbor of a pixel, then you do\r\n% something to each neighbor of those neighbors, and so on.\r\n% _Flood filling_ is a good example, and neighbor indexing is a\r\n% good way to implement this type of operation in MATLAB.  If you\r\n% implement your own image processing algorithms in MATLAB,\r\n% you'll probably find neighbor indexing useful.\r\n%\r\n% I'll start by reviewing the basics of neighbor indexing, and\r\n% then I'll show how to use it to implement flood filling.\r\n%\r\n% Let's make a sample 5-by-5 matrix and consider three _seed\r\n% locations_, or starting locations.\r\n\r\nA = magic(5)\r\n\r\n%%\r\n% Starting locations at (2,2), (1,4), (3,4):\r\n\r\nrows = [2 1 3];\r\ncols = [2 4 4];\r\n\r\n%%\r\n% As you'll see, the neighbor indexing technique is based on\r\n% linear indexing, so let's convert the starting locations to\r\n% linear indices:\r\n\r\nidx = sub2ind(size(A), rows, cols)\r\n\r\n%%\r\n% The linear indices can be used to extract the pixel values of\r\n% each of the three seed locations:\r\n\r\nA(idx)\r\n\r\n%%\r\n% How do you find the linear indices of the eastern neighbor of\r\n% each of the starting locations?\r\n%\r\n% _Answer_: Add the number of rows\r\n\r\nM = size(A, 1);\r\neast_neighbors = idx + M\r\n\r\n%%\r\n% M is called the _linear offset_ for the eastern direction.\r\n%\r\n% A(east_neighbors) gives you the pixel values of the eastern\r\n% neighbors:\r\n\r\nA(east_neighbors)\r\n\r\n%%\r\n% What is the linear offset for the northern direction?\r\n\r\nnorthern_offset = -1;\r\nnorthern_neighbors = idx + northern_offset\r\n\r\n%%\r\n% OOPS! The (1,4) pixel doesn't have a northern neighbor!\r\n% Subtracting 1 from the linear indexing of a pixel on the top\r\n% row will just give you the pixel on the bottom row of the\r\n% previous column.\r\n%\r\n% The neighbor indexing technique DOES NOT WORK FOR PIXELS ON THE\r\n% BORDERS!\r\n%\r\n% Therefore some form of padding is usually necessary.\r\n%\r\n% Here are the offsets corresponding to all eight neighbor\r\n% directions:\r\n%\r\n%   East         M\r\n%   Southeast    M + 1\r\n%   South        1\r\n%   Southwest   -M + 1\r\n%   West        -M\r\n%   Northwest   -M - 1\r\n%   North       -1\r\n%   Northeast    M - 1\r\n\r\n%%\r\n% The function bsxfun gives you a way to compute all the\r\n% neighbors of a set of pixels in a single, vectorized\r\n% expression.\r\n%\r\n% For example, suppose the linear indices of our seed pixels are:\r\n\r\nidx = [275 2348 8100]\r\n\r\n%%\r\n% And we are interested in finding the north, east, south, and\r\n% west neighbors of all these pixels:\r\n\r\n% Assume a 100-by-100 image for this example.\r\nM = 100;\r\nneighbor_offsets = [-1, M, 1, -M]';\r\n\r\n%%\r\n% Add every neighbor_offset to every linear index:\r\n\r\nneighbors = bsxfun(@plus, idx, neighbor_offsets)\r\n\r\n%%\r\n% Now we have enough indexing tools to implement a flood-fill\r\n% algorithm.  Let's try it with this image and starting location:\r\n\r\nbw = bwperim(imread('circles.png'));\r\nimshow(bw)\r\n\r\nr = 100;\r\nc = 100;\r\nhold on\r\nplot(c, r, 'g*')\r\nhold off\r\n\r\n%%\r\n% Pad the image with ones.  This step will prevent the flood\r\n% filling operation from reaching the border of the image.\r\n\r\nbw = padarray(bw, [1 1], 1);\r\n\r\n%%\r\n% Initialize a linear index array containing the \"active pixels.\"\r\n% This is the set of pixels to be changed from black to white. \r\n% Remember that we have to add 1 to the original row and column\r\n% coordinates because of the padding step above.\r\n\r\nactive_pixels = sub2ind(size(bw), r+1, c+1);\r\nbw_out = bw;\r\nM = size(bw, 1);\r\nneighbor_offsets = [-1, M, 1, -M];\r\n\r\n%%\r\n% Our main processing loop will iterate until there are no more\r\n% active pixels.  In each loop iteration, we set the active\r\n% pixels to 1 in the output image.  Then we get all the neighbors\r\n% of all the active pixels, and we make those neighbors the new\r\n% active pixels.\r\n%\r\n% That's the basic logic, but there are two special steps you\r\n% should keep in mind if you use this technique yourself.  First,\r\n% we need to remove from the active pixel list the pixels that\r\n% are already 1.  If we don't do this, the flood fill operation\r\n% will not stop at the white boundaries.\r\n%\r\n% Second, we need to \"dedup\" the active pixel list at each\r\n% iteration.  That is, when you find all the neighbors of all\r\n% the active pixels, there will be duplicates in the list.  If\r\n% you don't prune out the duplicates at each iteration, the\r\n% number of duplicates can quickly grow to be quite large,\r\n% causing the algorithm to run slowly and to use a lot of memory.\r\n%\r\n% Here's the main processing loop:\r\n\r\nwhile ~isempty(active_pixels)\r\n    % Set the active pixels to 1.\r\n    bw_out(active_pixels) = 1;\r\n    \r\n    % The new active pixels list is the set of neighbors of the\r\n    % current list.\r\n    active_pixels = bsxfun(@plus, active_pixels, neighbor_offsets);\r\n    active_pixels = active_pixels(:);\r\n    \r\n    % Remove from the active_pixels list pixels that are already\r\n    % 1.  This is how we keep the flood fill operation \"inside\"\r\n    % the white boundaries.\r\n    active_pixels(bw_out(active_pixels)) = [];\r\n    \r\n    % Remove duplicates from the list.\r\n    active_pixels = unique(active_pixels);\r\nend\r\n\r\n%%\r\n% Remember to remove the extra padding:\r\n\r\nbw_out = bw_out(2:end-1, 2:end-1);\r\n\r\n%%\r\n% And here's the final result:\r\n\r\nimshow(bw_out)\r\n##### SOURCE END ##### bc1794d2a791482cb51ad1cc2e16d8b6\r\n-->","protected":false},"excerpt":{"rendered":"<p>\r\n   Earlier this year I wrote about logical indexing and linear indexing.\r\n   \r\n   Now I want to finish this little indexing series with an example of a technique I call neighbor indexing.  I've... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2008\/02\/25\/neighbor-indexing-2\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[17],"tags":[320,140,90,36,472,54,344,68,190,468,350],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/196"}],"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=196"}],"version-history":[{"count":1,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/196\/revisions"}],"predecessor-version":[{"id":3590,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/196\/revisions\/3590"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=196"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=196"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=196"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}