Java+OpenCV实现图片切割

OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库,由英特尔公司发起并参与开发,首次发布于2000年。它现在由OpenCV基金会管理,是计算机视觉领域最受欢迎和广泛使用的库之一。

原理说明

图像在内存中就是一个连续的或逐行存储的多维数组,只要算出要保留的那一段数据在数组中的起始位置与长度,把它拷出来就完成了切割。下面对这个原理进行说明。

1、内存布局:Mat = 元数据 + 数据指针

  • OpenCV 的 cv::Mat(或 Python 里 numpy.ndarray 包装成的 Mat)里真正保存像素的是uchar*data 这个指针。
  • 对于 8-bit 3 通道图像,每行占 width*3 字节;如果宽不是 4 的倍数,OpenCV 会自动在行尾补 0 做 4-byte对齐,得到 step = width*3 + padding
  • 因此,只要知道 (x, y) 在数组里的偏移量,就能 O(1) 定位到那一行、那一列的像素: offset = y * step + x * channels

2、切片操作:构造"新头 + 旧身"的轻量 Mat

  • cv::Mat 有一个构造函数

    Mat(const Mat& m, const Rect& roi)它不会复制像素数据,而是把新 Mat 的 data 指针指向原图对应偏移位置,并重新填写 rows/cols/step/flags 等头信息。

  • 因此 img(Rect(x,y,w,h)) 得到的仍然指向同一块内存,只是"看起来"变小了;若后续还要对原图操作,需要 clone()copyTo() 做一次深拷贝。

3、逐像素复制(只有必要时才做)

如果我们用 img(roi).copyTo(cropped),OpenCV 内部就走 memcpy 逐行拷贝:

cpp 复制代码
for i = 0..h-1
    memcpy(dst + i*dst.step,
           src + (y0+i)*src.step + x0*cn,
           w*cn);

复杂度 O(h·w·c),内存连续时效率极高。

一、 环境搭建

安装JDK

版本大于jdk8,用于支持Java基础程序。

安装OpenCV(windows)

方法一: 直接下载

下载
https://github.com/opencv/opencv/releases/tag/4.5.1

解压后得到opencv_videoio_ffmpeg451_64.dll文件,放到C:\Windows\System32
方法二: 使用pip下载

cpp 复制代码
# 清华镜像加速
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python opencv-contrib-python

验证是否安装成功

cpp 复制代码
python -c "import cv2,sys;print(cv2.__version__,sys.version.split()[0])"
# 输出示例:4.10.0 3.11.5

安装OpenCV(linux)

方法一: 通过apt下载

cpp 复制代码
sudo apt update
sudo apt install python3-pip python3-venv -y
python3 -m venv ~/cv_env
source ~/cv_env/bin/activate
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python opencv-contrib-python

方法二: 下载源码编译

安装依赖

cpp 复制代码
sudo apt update && sudo apt upgrade -y
sudo apt install -y build-essential cmake ninja-build \
     git pkg-config libjpeg-dev libpng-dev libtiff-dev \
     libavcodec-dev libavformat-dev libswscale-dev \
     libgtk-3-dev libcanberra-gtk3-module \
     python3-dev python3-numpy

下载、编译

cpp 复制代码
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git
cd opencv
cmake -B build -G Ninja \
      -DCMAKE_BUILD_TYPE=Release \
      -DCMAKE_INSTALL_PREFIX=/usr/local \
      -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules \
      -DWITH_CUDA=ON \
      -DCUDA_ARCH_BIN=7.5,8.6
cmake --build build --parallel $(nproc)
sudo cmake --install build
sudo ldconfig   # 刷新动态库缓存

验证

cpp 复制代码
pkg-config --modversion opencv4
python3 -c "import cv2; print(cv2.getBuildInformation())" | head -20

二、代码工程配置

在项目的pom.xml文件中添加Seata的依赖,如下所示:

xml 复制代码
        <dependency>
            <groupId>org.openpnp</groupId>
            <artifactId>opencv</artifactId>
            <version>4.5.1-2</version>
        </dependency>

三、 服务接入

这里写的方法用于实现将一张图片平均分割成12张子图,并上传minio存储

