背景:工作中我们经常需要将一张张的截图拼接成一整张图。但是因为图片顶部和底部tabBar的存在,我们没有办法直接拼接。下面我们利用opencv的方法实现图片的完美拼接。
素材图片:
基本算法:
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);
}
}
拼接结果:
对比上面的素材图片,这里实现了无缝拼接。