opencv实践项目-修改表格缺失轮廓

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;
}

结果图:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值