news 2026/9/4 4:33:44

易语言离线OCR模块封装:基于PaddleOCR的本地化文字识别解决方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
易语言离线OCR模块封装:基于PaddleOCR的本地化文字识别解决方案

简介:本资源是一套基于飞桨PaddleOCR实现的易语言离线OCR文字识别模块,面向熟悉易语言开发、需在无网络环境下完成图像文字提取的Windows桌面应用开发者。模块完全离线运行,兼容Win7/Win10系统,无需安装Python或额外运行库,支持JPG等常见图片格式及字节集、倾斜图像等多源输入,提供字体大小、方向校正、语言模型切换等关键参数调优能力,显著提升复杂场景识别准确率。压缩包共8个文件(1.78MB),含4张实测效果图(JPG)、1份详细使用说明(DOCX)、1份技术原理与调用示例HTML文档、1份PDF版部署指南及1份配置说明TXT,内容覆盖基础调用、高级参数设置、模型热替换与典型问题应对。目前已有154人学习下载,可直接集成至现有易语言项目,快速构建稳定高效的本地化OCR功能。

1. 项目概述:为什么我们需要一个“离线、易用”的OCR模块?

在易语言开发者的日常工作中,处理图片中的文字信息是个绕不开的需求。无论是开发一个票据管理软件、一个证件信息自动录入工具,还是一个简单的截图识字工具,OCR(光学字符识别)技术都是核心。过去,我们通常有两种选择:一是调用在线的OCR API,比如百度、腾讯的接口,优点是识别率高、更新快,但缺点也显而易见——必须联网,有调用次数限制,数据隐私存在顾虑,而且网络延迟会影响软件响应速度。二是集成开源的OCR引擎,比如Tesseract,这解决了离线问题,但对易语言开发者来说,集成过程堪称“劝退级”:需要处理C++的DLL依赖、配置环境变量、编译语言包,更别提在Win7这种老系统上可能遇到的各种“找不到xxx.dll”的报错。

这个“易语言OCR文字识别模块”项目,就是瞄准了这个痛点。它基于百度的飞桨(PaddlePaddle)PaddleOCR框架,但经过封装,让易语言开发者可以像调用一个普通支持库那样,几行代码就实现高精度的离线文字识别。关键词“无网离线使用”和“支持Win7/Win10”直击要害,意味着你开发的软件可以部署在任何内网环境、老旧电脑上,无需担心网络问题。“高效简单,可调整参数”则承诺了它不是个黑盒,开发者能根据实际场景(如识别模糊的截图、带背景色的文档)进行微调,以平衡速度和准确率。这不仅仅是封装了一个工具,更是为易语言这个以快速开发见长的生态,补上了一块长期缺失的高阶AI能力拼图。

2. 核心设计思路:如何将飞桨OCR“塞进”易语言?

2.1 技术选型:为什么是飞桨PaddleOCR?

在决定封装哪个OCR引擎时,我们需要权衡识别精度、模型大小、部署难度和社区支持。Tesseract历史悠久,但中文识别精度(尤其是对非常规字体、复杂背景)长期是短板,且模型优化更偏向于学术。而飞桨PaddleOCR是后起之秀,它有几个对易语言封装极其友好的特点:

  1. 精度与速度的平衡:PaddleOCR提供了一系列预训练模型,从轻量级的PP-OCRv4到高精度的PP-Structure,覆盖了从快速识别到文档结构分析的多种场景。其针对中文优化的模型,在公开测试集上的表现通常优于同体积的Tesseract模型。
  2. 部署相对友好:飞桨提供了完整的推理部署方案,包括将动态图模型转换为静态图推理模型(inference model),并提供了C++的预测库。这正是关键——我们可以用C++编写一个中间层DLL,这个DLL链接飞桨的C++预测库,负责加载模型、执行推理。然后,易语言通过调用这个自定义DLL的接口,实现与OCR引擎的交互。这种“易语言 -> 自定义C++ DLL -> PaddleOCR C++预测库”的三层架构,是项目可行的技术基石。
  3. 活跃的生态:百度持续的更新和维护,意味着能持续获得最新的模型优化和Bug修复,对于需要长期稳定的项目来说至关重要。

注意:这里有一个常见的误解,认为“基于飞桨”就需要在用户电脑上安装完整的Python和PaddlePaddle环境。实际上,我们使用的是飞桨的推理部署版本,它只包含运行模型所需的最小依赖库(如矩阵运算库),通常只有几十MB,可以通过DLL依赖一并打包,实现真正的开箱即用。

2.2 架构拆解:模块的四个核心层次

为了让这个模块真正“高效简单”,其内部设计必须清晰。我们可以将其分为四个层次:

  1. 接口层(易语言模块):这是开发者直接接触的部分。它提供一组易语言命令,如OCR_初始化()OCR_识别图片()OCR_释放()。这些命令内部主要做两件事:一是管理图像数据在易语言数据类型(如字节集)与C++可处理格式(如OpenCV的Mat)之间的转换;二是调用中间层DLL的函数。
  2. 中间层(C++封装DLL):这是项目的核心枢纽。它用C++编写,承担了所有繁重的工作:
    • 模型加载与管理:读取我们预先转换好的PaddleOCR静态推理模型文件(.pdmodel.pdiparams)。
    • 推理引擎封装:调用PaddleOCR的C++ API,创建预测器(paddle_infer::Predictor),配置计算后端(如CPU、GPU)。
    • 图像预处理:接收从易语言传来的图片数据,进行解码、颜色空间转换(BGR/RGB)、尺寸缩放、归一化等操作,转换成模型需要的输入张量。
    • 结果后处理:将模型输出的识别结果(文字框坐标、置信度、文本内容)整理成结构化的数据(如JSON字符串或特定内存格式),传回给易语言。
  3. 引擎层(PaddleOCR C++预测库):即飞桨官方提供的推理库文件(.dll.so,以及对应的.lib链接库)。中间层DLL在编译时需要链接它。
  4. 资源层(模型文件与依赖库):包括:
    • OCR模型文件:轻量化的中英文识别模型(ch_PP-OCRv4_det_infer,ch_PP-OCRv4_rec_infer)和方向分类模型(ch_ppocr_mobile_v2.0_cls_infer)。这些文件需要在发布时随模块一起提供。
    • 运行时依赖:飞桨推理库可能依赖的一些基础运行时库,如paddle_inference.dllopenblas.dllonnxruntime.dll等。确保这些DLL位于系统的可查找路径(如程序同级目录)是模块能正常运行的前提。

这样的分层设计,将复杂的AI推理过程隐藏在底层,给易语言开发者暴露出一个极其简洁的接口,完美契合了“高效简单”的目标。

3. 模块核心功能与参数详解

3.1 支持的图片格式与输入方式

一个健壮的OCR模块必须能处理各种来源的图片。本模块在设计时,通常支持以下几种输入方式:

  1. 文件路径识别:直接传入图片文件的完整路径(如“C:\test.png”)。模块内部会使用OpenCV或飞桨自带的图像解码库来读取文件。这是最常用的方式。
  2. 内存字节集识别:传入易语言的字节集数据,即图片文件在内存中的二进制流。这对于处理网络下载的图片、从剪贴板获取的图片数据非常有用,避免了先保存为临时文件的磁盘IO开销。
  3. 屏幕区域识别:可以结合易语言自带的快照命令,先获取屏幕指定区域的图片字节集,再传给OCR模块识别,实现“即指即译”的屏幕取词功能。

在图片格式上,得益于底层图像库的支持,模块应能覆盖绝大多数常见格式:

  • 位图格式:BMP、JPEG/JPG、PNG、TIFF。
  • 其他格式:GIF(通常取第一帧)、WebP等。

