目录
- 1. 背景
- 2. 修复步骤
- 2.1 图像灰度化,并进行高斯模糊
- 2.2 对图像进行阀值处理
- 2.3 查找轮廓
- 2.4 利用存储的值了解表格的位置
- 2.5 提取所有的水平线和垂直线
- 2.6 合并垂直和水平的两个模版
- 3. 完整代码
1. 背景
如果大家在输入图像时,看到的第二行中的单元格线未完全链接,在表格识别种,由于单元格不是闭合的框,算法将无法识别和考虑第二行,本文提出的解决方案不仅适用于这种情况。它也适用于表格中的其他虚线和孔。
2. 修复步骤
2.1 图像灰度化,并进行高斯模糊
有助于识别线条
cv::Mat img = cv::imread("/Users/xialz/Downloads/5.png");
cv::Mat gray_img;
cv::cvtColor(img, gray_img, cv::COLOR_BGR2GRAY);
cv::Mat gauss_img;
cv::GaussianBlur(gray_img, gauss_img, Size(3,3), 1);
2.2 对图像进行阀值处理
cv::Mat threshold_img;
cv::threshold(gray_img, threshold_img, 0, 255, cv::THRESH_BINARY_INV|THRESH_OTSU);
2.3 查找轮廓
对于所有轮廓,将绘制一个边界矩形以创建表格的框/单元格,然后将这些框与四个值x,y,宽度,高度一起存储起来,并计算最小、最大高度、宽度以及x,y
std::vector<std::vector<cv::Point>> contrours;
cv::findContours(threshold_img, contrours, cv::RETR_TREE, cv::CHAIN_APPROX_NONE);
std::vector<cv::Rect> box;
std::vector<int> heights, widths, xs, ys;
for (auto &c : contrours)
{
cv::Rect rect = cv::boundingRect(c);
box.push_back(rect);
heights.push_back(rect.height);
widths.push_back(rect.width);
xs.push_back(rect.x);
ys.push_back(rect.y);
}
sort(heights.begin(), heights.end());
int min_height = heights[0];
int max_height = heights.at(heights.size()-1);
sort(widths.begin(), widths.end());
int min_weight = widths[0];
int max_weight = widths.at(widths.size()-1);
sort(xs.begin(), xs.end());
int min_x = xs[0];
int max_x = xs.at(xs.size()-1);
sort(ys.begin(), ys.end());
int min_y = ys[0];
int max_y = ys.at(ys.size()-1);
2.4 利用存储的值了解表格的位置
最小的y用于获取表的最上一行,该行可以视为表的起点。
x的最小值是表格的左边缘,要获得近似大小,我们需要检索最大y值,该值是表底部的单元格或行。
最后一行的y值表示单元格的上边缘,而不是单元格的底部。要考虑单元格和表格的整体大小,必须将最后一行的高度加到最大y以检索表格的完整高度。
最大的x将是表格的最后一列,并且连续的是表格的最右边的单元格或者行。
x值是每个单元格的左边缘,并且连续
int max_y_height = 0, max_x_width = 0;
for (auto &b : box){
if (b.y == max_y)
{
max_y_height = b.height;
}
if (b.x == max_x)
max_x_width = b.width;
}
2.5 提取所有的水平线和垂直线
由于反转,背景为黑色,前景为白色,这意味着表格是白色的,使用形态学的扩张可以是白色区域扩大,现在修复孔和虚线,为了进一步识别表,将考虑所有的单元格
auto horizontal_kernal = cv::getStructuringElement(cv::MORPH_RECT, Size(50, 1));
cv::Mat horizontal_mask;
cv::morphologyEx(threshold_img, horizontal_mask, cv::MORPH_OPEN, horizontal_kernal);
cv::dilate(horizontal_mask, horizontal_mask, horizontal_kernal, cv::Point(-1, -1), 9);
auto vertcal_kernal = cv::getStructuringElement(cv::MORPH_RECT, Size(1, 50));
cv::Mat vertical_mask;
cv::morphologyEx(threshold_img, vertical_mask, cv::MORPH_OPEN, vertcal_kernal);
cv::dilate(vertical_mask, vertical_mask, vertcal_kernal, cv::Point(-1, -1), 9);
2.6 合并垂直和水平的两个模版
使用bitwise_or合并表
使用255-bitwise_or的结果进行反色
cv::Mat result;
cv::bitwise_or(vertical_mask, horizontal_mask, result);
result = 255 - result;
3. 完整代码
int main()
{
cv::Mat img = cv::imread("/Users/xialz/Downloads/5.png");
cv::Mat gray_img;
cv::cvtColor(img, gray_img, cv::COLOR_BGR2GRAY);
cv::Mat gauss_img;
cv::GaussianBlur(gray_img, gauss_img, Size(3,3), 1);
cv::Mat threshold_img;
cv::threshold(gray_img, threshold_img, 0, 255, cv::THRESH_BINARY_INV|THRESH_OTSU);
std::vector<std::vector<cv::Point>> contrours;
cv::findContours(threshold_img, contrours, cv::RETR_TREE, cv::CHAIN_APPROX_NONE);
std::vector<cv::Rect> box;
std::vector<int> heights, widths, xs, ys;
for (auto &c : contrours)
{
cv::Rect rect = cv::boundingRect(c);
box.push_back(rect);
heights.push_back(rect.height);
widths.push_back(rect.width);
xs.push_back(rect.x);
ys.push_back(rect.y);
}
sort(heights.begin(), heights.end());
int min_height = heights[0];
int max_height = heights.at(heights.size()-1);
sort(widths.begin(), widths.end());
int min_weight = widths[0];
int max_weight = widths.at(widths.size()-1);
sort(xs.begin(), xs.end());
int min_x = xs[0];
int max_x = xs.at(xs.size()-1);
sort(ys.begin(), ys.end());
int min_y = ys[0];
int max_y = ys.at(ys.size()-1);
int max_y_height = 0, max_x_width = 0;
for (auto &b : box){
if (b.y == max_y)
{
max_y_height = b.height;
}
if (b.x == max_x)
max_x_width = b.width;
}
auto horizontal_kernal = cv::getStructuringElement(cv::MORPH_RECT, Size(50, 1));
cv::Mat horizontal_mask;
cv::morphologyEx(threshold_img, horizontal_mask, cv::MORPH_OPEN, horizontal_kernal);
cv::dilate(horizontal_mask, horizontal_mask, horizontal_kernal, cv::Point(-1, -1), 9);
auto vertcal_kernal = cv::getStructuringElement(cv::MORPH_RECT, Size(1, 50));
cv::Mat vertical_mask;
cv::morphologyEx(threshold_img, vertical_mask, cv::MORPH_OPEN, vertcal_kernal);
cv::dilate(vertical_mask, vertical_mask, vertcal_kernal, cv::Point(-1, -1), 9);
cv::Mat result;
cv::bitwise_or(vertical_mask, horizontal_mask, result);
result = 255 - result;
cv::imshow("result", result);
cv::waitKey(0);
return 0;
}
结果图: