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 逐行拷贝:
for i = 0..h-1memcpy(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下载
# 清华镜像加速
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python opencv-contrib-python
验证是否安装成功
python -c "import cv2,sys;print(cv2.__version__,sys.version.split()[0])"
# 输出示例:4.10.0 3.11.5
安装OpenCV(linux)
方法一: 通过apt下载
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
方法二: 下载源码编译
安装依赖
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
下载、编译
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 # 刷新动态库缓存
验证
pkg-config --modversion opencv4
python3 -c "import cv2; print(cv2.getBuildInformation())" | head -20
二、代码工程配置
在项目的pom.xml文件中添加Seata的依赖,如下所示:
<dependency><groupId>org.openpnp</groupId><artifactId>opencv</artifactId><version>4.5.1-2</version></dependency>
三、 服务接入
这里写的方法用于实现将一张图片平均分割成12张子图,并上传minio存储
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 = 12for (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);// 转换为 MultipartFileparts.add(convertMatToMultipartFile(subImage, file.getOriginalFilename()));}}return parts;}/*** 将 Mat 转换为 MultipartFile*/private static MultipartFile convertMatToMultipartFile(Mat image, String originalFilename) throws IOException {MatOfByte matOfByte = new MatOfByte();// 分割后的图片暂定为jpgImgcodecs.imencode(".jpg", image, matOfByte);ByteArrayInputStream inputStream = new ByteArrayInputStream(matOfByte.toArray());return new MockMultipartFile(originalFilename, originalFilename, "image/jpeg", inputStream);}
四、 结语
OpenCV 切图就是指针偏移、零拷贝,代码简单,执行的速度也比较快,省内存且无缝衔接后续的算法;但只能轴向矩形,且默认与原图共享内存,一改 ROI 母图同步变,需手动 .clone() 才能独立。
