java表格识别_OpenCV3识别图中表格-JAVA实现
关于 JAVA 学习 OpenCV 的内容,函数讲解。内容我均整理在 GitHubd的OpenCV3-Study-JAVA
OpenCV 3 识别图中表格-Java 实现
1. 说明
⽹上⼤部分资料,都是针对 C++的,python、java 的例⼦太少了。所以最近在做这个的时候,把他记录下来,也可以帮助⼀些⼈少⾛弯路。
OpenCV 确实强⼤,强⼤到每⼀个⽅法,都能 google 到⼀篇专题⽂章,在写的过程中,参考了许多资料,最终完成了实现和注释。
但是这仅仅是⼊门,到表格后的利⽤才是后⾯的核⼼。⽐如:
表格的 OCR 识别,识别表头,内容数据,形成结构化数据。
图⽚按照顺序,转 Word⽂档或者保存为 html,这样就可以完成格式的转化,⽅便在 web 端查看,⽤户下载。
其他利⽤...
本⽂仅针对效果较好的,⽆倾斜,背景⼲净的图⽚进⾏识别。复杂的情况会可能⽆法满⾜,需要进⼀步处理。仅仅是个⼊门。
2. 开发环境
macOS Sierra 10.12.4
IntelliJ IDEA 2017
Junit 4.12
JDK 1.8
因为在 mac 下通过 brew 安装的 opencv ,所以包都是跟当前系统匹配的,安装⽬录也是⼀致的。
Windows 下需要根据⾃⼰的系统环境,位数,修改代码的loadLibraries,决定加载的动态库⽂件。
3. 代码实现
import org.junit.Test;
import *;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import java.io.File;
import java.util.*;
/**
* @Author : alexliu
* @Description : opencv 测试
* @Date : Create at 下午3:12 2018/1/26
*/
public class TestOpenCV {
String test_file_path = Property("user.dir") + File.separator + "testFiles";
static {
//加载动态链接库时,不使⽤System.loadLibrary(xxx);。 ⽽是使⽤ 绝对路径加载:System.load(xxx);
/*
* 加载动态库
*
* 第⼀种⽅式 --------------System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
* loadLibrary(Core.NATIVE_LIBRARY_NAME); //使⽤这种⽅式加载,需要在 IDE 中配置参数.
*
* 第⼆种⽅式 --------------System.load(path of lib);
* System.load(your path of lib) ,⽅式⽐较灵活,可根据环境的系统,位数,决定加载内容
*/
loadLibraries();
}
/**
* 读取 table
*/
@Test
public void readTable(){
Mat source_image = Imgcodecs.imread(test_file_path + "/table-3.jpg");
//灰度处理
Mat gray_image = new Mat(source_image.height(), source_image.width(), CvType.CV_8UC1);
Imgproc.cvtColor(source_image,gray_image,Imgproc.COLOR_RGB2GRAY);
//⼆值化
Mat thresh_image = new Mat(source_image.height(), source_image.width(), CvType.CV_8UC1);
// C 负数,取反⾊,超过阈值的为⿊⾊,其他为⽩⾊
Imgproc.adaptiveThreshold(gray_image, thresh_image,255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY,7,-2);
this.saveImage("out-table/1-thresh.png",thresh_image);
//克隆⼀个 Mat,⽤于提取⽔平线
Mat horizontal_image = thresh_image.clone();
//克隆⼀个 Mat,⽤于提取垂直线
Mat vertical_image = thresh_image.clone();
/*
* 求⽔平线
* 1. 根据页⾯的列数(可以理解为宽度),将页⾯化成若⼲的扫描区域
* 2. 根据扫描区域的宽度,创建⼀根⽔平线
* 3. 通过腐蚀、膨胀,将满⾜条件的区域,⽤⽔平线勾画出来
*
* scale 越⼤,识别的线越多,因为,越⼤,页⾯划定的区域越⼩,在腐蚀后,多⾏⽂字会形成⼀个块,那么就会有⼀条线* 在识别表格时,我们可以理解线是从页⾯左边 到 页⾯右边的,那么划定的区域越⼩,满⾜的条件越少,线条也更准确
*/
int scale = 10;
int horizontalsize = ls() / scale;
// 为了获取横向的表格线,设置腐蚀和膨胀的操作区域为⼀个⽐较⼤的横向直条
Mat horizontalStructure = StructuringElement(Imgproc.MORPH_RECT, new Size(horizontalsize, 1)); // 先腐蚀再膨胀 new Point(-1, -1) 以中⼼原点开始
// iterations 最后⼀个参数,迭代次数,越多,线越多。在页⾯清晰的情况下1次即可。
Imgproc.dilate(horizontal_image, horizontal_image, horizontalStructure, new Point(-1, -1),1);
this.saveImage("out-table/2-horizontal.png",horizontal_image);
// 求垂直线
scale = 30;
int verticalsize = ws() / scale;
Mat verticalStructure = StructuringElement(Imgproc.MORPH_RECT, new Size(1, verticalsize)); de(vertical_image, vertical_image, verticalStructure, new Point(-1, -1),1);
Imgproc.dilate(vertical_image, vertical_image, verticalStructure, new Point(-1, -1),1);
this.saveImage("out-table/3-vertical.png",vertical_image);
/*
* 合并线条
* 将垂直线,⽔平线合并为⼀张图
*/
Mat mask_image = new Mat();
Core.add(horizontal_image,vertical_image,mask_image);
this.saveImage("out-table/4-mask.png",mask_image);
/*
* 通过 bitwise_and 定位横线、垂直线交汇的点
*/
Mat points_image = new Mat();
Core.bitwise_and(horizontal_image, vertical_image, points_image);
this.saveImage("out-table/5-points.png",points_image);
/*
* 通过 findContours 轮廓
*
* 第⼀个参数,是输⼊图像,图像的格式是8位单通道的图像,并且被解析为⼆值图像(即图中的所有⾮零像素之间都是相等的)。
* 第⼆个参数,是⼀个 MatOfPoint 数组,在多数实际的操作中即是STL vectors的STL vector,这⾥将使⽤到的轮廓的列表进⾏填充(即,这将是⼀个contours的vector,其中contours[i]表⽰⼀个特定的轮廓,这样,contours[i][j]将表⽰contour[i]的⼀个特定的端点)。
* 第三个参数,hierarchy,这个参数可以指定,也可以不指定。如果指定的话,输出hierarchy,将会描述输出轮廓树的结构信息。0号元素表⽰下⼀个轮廓(同⼀层级);1号元素表⽰前⼀个轮廓(同⼀层级);2号元素表⽰第⼀个⼦轮廓(下⼀层级);3号元素表⽰⽗轮廓(上⼀层级)
* 第四个参数,轮廓的模式,将会告诉OpenCV你想⽤何种⽅式来对轮廓进⾏提取,有四个可选的值:
* CV_RETR_EXTERNAL (0):表⽰只提取最外⾯的轮廓;
* CV_RETR_LIST (1):表⽰提取所有轮廓并将其放⼊列表;
* CV_RETR_CCOMP (2):表⽰提取所有轮廓并将组织成⼀个两层结构,其中顶层轮廓是外部轮廓,第⼆层轮廓是“洞”的轮廓;
* CV_RETR_TREE (3):表⽰提取所有轮廓并组织成轮廓嵌套的完整层级结构。
* 第五个参数,见识⽅法,即轮廓如何呈现的⽅法,有三种可选的⽅法:
* CV_CHAIN_APPROX_NONE (1):将轮廓中的所有点的编码转换成点;
* CV_CHAIN_APPROX_SIMPLE (2):压缩⽔平、垂直和对⾓直线段,仅保留它们的端点;
* CV_CHAIN_APPROX_TC89_L1 (3)or CV_CHAIN_APPROX_TC89_KCOS(4):应⽤Teh-Chin链近似算法中的⼀种风格
* 第六个参数,偏移,可选,如果是定,那么返回的轮廓中的所有点均作指定量的偏移
*/
List contours = new ArrayList();
Mat hierarchy = new Mat();
Imgproc.findContours(mask_image,contours,hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE,new Point(0,0));
List contours_poly = contours;
Rect[] boundRect = new Rect[contours.size()];
LinkedList tables = new LinkedList();
//循环所有到的轮廓-点
for(int i=0 ; i< contours.size(); i++){
MatOfPoint point = (i);
MatOfPoint contours_poly_point = (i);
/*
* 获取区域的⾯积
* 第⼀个参数,InputArray contour:输⼊的点,⼀般是图像的轮廓点
* 第⼆个参数,bool oriented = false:表⽰某⼀个⽅向上轮廓的的⾯积值,顺时针或者逆时针,⼀般选择默认false
*/
double area = (i));
//如果⼩于某个值就忽略,代表是杂线不是表格
if(area < 100){
continue;
}
/*
* approxPolyDP 函数⽤来逼近区域成为⼀个形状,true值表⽰产⽣的区域为闭合区域。⽐如⼀个带点幅度的曲线,变成折线
*
* MatOfPoint2f curve:像素点的数组数据。
* MatOfPoint2f approxCurve:输出像素点转换后数组数据。
* double epsilon:判断点到相对应的line segment 的距离的阈值。(距离⼤于此阈值则舍弃,⼩于此阈值则保留,epsilon越⼩,折线的形状越“接近”曲线。)
* bool closed:曲线是否闭合的标志位。
*/
Imgproc.approxPolyDP(new Array()),new MatOfPoint2f(contours_Array()),3,true);
//为将这⽚区域转化为矩形,此矩形包含输⼊的形状
boundRect[i] = Imgproc.boundingRect((i));
// 到交汇处的的表区域对象
Mat table_image = points_image.submat(boundRect[i]);
List table_contours = new ArrayList();
Mat joint_mat = new Mat();
Imgproc.findContours(table_image, table_contours,joint_mat, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
//从表格的特性看,如果这⽚区域的点数⼩于4,那就代表没有⼀个完整的表格,忽略掉
if (table_contours.size() < 4)
continue;
//保存图⽚
tables.addFirst(source_image.submat(boundRect[i]).clone());
//将矩形画在原图上
}
for(int i=0; i< tables.size(); i++ ){
//拿到表格后,可以对表格再次处理,⽐如 OCR 识别等
rectangle函数opencv
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论