首页 > 其他分享 >小白PDF阅读器开发-页面元素分割

小白PDF阅读器开发-页面元素分割

时间:2023-07-08 17:44:06浏览次数:53  
标签:分割 mat int element 阅读器 PDF 页面 cv row

以前用手机看PDF格式的电子书时,总感觉非常别扭,PDF格式的电子书在手机上缩放严重,字体太小,想看清楚得来回放大拖动,看书的兴致就在来回缩放拖动间被消耗没了!每次用手机看PDF电子书时就想着得做款能自动重排版的阅读器给我自己用。但是第一步就难住了,怎么分割页面元素?后来偶然间看到一篇介绍文字识别方面的技术文章,在传统的文字识别算法中,第一步是分割文字,然后再进行文字识别。这正好跟要做的重排PDF的第一步类似。下面就介绍下所用到的文字分割算法“投影法”。

“投影法”简单来说就是先统计每一行的像素数量,行与行之间会有明显的空白边界,这样就可以将行给分割出来,然后再按照行统计每一列的像素数量,文字之间也会又明显的空白边界,这样就可以将文字分割出来了。这就像用灯照射物体一样,有遮挡的地方是黑色,没有遮挡的地方光就透过去了,所以叫“投影法”。

按行统计

上面这张图按行统计像素数后,画出每行的像素数量,可以很明显的看到行与行之间的空白,如下图所示

分割每行的文字 

行分割好后就可以分割每行的文字了,方法是统计文字行上每列的像素数量

 

通过分割图就可以明显看出来文字的边界了,这样就可以将文字分割开来。最终分割效果图如下:

下面是经过简化的小白PDF阅读器的实现代码

#include<opencv2/core/core.hpp>
#include<opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
/*
* @Author 吴立中
* @Date   2023-07-08
*/
class Element {
public:
    int x = -1;
    int y = -1;
    int width = -1;
    int height = 0;

};
class Row {
public:
	int start = -1;//每一行的起始位置
	int end = -1;//每一行的结束位置
    std::vector<Element> elements;//每行的元素
};
/*
* 分割行
*/
std::vector<Row> splitRow(cv::Mat& mat) {
    std::vector<Row> rows;
    if (mat.empty()) {
        return rows;
    }
    std::vector<int> pos(mat.rows);
    //统计每行像素为黑色的数量
    for (int row = 0; row < mat.rows; row++) {
        for (int col = 0; col < mat.cols; col++) {
            if (mat.at<uchar>(row, col) == 0) {
                pos[row] = pos[row] + 1;
            }
        }
    }
    //画出统计结果
    cv::Mat result = mat.clone();
    for (int row = 0; row < result.rows; row++) {
        int size = pos[row];
        if (size > 0) {
            cv::line(result, cv::Point(0, row), cv::Point(size,row), cv::Scalar(0, 0, 0));
        }
    }
    cv::imwrite("D:\\workspace\\opencv\\image\\1_row.jpg", result);
    //根据统计分割每行
    Row row;
    for (int i = 0; i < mat.rows; i++) {
        if (pos.at(i) > 0) {
            if (row.start == -1 && row.end == -1) {
                row.start = i;
            }

        }
        else if (pos.at(i) == 0) {
            if (row.start > -1) {
                row.end = i;
                rows.push_back(row);
                row = Row();
            }
        }
    }
    if (row.start > row.end) {
        row.end = mat.rows - 1;
        if (row.end > row.start) {
            rows.push_back(row);
        }
    }

    return rows;
}
/*
* 分割列
*/
std::vector<Element> splitElement(Row& row,cv::Mat mat, cv::Mat drawMath) {
    std::vector<Element> elements;
    std::vector<int> pos(mat.cols);
    for (int c = 0; c < mat.cols; c++) {
        for (int r = row.start; r < row.end && r < mat.rows; r++) {
            if (mat.at<uchar>(r, c) == 0) {
                pos[c ] = pos[c] + 1;
            }
        }

    }
    //画出统计结果
    for (int c = 0; c < drawMath.cols; c++) {
        int size = pos[c];
        if (size > 0) {
            cv::line(drawMath, cv::Point(c, row.end), cv::Point(c, row.end -size), cv::Scalar(0, 0, 255));
        }
    }
    Element element;
    for (int i = 0; i < mat.cols; i++) {
        if (pos[i] > 0) {
            if (element.x == -1 && element.y == -1) {
                element.x = i;
                element.y = row.start;
                element.height = row.end - row.start;
            }
        }
        else if (pos[i] == 0) {
            if (element.x > -1 && element.width == -1) {
                element.width = i - element.x;
                elements.push_back(element);
                element = Element();
            }

        }

    }
    if (element.x > -1 && element.width == -1) {

        element.width = mat.cols - 1 - element.x;

        elements.push_back(element);
    }
    return elements;
}

int main(){
    //原图
    cv::Mat src = cv::imread("D:\\workspace\\opencv\\image\\1.png");
    //灰度化,变为灰度图
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    //二值化,也就是变为黑白图片
    cv::Mat binary;
    cv::threshold(gray, binary,200,255, cv::THRESH_BINARY);
    //分割
    std::vector<Row> rows = splitRow(binary);
    cv::Mat temp = src.clone();

    for (int i = 0; i < (int)rows.size(); i++) {
        std::vector<Element> elements = splitElement(rows.at(i), binary, temp);
        //画文字边框
        for (int j = 0; j < (int)elements.size(); j++) {
            cv::Rect rect(elements.at(j).x, elements.at(j).y, elements.at(j).width, elements.at(j).height);
            cv::rectangle(src, rect, cv::Scalar(0, 0, 255));
        }
    }
    cv::imwrite("D:\\workspace\\opencv\\image\\2_col.jpg", temp);
    cv::imwrite("D:\\workspace\\opencv\\image\\2_r.jpg", src);
    return 0;
}

