OpenCV for Java 手记

Author Avatar
ciaoly 2019年10月17日
  • 在其它设备中阅读本文章

OpenCV for Java 手记

接手一个Java-spring项目,需要实现OCR功能。项目原有的实现方案是按照固定像素裁剪图片、再使用tesseract进行OCR识别,识别率低不说,还特别容易报异常。遂决定使用算法实现自动裁剪图片并辅以tesseract进行识别。


系里的老师很多都是搞图像处理的,因为个人太菜的原因,本来是“坚决不搞图像处理“的。。。。还是真香地拿起了OpenCV。 不过并不在本文记录复杂算法,就是简单粗暴的一些循环计数、调API。也因此分类为“经验记录”。


1. OpenCV for Java 的类库

OpenCV官网的Release页面有提供OpenCV的源码和为Windows编译的动态链接库, 但是不提供Jar包和Linux系统的动态链接库, 因此需要自己编译. 为Java编译OpenCV可以参考:

Linux下安装并使用Java开发OpenCV的配置Introduction to Java Development

自己编译也可以做适当裁剪, 因此得到的动态链接库体积可以控制.

方便起见, 也可以使用这个已经编译并打包好的jar包(Github), 官方提供了 Maven 仓库, 对于支持 Maven 的项目非常方便.

需要注意的是, 如果使用了 OpenPnP 的jar包, 在 Java 类中加载本地库时不要使用 System.loadLibrary 方法加载动态链接库, OpenPnP 提供了专有的静态方法用来自动解包自己的动态链接库并按照运行的平台自动地加载:

Readme

2. 简单的印刷文本的行识别和列识别算法

OpenCV 实现图片的水平投影与垂直投影,并进行行分割

印刷文本中的文字一般排列较为整齐, 对于简单的文本(纯色背景/ 印刷字体), 可以使用简单的"投影法"将文本区域标记出来.

投影法的思路较为简单, 首先横向扫描图片, 通过计数像素值可以统计出每行的像素分布, 通过设定合适的阈值可以将多个"达标"的像素行组合成一个区间, 这个区间便可以认为是一行文字; 之后在每一个区间内(即每一行文字内)纵向的扫描图片, 再计数像素值便可以统计出每一列的像素分布, 再次划分合适的阈值便可将每一个文字的左右像素范围提取出.

不过以上所述的"投影法"只是一种简单的实现方案, 实际上还面临着噪点/ 倾斜等等很多问题; 即使是理想的图片, 每一行文本的长度/每一个文字的高度也可能不相同, 因此只简单地设定统计阈值是不够完善的, 还需要更有效的改进方案.

3. 关于”腐蚀“和”膨胀“

腐蚀与膨胀(Eroding and Dilating)

在表象上, 简单来说, "膨胀"操作将图片中的"亮区"扩展(即膨胀亮区), "腐蚀"操作将图片中的"亮区"缩小(即腐蚀亮区).

另外, 还要理解这两个操作的"内核"的概念, 可以理解为: 内核是一个形状规则的"滑块", 该滑块从图片左上角开始遍历全图, 它将自身能覆盖的区域内的颜色值的最值提取出并将"锚点"处的颜色值替换为该最值.

对于文本识别应用, "腐蚀"和"膨胀"的作用是将图片中的相似区域扩大, 以使其与相邻区间连通, 这样便可以突出这些区域的特点, 以备后续处理. 例如可以将每一个文字"加粗"使其在横向上与其它文字互相重叠, 这样在使用投影法时就可以很容易的将每一行文字的位置识别出.

4. HSV通道与RGB通道对比与思考

【OpenCV】HSV颜色识别-HSV基本颜色分量范围 / 由RGB到HSV颜色空间的理解 / OpenCV学习笔记——HSV颜色空间超极详解&inRange函数用法及实战从 RGB 到 HSV 的转换详细介绍

通常的图片颜色使用 RGB 三通道来表示每个像素的颜色, 这三个分量分别计数了三种RGB颜色的亮度值, 因此对于某个具体的颜色而言很难使用一个简单的区间范围来表示出(三种颜色混合在一起, 若只想提取某一个具体的颜色必然较为困难). 还要注意, OpenCV中使用 BGR 通道

而HSV通道则将"颜色值"单独的作为一个通道值, 它的中文直译为[色相, 饱和度, 亮度]. 因此只需要使用一个简单的区间就可以将某一个具体的颜色提取出. 色相色相, "颜色的相位", 这个词现在变得既具体又形象

给出一个参考的颜色范围表: 来源于百度文库

绿
H [0, 180] [0, 180] [0, 180] [0, 10] ∪ [156, 180] [11, 25] [26, 34] [35, 77] [78, 99] [100, 124] [125, 155]
S [0, 255] [0, 43] [0, 30] [43, 255] [43, 255] [43, 255] [43, 255] [43, 255] [43, 255] [43, 255]
V [0, 46] [46, 220] [221, 255] [46, 255] [46, 255] [46, 255] [46, 255] [46, 255] [46, 255] [46, 255]

5. 二值化、灰度

