{"id":3229,"date":"2019-04-16T19:15:57","date_gmt":"2019-04-16T23:15:57","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=3229"},"modified":"2019-11-01T22:39:53","modified_gmt":"2019-11-02T02:39:53","slug":"multiresolution-pyramids-part-3-laplacian-pyramids","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/16\/multiresolution-pyramids-part-3-laplacian-pyramids\/","title":{"rendered":"Multiresolution pyramids part 3: Laplacian pyramids"},"content":{"rendered":"<div class=\"content\"><p>In my <a href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/02\/multiresolution-image-pyramids-and-impyramid-part-1\/\">April 2 post<\/a>, I introduced <i>multiresolution pyramids<\/i>, and I explained my dissatisfaction with the function <tt>impyramid<\/tt>, which (sadly) I designed. (Aside: I had that post ready to go on April 1, but I decided that maybe I should wait a day to publish it.) I followed that up on <a href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/09\/multiresolution-image-pyramids-and-impyramid-part-2\/\">April 9<\/a> with an alternative computation method and interface for computing such a pyramid.<\/p><p>Today, I'll introduce a variation called the <i>Laplacian pyramid<\/i>. While the ordinary multiresolution pyramid contains a bunch of images that have been successively lowpass filtered and downsampled, the Laplacian pyramid contains a bunch of images that have essentially been bandpass filtered and downsampled. Burt and Adelson described the Laplacian pyramid as a data structure useful for image compression in \"The Laplacian Pyramid as a Compact Image Code,\" IEEE Transactions on Communications, vol. COM-31, no. 4, April 1983, pp. 532-540.<\/p><p>The i-th image in the Laplacian pyramid is computed by taking the (i+1)-st image in the multiresolution pyramid, expanding it by a factor of 2, and subtracting the result from the i-th multiresolution pyramid image. The only thing to be particulary careful about is to handle the case where the level i image is not exactly twice the size of the level (i+1) image. If you compute the multiresolution pyramid the way I did last time, that can only happen between the first two levels.<\/p><p>Let's try it on the image I used last time.<\/p><pre class=\"codeinput\">A = imread(<span class=\"string\">'https:\/\/blogs.mathworks.com\/steve\/files\/IMG_9968.jpg'<\/span>);\r\nimshow(A)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_3_01.png\" alt=\"\"> <p>First, we need to compute the multiresolution pyramid using the function I presented previously.<\/p><pre class=\"codeinput\">mrp = multiresolutionPyramid(im2double(A));\r\n<\/pre><p>Now we follow the computational recipe that I described in words above.<\/p><pre class=\"codeinput\">lapp = cell(size(mrp));\r\nnum_levels = numel(mrp);\r\nlapp{num_levels} = mrp{num_levels};\r\n<span class=\"keyword\">for<\/span> k = 1:(num_levels - 1)\r\n   A = mrp{k};\r\n   B = imresize(mrp{k+1},2,<span class=\"string\">'lanczos3'<\/span>);\r\n   [M,N,~] = size(A);\r\n   lapp{k} = A - B(1:M,1:N,:);\r\n<span class=\"keyword\">end<\/span>\r\nlapp{end} = mrp{end};\r\n<\/pre><p>Another function, <tt>showLaplacianPyramid<\/tt> (also at the end of this script), visualizes the pyramid.<\/p><pre class=\"codeinput\">showLaplacianPyramid(lapp)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_3_02.png\" alt=\"\"> <p>Given a Laplacian pyramid, we can reconstruct the original image by reversing the recipe above.<\/p><pre class=\"codeinput\">num_levels = numel(lapp);\r\nB = lapp{end};\r\n<span class=\"keyword\">for<\/span> k = (num_levels - 1) : -1 : 1\r\n   B = imresize(B,2,<span class=\"string\">'lanczos3'<\/span>);\r\n   Lk = lapp{k};\r\n   [M,N,~] = size(Lk);\r\n   B = B(1:M,1:N,:) + Lk;\r\n<span class=\"keyword\">end<\/span>\r\nimshow(B)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_3_03.png\" alt=\"\"> <p>See the function <tt>reconstructFromLaplacianPyramid<\/tt> below.<\/p><p>In the Burt and Adelson paper, a key idea was to quantize the level images in the Laplacian pyramid so that they could be stored using fewer bits per pixel (on average). To simulate the effect, I'll round each pixel in the Laplacian level images (except the last one) to the nearest 0.1 (in a normalized range) and reconstruct from that.<\/p><pre class=\"codeinput\"><span class=\"keyword\">for<\/span> k = 1:(numel(lapp) - 1)\r\n   lapp_quantized{k} = round(lapp{k},1);\r\n<span class=\"keyword\">end<\/span>\r\nlapp_quantized{numel(lapp)} = lapp{end};\r\n\r\nB_coded = reconstructFromLaplacianPyramid(lapp_quantized);\r\nimshow(B_coded)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_3_04.png\" alt=\"\"> <p>You can see the distortions resulting from this crude quantization method, but the overall image is surprisingly recognizable and even detailed considering how much information we tossed away.<\/p><p>Next time, I plan to discuss the application that I was originally interested when I started writing this series on multiresolution pyramids: image blending.<\/p><pre class=\"codeinput\"><span class=\"keyword\">function<\/span> lapp = laplacianPyramid(mrp)\r\n\r\n<span class=\"comment\">% Steve Eddins<\/span>\r\n<span class=\"comment\">% MathWorks<\/span>\r\n\r\nlapp = cell(size(mrp));\r\nnum_levels = numel(mrp);\r\nlapp{num_levels} = mrp{num_levels};\r\n<span class=\"keyword\">for<\/span> k = 1:(num_levels - 1)\r\n   A = mrp{k};\r\n   B = imresize(mrp{k+1},2,<span class=\"string\">'lanczos3'<\/span>);\r\n   [M,N,~] = size(A);\r\n   lapp{k} = A - B(1:M,1:N,:);\r\n<span class=\"keyword\">end<\/span>\r\nlapp{end} = mrp{end};\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> showLaplacianPyramid(p)\r\n\r\n<span class=\"comment\">% Steve Eddins<\/span>\r\n<span class=\"comment\">% MathWorks<\/span>\r\n\r\nM = size(p{1},1);\r\nN = size(p{1},2);\r\n\r\nstretch_factor = 3;\r\n\r\n<span class=\"keyword\">for<\/span> k = 1:numel(p)\r\n    Mk = size(p{k},1);\r\n    Nk = size(p{k},2);\r\n    Mpad1 = ceil((M - Mk)\/2);\r\n    Mpad2 = M - Mk - Mpad1;\r\n    Npad1 = ceil((N - Nk)\/2);\r\n    Npad2 = N - Nk - Npad1;\r\n\r\n    <span class=\"keyword\">if<\/span> (k &lt; numel(p))\r\n        pad_value = -0.1\/stretch_factor;\r\n    <span class=\"keyword\">else<\/span>\r\n        pad_value = 0.4;\r\n    <span class=\"keyword\">end<\/span>\r\n    A = p{k};\r\n    A = padarray(A,[Mpad1 Npad1],pad_value,<span class=\"string\">'pre'<\/span>);\r\n    A = padarray(A,[Mpad2 Npad2],pad_value,<span class=\"string\">'post'<\/span>);\r\n    p{k} = A;\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">for<\/span> k = 1:(numel(p)-1)\r\n    p{k} = (stretch_factor*p{k} + 0.5);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nimshow(imtile(p,<span class=\"string\">'GridSize'<\/span>,[NaN 2],<span class=\"keyword\">...<\/span>\r\n    <span class=\"string\">'BorderSize'<\/span>,20,<span class=\"string\">'BackgroundColor'<\/span>,[0.3 0.3 0.3]))\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<span class=\"keyword\">function<\/span> out = reconstructFromLaplacianPyramid(lapp)\r\n\r\n<span class=\"comment\">% Steve Eddins<\/span>\r\n<span class=\"comment\">% MathWorks<\/span>\r\n\r\nnum_levels = numel(lapp);\r\nout = lapp{end};\r\n<span class=\"keyword\">for<\/span> k = (num_levels - 1) : -1 : 1\r\n   out = imresize(out,2,<span class=\"string\">'lanczos3'<\/span>);\r\n   g = lapp{k};\r\n   [M,N,~] = size(g);\r\n   out = out(1:M,1:N,:) + g;\r\n<span class=\"keyword\">end<\/span>\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre><script language=\"JavaScript\"> <!-- \r\n    function grabCode_225850e75a3a4842b2b1b24fae9eeb33() {\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='225850e75a3a4842b2b1b24fae9eeb33 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 225850e75a3a4842b2b1b24fae9eeb33';\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 2019 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_225850e75a3a4842b2b1b24fae9eeb33()\"><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; R2019a<br><\/p><\/div><!--\r\n225850e75a3a4842b2b1b24fae9eeb33 ##### SOURCE BEGIN #####\r\n%%\r\n% In my\r\n% <https:\/\/blogs.mathworks.com\/steve\/2019\/04\/02\/multiresolution-image-pyramids-and-impyramid-part-1\/\r\n% April 2 post>, I introduced _multiresolution pyramids_, and I\r\n% explained my dissatisfaction with the function |impyramid|, which\r\n% (sadly) I designed. (Aside: I had that post ready to go on April 1,\r\n% but I decided that maybe I should wait a day to publish it.) I\r\n% followed that up on\r\n% <https:\/\/blogs.mathworks.com\/steve\/2019\/04\/09\/multiresolution-image-pyramids-and-impyramid-part-2\/\r\n% April 9> with an alternative computation method and interface for\r\n% computing such a pyramid.\r\n%\r\n% Today, I'll introduce a variation called the _Laplacian pyramid_.\r\n% While the ordinary multiresolution pyramid contains a bunch of images\r\n% that have been successively lowpass filtered and downsampled, the\r\n% Laplacian pyramid contains a bunch of images that have essentially\r\n% been bandpass filtered and downsampled. Burt and Adelson described the\r\n% Laplacian pyramid as a data structure useful for image compression in\r\n% \"The Laplacian Pyramid as a Compact Image Code,\" IEEE Transactions on\r\n% Communications, vol. COM-31, no. 4, April 1983, pp. 532-540.\r\n%\r\n% The i-th image in the Laplacian pyramid is computed by taking the\r\n% (i+1)-st image in the multiresolution pyramid, expanding it by a\r\n% factor of 2, and subtracting the result from the i-th multiresolution\r\n% pyramid image. The only thing to be particulary careful about is to\r\n% handle the case where the level i image is not exactly twice the size\r\n% of the level (i+1) image. If you compute the multiresolution pyramid\r\n% the way I did last time, that can only happen between the first two\r\n% levels.\r\n%\r\n% Let's try it on the image I used last time.\r\n\r\nA = imread('https:\/\/blogs.mathworks.com\/steve\/files\/IMG_9968.jpg');\r\nimshow(A)\r\n\r\n%%\r\n% First, we need to compute the multiresolution pyramid using the\r\n% function I presented previously.\r\n\r\nmrp = multiresolutionPyramid(im2double(A));\r\n\r\n%%\r\n% Now we follow the computational recipe that I described in words\r\n% above.\r\n\r\nlapp = cell(size(mrp));\r\nnum_levels = numel(mrp);\r\nlapp{num_levels} = mrp{num_levels};\r\nfor k = 1:(num_levels - 1)\r\n   A = mrp{k};\r\n   B = imresize(mrp{k+1},2,'lanczos3');\r\n   [M,N,~] = size(A);\r\n   lapp{k} = A - B(1:M,1:N,:);\r\nend\r\nlapp{end} = mrp{end};\r\n\r\n%%\r\n% Another function, |showLaplacianPyramid| (also at the end of this\r\n% script), visualizes the pyramid.\r\n\r\nshowLaplacianPyramid(lapp)\r\n\r\n%%\r\n% Given a Laplacian pyramid, we can reconstruct the original image by\r\n% reversing the recipe above. \r\n\r\nnum_levels = numel(lapp);\r\nB = lapp{end};\r\nfor k = (num_levels - 1) : -1 : 1\r\n   B = imresize(B,2,'lanczos3');\r\n   Lk = lapp{k};\r\n   [M,N,~] = size(Lk);\r\n   B = B(1:M,1:N,:) + Lk;\r\nend\r\nimshow(B)\r\n\r\n%%\r\n% See the function |reconstructFromLaplacianPyramid| below.\r\n%\r\n% In the Burt and Adelson paper, a key idea was to quantize the level\r\n% images in the Laplacian pyramid so that they could be stored using\r\n% fewer bits per pixel (on average). To simulate the effect, I'll round\r\n% each pixel in the Laplacian level images (except the last one) to the\r\n% nearest 0.1 (in a normalized range) and reconstruct from that.\r\n\r\nfor k = 1:(numel(lapp) - 1)\r\n   lapp_quantized{k} = round(lapp{k},1);\r\nend\r\nlapp_quantized{numel(lapp)} = lapp{end};\r\n\r\nB_coded = reconstructFromLaplacianPyramid(lapp_quantized);\r\nimshow(B_coded)\r\n\r\n%%\r\n% You can see the distortions resulting from this crude quantization\r\n% method, but the overall image is surprisingly recognizable and even\r\n% detailed considering how much information we tossed away.\r\n%\r\n% Next time, I plan to discuss the application that I was originally\r\n% interested when I started writing this series on multiresolution\r\n% pyramids: image blending.\r\n\r\n%%\r\nfunction lapp = laplacianPyramid(mrp)\r\n\r\n% Steve Eddins\r\n% MathWorks\r\n\r\nlapp = cell(size(mrp));\r\nnum_levels = numel(mrp);\r\nlapp{num_levels} = mrp{num_levels};\r\nfor k = 1:(num_levels - 1)\r\n   A = mrp{k};\r\n   B = imresize(mrp{k+1},2,'lanczos3');\r\n   [M,N,~] = size(A);\r\n   lapp{k} = A - B(1:M,1:N,:);\r\nend\r\nlapp{end} = mrp{end};\r\nend\r\n\r\nfunction showLaplacianPyramid(p)\r\n\r\n% Steve Eddins\r\n% MathWorks\r\n\r\nM = size(p{1},1);\r\nN = size(p{1},2);\r\n\r\nstretch_factor = 3;\r\n\r\nfor k = 1:numel(p)\r\n    Mk = size(p{k},1);\r\n    Nk = size(p{k},2);\r\n    Mpad1 = ceil((M - Mk)\/2);\r\n    Mpad2 = M - Mk - Mpad1;\r\n    Npad1 = ceil((N - Nk)\/2);\r\n    Npad2 = N - Nk - Npad1;    \r\n\r\n    if (k < numel(p))\r\n        pad_value = -0.1\/stretch_factor;\r\n    else\r\n        pad_value = 0.4;\r\n    end\r\n    A = p{k};\r\n    A = padarray(A,[Mpad1 Npad1],pad_value,'pre');\r\n    A = padarray(A,[Mpad2 Npad2],pad_value,'post');\r\n    p{k} = A;\r\nend\r\n\r\nfor k = 1:(numel(p)-1)\r\n    p{k} = (stretch_factor*p{k} + 0.5);\r\nend\r\n\r\nimshow(imtile(p,'GridSize',[NaN 2],...\r\n    'BorderSize',20,'BackgroundColor',[0.3 0.3 0.3]))\r\nend\r\n\r\nfunction out = reconstructFromLaplacianPyramid(lapp)\r\n\r\n% Steve Eddins\r\n% MathWorks\r\n\r\nnum_levels = numel(lapp);\r\nout = lapp{end};\r\nfor k = (num_levels - 1) : -1 : 1\r\n   out = imresize(out,2,'lanczos3');\r\n   g = lapp{k};\r\n   [M,N,~] = size(g);\r\n   out = out(1:M,1:N,:) + g;\r\nend\r\nend\r\n\r\n##### SOURCE END ##### 225850e75a3a4842b2b1b24fae9eeb33\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_3_02.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>In my April 2 post, I introduced multiresolution pyramids, and I explained my dissatisfaction with the function impyramid, which (sadly) I designed. (Aside: I had that post ready to go on April 1,... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/16\/multiresolution-pyramids-part-3-laplacian-pyramids\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":3231,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[539,504,76,156,36,1235,575,162,344,188,190],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3229"}],"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=3229"}],"version-history":[{"count":3,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3229\/revisions"}],"predecessor-version":[{"id":3243,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3229\/revisions\/3243"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/3231"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=3229"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=3229"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=3229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}