{"id":3269,"date":"2019-05-20T07:00:21","date_gmt":"2019-05-20T11:00:21","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=3269"},"modified":"2024-01-01T16:38:09","modified_gmt":"2024-01-01T21:38:09","slug":"multiresolution-pyramids-part-4-image-blending","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2019\/05\/20\/multiresolution-pyramids-part-4-image-blending\/","title":{"rendered":"Multiresolution pyramids part 4: Image blending"},"content":{"rendered":"<div class=\"content\">\r\n\r\nToday I want to wrap up (for now) my series on multiresolution pyramids by showing you how to make this strange-looking fruit:\r\n\r\n<img decoding=\"async\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/orange_apple_core.jpg\" hspace=\"5\" vspace=\"5\" \/>\r\n\r\nThis a slightly nontraditional demonstration of a technique called image blending. I first considered writing a blog about image blending when I saw an example recently that was posted inside MathWorks. I asked the engineer who wrote the example, Steve Kuznicki, for more information about the example, and he shared his method and code with me.\r\n\r\nThe method for blending two images together was based on using Laplacian pyramids. The earliest reference I know about is Burt and Adelson, \"A Multiresolution Spline with Application to Image Mosaics,\" <i>ACM Transactions on Graphics<\/i>, vol. 2, no. 4, October 1983, pp. 217-236.\r\n\r\nThe code, though, looked a little complicated. I realized that the code was complicated because of underlying limitations in the functional design of <tt>impyramid<\/tt>, which I wrote about earlier in the series (<a href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/02\/multiresolution-image-pyramids-and-impyramid-part-1\/\">02-Apr-2019<\/a> and <a href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/04\/09\/multiresolution-image-pyramids-and-impyramid-part-2\/\">09-Apr-2019<\/a>).\r\n\r\nSo, instead of jumping right into image blending, I spent some time first writing about <a href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/03\/17\/a-new-image-tiling-function-in-matlab\/\">image tiling<\/a> and revised interfaces for computing and visualization multiresolution and Laplacian pyramids.\r\n\r\nNow, I'm ready to come back to image blending.\r\n\r\nBecause, I think, of the influence of the Burt and Adelson paper, it has become common to demonstrate the technique by blending images of an apple and orange, with the seam between the two images split right down the middle. I found a couple of public-domain images (<a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:Big_red_apple.jpg\">Big red apple<\/a> and <a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:20140119Hockenheim1.jpg\">20140119Hockenheim1<\/a>) to use for a demonstration.\r\n<pre class=\"codeinput\">apple_url = <span class=\"string\">'https:\/\/blogs.mathworks.com\/steve\/files\/apple.jpg'<\/span>;\r\nA = im2double(imread(apple_url));\r\n\r\norange_url = <span class=\"string\">'https:\/\/blogs.mathworks.com\/steve\/files\/orange.jpg'<\/span>;\r\nB = im2double(imread(orange_url));\r\n\r\nsubplot(1,2,1)\r\nimshow(A)\r\nsubplot(1,2,2)\r\nimshow(B)\r\n<\/pre>\r\n<img decoding=\"async\" src=\"http:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_4_01.png\" alt=\"\" hspace=\"5\" vspace=\"5\" \/>\r\n\r\nNow I will make a mask image to define which part of the blended image will come from image A (the apple).\r\n<pre class=\"codeinput\">mask_A = zeros(1024,1024);\r\nmask_A(:,1:512) = 1;\r\n\r\nclf\r\nimshow(mask_A)\r\nxticks([])\r\nyticks([])\r\naxis <span class=\"string\">on<\/span>\r\n<\/pre>\r\n<img decoding=\"async\" src=\"http:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_4_02.png\" alt=\"\" hspace=\"5\" vspace=\"5\" \/>\r\n\r\nThe simplest way to blend the images is to just put the pieces together. We can do that with a little mask arithmetic.\r\n<pre class=\"codeinput\">C = (A .* mask_A) + (B .* (1 - mask_A));\r\nclf\r\nimshow(C)\r\nxticks([])\r\nyticks([])\r\n<\/pre>\r\n<img decoding=\"async\" src=\"http:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_4_03.png\" alt=\"\" hspace=\"5\" vspace=\"5\" \/>\r\n\r\nYou can see the sharp edge and contrast at the seam between the two images. The idea behind using Laplacian pyramids is to smooth the seam in a spatial-frequency-dependent way. Here is the procedure:\r\n<div>\r\n<ol>\r\n \t<li>Construct Laplacian pyramids for the two images.<\/li>\r\n \t<li>Make a third Laplacian pyramid that is contructed by a mask-based joining of the two original-image Laplacian pyramids at every pyramid level.<\/li>\r\n \t<li>Reconstruct the output image from the blended Laplacian pyramid.<\/li>\r\n<\/ol>\r\n<\/div>\r\nThe overall effect is to smooth low spatial frequencies at lot at the seam, but to smooth high spatial frequencies only a little. It's a clever, deceptively simple idea, really.\r\n\r\nHere's the first step. (Note: the multiresolution pyramid formed from the mask will be used to combine the Laplacian pyramids at each level.)\r\n<pre class=\"codeinput\">mrp_A = multiresolutionPyramid(A);\r\nmrp_B = multiresolutionPyramid(B);\r\nmrp_mask_A = multiresolutionPyramid(mask_A);\r\n\r\nlap_A = laplacianPyramid(mrp_A);\r\nlap_B = laplacianPyramid(mrp_B);\r\n<\/pre>\r\nNext, form the blended Laplacian pyramid.\r\n<pre class=\"codeinput\"><span class=\"keyword\">for<\/span> k = 1:length(lap_A)\r\n    lap_blend{k} = (lap_A{k} .* mrp_mask_A{k}) + <span class=\"keyword\">...<\/span>\r\n        (lap_B{k} .* (1 - mrp_mask_A{k}));\r\n<span class=\"keyword\">end<\/span>\r\n<\/pre>\r\nFinally, reconstruct the output image from the blended Laplacian pyramid. (The code for <tt>reconstructFromLaplacianPyramid<\/tt> is below.)\r\n<pre class=\"codeinput\">C_blended = reconstructFromLaplacianPyramid(lap_blend);\r\nimshow(C_blended)\r\n<\/pre>\r\n<img decoding=\"async\" src=\"http:\/\/blogs.mathworks.com\/steve\/files\/multiresolution_pyramids_4_04.png\" alt=\"\" hspace=\"5\" vspace=\"5\" \/>\r\n\r\nA side-by-side blend is boring, though. Note that the two regions in the mask can have any shape. Here's how I made the blended image that started this post. It is based on a mask with a circular region in the center.\r\n<pre class=\"codeinput\">x = linspace(-1,1,1024);\r\ny = x';\r\nmask2_A = hypot(x,y) &lt;= 0.5;\r\n\r\nmrp_mask2_A = multiresolutionPyramid(mask2_A);\r\n\r\n<span class=\"keyword\">for<\/span> k = 1:length(lap_A)\r\n    lap_blend2{k} = (lap_A{k} .* mrp_mask2_A{k}) + <span class=\"keyword\">...<\/span>\r\n        (lap_B{k} .* (1 - mrp_mask2_A{k}));\r\n<span class=\"keyword\">end<\/span>\r\n\r\nC2_blended = reconstructFromLaplacianPyramid(lap_blend2);\r\nimshow(C2_blended)\r\n<\/pre>\r\n<img decoding=\"async\" src=\"https:\/\/blogs.mathworks.com\/steve\/files\/orange_apple_core.jpg\" alt=\"\" hspace=\"5\" vspace=\"5\" \/>\r\n\r\nDoesn't that look yummy?\r\n\r\nFunctions used above:\r\n<pre class=\"codeinput\"><span class=\"keyword\">function<\/span> mrp = multiresolutionPyramid(A,num_levels)\r\n<span class=\"comment\">%multiresolutionPyramid(A,numlevels)<\/span>\r\n<span class=\"comment\">%   mrp = multiresolutionPyramid(A,numlevels) returns a multiresolution<\/span>\r\n<span class=\"comment\">%   pyramd from the input image, A. The output, mrp, is a 1-by-numlevels<\/span>\r\n<span class=\"comment\">%   cell array. The first element of mrp, mrp{1}, is the input image.<\/span>\r\n<span class=\"comment\">%<\/span>\r\n<span class=\"comment\">%   If numlevels is not specified, then it is automatically computed to<\/span>\r\n<span class=\"comment\">%   keep the smallest level in the pyramid at least 32-by-32.<\/span>\r\n\r\n<span class=\"comment\">%   Steve Eddins<\/span>\r\n<span class=\"comment\">%   Copyright The MathWorks, Inc. 2019<\/span>\r\n\r\nA = im2double(A);\r\n\r\nM = size(A,1);\r\nN = size(A,2);\r\n\r\n<span class=\"keyword\">if<\/span> nargin &lt; 2\r\n    lower_limit = 32;\r\n    num_levels = min(floor(log2([M N]) - log2(lower_limit))) + 1;\r\n<span class=\"keyword\">else<\/span>\r\n    num_levels = min(num_levels, min(floor(log2([M N]))) + 2);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nmrp = cell(1,num_levels);\r\n\r\nsmallest_size = [M N] \/ 2^(num_levels - 1);\r\nsmallest_size = ceil(smallest_size);\r\npadded_size = smallest_size * 2^(num_levels - 1);\r\n\r\nAp = padarray(A,padded_size - [M N],<span class=\"string\">'replicate'<\/span>,<span class=\"string\">'post'<\/span>);\r\n\r\nmrp{1} = Ap;\r\n<span class=\"keyword\">for<\/span> k = 2:num_levels\r\n    mrp{k} = imresize(mrp{k-1},0.5,<span class=\"string\">'lanczos3'<\/span>);\r\n<span class=\"keyword\">end<\/span>\r\n\r\nmrp{1} = A;\r\n<span class=\"keyword\">end<\/span>\r\n\r\n<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\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>\r\n<script language=\"JavaScript\"> <!-- \r\n    function grabCode_83a987aa809a4a9ea298912eac6adcd2() {\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='83a987aa809a4a9ea298912eac6adcd2 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 83a987aa809a4a9ea298912eac6adcd2';\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('<\/p>\r\n<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>\r\n<p>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script>\r\n<p style=\"text-align: right; font-size: xx-small; font-weight: lighter; font-style: italic; color: gray;\">\r\n\r\nPublished with MATLAB\u00ae R2019a<\/p>\r\n\r\n<\/div>\r\n\r\n<div class=\"pull-right\"><div class=\"col-xs-12 containing-block\"><a href=\"#\" class=\"btn btn-sm btn_color_blue add_margin_20  hidden-xs try_live_editor_example\" data-liveeditorexample=\"{\n  &quot;repository&quot; : &quot;Blogs&quot;,\n  &quot;id&quot; : &quot;\/steve\/files\/multiresolution_pyramids_4.mlx&quot;\n}\"><span class=\"icon-edit icon_16\"><\/span>Run in your browser<span style=\"color:grey\" class=\"series\"><\/span><\/a><\/div><\/div>","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/orange_apple_core.jpg\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>\r\n\r\nToday I want to wrap up (for now) my series on multiresolution pyramids by showing you how to make this strange-looking fruit:\r\n\r\n\r\n\r\nThis a slightly nontraditional demonstration of a technique... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/05\/20\/multiresolution-pyramids-part-4-image-blending\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":3263,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[50,539,504,178,252,334,390,76,156,36,705,32,1239,380,162,344,190,72,1249,1251,130],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3269"}],"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=3269"}],"version-history":[{"count":11,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3269\/revisions"}],"predecessor-version":[{"id":7479,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3269\/revisions\/7479"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/3263"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=3269"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=3269"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=3269"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}