{"id":4244,"date":"2020-11-11T11:10:03","date_gmt":"2020-11-11T16:10:03","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=4244"},"modified":"2020-11-11T11:10:21","modified_gmt":"2020-11-11T16:10:21","slug":"transforming-a-color-image-to-a-weighted-adjacency-matrix","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2020\/11\/11\/transforming-a-color-image-to-a-weighted-adjacency-matrix\/","title":{"rendered":"Transforming a color image to a weighted adjacency matrix"},"content":{"rendered":"<div class=\"content\"><p>A <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/answers\/644365-how-to-transform-a-rgb-matrix-to-a-weighted-adjacency-matrix\">question on MATLAB Answers<\/a> caught my eye earlier today. <a href=\"https:\/\/www.mathworks.com\/matlabcentral\/profile\/authors\/20196075\">Borys<\/a> has this pseudocolor image of a weighted adjacency matrix:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410520\/image.png\" alt=\"\"> <\/p><p>And he has this image of the color scale:<\/p><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410525\/image.png\" alt=\"\"> <\/p><p>Borys wants to know how to compute the real adjacency matrix from this image, knowing that the color scale represents the range [0,5].<\/p><p>The problem looked interesting to me, and I wanted to give it a try.<\/p><pre class=\"codeinput\">image_url = <span class=\"string\">\"https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410520\/image.png\"<\/span>;\r\nscale_url = <span class=\"string\">\"https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410525\/image.png\"<\/span>;\r\n\r\nA = imread(image_url);\r\n<\/pre><p>Here is my planned approach:<\/p><p>1. Use the white squares to determine the grid of coordinates for the cells of the adjacency matrix.<\/p><p>2. Extract the colors from the locations found in step 1.<\/p><p>3. For each extracted color, find the minimum color difference betweent that color and the colors in the scale.<\/p><p>4. Use linear interpolation to determine the weight value (between 0 and 5) for the corresponding adjacency matrix cell.<\/p><p>The first thing I did was to use the Pixel Region tool to check if the white squares are exactly white ([255,255,255]).<\/p><pre>imshow(A)\r\nimpixelregion<\/pre><p><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/impixelregion-screen-shot.png\" alt=\"\"> <\/p><p>And they are.<\/p><p>Next, I'm going to compute a mask image corresponding to just the white squares.<\/p><pre class=\"codeinput\">mask = all(rgb == reshape([255 255 255],1,1,3),3);\r\nimshow(mask)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/image_to_adjacency_matrix_01.png\" alt=\"\"> <p>At a quick glance, it appears that the white squares aren't touching each other, but just to sure, let's erode them a bit.<\/p><pre class=\"codeinput\">mask2 = imerode(mask,ones(3,3));\r\nimshow(mask2)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/image_to_adjacency_matrix_02.png\" alt=\"\"> <p>Now, let's find the center of each white square.<\/p><pre class=\"codeinput\">T = regionprops(<span class=\"string\">\"table\"<\/span>,mask2,<span class=\"string\">\"Centroid\"<\/span>);\r\nhead(T)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  8&times;1 table\r\n\r\n        Centroid    \r\n    ________________\r\n\r\n       5.5       5.5\r\n        19        19\r\n        32        32\r\n      44.5        45\r\n      57.5      57.5\r\n    70.545    70.545\r\n        83        83\r\n        96        96\r\n\r\n<\/pre><p>Round the centroid coordinates to integer locations.<\/p><pre class=\"codeinput\">m = round(T.Centroid(:,2));\r\nn = round(T.Centroid(:,1));\r\n<\/pre><p>Let's double-check our locations<\/p><pre class=\"codeinput\">figure\r\nimshow(A)\r\naxis([n(1) n(6) m(1) m(6)])\r\nhold <span class=\"string\">on<\/span>\r\n<span class=\"keyword\">for<\/span> q = 2:5\r\n    <span class=\"keyword\">for<\/span> r = 2:5\r\n        x = n(r);\r\n        y = m(q);\r\n        plot(x,y,<span class=\"string\">\"o\"<\/span>,<span class=\"string\">\"MarkerFaceColor\"<\/span>,<span class=\"string\">\"w\"<\/span>,<span class=\"string\">\"MarkerEdgeColor\"<\/span>,<span class=\"string\">\"k\"<\/span>)\r\n        text(x,y,<span class=\"string\">\"(\"<\/span> + q + <span class=\"string\">\",\"<\/span> + r + <span class=\"string\">\")\"<\/span>,<span class=\"keyword\">...<\/span>\r\n            <span class=\"string\">\"HorizontalAlignment\"<\/span>,<span class=\"string\">\"center\"<\/span>,<span class=\"keyword\">...<\/span>\r\n            <span class=\"string\">\"VerticalAlignment\"<\/span>,<span class=\"string\">\"bottom\"<\/span>)\r\n    <span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/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\/image_to_adjacency_matrix_03.png\" alt=\"\"> <p>That looks good. Now, let's extract one of the colors and match it to a location on the scale.<\/p><p>Read the scale image and change it into an Mx3 matrix of color values.<\/p><pre class=\"codeinput\">scale = imread(scale_url);\r\nscale = scale(1,:,:);\r\nscale = reshape(scale,[],3);\r\n<\/pre><p>Grab the color in the (5,3) cell of the adjacency matrix image.<\/p><pre class=\"codeinput\">c = A(m(5),n(3),:);\r\n<\/pre><p>Display the color<\/p><pre class=\"codeinput\">image(c)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/image_to_adjacency_matrix_04.png\" alt=\"\"> <p>Now find the closest match in the color scale. I'll use <tt>deltaE<\/tt>, which I described in my <a href=\"https:\/\/blogs.mathworks.com\/steve\/2020\/10\/30\/how-to-compute-perceptual-color-difference\/\">30-Oct-2020 post<\/a>.<\/p><pre class=\"codeinput\">c = reshape(c,1,3);\r\nD = deltaE(repmat(c,size(scale,1),1), scale);\r\n<\/pre><p>Let's double-check our work at this point. I'm expecting there to be a clear minimum, which identifies the closest color on the scale. Is that the case?<\/p><pre class=\"codeinput\">plot(D)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/image_to_adjacency_matrix_05.png\" alt=\"\"> <p>That looks good.<\/p><p>What is the minimum value of <tt>D<\/tt>, and where does it occur?<\/p><pre class=\"codeinput\">[D_min,idx] = min(D);\r\nD_min\r\n<\/pre><pre class=\"codeoutput\">\r\nD_min =\r\n\r\n  single\r\n\r\n    3.9335\r\n\r\n<\/pre><pre class=\"codeinput\">idx\r\n<\/pre><pre class=\"codeoutput\">\r\nidx =\r\n\r\n   341\r\n\r\n<\/pre><p>Now we can use interpolation to get the adjacency cell weight for this cell. Recall that the weights range from 0 to 5.<\/p><pre class=\"codeinput\">w = interp1([1 size(scale,1)], [0 5], idx)\r\n<\/pre><pre class=\"codeoutput\">\r\nw =\r\n\r\n    3.7363\r\n\r\n<\/pre><p>We have all the pieces of the computation. Now, let's figure out the weight for every cell.<\/p><pre class=\"codeinput\"><span class=\"keyword\">for<\/span> q = 1:length(m)\r\n    <span class=\"keyword\">for<\/span> r = 1:length(n)\r\n        <span class=\"comment\">% Skip the cells on the diagonal.<\/span>\r\n        <span class=\"keyword\">if<\/span> q ~= r\r\n            c = A(m(q),n(r),:);\r\n            c = reshape(c,1,3);\r\n            c = repmat(c,size(scale,1),1);\r\n            D = deltaE(c,scale);\r\n            [D_min,idx] = min(D);\r\n            Aw(q,r) = interp1([1 size(scale,1)],[0 5],idx);\r\n        <span class=\"keyword\">end<\/span>\r\n    <span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><p>Finally, we can display our computed weighted adjacency matrix. Except for the diagonal elements, which will display as blue using this procedure, it should look very similar to the image we started with.<\/p><pre class=\"codeinput\">imshow(Aw,[])\r\ncolormap(scale)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/image_to_adjacency_matrix_06.png\" alt=\"\"> <p>That was fun!<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_d4c14ff517e24295989482ae2d0214e6() {\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='d4c14ff517e24295989482ae2d0214e6 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' d4c14ff517e24295989482ae2d0214e6';\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 2020 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_d4c14ff517e24295989482ae2d0214e6()\"><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; R2020b<br><\/p><\/div><!--\r\nd4c14ff517e24295989482ae2d0214e6 ##### SOURCE BEGIN #####\r\n%%\r\n% A\r\n% <https:\/\/www.mathworks.com\/matlabcentral\/answers\/644365-how-to-transform-a-rgb-matrix-to-a-weighted-adjacency-matrix\r\n% question on MATLAB Answers> caught my eye earlier today. Borys has this\r\n% pseudocolor image of a weighted adjacency matrix:\r\n%\r\n% <<https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410520\/image.png>>\r\n%\r\n% And he has this image of the color scale:\r\n%\r\n% <<https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410525\/image.png>>\r\n%\r\n% Borys wants to know how to compute the real adjacency matrix from this\r\n% image, knowing that the color scale represents the range [0,5].\r\n% \r\n% The problem looked interesting to me, and I wanted to give it a try.\r\n\r\nimage_url = \"https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410520\/image.png\";\r\nscale_url = \"https:\/\/www.mathworks.com\/matlabcentral\/answers\/uploaded_files\/410525\/image.png\";\r\n\r\nA = imread(image_url);\r\n\r\n%%\r\n% Here is my planned approach:\r\n%\r\n% 1. Use the white squares to determine the grid of coordinates for the\r\n% cells of the adjacency matrix.\r\n%\r\n% 2. Extract the colors from the locations found in step 1.\r\n%\r\n% 3. For each extracted color, find the minimum color difference betweent\r\n% that color and the colors in the scale.\r\n%\r\n% 4. Use linear interpolation to determine the weight value (between 0 and\r\n% 5) for the corresponding adjacency matrix cell.\r\n%\r\n% The first thing I did was to use the Pixel Region tool to check if the\r\n% white squares are exactly white ([255,255,255]).\r\n%\r\n%  imshow(A)\r\n%  impixelregion\r\n%\r\n% <<https:\/\/blogs.mathworks.com\/steve\/files\/impixelregion-screen-shot.png>>\r\n%\r\n% And they are.\r\n%\r\n% Next, I'm going to compute a mask image corresponding to just the white\r\n% squares.\r\n\r\nmask = all(rgb == reshape([255 255 255],1,1,3),3);\r\nimshow(mask)\r\n\r\n%%\r\n% At a quick glance, it appears that the white squares aren't touching each\r\n% other, but just to sure, let's erode them a bit.\r\n\r\nmask2 = imerode(mask,ones(3,3));\r\nimshow(mask2)\r\n\r\n%%\r\n% Now, let's find the center of each white square.\r\n\r\nT = regionprops(\"table\",mask2,\"Centroid\");\r\nhead(T)\r\n\r\n%%\r\n% Round the centroid coordinates to integer locations.\r\nm = round(T.Centroid(:,2));\r\nn = round(T.Centroid(:,1));\r\n\r\n%%\r\n% Let's double-check our locations\r\nfigure\r\nimshow(A)\r\naxis([n(1) n(6) m(1) m(6)])\r\nhold on\r\nfor q = 2:5\r\n    for r = 2:5\r\n        x = n(r);\r\n        y = m(q);\r\n        plot(x,y,\"o\",\"MarkerFaceColor\",\"w\",\"MarkerEdgeColor\",\"k\")\r\n        text(x,y,\"(\" + q + \",\" + r + \")\",...\r\n            \"HorizontalAlignment\",\"center\",...\r\n            \"VerticalAlignment\",\"bottom\")\r\n    end\r\nend\r\nhold off\r\n\r\n%%\r\n% That looks good. Now, let's extract one of the colors and match it to a\r\n% location on the scale.\r\n%\r\n% Read the scale image and change it into an Mx3 matrix of color values.\r\n\r\nscale = imread(scale_url);\r\nscale = scale(1,:,:);\r\nscale = reshape(scale,[],3);\r\n\r\n%%\r\n% Grab the color in the (5,3) cell of the adjacency matrix image.\r\nc = A(m(5),n(3),:);\r\n\r\n%%\r\n% Display the color\r\nimage(c)\r\n\r\n%%\r\n% Now find the closest match in the color scale. I'll use |deltaE|, which I\r\n% described in my <https:\/\/blogs.mathworks.com\/steve\/2020\/10\/30\/how-to-compute-perceptual-color-difference\/ \r\n% 30-Oct-2020 post>.\r\n\r\nc = reshape(c,1,3);\r\nD = deltaE(repmat(c,size(scale,1),1), scale);\r\n\r\n%%\r\n% Let's double-check our work at this point. I'm expecting there to be a\r\n% clear minimum, which identifies the closest color on the scale. Is that\r\n% the case?\r\n\r\nplot(D)\r\n\r\n%%\r\n% That looks good. \r\n%\r\n% What is the minimum value of |D|, and where does it occur?\r\n\r\n[D_min,idx] = min(D);\r\nD_min\r\n\r\n%%\r\nidx\r\n\r\n%%\r\n% Now we can use interpolation to get the adjacency cell weight for this\r\n% cell. Recall that the weights range from 0 to 5.\r\n\r\nw = interp1([1 size(scale,1)], [0 5], idx)\r\n\r\n%%\r\n% We have all the pieces of the computation. Now, let's figure out the\r\n% weight for every cell.\r\n\r\nfor q = 1:length(m)\r\n    for r = 1:length(n)\r\n        % Skip the cells on the diagonal.\r\n        if q ~= r\r\n            c = A(m(q),n(r),:);\r\n            c = reshape(c,1,3);\r\n            c = repmat(c,size(scale,1),1);\r\n            D = deltaE(c,scale);\r\n            [D_min,idx] = min(D);\r\n            Aw(q,r) = interp1([1 size(scale,1)],[0 5],idx);\r\n        end\r\n    end\r\nend\r\n\r\n%%\r\n% Finally, we can display our computed weighted adjacency matrix. Except\r\n% for the diagonal elements, which will display as blue using this\r\n% procedure, it should look very similar to the image we started with.\r\nimshow(Aw,[])\r\ncolormap(scale)\r\n\r\n%%\r\n% That was fun!\r\n##### SOURCE END ##### d4c14ff517e24295989482ae2d0214e6\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/image_to_adjacency_matrix_03.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>A question on MATLAB Answers caught my eye earlier today. Borys has this pseudocolor image of a weighted adjacency matrix: And he has this image of the color scale: Borys wants to know how to compute... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2020\/11\/11\/transforming-a-color-image-to-a-weighted-adjacency-matrix\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":4250,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4244"}],"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=4244"}],"version-history":[{"count":5,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4244\/revisions"}],"predecessor-version":[{"id":4266,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/4244\/revisions\/4266"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/4250"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=4244"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=4244"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=4244"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}