二值化只记录亮和暗两种状态/ 灰度只记录亮度值.

有两种方法可以得到图片的灰度图和二值图, 其中二值图更是可以使用简单的循环和判断即可实现.

一篇介绍原理的博客: Java实现图像灰度化

1. 使用OpenCV

  • 二值

OpenCV doc: threshold

函数 threshold (这个单词译为"阈值")

C++ 函数原型: double threshold(InputArray src, OutputArray dst, double thresh, double maxVal, int thresholdType)

参数说明 src:原始数组,可以是Mat类型。
dst:输出数组,必须与 src 的类型一致。
threshold:阈值
maxval:使用 CV_THRESH_BINARY 和 CV_THRESH_BINARY_INV 的最大值。
type:阈值类型
type=CV_THRESH_BINARY:如果 src(x,y)>threshold ,dst(x,y) = max_value; 否则,dst(x,y)=0;
type=CV_THRESH_BINARY_INV:如果 src(x,y)>threshold,dst(x,y) = 0; 否则,dst(x,y) = max_value.
type=CV_THRESH_TRUNC:如果 src(x,y)>threshold,dst(x,y) = max_value; 否则dst(x,y) = src(x,y).
type=CV_THRESH_TOZERO:如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否则 dst(x,y) = 0。
type=CV_THRESH_TOZERO_INV:如果 src(x,y)>threshold,dst(x,y) = 0 ; 否则dst(x,y) = src(x,y).

————————————————
版权声明:本文为CSDN博主「PtaQ」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_40969467/article/details/80351

  • 灰度

OpenCV doc: cvtColor

鉴于 CSDN 的版权声明, 不引用 CSDN 的东西了, 反正都是抄的官方文档.

函数原型:

void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0 )  // C++
cv2.cvtColor(src, code[, dst[, dstCn]]) → dst   #Python
void cvCvtColor(const CvArr* src, CvArr* dst, int code) // C
cv.CvtColor(src, dst, code) → None  #Python

Parameters:

  • src – Source image: 8-bit unsigned, 16-bit unsigned ( CV_16UC... ), or single-precision floating-point.
  • dst – Destination image of the same size and depth as src .
  • code – Color space conversion code. See the description below.
  • dstCn – Number of channels in the destination image. If the parameter is 0, the number of the channels is derived automatically from src andcode .

The conversion from a RGB image to gray is done with:

cvtColor(src, bwsrc, CV_RGB2GRAY);

该函数不仅可以用来转灰度, 可实现功能包括但不限于 转 16 位颜色通道/ HSV 等

?: 16位颜色和灰度有什么关系? 为什么官方doc里将这两者并列了?

2. 使用 Java 的BuffedImage

JAVA灰度化、二值化图片如此简单方便

Java doc: BuffedImage

  • BufferedImage(int width, int height, int imageType)

Constructs a BufferedImage of one of the predefined image types.

  • BufferedImage(int width, int height, int imageType, IndexColorModel cm)

Constructs a BufferedImage of one of the predefined image types: TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED.

imageType 可选 二进制 和 灰度, 上述第二个中的 IndexColorModel 是一个"颜色表"对象, 图片中的颜色值从这个对象里"查表"得到

6. Java ImageIO与流

这里只记录一个问题和解决方案, 原理待日后填坑:

//Some code
dst = new BufferedImage(imgWidth, temp.getLength()+1, image.getType());
graphics2D = dst.createGraphics();
graphics2D.drawImage(image, 0, 0, imgWidth, dst.getHeight(),
             0, temp.getStart(), imgWidth, temp.getEnd(), null);
ImageIO.write(dst, "jpg", new File((String)entry.getKey() + ".jpg"));
// Some code

上述代码在控制台程序里可以正常输出图片, 在 Java Spring MVC 项目 (使用Tomcat) 里就不能输出图片, 要改成下面这样就可以了:

// 彩色读入, 灰度化输出
dst = new BufferedImage(imgWidth, temp.getLength()+1, BufferedImage.TYPE_BYTE_GRAY);
graphics2D = dst.createGraphics();
graphics2D.drawImage(image, 0, 0, imgWidth, dst.getHeight(),
                leftOffset, temp.getStart(), imgWidth + leftOffset, temp.getEnd(), null);
try {
    FileOutputStream foutput = new FileOutputStream(new File(StatusUtils.DIR + (String)entry.getKey() + ".jpg"));
    // 异步/ 同步问题?
    ImageIO.write(dst, "jpg", foutput);
    foutput.flush(); // 问题可能在这里
    foutput.close();
    System.out.println("Output img: " + StatusUtils.DIR + (String)entry.getKey() + ".jpg");
} catch (IOException e) {
    e.printStackTrace();
}

不知道为什么.

7. idea导入(各种疑难问题

1. 找不到符号

2. Maven导入本地包

8. 频域分析?不,不存在的。

是的, 不存在的. 虽然本科的专业基础课就是信号处理

或许可以用高频滤波器识别出图片中的边缘?

要不, 也搞搞? 【图像处理】 常用边缘检测算法对比分析