一、前言
随着互联网技术以及社会经济等的高速发展,教育行业迎来了人工智能技术的蓬勃发展,而近年来随着教育信息化的不断推进,智能教育成为国家的重要战略之一。尤其在经过新冠疫情之后,社会对智能在线教育的迫切需求,使得对教科书、习题册等文档的自动分析成为研究的热点。其中的文档图像分析与识别技术被广泛应用在人们生活的方方面面,比如银行票据的自动分析处理、快递运单的自动识别、教科书的分析与识别、古籍文稿的分析与理解、数字档案、数字图书馆等等,极大地提高了信息的检索、处理、传播速率。总之,文档图像分析与识别技术的出现和发展极大地方便了人们的生活,也极大地促进了我们的社会向智能化、数字化、信息化发展。
基于此,本项目跟随Aidlux训练营的刘老师,利用YOLOv8模型以及DBNet算法,进行版面元素检测及文本行识别,并最终基于AidLux平台,利用已经闲置已久的小米cc9e手机的算力,实现到了PDF转WORD功能的部署。
二、环境配置
2.1 AidLux简介
AidLux是一个智能物联网(AIoT)应用开发和部署平台,构建在ARM硬件上,基于创新性跨Android/鸿蒙 + Linux融合系统环境。简单来说,AidLux提供了一种简洁而高效的方式,用于编写、训练和测试模型,并将其应用到不同形态的设备上。
通常情况下,我们在编写和测试模型时使用的是Linux或Windows系统。然而,当将模型应用到实际场景时,我们常常会遇到几种不同的设备形态,如GPU服务器、嵌入式设备(如Android手机、人脸识别闸机)和边缘设备。其中,Android嵌入式设备的底层芯片通常采用ARM架构,而Linux底层也是基于ARM架构开发的,同时Android又是基于Linux内核的操作系统,因此它们可以共享Linux内核。基于这种背景,AidLux提供了一种从底层开发应用系统的方法,同时提供原生Android和原生Linux的使用体验。
2.2 手机安装AidLux
本项目需要利用安卓手机下载并安装AidLux,在应用市场查找然后下载安装即可。如图2.1所示
图2.1 应用市场中的AidLux
安装完成后, 打开手机端的AidLux应用。第一次进入时,应用会进行初始化设置。完成初始化后,进入系统登录页面。在这一步,建议使用手机进行注册,当然也可以直接点击“我已阅读并同意”并跳过登录步骤。一旦登录成功,进入主页面后,可以点击左上角的红色叉号,关闭说明页面。之后可以点击页面最上方的 Cloud_ip,将界面映射到电脑上进行之后的操作.如图2.2所示。
图2.2 AidLux手机端主界面
本项目在这里是http://192.168.0.103:8000,如图2.3所示。在电脑端浏览器中输入链接后即可进入主界面,如图2.4所示,其中,此处的默认密码为aidlux 。
图2.3 AidLux的Cloud_ip
图2.4 AidLux的电脑端主界面
2.3 VScode远程连接调试AidLux
首先要下载VScode:点击官网https://code.visualstudio.com/,选择Download按钮进行下载,下载后根据提示一直进行安装,安装完成后需要安装插件Remote SSH:点击VScode左侧的输入Extensions“Remote针对跳出的Remote-SSH,点击安装,如图2.5所示。
图2.5 安装Remote-SSH
之后进行远程连接调试:①点击"Remote Explorer",②点击左下角选择“Connect to Host”,③再选择“Configure SSH Hosts”,如图2.6所示。
图2.6 VScode远程连接AidLux
然后对于跳出的弹窗,再选择第一个config
输入连接信息,需要注意的是这里的Host Name填写自己对应的AidLux里面Cloud_ip的地址。同时Port统一为9022,User均为root.保存后,在左侧会生成一个SSH服务器,鼠标放上后,会跳出一个“Connect to Host in New Window",之后再跳出密码框输入“aidlux”进行连接(注意此处密码均为aidlux),右下角显示SSH AidLux时,表示已经连接成功。
三 案例分析
3.1 版面元素检测
本部分利用Yolov8算法(https://github.com/ultralytics/ultralytics),数据集为CDLA(A Chinese document layout analysis (CDLA) dataset,GitHub - buptlihang/CDLA: CDLA: A Chinese document layout analysis (CDLA) dataset),其为中文文档版面分析数据集,面向中文文献类(论文)场景。包含以下10个label:
共包含5000张训练集和1000张验证集,分别在train和val目录下。本文在win11系统下,使用yolov8n的预训练权重,进行微调,训练100个epoch,指标如下:最终的MAP50如下所示:
3.2 文本行检测
此部分使用DBNet和CRNN算法,此处包括两个小项目:
3.2.1 单张文档图片的文本检测识别
此部分的效果如下图所示。
主要代码为: # 导入必要的库 import copy import onnxruntime # 设置onnxruntime默认的日志级别 onnxruntime.set_default_logger_severity(3) from PIL import Image, ImageDraw, ImageFont from tools.config import * # 从配置模块导入所有内容 from tools.utils import * # 从工具模块导入所有内容 from line_ocr import CRNNHandle # 导入CRNN处理模块 from line_det.predict import DBNET # 导入DBNET推断模块 class OcrEngine(object): def __init__(self): # 初始化文本检测和识别处理器 self.text_handle = DBNET(model_path) self.crnn_handle = CRNNHandle(crnn_model_path) def crnnRecWithBox(self, im, boxes_list, score_list): # 初始化结果列表 results = [] # 对边界框列表进行排序 boxes_list = sorted_boxes(np.array(boxes_list)) count = 1 # 初始化计数器 # 遍历每个边界框和对应的分数 for index, (box, score) in enumerate(zip(boxes_list, score_list)): tmp_box = copy.deepcopy(box) # 深拷贝边界框 # 获取旋转裁剪后的图像部分 partImg_array = get_rotate_crop_image(im, tmp_box.astype(np.float32)) partImg = Image.fromarray(partImg_array).convert("RGB") # 将数组转换为RGB图像 # 如果图像不是RGB,则转换为灰度图像 if not is_rgb: partImg = partImg.convert('L') try: # 根据图像类型(RGB或灰度)调用相应的预测函数 if is_rgb: simPred = self.crnn_handle.predict_rbg(partImg) else: simPred = self.crnn_handle.predict(partImg) except Exception as e: print(e) # 打印异常信息 continue # 跳过当前循环的剩余部分,进入下一次循环 # 如果预测结果不为空字符串,则添加到结果列表中,并增加计数器 if simPred.strip() != '': results.append([tmp_box, simPred]) count += 1 return results # 返回结果列表 def text_predict(self, img, short_size): # 进行文本检测,获取边界框列表和分数列表 boxes_list, score_list = self.text_handle.process(np.asarray(img).astype(np.uint8), short_size=short_size) # 在图像上绘制边界框 img_draw = draw_bbox(img, boxes_list) # 使用CRNN进行文本识别,获取识别结果 result = self.crnnRecWithBox(np.array(img), boxes_list, score_list) return img_draw, result # 返回绘制了边界框的图像和识别结果 # 定义在图像上添加文本的函数 def cv2ImgAddText(img, text, left, top, textColor=(255, 0, 0), textSize=20): draw = ImageDraw.Draw(img) # 创建一个用于绘制的对象 fontStyle = ImageFont.truetype("tools/simsun.ttc", textSize, encoding="utf-8") # 加载字体样式 draw.text((left, top - textSize), text, textColor, font=fontStyle) # 在图像上添加文本 return img # 返回添加了文本的图像 if __name__ == "__main__": # 创建OCR引擎实例 OCR = OcrEngine() # 读取图片 img_path = "inputs/picture/224.png" img_name = img_path.split("/")[-1] img = cv2.imread(img_path) # 使用OCR引擎进行文本预测,并返回绘制后的图片和结果列表 img_draw, result_list = OCR.text_predict(img, 960) # 将绘制后的图片从BGR格式转换为RGB格式 img_draw_PIL = Image.fromarray(cv2.cvtColor(img_draw, cv2.COLOR_BGR2RGB)) # 遍历结果列表,为每个识别到的文本添加边框和文本内容 for result in result_list: box, text = result[0].tolist(), result[1] img_draw_PIL = cv2ImgAddText(img_draw_PIL, text, box[0][0], box[0][1]) # 将PIL图片转换为OpenCV格式,并从RGB格式转换为BGR格式 img_draw_cv = cv2.cvtColor(np.asarray(img_draw_PIL), cv2.COLOR_RGB2BGR) # 将处理后的图片保存为文件 cv2.imwrite(f'outputs/picture/{img_name}', img_draw_cv) print("Done! Save_Path:",f'outputs/picture/{img_name}')
3.2.2 自然图像视频流文本检测识别
该部分流程为:
主要代码为:
# 导入相关依赖库 import cv2 import copy import tqdm import traceback import numpy as np import onnxruntime # 设置onnxruntime默认的日志级别 onnxruntime.set_default_logger_severity(3) from PIL import Image, ImageDraw, ImageFont # 导入配置和实用工具函数 from tools.config import * from line_ocr import CRNNHandle from line_det.predict import DBNET from tools.utils import draw_bbox, sorted_boxes, get_rotate_crop_image # 定义OCR引擎类 class OcrEngine(object): def __init__(self): # 初始化文本检测和识别模型 self.text_handle = DBNET(model_path) self.crnn_handle = CRNNHandle(crnn_model_path) # 对提供的图像和框执行CRNN识别 def crnnRecWithBox(self, im, boxes_list, score_list): results = [] # 对框进行排序 boxes_list = sorted_boxes(np.array(boxes_list)) count = 1 # 遍历排序后的框及其对应的得分 for index, (box, score) in enumerate(zip(boxes_list, score_list)): # 创建框的深层副本 tmp_box = copy.deepcopy(box) # 提取旋转裁剪后的图像,使用浮点数表示框 partImg_array = get_rotate_crop_image(im, tmp_box.astype(np.float32)) # 将numpy数组转换为PIL图像 partImg = Image.fromarray(partImg_array).convert("RGB") # 如果输入图像不是RGB格式,则转换为灰度图像 if not is_rgb: partImg = partImg.convert('L') try: # 在提取的图像上执行CRNN预测 if is_rgb: simPred = self.crnn_handle.predict_rbg(partImg) else: simPred = self.crnn_handle.predict(partImg) except Exception as e: # 处理异常并继续下一次迭代 print(traceback.format_exc()) continue # 如果预测的文本不为空,则添加结果 if simPred.strip() != '': results.append([tmp_box, simPred]) count += 1 return results # 对提供的图像执行文本检测和识别 def text_predict(self, img, short_size): # 处理文本检测,获取框和得分 boxes_list, score_list = self.text_handle.process(np.asarray(img).astype(np.uint8), short_size=short_size) # 在图像上绘制边界框 img_draw = draw_bbox(img, boxes_list) # 使用边界框和得分执行CRNN识别 result_list = self.crnnRecWithBox(np.array(img), boxes_list, score_list) return img_draw, result_list # 在给定图像中添加文本的函数 def cv2ImgAddText(img, text, left, top, textColor=(255, 0, 0), textSize=20): draw = ImageDraw.Draw(img) fontStyle = ImageFont.truetype("tools/simsun.ttc", textSize, encoding="utf-8") draw.text((left, top - textSize), text, textColor, font=fontStyle) return img # 主代码块 if __name__ == "__main__": # 初始化OCR引擎 OCR = OcrEngine() # 视频文件路径 video_path = "inputs/video/video3.mp4" # 打开视频文件进行读取 cap = cv2.VideoCapture(video_path) length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = cap.get(cv2.CAP_PROP_FPS) size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) # 为输出创建VideoWriter对象 fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D') output = cv2.VideoWriter("outputs/video/video3.avi", fourcc, fps, size) frame_id = 0 # 遍历视频中的帧 for i in tqdm.tqdm(range(length)): retval, frame = cap.read() frame_id += 1 if int(frame_id) % 5 == 0: if retval: # 对当前帧执行文本检测和识别 img_draw, result_list = OCR.text_predict(frame, 960) img_draw_PIL = Image.fromarray(cv2.cvtColor(img_draw, cv2.COLOR_BGR2RGB)) # 在图像上添加识别到的文本 for result in result_list: box, text = result[0].tolist(), result[1] img_draw_PIL = cv2ImgAddText(img_draw_PIL, text, box[0][0], box[0][1]) img_draw_cv = cv2.cvtColor(np.asarray(img_draw_PIL), cv2.COLOR_RGB2BGR) # 将处理后的帧写入输出视频 output.write(img_draw_cv) cv2.imshow("frame",img_draw_cv) else: break if cv2.waitKey(1) & 0xFF == ord('q'): break else: output.write(frame) cv2.imshow("frame", frame) # 释放视频捕捉和写入对象 cap.release() cv2.destroyAllWindows()
最终达到的效果为:
OCR推理结果
3.3 基于AIMO的模型转换
该部分基于AidLux的模型转换工具——AIMO,AIMO(AI Model Optimizer,https://aidlux.com/product/aimo)旨在帮助用户能够在边缘端芯片上无精度损失的快速迁移、部署和运行各种机器学习模型。AIMO是以网页的方式与用户交互,用户只需要上传模型并设置一些选项即可快速完成转换。其转换流程为:
step1: 上传训练后的模型,选择YOLOv8训练后得到的best.pt模型使用代码转换成onnx模型之后,将onnx模型上传;
step2: 选择目标平台,点选转换后的模型,这里选择TensorFlowLite,即从onnx转换为Tflite;
step3: 参数设定,不需要特别选择,只需要选择网络简化即可,之后点击右下角的submit即可开始转换;
step4: 转换后的结果可点选右下角的"模型对比"查看onnx模型与转换后的tflite模型,以及"查看输出"查看模型细节部份如输入节点、输出节点、耗时时间、优化前后的网路对比,最后点选下载模型即可得到tflite模型。
上图中,左边为原模型,右边为优化后的模型。
此外,ONNX到Tflite格式转换路线如下所示:
4 基于Aidlux的PDF转Word
该部分需要先将处理好的代码包上传至AidLux,此处可以选择使用AidLux网页端上传,也可以选择使用VScode直接拖拽上传。
此部分代码如下所示:
from layout_engine import * # cap = cvs.VideoCapture() if __name__ == "__main__": print("----------------------------- 相关配置 --------------------------------") # 加载检测和识别模型 OCR_model = OcrEngine() layout_model = predictor.load_layout_model() print("-->模型加载成功") # 输入的PDF路径 pdf_path = "inputs/paper1.pdf" pdf_name = pdf_path.split("/")[-1].split(".pdf")[0] print("----------------------------- PDF转图片 --------------------------") # 获取当前请求时间 ti = time.localtime() date = f"{ti[0]}_{ti[1]}_{ti[2]}" uid = uuid.uuid4().hex[:10] # 需要储存图片的目录 imagePath = f"outputs/pdf/{ti[0]}_{ti[1]}_{ti[2]}_{ti[3]}_{ti[4]}_{ti[5]}_{uid}" os.makedirs(imagePath, exist_ok=True) pyMuPDF_fitz(pdf_path, imagePath) # 创建一个doc文档,用于后续填充内容 doc = docx.Document() default_section = doc.sections[0] default_section.page_width = Cm(21) default_section.page_height = Cm(30) pdf_image_path_list = os.listdir(imagePath) # os.listdir的数字从小到大排序 pdf_image_path_list.sort(key=lambda x: int(x[:-4])) img_num = 0 for pdf_image in tqdm.tqdm(pdf_image_path_list): print("----------------------------- 版面检测--------------------------") pdf_image_path = os.path.join(imagePath, pdf_image) im_cv2 = cv2.imread(pdf_image_path) im_b64 = np2base64(im_cv2) layout_result,results = predictor.layout_predict(layout_model, im_b64) results = results[0].plot() # 填充图像、表格、页眉、页脚区域为白色,避免文本OCR的干扰 im_cv2_plot = im_cv2.copy() for item in layout_result: points = item.values() for point in points: im_cv2_plot = cv2.rectangle(im_cv2_plot, (point[0], point[1]), (point[2], point[3]), (255, 255, 255), -1) print("----------------------------- 文本检测和识别--------------------------") img_draw, result_list = OCR_model.text_predict(im_cv2_plot, 960) # 文本检测和识别 # 将绘制后的图片从BGR格式转换为RGB格式 img_draw_PIL = Image.fromarray(cv2.cvtColor(results, cv2.COLOR_BGR2RGB)) ocr_result = [] for result in result_list: ocr_dict = {} box, text = result[0].tolist(), result[1] box_xy = [box[0][0], box[0][1], box[2][0], box[2][1]] ocr_dict[text] = box_xy ocr_result.append(ocr_dict) img_draw_PIL = cv2ImgAddText(img_draw_PIL, text, box[0][0], box[0][1]) img_draw_cv = cv2.cvtColor(np.asarray(img_draw_PIL), cv2.COLOR_RGB2BGR) # cvs.imshow(img_draw_cv) cv2.imwrite(f"outputs/plot/{img_num}.jpg",img_draw_cv) img_num = img_num + 1 print("----------------------------- 写入Word--------------------------") # 图片和文本行按照y轴方向进行排序(单栏适用,多栏请先做好分栏操作) final_result = ocr_result + layout_result final_result_sort = sorted(final_result, key=lambda x: x[list(x.keys())[0]][1]) for item in final_result_sort: keys_list = item.keys() for key in keys_list: # 对图片和表格进行处理:裁剪-->保存-->写入Word文档 if key in ["Figure", "Table"]: points = item[key] crop_img = im_cv2[points[1]:points[3], points[0]:points[2]] uid = uuid.uuid4().hex[:10] name = f"{ti[0]}_{ti[1]}_{ti[2]}_{ti[3]}_{ti[4]}_{ti[5]}_{uid}" crop_img_path = f"outputs/crop/{name}.jpg" cv2.imwrite(crop_img_path, crop_img) doc.add_picture(crop_img_path, width=Cm(11)) # 对页眉和页脚不做写入操作,跳过 elif key in ["Header", "Footer"]: continue # 对其他情况(Text正文部分):保存并设置字体和大小 else: paragraph = doc.add_paragraph() run = paragraph.add_run(key) font = run.font font.name = 'Times New Roman' font.size = docx.shared.Pt(11) # 保存文档 word_name = f"{pdf_name}_{ti[0]}_{ti[1]}_{ti[2]}_{ti[3]}_{ti[4]}_{ti[5]}_{uid}" word_path = f'outputs/words/{word_name}.docx' doc.save(word_path) print("Done!")
代码运行过程如下:
代码运行过程
当然,以上过程也可以通过电脑端登录AidLux进行操作,会更方便一些。
最终结果展示:
原PDF文件:
转换为word的结果:
此处在代码中设置去除了页脚和页眉,即转换为word后的文件是不包含页眉和页脚的。文章来源:https://www.toymoban.com/news/detail-804088.html
5 心得体会
之前也了解过AidLux的几期培训,但很遗憾的是因为某些原因总是没能最终实现部署,这次培训刚好在寒假期间,又刚好最近在做关于YOLO的项目,非常感谢AidLux和刘一手老师在此次训练营中带来的起发,让我更加深入的了解了Aidlux这一开发平台,真的非常的实用,并且这次完全实现了部署,并最终发现部署起来并不难,而且全程又有老师和小助手的帮助,解决了很多代码方面的困惑,下一步准备尝试在AidLux平台上部署更多的算法,以期将自己的研究生阶段研究的方向与AidLux平台的部署进行结合,同时更加深入的了解算法的部署。 文章来源地址https://www.toymoban.com/news/detail-804088.html
到了这里,关于将闲置手机变废为宝——基于AidLux平台的智慧教育版面分析应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!