背景
通过无人机/相机等拍摄设备,对某一建筑拍摄多个影像,通过后期将这些图像合成一个全景图像。网上查阅很多资料,发现通过纯java实现的方案比较少,所以记录此篇文章,供有相同需求的朋友参考。
代码
1.依赖
使用opencv,网上很多教程需要下载安装,十分繁琐。亲测maven引入这两个依赖,就可以用了,不需要下载opencv安装包、idea导入等操作。
<!-- https://mvnrepository.com/artifact/org.bytedeco/opencv -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.7.0-1.5.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bytedeco/opencv-platform -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv-platform</artifactId>
<version>4.7.0-1.5.9</version>
</dependency>
2.实现
生成全景接口:
目前采用将生成的图片写入本地,然后再提供下载,没发现有什么api可以直接提供接口下载,有知道的大佬欢迎指正。
package com.ruoyi.drone.controller;
import com.ruoyi.common.config.RuoYiConfig;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.MatVector;
import org.bytedeco.opencv.opencv_stitching.Stitcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
@RestController
public class PanoramaController {
private static Logger logger = LoggerFactory.getLogger(PanoramaController.class);
/**
* 将多个图片生成全景图并提供下载
*/
@PostMapping("/panorama")
public void createPanorama(@RequestParam("files") List<MultipartFile> files,
HttpServletResponse response) throws IOException {
//通过不同的系统获取不同的文件存储路径
String fileUrl = getFileUrl();
if (CollectionUtils.isEmpty(files) || StringUtils.isBlank(fileUrl)) {
logger.info("图片文件内容或图片转存url为空,请检查!");
return;
}
//获取生成全景的Mat对象
List<Mat> mats = getMats(files, fileUrl);
if (CollectionUtils.isEmpty(mats)) {
logger.info("Mat对象为空");
return;
}
//生成的全景图片存储文件路径
String imagePath = fileUrl + "/" + "panorama.jpg";
//生成全景图片,写入本地
logger.info("开始写入");
panoramaWrite(mats, imagePath);
logger.info("写入结束");
File imageFile = new File(imagePath);
//通过流输出全景图片
if (imageFile.exists()) {
imageWrite(response, imageFile);
} else {
logger.error("全景文件不存在");
}
}
/**
* 通过流输出全景图
*/
private static void imageWrite(HttpServletResponse response, File imageFile) {
// 设置响应头信息,告诉浏览器这是一个图片文件
response.setContentType("image/jpeg");
response.setHeader("Content-Disposition", "attachment; filename=" + imageFile.getName());
// 读取图片文件并写入到输出流中
try (FileInputStream fis = new FileInputStream(imageFile);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
logger.error("文件读取或写入失败", e);
}
// 文件通过流输出后就没用了 删除文件
boolean isDeleted = imageFile.delete();
if (isDeleted) {
logger.info("文件删除成功");
} else {
logger.error("文件删除失败");
}
}
/**
* 生成全景图片
*/
private void panoramaWrite(List<Mat> mats, String imagePath) {
MatVector vector = new MatVector(mats.size());
for (int i = 0; i < mats.size(); i++) {
vector.put(i, mats.get(i));
mats.get(i).close();
}
Stitcher stitcher = Stitcher.create();
Mat result = new Mat();
stitcher.stitch(vector, result);
stitcher.close();
imwrite(imagePath, result);
}
/**
* 获取生成全景的Mat对象
*/
private List<Mat> getMats(List<MultipartFile> files, String fileUrl) {
List<Mat> mats = new ArrayList<>();
int i = 1;
//遍历入参图片
for (MultipartFile file : files) {
String fileName = file.getOriginalFilename();
if (fileName != null && !fileName.endsWith(".jpg") && !fileName.endsWith(".png") && !fileName.endsWith(".jpeg")) {
logger.info("文件不是图片,无法生成全景");
continue;
}
if (StringUtils.isBlank(fileName)) {
logger.info("文件为空");
continue;
}
int lastDotIndex = fileName.lastIndexOf(".");
//文件名如果有中文会报错,这里做下修改
fileName = "part" + i + fileName.substring(lastDotIndex);
i++;
File targetFile = new File(fileUrl, fileName);
try (InputStream inputStream = file.getInputStream()) {
Files.copy(inputStream, targetFile.toPath());
} catch (IOException e) {
logger.error("文件复制失败", e);
}
Mat mat = imread(fileUrl + "/" + fileName);
mats.add(mat);
// 检查文件是否存在
if (targetFile.exists()) {
// 删除文件
boolean isDeleted = targetFile.delete();
if (isDeleted) {
logger.info("文件删除成功");
} else {
logger.error("文件删除失败");
}
} else {
logger.error("文件不存在");
}
}
return mats;
}
/**
* 通过不同的系统获取不同的文件存储路径
*/
private String getFileUrl() {
String fileUrl;
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
fileUrl = "winFileUrl";
} else {
fileUrl = "linuxFileUrl";
}
logger.info("文件路径是:{}", fileUrl);
return fileUrl;
}
}
效果
目前测试效果,需要多个图片之间有大部分重合,且重合部分不能有太大变化。
源图片取自网络:
效果: