将闲置手机变废为宝——基于AidLux平台的智慧教育版面分析应用

一、前言

       随着互联网技术以及社会经济等的高速发展,教育行业迎来了人工智能技术的蓬勃发展,而近年来随着教育信息化的不断推进,智能教育成为国家的重要战略之一。尤其在经过新冠疫情之后,社会对智能在线教育的迫切需求,使得对教科书、习题册等文档的自动分析成为研究的热点。其中的文档图像分析与识别技术被广泛应用在人们生活的方方面面,比如银行票据的自动分析处理、快递运单的自动识别、教科书的分析与识别、古籍文稿的分析与理解、数字档案、数字图书馆等等,极大地提高了信息的检索、处理、传播速率。总之,文档图像分析与识别技术的出现和发展极大地方便了人们的生活,也极大地促进了我们的社会向智能化、数字化、信息化发展。

        基于此,本项目跟随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后的文件是不包含页眉和页脚的。

5 心得体会

        之前也了解过AidLux的几期培训,但很遗憾的是因为某些原因总是没能最终实现部署,这次培训刚好在寒假期间,又刚好最近在做关于YOLO的项目,非常感谢AidLux和刘一手老师在此次训练营中带来的起发,让我更加深入的了解了Aidlux这一开发平台,真的非常的实用,并且这次完全实现了部署,并最终发现部署起来并不难,而且全程又有老师和小助手的帮助,解决了很多代码方面的困惑,下一步准备尝试在AidLux平台上部署更多的算法,以期将自己的研究生阶段研究的方向与AidLux平台的部署进行结合,同时更加深入的了解算法的部署。 

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值