今天介绍一个正常表格的检测方法,针对在本次项目中的另一个对象。这个算法采用的是opencv中的查找闭合轮廓的方法来确定是否为一个表格。但是这个方法很有很大的缺点,闭合轮廓里面是否为表格的准确性不好确定。
- ---->Today I will introduce a normal table detection method for another object in this project.This algorithm uses the method of finding closed contour in opencv to determine whether it is a table or not.However, this method has great disadvantages. The accuracy of the table in the closed contour is not sure.
基本上只要是正常表格,传统的算法还是很可观的。这种方法很简单,这里不多做解释了。
- ---->Basically as long as it is a normal table, the traditional algorithm is still very impressive.This method is very simple, and I won't explain it here.
首先,形态学检测直线,提取横向直线和纵向直线。
- ---->First, the morphology detects the straight lines, and extracts the horizontal and vertical lines.
将横向直线图像与纵向直线图像进行叠加,可以得到只有横向和纵向的表格框线图像。
- ---->The horizontal and vertical line images can be superimposed to obtain only horizontal and vertical line images.
string filename = "1-0.jpg";
Mat src = imread(filename);
// 检查图像是否加载良好
if (!src.data)
cerr << "Problem loading image!!!" << endl;
// // Show source image
// imshow("src", src);
// 出于实际原因调整大小
//Mat rsz;
//Size size(800, 900);
//resize(src, rsz, size);
resize(src, src, Size(src.cols / 1.5, src.rows / 1.5), 0, 0, INTER_LINEAR);
//imshow("rsz", rsz);
// 源图像转换为灰度图
Mat gray;
if (src.channels() == 3)
{
cvtColor(src, gray, CV_BGR2GRAY);
}
else
{
gray = src;
}
//imshow("gray", gray);// Show gray image
// Apply adaptiveThreshold at the bitwise_not of gray, notice the ~ symbol
Mat bw;
threshold(gray, bw, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//threshold(~gray, bw, 170, 255, CV_THRESH_BINARY);
//imshow("binary", bw);
//创建将用于提取水平线和垂直线的图像
Mat horizontal = bw.clone();
Mat vertical = bw.clone();
int scale = 64; // 使用此变量以增加/减少要检测的行数
int horizontalsize = horizontal.cols / scale; //在水平轴上指定尺寸
//创建用于通过形态学操作提取水平线的结构元素
Mat horizontalStructure = getStructuringElement(MORPH_RECT/*内核的形状是矩形*/, Size(horizontalsize, 1)/*内核尺寸*/);
// 应用形态学运算,先腐蚀再膨胀
erode(horizontal, horizontal, horizontalStructure, Point(-1, -1));
dilate(horizontal, horizontal, horizontalStructure, Point(-1, -1));
// 显示提取的水平线
//imshow("horizontal", horizontal);
// Specify size on vertical axis
int verticalsize = vertical.rows / scale;
// 创建用于通过形态学操作提取垂直线的结构元素
Mat verticalStructure = getStructuringElement(MORPH_RECT, Size(1, verticalsize));
// 应用形态学运算,先腐蚀再膨胀
erode(vertical, vertical, verticalStructure, Point(-1, -1));
dilate(vertical, vertical, verticalStructure, Point(-1, -1));
// 显示提取的垂直线
//imshow("vertical", vertical);
//创建一个包含表格的遮罩
Mat mask = horizontal + vertical;
//imshow("mask", mask);
通过函数与操作实现交点的提取, 通过函数findContours找到轮廓图像。
- --->Through the function of "&" to extract the intersection, find the contour image through the function findContours.
/*找到表格线之间的接缝,我们将使用此信息从图片中区分表格
(表格将包含4个以上的接缝,而图片仅包含4个接缝(即,在角落处))*/
Mat joints;
bitwise_and(horizontal, vertical, joints);
//imshow("joints", joints);
// 查找外部轮廓,该轮廓很可能属于表格或图像
vector<Vec4i> hierarchy;
std::vector<std::vector<cv::Point> > contours;
cv::findContours(mask, //输入图像
contours, //检测到的轮廓,每个轮廓被表示成一个point向量
hierarchy, //可选的输出向量,包含图像的拓扑信息。其中元素的个数和检测到的轮廓的数量相等
CV_RETR_EXTERNAL, //说明需要的轮廓类型和希望的返回值方式,CV_RETR_EXTERNAL 只检测出最外轮廓
CV_CHAIN_APPROX_SIMPLE, //压缩水平,垂直或斜的部分,只保存最后一个点
Point(0, 0));
正确框选可能存在的表格区域,准确来说框选出来的是一个闭合区域,不一定是表格区域。只要是闭合的都是可以框选出来的。这个算法需要更近一步的改进。那么如何进行进一步的改进,这是个遗留的问题,那么继续思考。
- ---->The correct box selects the possible table area, exactly the box selects a closed area, not necessarily the table area.Anything that's closed can be boxed.This algorithm needs further improvement.So how to make further improvements, that's a left question, so keep thinking about it.
对于传统算法的改进,是一个比较难搞的话题。现在只能慢慢思考啦。
- --->It is a difficult topic to improve the traditional algorithm.Now I can only think slowly.
//contours代表输出的多个轮廓
vector<vector<Point> > contours_poly(contours.size());//描述多个轮廓,即将多个轮廓存在一个vector中
vector<Rect> boundRect(contours.size());
vector<Mat> rois;
for (size_t i = 0; i < contours.size(); i++)
{
//获取区域的面积,如果小于某个值就忽略,代表是杂线不是表格
double area = contourArea(contours[i]);
if (area < 40) // 值是随机选择的,需要通过反复试验程序自行找到该值
continue;
//approxPolyDP 函数用来逼近区域成为一个形状,true值表示产生的区域为闭合区域
//boundingRect 函数为将这片区域转化为矩形,此矩形包含输入的形状
approxPolyDP(Mat(contours[i]), contours_poly[i], 10, true);
boundRect[i] = boundingRect(Mat(contours_poly[i]));//获取最小外接矩形
// 查找每个表具有的节点数
Mat roi = joints(boundRect[i]);
vector<vector<Point> > joints_contours;
findContours(roi, joints_contours, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
// 从表格的特性看,如果这片区域的点数小于4,那就代表没有一个完整的表格,忽略掉
if (joints_contours.size() <= 4)
continue;
//保存这片区域
//rois.push_back(src(boundRect[i]).clone());
int x0 = 0, y0 = 0, w0 = 0, h0 = 0;
x0 = boundRect[i].x;
y0 = boundRect[i].y;
w0 = boundRect[i].width;
h0 = boundRect[i].height;
//rois.push_back(src(boundRect[i]).clone());
Rect m_select(
(x0 - 10),
(y0 - 40),//开始的Y坐标//Min_Y[0] + delta_title
(w0 + 20),
(h0 + 50));//终止的Y坐标减去初始的Y坐标 //Variable_Y_End[1] - Vertical_Black_Y[0] +50
Mat rectangle_roi = src(m_select);//对目标图像进行裁剪保存
rois.push_back(rectangle_roi);
//rectangle(src, Point(x0 - 10, y0 - 10), Point(x0 + w0 + 10, y0 + h0 + 10), Scalar(0, 255, 0), 2, 8);
//drawContours( src, contours, i, Scalar(0, 0, 255), CV_FILLED, 8, vector<Vec4i>(), 0, Point() );
//将矩形画在原图上
//rectangle(src, Point(10, 15), Point(790, 320), Scalar(0, 255, 0), 1, 1, 0);
//rectangle(src, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 2, 8, 0);//绘制边界
}
imwrite(".//output.jpg",src);
result:
I hope I can help you,If you have any questions, please comment on this blog or send me a private message. I will reply in my free time.