【JAVA】将多个相关图片智能拼接成一个全景图

背景

        通过无人机/相机等拍摄设备,对某一建筑拍摄多个影像,通过后期将这些图像合成一个全景图像。网上查阅很多资料,发现通过纯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;
    }
}

效果

目前测试效果,需要多个图片之间有大部分重合,且重合部分不能有太大变化。

源图片取自网络:

效果:

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值