Java图片处理
开发环境
本次处理图片主要使用的依赖是opencv
,具体的方法可以去参照官网。
首先去opencv官网去下载与系统对应的jar包和依赖文件。(这里以windows举例)
选择windows版本
opencv-4.7.0-windows.exe
,然后进行安装。(linux的版本需要先进行编译后获取动态库和jar包)在安装目录的
opencv/build/java
的目录下,获得jar
包和动态库文件。(注意是安装目录里)在maven项目引入
opencv
依赖。1
2
3
4
5
6
7
8<!-- OpenCv -->
<dependency>
<groupId>org.opencv</groupId>
<artifactId>opencv</artifactId>
<version>4.7.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/main/resources/lib/opencv-470.jar</systemPath>
</dependency>加载
opencv
动态库由于opencv的开发语言问题,所以Java在使用opencv相关类的时候,需要先加载对应的动态库才行,不然直接使用会出现异常。需要注意linux和windows的动态库是不一样的。
好像最新版本的opencv(4.7.0)不支持Java8,只能有Java11,如果用Java8的话,可以使用4.6.0的版本。
如果觉得opencv配置环境过于麻烦,可以使用Javacv,Javacv其实就是调用opencv进行操作的,虽然官方缺少文档(好像速度也慢一些,封装过的也正常),不过和opencv的调用方法都差不多。
linux的编译
上述的动态库dll
格式是windows上使用的,要想在对应的linux上使用,还得在linux上编译opencv的源码,获得对应的jar包
和so动态库
文件(jar包应该是不分系统的)。
加载动态库
因为Java调用的opencv
都是原生native
方法,所以使用opencv的方法之前,需要加载动态库。
1 | /** |
每次都加载太过于麻烦,如果使用springboot服务的话,可以在启动服务时初始化导入。
1 |
|
opencv基础操作
读入图片
1 | public class Test { |
写出图片
1 | public class Test { |
Imgcodecs.imread()
这个方法,偶然碰见过一次保存失败的情况,成功运行却没有保存文件,所以还可以通过字节方式保存。
1 | /** |
释放图片
在Java中使用opencv的话,这个操作比不可少。在opencv的库中,几乎都是native
方法,所以导致对象的内存管理大多都不是由Java虚拟机管理,需要手动进行释放内存,不然会出现服务的使用内存不断升高,直至内存不够崩溃。而且期间不会出现内存溢出,dump文件也找不到占用很多内存的Mat类。所以释放图片内存是需要注意的。
1 | public class Test { |
如果你使用了lombok
,可以使用注解@Cleanup
来进行释放内存的简化。
1 | public class Test { |
获取图片基本信息
获取图片的基本信息,使用opencv并不方便,所以可以使用其他的库来进行。
获取图片DPI
暂时有两种方法可以获取图片的DPI。
commons-imaging依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/**
* 获取图片的DPI,获取不到返回-1
* 依赖于commons-imaging
* <dependency>
* <groupId>org.apache.commons</groupId>
* <artifactId>commons-imaging</artifactId>
* <version>${commons-imaging.version}</version>
* </dependency>
*
* @param filePath 图片文件位置
* @return 图片DPI
*/
public static int getDpi1(String filePath) {
ImageInfo imageInfo = null;
try {
imageInfo = Imaging.getImageInfo(FileUtil.file(filePath));
} catch (ImageReadException | IOException e) {
throw new RuntimeException("获取图片信息出错!");
}
// 水平分辨率和垂直都一样
if (null == imageInfo) {
return -1;
} else {
return imageInfo.getPhysicalWidthDpi();
}
}
metadata-extractor依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/**
* 获取图片的DPI,获取不到返回-1
* 依赖于metadata-extractor
* <dependency>
* <groupId>com.drewnoakes</groupId>
* <artifactId>metadata-extractor</artifactId>
* <version>${metadata.version}</version>
* </dependency>
*
* @param filePath 图片文件位置
* @return 图片DPI
*/
public static int getDpi2(String filePath) {
Metadata metadata = null;
int res = -1;
try {
metadata = ImageMetadataReader.readMetadata(FileUtil.file(filePath));
for (Directory directory : metadata.getDirectories()) {
// 遍历图片信息,寻找水平分辨率
for (Tag tag : directory.getTags()) {
if ("X Resolution".equals(tag.getTagName())) {
res = Convert.toInt(tag.getDescription());
}
}
}
} catch (ImageProcessingException | IOException e) {
throw new RuntimeException("获取图片信息出错!");
}
return res;
}获取图片分辨率
1 | /** |
这些操作实质是获取图片的基本信息得到的,所以图片大部分的基本信息都可以通过这些方法修改得到。
图片旋转
Graphics2D旋转
1 | /** |
opencv旋转
1 | /** |
if (angle < 0) {
angle = 360 + angle;
}
Point center = new Point((double) w / 2, (double) h / 2);
double scale = 1.0;
Mat rotationMatrix = Imgproc.getRotationMatrix2D(center, angle, scale);
// 设置填充颜色为白色
Scalar backgroundColor = new Scalar(255, 255, 255);
Mat rotatedImage = new Mat();
Imgproc.warpAffine(image, rotatedImage, rotationMatrix, new Size(w, h), Imgproc.INTER_LINEAR, Core.BORDER_CONSTANT, backgroundColor);
rotationMatrix.release();
return rotatedImage;
}
1 |
|
灰度化
大多数彩色图片的每个像素都是由红绿蓝三个通道组成的,为了更方便的处理图像,将这三个通道的值按一定比例进行加权平均,得到一个单通道的灰度值,用来表示该像素的颜色亮度。大多数图片处理都会先进行图片灰度化,因为只有一个通道,处理起来方便很多。opencv的灰度化操作,只需要调用方法即可。灰度化操作的话,主要是要考虑图片通道数量的问题。
1 | /** |
高斯滤波
处理图像时,往往需要对图像进行平滑操作以去除噪声,同时也可以模糊图像以达到柔化的效果。高斯滤波(Gaussian blur)就是一种常用的平滑滤波器,其通过对图像像素进行加权平均的方式来实现平滑和模糊的效果。高斯滤波的原理是使用一个高斯核(Gaussian kernel)对图像像素进行加权平均。一般来说,高斯核的大小或标准差越大,平滑效果越明显,图像的细节丢失也会越多。
1 | /** |
图像二值化
图像二值化很好理解,就是把一张彩色图片或者灰度图片转化为黑白二值图像,即每个像素只有两种可能的选值,通常是黑色(0)和白色(255)。主要用于分割某些图片的用途。
二值化具体步骤:
- 灰度化。这一步是因为在大多数情况下,只考虑像素的亮度信息就足够了,灰度图像的每个像素值表示亮度的强度,通常在0(黑色)到255(白色)之间。
- 选择一个阈值,阈值是一个灰度值,用于判断像素应该被归类为黑色还是白色。选值方法有很多,大概有以下三种
- 全局阈值:简单地选择一个固定的全局阈值,将所有像素比较与该阈值,高于阈值的像素设置为白色,低于阈值的像素设置为黑色。
- 局部阈值:根据像素的局部区域来选择阈值,每个像素的阈值根据其周围像素的统计特性(如均值、方差等)来确定。
- 自适应阈值:根据图像的不同区域自适应地选择阈值。常见的方法有基于局部均值、基于Otsu算法等。
- 图片进行二值化处理,对于每个像素,如果其灰度值大于阈值,则将其设为白色(255),否则设为黑色(0)。
1 | /** |
图像膨胀与腐蚀
腐蚀与膨胀主要用于处理二值化图像或者是灰度图像。
膨胀
膨胀主要作用是对二值化物体边界点进行扩充,将与物体接触的所有背景点合并到该物体中,使边界向外部扩张。
如果两个物体间隔较近,会将两物体连通在一起。对填补图像分割后物体的空洞有用。
1 | /** |
腐蚀
腐蚀主要的作用就是消除物体的边界点,使边界向内收缩,可以把小于结构元素的物体去除。
可将两个有细小连通的物体分开,去除毛刺,小凸起等噪点。如果两个物体间有细小的连通,当结构足够大时,可以将两个物体分开。
1 | /** |
边缘检测
边缘是指图像中颜色或亮度发生急剧变化的区域,通常是物体之间的边界或物体内部的纹理。边缘检测算法旨在在图像中找到这些边缘并将其标记出来。具体的步骤如下:
- 图像灰度化
- 此处还可以进行(高斯模糊,二值化,图片膨胀,图片腐蚀等操作去除噪点)
- 进行边缘检测
1 | /** |
一些图片的实际应用
检测图片亮度
网上关于图片亮度检测的方法,大概有两种。
图片平均亮度值
使用图片的平均灰度值来代表图片的平均亮度。
1 | /** |
图片亮度异常值
1 | /** |
检测图片的清晰度
网上检测图片清晰度的方法有三种,需要注意的是,这些计算的指标在同一张图片里,越高代表这张图片越清晰。所以可能会出现一种情况,就是有一张模糊的图片指标比有一张清晰的指标高。
Tenengrad梯度
1 | /** |
Laplacian方法计算清晰度
1 | /** |
灰度方差获取图片清晰度
1 | /** |
图片亮度处理
亮度调整主要是调整平均亮度值到一个自定义的值当中。
1 | /** |
图片纠偏
图片纠偏是一个比较难的问题,主要的难点在于如何计算出图片的倾斜角度,最常见的方法就是霍夫变换检测直线了,以下是具体步骤。
图片边缘检测
霍夫变换,获取检测的直线
计算每条直线的倾斜角度
获取所有直线倾斜角度的平均值或者是众数来作为图片的倾斜角度。(关于平均值和众数,总会在某些情况下十分异常,我认为平均数比较可靠,但需要严格筛选需要内容的直线才行。也可以根据某些算法排除掉异常值。)
图片灰度化。
高斯模糊,二值化,膨胀,腐蚀等操作处理图片,去除噪点,以免出现不需要的直线。
图片边缘检测,通过调整参数,把一些无用的边框去除。
霍夫变换检测直线,通过调整参数把一些无用的直线去除。
收集并计算每条直线的倾斜角度。
通过每条直线的倾斜角度,推测出整体图片的倾斜角度。这里有很多种方法,比如平均值,众数,出现次数最多的数,或者是附近角度最多的数等等方法。这里使用了出现次数最多的数用来代替图片整体倾斜角度。
1 | /** |
去除红色印章
有很多场景,比如说发票,白底黑字文件这种,在OCR识别的过程中,会受到红色印章的影响,所以需要先去除红色印章,在进行OCR识别,这样可以提高OCR的识别率。
红色印章检测
我碰到比较多的场景是白底黑字的图片,所以我这里就使用了检测红色像素,痛过红色像素的数量,来检测是否该图片有红色印章。
1 | /** |
有些图片看着是没有红色像素的,但是其实会存在有,所以先使用一个只有红色印章的图片进行检测,然后选择一个比较合理的值进行判断就可以。
这种方法不适合除了印章外还有很多红色的图片,那种情况可以考虑使用霍夫圆检测,那种可能会耗费很多时间,所以我没有使用。
去除红色印章
我这里去除红色印章的方法,主要的原理就是使用图片二值化,让印章的变成白色即可,但是会出现一个问题,就是红色比黑色更难二值化,所以需要先把图片的红色通道分离出来,红色通道的红色印章的颜色会变淡很多,从而二值化能去除掉红色印章。
1 | /** |
这种方法的结果图片,因为去除了蓝绿通道,所以会变得灰色,不过因为主要的目的是给OCR识别,所以无所谓,如果想不改变图片的话,还是挺麻烦的,可能还得识别出椭圆印章,然后精确的去除。
切边矫正
图片的切边矫正还是使用深度学习模型比较准确,使用opencv局限性还是挺大的,简单使用还是可以的。以下的方法借鉴了网上流传比较广的一个关于截取发票的边缘的方法。
1 | public static void remove(String src,String dst) { |