背景:工作中我们经常需要将一张张的截图拼接成一整张图。但是因为图片顶部和底部tabBar的存在,我们没有办法直接拼接。下面我们利用opencv的方法实现图片的完美拼接。

素材图片:

java本地图片合并视频 java实现图片拼接_java本地图片合并视频

java本地图片合并视频 java实现图片拼接_java本地图片合并视频_02

java本地图片合并视频 java实现图片拼接_图像处理_03

基本算法:


1、需要拼接的图片需要有1/3重合的部分,否则无法拼接。所以在截图的时候需要尽量保证图片重合部分超过1/3,否则会拼接失败。 2、重点是需要找到从哪里开始拼接。人眼很容易找到拼接的地方,但是机器需要一个明确的坐标才能进行拼接,所以需要下面的算法来实现拼接。 3、从第一张图片的2/3处截取1/6的图片作为模版,这里最后留下1/6是因为部分截图有底部tabBar,需要排除。 4、用截取到的模版,在第二张图片中查找是否有这块,如果没有则两个图片无法拼接。找到之后返回坐标。 5、根据第四步返回的坐标,截取图片,然后两个图片拼接即可。


相关依赖:

<dependency>
          <groupId>org.bytedeco</groupId>
          <artifactId>javacv</artifactId>
          <version>1.5.7</version>
      </dependency>
      <dependency>
          <groupId>org.bytedeco</groupId>
          <artifactId>javacv-platform</artifactId>
          <version>1.5.7</version>
      </dependency>

这里是老的依赖版本,但是我这边都进行了验证是可以正常运行的。如果你需要升级新的版本,需要自己验证依赖是否冲突。

实现代码:

package com.test.image;

import org.apache.commons.collections.CollectionUtils;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Point;
import org.bytedeco.opencv.opencv_core.Rect;

import java.util.ArrayList;
import java.util.List;

import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
import static org.bytedeco.opencv.global.opencv_imgproc.*;


public class ImageMergeService {

    /**
     * 基础算法:
     *    1、需要拼接的图片需要有1/3重合的部分,否则无法拼接。
     *    2、重点是需要找到从哪里开始拼接。
     *    3、从第一张图片的2/3处截取1/6的图片作为模版,这里最后留下1/6是因为部分截图有底部tabBar,需要排除。
     *    4、用截取到的模版,在第二张图片中查找是否有这块,如果没有则两个图片无法拼接。找到之后返回坐标。
     *    5、根据第四步返回的坐标,截取图片,然后两个图片拼接即可。
     *
     * @param picMats
     */


    public static Mat mergePic(List<Mat> picMats ){

        if(CollectionUtils.isEmpty(picMats)){
            return null;
        }


        Mat resultMat = picMats.get(0);
        for( int i=1; i< picMats.size(); i++   ){

            resultMat = mergePic(resultMat,picMats.get(i));
        }

        return resultMat;
    }

    public static Mat mergePic( Mat baseMat, Mat targetMat ){

        if(baseMat.size().height()<targetMat.size().height()){
            return null;
        }

        if(baseMat.size().width()!=targetMat.size().width()){
            return null;
        }

        int height = targetMat.size().height();
        int startHeight = (int)(height*0.7);
        int templateHeight = height/6;

        Mat template  = new Mat(baseMat, new Rect(0, baseMat.size().height()+startHeight-targetMat.size().height(), baseMat.size().width(), templateHeight));



        imwrite( "template.png",template);


        Mat sameMat = targetMat.clone();

        matchTemplate(targetMat,template,sameMat,TM_CCOEFF_NORMED );
        DoublePointer minVal = new DoublePointer();
        DoublePointer maxVal = new DoublePointer();
        Point minLoc = new Point();
        Point maxLoc = new Point();

        minMaxLoc(sameMat,minVal,maxVal,minLoc,maxLoc,null);

        System.out.println(sameMat.ptr(maxLoc.x(),maxLoc.y()));
        System.out.println(maxVal);
        System.out.println(maxLoc.y());

        if(maxLoc.y()>targetMat.size().height()-templateHeight){
            System.out.println("没有找到重合的部分!");
            return baseMat;
        }

        Mat findTemplate  = new Mat(targetMat, new Rect(0,maxLoc.y(), targetMat.size().width(), templateHeight));

        double diff = compareImage(template,findTemplate);

        System.out.println(diff);

        if(diff<0.999){
            System.out.println("没有找到重合的部分!");
            return baseMat;
        }

        Mat topMat = new Mat(baseMat,new Rect(0,         0, baseMat.size().width(),baseMat.size().height()+startHeight-targetMat.size().height() ));

        Mat tailMat  = new Mat(targetMat, new Rect(0,         maxLoc.y(), targetMat.size().width(), targetMat.size().height()-maxLoc.y()));

        Mat resultMat = baseMat.clone();

        vconcat(topMat, tailMat, resultMat);

        imwrite( "resultMat.png",resultMat);

        return resultMat;
    }

    public static double compareImage( Mat targetImage, Mat baseImage ){

        Mat targetImageClone = targetImage.clone();
        Mat baseImageColne = baseImage.clone();
        Mat imgDiff1 = targetImage.clone();
        Mat imgDiff = targetImage.clone();

        /**
         * 首先将图片转成灰度图,
         */
        cvtColor(targetImage, targetImageClone, COLOR_BGR2GRAY);
        cvtColor(baseImage, baseImageColne, COLOR_BGR2GRAY);

        /**
         * 两个矩阵相减,获得差异图。
         */
        subtract(targetImageClone, baseImageColne, imgDiff1);
        subtract(baseImageColne, targetImageClone, imgDiff);

        /**
         * 按比重进行叠加。
         */
        addWeighted(imgDiff, 1, imgDiff1, 1, 0, imgDiff);

        /**
         * 图片二值化,大于24的为1,小于24的为0
         */
        threshold(imgDiff, imgDiff, 24, 255, THRESH_BINARY);
        erode(imgDiff, imgDiff, new Mat());
        dilate(imgDiff, imgDiff, new Mat());

        return 1-((double) countNonZero(imgDiff) / (imgDiff.size().height() * imgDiff.size().width()));
    }



    public static void main( String[] args ){


        String pic1="picture0.jpg";
        String pic2="picture1.jpg";
        String pic3="picture2.jpg";


        List<Mat> picMatList = new ArrayList<Mat>();

        picMatList.add(imread(pic1));
        picMatList.add(imread(pic2));
        picMatList.add(imread(pic3));

        mergePic(picMatList);
    }
}

 拼接结果:

java本地图片合并视频 java实现图片拼接_图像处理_04

对比上面的素材图片,这里实现了无缝拼接。