实操心得:在处理JPEG等压缩格式时,如果图片质量过低(压缩比太高),识别准确率会显著下降。建议在可能的情况下,优先使用PNG或高质量JPEG作为输入源。对于从网络下载的图片,如果发现识别效果差,可以尝试先使用图像处理库(如易语言调用GDI+)进行一次简单的锐化或对比度增强,再送入OCR,往往有奇效。

3.2 核心识别参数解析与调优

“可调整参数应对”是体现模块专业性的关键。一个优秀的OCR模块不应只有“识别”一个动作,而应提供一系列开关和旋钮,让开发者适配不同场景。以下是关键参数及其背后的逻辑:

  1. 识别语言(language

    • 选项:通常为“ch”(中英文混合)、“en”(英文)。这决定了加载哪个识别(Recognition)模型。虽然中英文模型也能识别纯英文,但针对纯英文场景使用英文小模型,速度更快。
    • 调优建议:如果业务场景99%是中文,直接选“ch”。如果是国际化的软件,可能需要根据用户选择动态切换模型,但这涉及到运行时加载不同模型文件,实现稍复杂。
  2. 检测框阈值(det_db_thresh)与框大小阈值(det_db_box_thresh

    • 作用:这两个参数控制文本检测(Detection)环节的灵敏度。det_db_thresh是二值化阈值,值越高,只有更“像”文本的区域才会被框出,能过滤一些背景噪声,但可能漏掉模糊文字。det_db_box_thresh是检测框的得分阈值,用于在生成最终文本框前进行过滤。
    • 调优建议:默认值(如0.3和0.6)适用于大多数清晰文档。对于背景复杂、文字密集的图片(如海报),可以适当降低det_db_thresh(如0.2),避免漏检。对于背景干净、但文字有污渍或阴影的图片,可以适当提高det_db_box_thresh(如0.7),让框选更精确。
  3. 识别置信度阈值(rec_score_threshold

    • 作用:模型对识别出的每个字符都会有一个置信度分数(0~1)。这个参数会过滤掉置信度低于阈值的结果。比如设为0.5,那么模型认为识别正确可能性低于50%的字就不会输出。
    • 调优建议:这是平衡准确率与召回率最重要的参数。在票据、证件识别等要求绝对准确的场景,可以设高(如0.8或0.9),宁可识别不出来(返回空或标记),也不接受错字。在内容预览、快速信息提取场景,可以设低(如0.3),先尽可能把文字都提取出来,后续再人工校对或通过上下文纠错。
  4. 方向分类开关(use_angle_cls

    • 作用:是否启用方向分类器。开启后,模块会先判断图片中文字的方向(0°、90°、180°、270°),并自动旋转至正向再进行识别。对于手机拍摄的、方向不确定的图片非常有用。
    • 调优建议:对于扫描的PDF转图片或电脑截图,文字方向基本是正的,可以关闭此功能以提升速度。对于用户上传的随手拍图片,务必开启。注意,启用方向分类会额外增加约10-30%的识别时间,并多加载一个约2MB的模型文件。
  5. 最大边长限制(max_side_len

    • 作用:在检测前,将图片的长边缩放到此指定值,短边按比例缩放。这是为了控制输入模型的图片尺寸,避免超大图片导致内存暴涨和速度剧降。
    • 调优建议:PaddleOCR的轻量模型推荐值为960或1280。如果你的图片都是手机拍摄的高清图(常超过2000像素),将其设为1280可以大幅提速且对精度影响很小。如果图片本身很小(如屏幕截图),可以适当调小(如640)以进一步加速。这是一个用小幅精度损失换取显著速度提升的典型参数。
参数名典型默认值适用场景(调高/调低)对性能的影响
det_db_thresh0.3调低:复杂背景,文字模糊。
调高:背景干净,想过滤噪声。
主要影响检测阶段的计算量,调整影响不大。
rec_score_threshold0.5调高:证件、票据识别,要求极高准确率。
调低:内容预览、全文提取,要求尽可能全。
不影响速度,只影响输出结果过滤。
use_angle_clstrue (开启)关闭:源图片文字方向确定为正。
开启:用户上传的随意角度图片。
开启会增加约10-30%的识别耗时。
max_side_len960调小:图片原尺寸小,追求极限速度。
调大:图片中文字非常小,需要保留更多细节。
影响显著!值越大,内存占用和耗时越高。

4. 从零开始的完整集成与调用流程

4.1 环境准备与模块部署

假设你已经拿到了封装好的模块文件包,通常包含以下部分:

  • PaddleOCR_ELang.fne(或.ec模块文件)
  • PaddleOCR_ELang_static.lib(静态库,可选)
  • paddle_inference.dll,openblas.dll等运行时依赖DLL
  • inference_model/目录,内含det/,rec/,cls/子目录及对应的模型文件。

部署步骤:

  1. 放置文件:将所有的.dll文件、inference_model模型文件夹,与你的易语言程序(.exe)放在同一个目录下。这是确保动态链接库能被正确加载的最简单方法。也可以将DLL放入系统目录,但不推荐,容易引发版本冲突。
  2. 安装模块:在易语言IDE中,通过“工具”->“类型库或OCX组件->安装”功能,选择PaddleOCR_ELang.fne文件进行安装。安装成功后,在支持库面板中应该能看到该模块。
  3. 初始化验证:在程序启动时,最早调用的地方(如_启动窗口_创建完毕事件下),编写初始化代码。一个健壮的初始化流程应包括检查模型文件是否存在。
.版本 2 .支持库 PaddleOCR_ELang .程序集 窗口程序集_启动窗口 .程序集变量 OCR引擎, OCR引擎类 .子程序 __启动窗口_创建完毕 .局部变量 模型路径, 文本型 .局部变量 初始化结果, 逻辑型 模型路径 = 取运行目录 () + “\inference_model\” .如果真 (目录_是否存在 (模型路径) = 假) 信息框 (“未找到OCR模型文件,请确保inference_model文件夹与程序在同一目录!”, 0, , ) 返回 () .如果真结束 初始化结果 = OCR引擎.初始化 (模型路径, “ch”, 真, 960, 0.3, 0.6, 0.5) .如果真 (初始化结果 = 假) 信息框 (“OCR引擎初始化失败,请检查依赖DLL是否齐全!”, 0, , ) .如果真结束

4.2 核心识别代码编写与结果解析

初始化成功后,就可以在需要的地方调用识别功能了。以下是识别图片文件并处理结果的典型代码:

.子程序 _按钮_识别_被单击 .局部变量 图片路径, 文本型 .局部变量 识别结果, 文本型 .局部变量 结果数组, 文本型, , "0" .局部变量 单行结果, 文本型 .局部变量 i, 整数型 图片路径 = “C:\测试图片\发票.jpg” 识别结果 = OCR引擎.识别图片文件 (图片路径) .如果真 (识别结果 = “”) 信息框 (“识别失败或图片中无文字”, 0, , ) 返回 () .如果真结束 ' 假设模块返回的是JSON格式字符串,每行一个对象,包含text, confidence, box等信息 ' 我们需要解析这个JSON。这里演示一个简单拆分(假设模块已按行分隔) 结果数组 = 分割文本 (识别结果, #换行符, ) .计次循环首 (取数组成员数 (结果数组), i) 单行结果 = 结果数组 [i] ' 在实际项目中,这里应该使用一个JSON解析库(如E2EE)来解析单行结果 ' 假设我们简单提取文本内容(格式可能为:{“text”: “你好世界”, “score”:0.98}) .如果真 (寻找文本 (单行结果, “text”, , 假) > 0) ' 简易提取文本(实际应用请用正则或JSON解析) 编辑框_结果.加入文本 (文本_取出中间文本 (单行结果, #引号 + “text” + #引号 + “:” + #引号, #引号) + #换行符) .如果真结束 .计次循环尾 ()

结果解析的注意事项: 一个专业的OCR模块返回的应该不仅仅是文本字符串,而是结构化的数据。理想的数据结构应该是一个数组,数组的每个元素是一个“文本行”对象,对象里至少包含:

  • text: 识别出的文本。
  • confidence: 该行文本的平均置信度或最小置信度。
  • box: 文本所在区域的四个顶点坐标([[x1,y1],[x2,y2],[x3,y3],[x4,y4]]),这个信息对于需要高亮显示识别区域或进行版式分析至关重要。 因此,在易语言中集成一个JSON解析支持库(如E2EEzyJson)来处理返回结果,是标准做法。

4.3 高级应用:批量识别与性能优化

当需要处理大量图片时,直接循环调用识别图片文件会导致频繁的模型加载和释放(如果模块内部如此设计),效率低下。正确的做法是:

  1. 保持引擎常驻:在程序启动时初始化一次,在整个程序运行期间重复使用同一个引擎对象。
  2. 批量处理与队列:创建一个生产-消费者模型。一个线程负责遍历图片文件,将路径放入队列;另一个或多个工作线程从队列取路径,调用OCR引擎识别,并将结果存入共享数据结构。这能充分利用多核CPU,特别是在启用MKL-DNN等加速库的情况下。
  3. 资源释放:在程序退出时,务必调用引擎的释放()销毁()方法,以确保正确释放模型占用的内存和GPU资源(如果使用了GPU推理)。
.子程序 __启动窗口_将被销毁 OCR引擎.释放 () ' 确保程序退出前清理资源

对于性能要求极高的场景,可以探索以下优化方向:

  • 启用GPU推理:如果用户电脑有NVIDIA GPU,可以在初始化时传入参数指定使用GPU。这需要模块封装时支持,并且用户电脑安装有对应的CUDA和cuDNN环境。对于普通离线部署,CPU方案更通用。
  • 使用更轻量模型:PaddleOCR提供了多种尺寸的模型。如果对精度要求不是极端高,可以尝试使用更小的模型(如ch_PP-OCRv4_rec_slim),识别速度会有大幅提升。
  • 图片预处理:在送入OCR前,自行进行灰度化、二值化、降噪等操作,有时能显著提升特定场景下的识别精度和速度。

5. 常见问题排查与实战技巧

即使模块封装得再好,在实际部署中也会遇到各种环境问题。以下是基于经验的排查清单和技巧。

5.1 初始化失败问题排查

问题现象可能原因解决方案
调用初始化()返回假1. 模型文件路径错误或缺失。
2. 依赖的DLL(如paddle_inference.dll)未找到。
3. 系统缺少运行时库(如VC++ Redistributable)。
4. 模型文件损坏。
1. 检查inference_model文件夹及其子目录是否完整,路径中是否包含中文或特殊字符(建议用全英文路径)。
2. 使用Dependency WalkerProcess Explorer工具检查程序启动时加载了哪些DLL,确认所有必需的DLL都在可访问路径。
3. 在目标系统安装对应版本的Visual C++ Redistributable(如VS2015/2017/2019)。
4. 重新下载或从原始PaddleOCR仓库转换模型文件。
初始化成功,但识别时程序崩溃1. 多线程调用冲突(非线程安全的函数被同时调用)。
2. 传入的图片数据格式异常(如空字节集、非图片数据)。
3. 内存不足。
1. 确保对OCR引擎对象的调用(尤其是识别函数)是线程同步的,例如使用易语言的进入许可区/退出许可区
2. 在调用识别函数前,先验证图片文件是否存在,或字节集是否有效。
3. 检查系统内存,对于大图片,尝试调低max_side_len参数。
在Win7上无法运行1. Win7系统版本过低(如未安装SP1)。
2. 缺少必要的系统更新(如KB2533623)。
3. 使用的运行时库版本太新,不兼容Win7。
1. 确保Win7已安装Service Pack 1。
2. 安装微软官方更新补丁KB2533623,该补丁更新了内核API,许多新软件依赖它。
3. 要求模块封装者提供针对Win7编译的、使用较旧版本运行库的版本。

5.2 识别效果不佳的调优技巧

  1. 文字断行或粘连

    • 现象:一行文字被拆成两行识别,或两行文字被合并成一行。
    • 对策:调整检测模型的参数det_db_unclip_ratio(文本框膨胀比例)。这个值影响检测框的大小。如果文字被切分,可以适当增大此值(如从1.5调到2.0),让框更大一些。如果不同行文字被框在一起,可以适当减小此值。但更根本的解决方案是,在识别前,如果图片倾斜,先进行图像矫正
  2. 特定场景识别率低(如手写体、艺术字、古文字):

    • 通用模型有其局限性。PaddleOCR的预训练模型主要针对印刷体、常规字体优化。
    • 终极方案是自定义训练:虽然对易语言开发者门槛较高,但飞桨PaddleOCR提供了完整的模型微调(Fine-tuning)工具链。你可以收集自己的场景图片(几百张即可),标注文字位置和内容,使用PaddleOCR的代码对识别(Rec)模型进行微调,得到一个专属于你业务场景的增强模型,替换掉默认模型,识别率会有质的飞跃。
  3. 识别速度慢

    • 首先检查max_side_len参数是否设置过大。
    • 确认是否开启了方向分类(use_angle_cls),如果不需要可关闭。
    • 检查CPU占用。如果CPU单核满载,说明推理是单线程的。可以咨询模块封装者是否启用了飞桨的SetCpuMathLibraryNumThreads函数来设置多线程计算。对于四核CPU,设置为4通常能获得接近线性的加速。

5.3 关于“无网离线使用”的深层理解

这里的“离线”不仅仅是运行时不需要网络。它意味着:

  • 模型离线:所有神经网络模型文件(.pdmodel等)都已本地化。
  • 依赖离线:所有运行所需的动态链接库(DLL)都已打包。
  • 无需安装Python:整个运行环境与Python解耦。
  • 可脱离安装程序部署:理论上,将你的.exe、模块相关DLL、模型文件夹一起拷贝到一台全新的、甚至断网的Windows电脑上,就能直接运行。

这带来了巨大的优势:数据隐私安全(图片不上传)、响应速度快(无网络延迟)、运行环境稳定(不受网络服务商接口变更影响)。对于开发企业内网工具、涉密信息处理软件、高实时性要求的桌面应用,这是唯一可行的技术方案。

最后,分享一个我个人的调试习惯:在开发OCR功能时,我总会写一个“调试模式”。在这个模式下,不仅返回识别文字,还会让程序把OCR检测到的文字框用绿色线条在原图上画出来,并保存为一张新的图片。这样,当识别出错时,我能直观地看到是检测框没框准,还是框准了但识别错了。前者调整检测参数,后者则可能需要考虑图像预处理或模型微调。这个视觉化的反馈环,对于调参和问题定位的效率提升,远超单纯看日志。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 4:33:41

桥梁缺陷检测数据集应用:基于YOLO的计算机视觉实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 4:33:06

AI智能体如何自动化电路仿真中的重复劳动

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 4:28:18

昇腾自定义算子性能分析实战:从瓶颈定位到优化落地

我一直在做昇腾方向的自定义算子开发,周期里最耗时间的往往不是写算子的逻辑,而是写完之后那个"跑起来没问题、但总感觉哪里不对劲"的阶段。你问旁边的人,大多数回答就是"用msprof看啊",可真打开msprof之后&a…

作者头像 李华
网站建设 2026/9/4 4:28:05

AI算力时代光模块技术演进:从800G到CPO/LPO的产业逻辑与实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 4:26:59

STM32F4通过ESP8266连接MQTT服务器:HAL库驱动与协议移植实战

简介:本资源是一套面向STM32F4系列开发者(尤其聚焦物联网应用)的MQTT通信实战代码库,解决基于HAL库与ESP8266 Wi-Fi模块实现稳定MQTT连接、主题订阅/发布及断线重连等核心问题。资源包共501个文件,涵盖131个C源码&…

作者头像 李华
网站建设 2026/9/4 4:26:57

LLM推理服务尾部延迟根因分析与可落地的修复方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华