对于比较标准的页面,按照这种方法分割页面元素还是比较简单高效的,但是现实中的PDF页面排版五花八门,各种形态都有,这种方法就不适用了。同时该算法还存在比较明显的缺点就是会把左右结构的汉字如“非、北”等类似的汉字会给分割成两部分,对于英文单词也会全都给分割开。这在重排版时会造成汉字分在两行显示,或者英文单词被分在两行显示等问题。

对于上面这几种类型的页面投影法或者说是单纯的应用投影法分割页面元素也是行不通的,还有像文本倾斜,干扰严重的,投影法分割效果也不尽理想。

 

小白PDF阅读器在用投影法做出第一版后,后面大部分时间就是在解决这些问题了。好在通过各种方法,元素分割中遇到的大部分问题都给解决了,算法比投影法要复杂的多,限于篇幅有限就不一一详述了,放几张小白PDF阅读器分割算法分割效果图

经过一年多的优化修改,现在小白PDF阅读器的分割算法已能正确分割绝大部分PDF页面元素,这也是小白PDF阅读器最终能正确重排版的第一步也是最关键的一步!

 

标签:分割,mat,int,element,阅读器,PDF,页面,cv,row
From: https://www.cnblogs.com/wlz8/p/17537571.html

相关文章

  • 页面显示查询耗时
    执行耗时接口,页面显示计时器lettt=document.querySelector('.spanTimer')asHTMLElement;letnum=0;tt.innerText=num+'秒';letmin='';letsec='';lettimeOut=setInterval(function(){//开启定时器num++;if(num<......
  • 浏览器种输入一个url到显示页面全过程
    原文合集地址如下,有需要的朋友可以关注本文地址合集地址所谓的‘三颗树’在浏览器中,当解析和加载网页时,会形成三个重要的树结构:DOM树、CSSOM树和渲染树(RenderTree)。这些树结构在网页的渲染和布局过程中起到关键作用。DOM树(DocumentObjectModelTree):DOM树表示HTML文档的......
  • 若依前端,菜单栏切换时刷新问题[页面菜单切换时,页面总是重新刷新,导致页面输入的查询参
    前端页面菜单切换时,页面总是重新刷新,导致页面输入的查询参数重载清空这样切换时,页面就刷新了,解决方法在这里1,页面代码,这里指定namename:"Item",注意name的首字母必须大写2,页面代码,这里指定idid="item"注意,id的首字母必须小写3,页面配置,这里的路由地址必须配置和name一......
  • APP页面设计软件大全,设计师必备这10款
    APP页面设计是APP实施过程中的关键步骤,APP页面设计是不是别具一格,取决于消费者对APP最直观的视觉效果分辨。要创建一个令人惊讶的APP页面设计,一个高效率的APP界面设计软件是不可缺少的。本文列出10款最流行的APP界面设计软件,并逐一展开了深入分析。1.即时设计即时设......
  • SpringMVC学习巩固(页面跳转——重定向与转发)
    **要求方法返回值为String**默认:逻辑视图视图解析器找到对应的页面Handler中返会的String类型为跳转到的页面eg:return"hello";则执行方法后页面会跳转到hello.html重定向forward:@GetMapping("forward")publicStringforward(Stringpath){Sy......
  • linux编译PDFium.so
    主要参考https://zhuanlan.zhihu.com/p/37729756这篇文章,不过编译后没有libPDFium.so这个文件。本方法已在龙芯3A5000,统信专业版测试通过。已打包PDFium及gyp的下载地址:链接:https://pan.baidu.com/s/1tG-gf3gehzXjSYnoanIDSg?pwd=xwzv提取码:xwzv一、取主代码pdfium的源码......
  • 如何实现计算机视觉 pdf的具体操作步骤
    实现计算机视觉PDF的步骤作为一名经验丰富的开发者,我很乐意教会你如何实现“计算机视觉PDF”。下面是整个过程的步骤表格:步骤操作代码示例第一步安装必要的库和工具pipinstallopencv-python<br>pipinstallPyPDF2第二步将PDF转换为图片importcv2<br>im......
  • 【快应用】快应用学习之页面周期函数onBackPress无法触发?
    ​【关键词】onBackPress、退出提示 【问题背景】在学习和调试快应用的过程中,我在子页面中的onBackPress()函数中定制了退出的一个弹框提醒,将它作为组件引入父页面中,弹框却无法触发?问题代码如下:子页面<template><!--Onlyonerootnodeisallowedintemplate.--><......
  • 学城项目课程详情页面
    1.建立course文件夹 /1 cmd中进入到apps路径下 输入python../../manage.pystartappcourses创建出courseapp/2 记得在dev中注册app2.创建课程的相关表fromdjango.dbimportmodelsfromutils.common_modelsimportBaseModel#继承基表#课程分类表classCo......
  • python: using pdfplumber Lib read pdf file
     fromopenpyxlimportWorkbookfromopenpyxl.stylesimportPatternFill,Side,Borderimportpdfplumberl=[]defvisitDir(path):ifnotos.path.isdir(path):print('Error:"',path,'"isnotadirectoryordoesnotexi......