java 复制代码
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // 加载 OpenCV
    }

// 上传并切割上传
    public List<String> uploadWithSplitPic(MultipartFile[] files) throws IOException {
        List<String> fileNames = new ArrayList<>();
        List<String> fileIds = new ArrayList<>();
        
        int parts = 12

        for (MultipartFile file : files) {
            this.dealImg(file);

            // 切割图片并上传
            List<MultipartFile> splitFiles = splitImage(file, parts);
            for (MultipartFile splitFile : splitFiles) {
                String splitFileMd5 = calculateMd5(splitFile);
                FileInfo splitFileInfo = uploadFile(splitFile, splitFileMd5);
                fileNames.add(splitFile.getOriginalFilename());
                fileIds.add(splitFileInfo.getId());
            }
        }
        return fileIds;
    }

    private String calculateMd5(MultipartFile file) throws IOException {
        Digester digester = new Digester(DigestAlgorithm.MD5);
        return digester.digestHex(file.getInputStream());
    }

    private FileInfo uploadFile(MultipartFile file, String md5) throws IOException {
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFile(file);
        fileInfo.setStoreGroup(minioBucketName);
        fileInfo.setFilePath(FILE_UPLOAD_PATH);
        fileInfo.setMd5(md5);
        fileInfo.setCustomPath("/");
        return fileService.uploadFile(fileInfo);
    }

    public static List<MultipartFile> splitImage(MultipartFile file , int p) throws IOException {
        // 读取图片
        Mat src = Imgcodecs.imdecode(new MatOfByte(file.getBytes()), Imgcodecs.IMREAD_UNCHANGED);
        if (src.empty()) {
            throw new IOException("无法加载图片");
        }

        // 行列数
        int rows = (int) Math.sqrt(p);
        int cols = (int) Math.ceil((double) p / rows);

        int pieceWidth = src.cols() / cols;
        int pieceHeight = src.rows() / rows;

        List<MultipartFile> parts = new ArrayList<>();

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                // 切割子图
                Rect roi = new Rect(j * pieceWidth, i * pieceHeight, pieceWidth, pieceHeight);
                Mat subImage = new Mat(src, roi);

                // 转换为 MultipartFile
                parts.add(convertMatToMultipartFile(subImage, file.getOriginalFilename()));
            }
        }
        return parts;
    }

    /**
     * 将 Mat 转换为 MultipartFile
     */
    private static MultipartFile convertMatToMultipartFile(Mat image, String originalFilename) throws IOException {
        MatOfByte matOfByte = new MatOfByte();

        // 分割后的图片暂定为jpg
        Imgcodecs.imencode(".jpg", image, matOfByte);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(matOfByte.toArray());

        return new MockMultipartFile(originalFilename, originalFilename, "image/jpeg", inputStream);
    }

四、 结语

OpenCV 切图就是指针偏移、零拷贝 ,代码简单,执行的速度也比较快,省内存且无缝衔接后续的算法;但只能轴向矩形,且默认与原图共享内存,一改 ROI 母图同步变,需手动 .clone() 才能独立。

相关推荐
兮动人3 小时前
Spring中@Configuration注解的proxyBeanMethods属性详解
java·后端·spring
zl9798993 小时前
SpringBoot-数据访问之Druid
java·spring boot
猫头虎3 小时前
解决升级IDEA2025.2后,每次打开Maven项目爆红的问题:Windows和Mac解决方案
java·ide·macos·maven·intellij-idea·idea·intellij idea
NFG89C3 小时前
Adobe Lightroom Classic 2025解锁版 (专业照片管理)
java·adobe·工具
蒙奇D索大3 小时前
【计算机网络】408计算机网络高分指南:物理层编码与调制技术精讲
java·前端·学习·计算机网络
豐儀麟阁贵3 小时前
5.2 类
java·开发语言
九皇叔叔3 小时前
Java循环结构全解析:从基础用法到性能优化(含经典案例)
java·开发语言·python
漫漫不慢.4 小时前
蓝桥杯-16955 岁月流转
java·jvm·蓝桥杯
JanelSirry4 小时前
如何查看java死锁?具体怎么做,怎么避免
java·开发语言