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() 才能独立。