好久之前,当我第一次看到这个算法时候,我就爱上它了,那个时候我不懂什么是高
斯金字塔,但是我知道埃及有金字塔。一番研究之后,搞懂了什么是图像金字塔于是
我写了一篇文章在我的博客上,可以看这里:
http://blog.csdn.net/jia20003/article/details/9116931
但是金字塔有什么应用呢,可能最广泛的一个应用就是实现图像融合和图像的无缝
拼接于是我决定在研究一番,于是就有了这篇文章。好了不废话了。算法需要三张
图片,两张图片是需要拼接的图片,最后一张是面罩图片,为什么需要后面我会解释
面罩图片就是选取待拼接两张图片的目标边缘部分,多少合适根据需要。Demo演示
我是各占原图的1/2这样省事。
算法大致的步骤可以分为如下几步:
1.对两张待拼接的图片分别生成DOG,关于什么是DOG,怎么生成,如果不知道
一定要看看这里:
2.对面罩图片(mask image)完成高斯金字塔,层数要跟DOG层数相同。
3. 根据面罩图片的权重,拼接两张图片的DOG,生成一个DOG图片
4.用生成的DOG图片与maskimage 金字塔expand生成的图片相加得到每层,把每一次
叠加得到最后输出图片。
基于高斯金字塔图像融合的原理:
懂得高斯金字塔DOG的生成原理都明白,如果把金字塔reduce与expand的结果相减则
得到DOG,而如果把expand结果与DOG结果相加则得到reduce处理后的图像,因为
reduce图像是间隔采样生成原图,而高斯金字塔融合正是巧妙的利用了这点。
关键代码解释:
实现目标图像DOG提取代码如下,默认情况下是三层:
PyramidBlendProcessor pyramid = new PyramidBlendProcessor(image1, image2, maskImage); BufferedImage[] image1Lapls = pyramid.getLaplacianPyramid(pyramid.pyramidDown(image1)); BufferedImage[] image2Lapls = pyramid.getLaplacianPyramid(pyramid.pyramidDown(image2)); BufferedImage[] maskPyramid = pyramid.pyramidDown(maskImage);
依靠mask权重实现两个目标图像DOG按层融合的代码如下:
public BufferedImage blendOneImage(BufferedImage image1, BufferedImage image2, BufferedImage maskImage, BufferedImage blendedImage) { int width = image1.getWidth(); int height = image1.getHeight(); if ( blendedImage == null ) blendedImage = createCompatibleDestImage( maskImage, null ); int[] image1Pixels = new int[width*height]; int[] image2Pixels = new int[width*height]; int[] maskPixels = new int[width*height]; int[] outPixels = new int[width*height]; getRGB( image1, 0, 0, width, height, image1Pixels ); getRGB( image2, 0, 0, width, height, image2Pixels ); getRGB( maskImage, 0, 0, width, height, maskPixels ); int index = 0; float mr = 0, mg = 0, mb = 0; for(int row=0; row<height; row++) { int ta1 = 0, tr1 = 0, tg1 = 0, tb1 = 0; int ta2 = 0, tr2 = 0, tg2 = 0, tb2 = 0; int ta3 = 0, tr3 = 0, tg3 = 0, tb3 = 0; for(int col=0; col<width; col++) { index = row * width + col; ta1 = (image1Pixels[index] >> 24) & 0xff; tr1 = (image1Pixels[index] >> 16) & 0xff; tg1 = (image1Pixels[index] >> 8) & 0xff; tb1 = image1Pixels[index] & 0xff; ta2 = (image2Pixels[index] >> 24) & 0xff; tr2 = (image2Pixels[index] >> 16) & 0xff; tg2 = (image2Pixels[index] >> 8) & 0xff; tb2 = image2Pixels[index] & 0xff; ta3 = (maskPixels[index] >> 24) & 0xff; tr3 = (maskPixels[index] >> 16) & 0xff; tg3 = (maskPixels[index] >> 8) & 0xff; tb3 = maskPixels[index] & 0xff; mr = tr3 / 255.0f; mg = tg3 / 255.0f; mb = tb3 / 255.0f; int br = (int)(mr * tr2 + (1.0f - mr) * tr1); int bg = (int)(mg * tg2 + (1.0f - mr) * tg1); int bb = (int)(mb * tb2 + (1.0f - mr) * tb1); outPixels[index] = (ta1 << 24) | (clamp(br) << 16) | (clamp(bg) << 8) | clamp(bb); } } setRGB( blendedImage, 0, 0, width, height, outPixels ); return blendedImage; }
合并DOG融合每层图片与mask expand之后的代码如下:
BufferedImage[] image1Lapls = this.pyramidUp(this.pyramidDown(maskImg)); BufferedImage result = null; int size = blendResults.length; for(int i=size - 1; i>=0; i--) { if((i-1) < 0){ result = this.collapse(image1Lapls[i], blendResults[i]); } else { image1Lapls[i-1] = this.pyramidExpand(this.collapse(image1Lapls[i], blendResults[i]), image1Lapls[i-1].getWidth(), image1Lapls[i-1].getHeight()); } } // return image1Lapls[0]; return result;
图片一:
图片二:
mask图片:一半是黑色一半是白色
最终效果如下:
完全源代码:
package com.gloomyfish.image.pyramid.blend; import java.awt.image.BufferedImage; import com.gloomyfish.image.pyramid.PyramidAlgorithm; public class PyramidBlendProcessor extends PyramidAlgorithm { BufferedImage image1; BufferedImage image2; BufferedImage maskImg; public PyramidBlendProcessor(BufferedImage image1, BufferedImage image2, BufferedImage maskImage) { this.image1 = image1; this.image2 = image2; this.maskImg = createMaskImage(image1, image2, maskImage); } public BufferedImage mergeMaskWithResult(BufferedImage[] blendResults) { BufferedImage[] image1Lapls = this.pyramidUp(this.pyramidDown(maskImg)); BufferedImage result = null; int size = blendResults.length; for(int i=size - 1; i>=0; i--) { if((i-1) < 0){ result = this.collapse(image1Lapls[i], blendResults[i]); } else { image1Lapls[i-1] = this.pyramidExpand(this.collapse(image1Lapls[i], blendResults[i]), image1Lapls[i-1].getWidth(), image1Lapls[i-1].getHeight()); } } // return image1Lapls[0]; return result; } private BufferedImage createMaskImage(BufferedImage image12, BufferedImage image22, BufferedImage maskImage) { int width = image1.getWidth(); int height = image1.getHeight(); BufferedImage realMaskImg = createCompatibleDestImage( image1, null ); int[] image1Pixels = new int[width*height]; int[] image2Pixels = new int[width*height]; int[] maskPixels = new int[width*height]; int[] outPixels = new int[width*height]; getRGB( image1, 0, 0, width, height, image1Pixels ); getRGB( image2, 0, 0, width, height, image2Pixels ); getRGB( maskImage, 0, 0, width, height, maskPixels ); int index = 0; for(int row=0; row<height; row++) { int ta1 = 0, tr1 = 0, tg1 = 0, tb1 = 0; int ta2 = 0, tr2 = 0, tg2 = 0, tb2 = 0; int ma = 0, mr = 0, mg = 0, mb = 0; for(int col=0; col<width; col++) { index = row * width + col; ta1 = (image1Pixels[index] >> 24) & 0xff; tr1 = (image1Pixels[index] >> 16) & 0xff; tg1 = (image1Pixels[index] >> 8) & 0xff; tb1 = image1Pixels[index] & 0xff; ta2 = (image2Pixels[index] >> 24) & 0xff; tr2 = (image2Pixels[index] >> 16) & 0xff; tg2 = (image2Pixels[index] >> 8) & 0xff; tb2 = image2Pixels[index] & 0xff; ma = (maskPixels[index] >> 24) & 0xff; mr = (maskPixels[index] >> 16) & 0xff; mg = (maskPixels[index] >> 8) & 0xff; mb = maskPixels[index] & 0xff; if(mr < 127) { outPixels[index] = (ta1 << 24) | (tr1 << 16) | (tg1 << 8) | tb1; } else if(mr >=127) { outPixels[index] = (ta2 << 24) | (tr2 << 16) | (tg2 << 8) | tb2; } else { //outPixels[index] = (ta2 << 24) | (mr << 16) | (mg << 8) | mb; } } } setRGB( realMaskImg, 0, 0, width, height, outPixels ); return realMaskImg; } }
PyramidAlgorithm代码参见这里:
http://blog.csdn.net/jia20003/article/details/9116931
UI部分的代码如下:
package com.gloomyfish.image.pyramid.blend; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; public class MainUI extends JComponent implements ActionListener { /** * */ private JButton blendBtn; private Dimension mySize; private BufferedImage resultImage; private BufferedImage maskImage; private BufferedImage image1; private BufferedImage image2; private static final long serialVersionUID = 1L; public MainUI(BufferedImage image1, BufferedImage image2, BufferedImage maskImage) { JPanel btnPanel = new JPanel(); btnPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); blendBtn = new JButton("Pyramid Blend"); blendBtn.addActionListener(this); btnPanel.add(blendBtn); mySize = new Dimension(600, 600); this.image1 = image1; this.image2 = image2; this.maskImage = maskImage; JFrame mainFrame = new JFrame("Pyramid Blend Demo - Gloomyfish"); mainFrame.getContentPane().setLayout(new BorderLayout()); mainFrame.getContentPane().add(this, BorderLayout.CENTER); mainFrame.getContentPane().add(btnPanel, BorderLayout.SOUTH); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainFrame.pack(); mainFrame.setVisible(true); } @Override public Dimension getPreferredSize() { return mySize; } @Override public void actionPerformed(ActionEvent e) { // get two DOG image PyramidBlendProcessor pyramid = new PyramidBlendProcessor(image1, image2, maskImage, 4); BufferedImage[] image1Lapls = pyramid.getLaplacianPyramid(pyramid.pyramidDown(image1)); BufferedImage[] image2Lapls = pyramid.getLaplacianPyramid(pyramid.pyramidDown(image2)); BufferedImage[] maskPyramid = pyramid.pyramidDown(maskImage); maskPyramid = pyramid.pyramidUp(maskPyramid); System.out.println("End first step......"); // get mask pyramid // blend them by level from top to bottom BufferedImage[] blendImages = new BufferedImage[image1Lapls.length]; for(int i=0; i<blendImages.length; i++) { blendImages[i] = pyramid.blendOneImage(image1Lapls[i], image2Lapls[i], maskPyramid[i], null); } // collapse them // BufferedImage[] cImages = new BufferedImage[blendImage.length-1]; // for(int i=(blendImage.length-1); i>0; i--) // { // BufferedImage destImage = pyramid.pyramidExpand(blendImage[i], blendImage[i-1].getWidth(), blendImage[i-1].getHeight()); // cImages[i-1] = pyramid.collapse(destImage, blendImage[i-1]); // } // resultImage = cImages[0]; resultImage = pyramid.mergeMaskWithResult(blendImages); // for(int i=cImages.length - 1; i>0; i--) // { // BufferedImage destImage = pyramid.pyramidExpand(cImages[i], cImages[i-1].getWidth(), cImages[i-1].getHeight()); // resultImage = pyramid.collapse(destImage, cImages[i-1]); // } repaint(); } @Override protected void paintComponent(Graphics g) { if(resultImage != null) { g.drawImage(resultImage, 10, 10, resultImage.getWidth(), resultImage.getHeight(), null); } super.paintComponent(g); } public static void main(String[] args) { try { File f2 = new File("D:\resource\orange.jpg"); File f1 = new File("D:\resource\apple.jpg"); File f3 = new File("D:\resource\mask512.jpg"); BufferedImage image1 = ImageIO.read(f1); BufferedImage image2 = ImageIO.read(f2); BufferedImage maskImg = ImageIO.read(f3); new MainUI(image1, image2, maskImg); } catch (IOException e1) { e1.printStackTrace(); } } }
代码写的比较乱,做的时候就是希望快点看到效果,可读性不是很好,可能需要
整一下代码,但是内容绝对值得一读,希望得到大家的支持。
层数越多,融合效果越好,当然计算时间也越长!
转载请务必注明,谢谢!