以前用手机看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阅读器最终能正确重排版的第一步也是最关键的一步!

小白PDF阅读器开发-页面元素分割的更多相关文章

  1. pdf阅读器开发

    文章基于sumatrapdf的实现(当中mupdf中的内容不会太多涉及).以及自己在此基础上做的 优化,扩展.详细效果能够參考百度阅读器精简版. 最NB的还是得属于foxit.渲染速度一流,展示大图片 ...

  2. 使用C#开发pdf阅读器初探(基于WPF,没有使用开源库)

    前言 pdf是最流行的版式格式文件标准,已成为国际标准.pdf相关的开源软件非常多,也基本能满足日常需要了.相关商业软件更是林林总总,几乎应有尽有!似乎没必要自己再独立自主开发!但,本人基于以下考虑, ...

  3. Ubuntu 18.10 安装PDF阅读器

    ======================================== 软件开发转移到了 Linux上,使用Ubuntu 18.10作为桌面开发环境 下面介绍 安装PDF阅读器 1.下载 福 ...

  4. 一个炒鸡好用的pdf阅读器

    下载地址:https://www.sumatrapdfreader.org/free-pdf-reader.html 一个关系很好的同事推荐的pdf阅读器  之前用的感觉不错  每次都记不住  自己收 ...

  5. Blazor组件自做十二 : Blazor Pdf Reader PDF阅读器 组件 (草稿)

    原文链接 [https://www.cnblogs.com/densen2014/p/16954812.html] Blazor Pdf Reader PDF阅读器 组件 应小伙伴要求撸了一个简单的P ...

  6. Blazor组件自做十二 : Blazor Pdf Reader PDF阅读器 组件 (新版 7.1 移除pdfobject)

    Blazor Pdf Reader PDF阅读器 组件 示例: https://www.blazor.zone/PdfReaders https://blazor.app1.es/pdfReaders ...

  7. Blazor Pdf Reader PDF阅读器 组件 更新

    Blazor Pdf Reader PDF阅读器 组件 https://www.nuget.org/packages/BootstrapBlazor.PdfReader#readme-body-tab ...

  8. Linux下pdf阅读器推荐

    由于需要在pdf文件上做标记,所以自带的文档查看器根本满足了需求,之前去网上查了查,Okular评价挺高,就安装了一个,确实能基本满足我的需求,但是 1.界面感觉还是不太友好,书签栏一直在那. 2.而 ...

  9. Foix_Reader_6.0|PDF阅读器

    福晰PDF阅读器,是阅读器中的精品.此版本是优化版本. 00:风格前卫 01:使用简洁 下载地址:        http://yunpan.cn/cHvyUfCdMKZz6  访问密码 ead7

  10. 关于linux上pdf阅读器

    今天也是倒腾linux 上pdf阅读器好久. 1.okular是挺好的,但是却太大了,好多功能,我没有细看.我简单的打开了几个pdf文件,发现加载速度还是太慢了.所以基于种种,我给卸载掉了. 安装直接 ...

随机推荐

  1. ChatGPT介绍与使用场景

    ChatGPT是OpenAI开发的一款基于GPT-3和GPT-4的人工智能聊天机器人."GPT"代表的是"Generative Pre-trained Transform ...

  2. SQL Server 语句日期格式查找方法

    1. SQL Server中,处理日期格式和查找特定日期格式方法示例 在SQL Server中,处理日期格式和查找特定日期格式的记录是一个常见的需求.SQL Server提供了多种函数和格式选项来处理 ...

  3. 【赵渝强老师】Weblogic域和域的组成

    一.什么是Weblogic WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于Java EE架构的中间件,WebLogic是用于开发.集成.部署和 ...

  4. android系统启动流程- ServiceManager进程启动流程

    *注:基于Android11源码 ServiceManager进程是在init进程创建的,所以我们从init进程的main()开始分析: // 文件路径: system/core/init/main. ...

  5. 使用nacos上传配置文件报错

    1.使用nacos导入配置文件报错:未读取到合法数据,请检查导入的数据文件. 对比在naocs server中导出的文件,发现是少了一级目录.需要创建一个文件夹,名称为组的名称.因为在nacos上传文 ...

  6. Linux+PXE+DHCP+TFTP+NFS实现无人值守安装

    一.实验环境:OS:Redhat6.4软件:DHCP服务.TFTP服务.NFS服务硬件:Dell R630服务器两台物理连接图: em1 em1 二.工作原理:服务器通过PXE网卡启动,从dhcp服务 ...

  7. 题解:AT_abc370_c [ABC370C] Word Ladder

    题目传送门 luogu观看 简要题意 给两个序列 \(S\) 和 \(T\),输出的第一个数是它能改变的总个数,后面跟着的第 \(i\) 个是改变 \(i\) 个数之后,字典序最小的结果. 思路 当 ...

  8. 可重入锁ReentrantLock

    ReentrantLock 重入锁,是实现Lock 接口 的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源重复加锁,即当前线程获取该锁后再次获取不会被阻塞. 要想支持重入 ...

  9. HDFS 重要机制之 checkpoint

    核心概念 hdfs checkpoint 机制对于 namenode 元数据的保护至关重要, 是否正常完成检查点是评估 hdfs 集群健康度和风险的重要指标 editslog : 对 hdfs 操作的 ...

  10. Machine Learning week_2 Environment Setup Instructions

    PerFace 第二周会使用矩阵来扩充之前的线性回归和梯度下降. 第二周一开始,吴恩达老师在课程开始前,引导我们做 MATLAB 和 Octave 的前期准备工作.这两个都可以完成编程作业.给了Oct ...