创新实训2024.05.01日志:document-loaders

在建立易学知识库的过程中,仅仅有向量数据库以及词嵌入模型、分词器是不够的,因为我们有大量的非结构化文本(如doc,pdf)或者是图片需要上传(例如pdf里面有图片),此时词嵌入无法直接向向量数据库中嵌入图片,需要对图片内文字进行识别,转换为文本后才能继续嵌入。

1. 一切的基础:langchain.document_loaders

langchain为我们提供了一个基类:Unstructured File | 🦜️🔗 LangChain

from langchain.document_loaders.unstructured import UnstructuredFileLoader

即这个UnstructuredFileLoader,LangChain官方对其介绍是:

This notebook covers how to use Unstructured package to load files of many types. Unstructured currently supports loading of text files, powerpoints, html, pdfs, images, and more.

这个类专门用来加载文本、ppt、html、pdf、图片等文件。并且提供了一系列划分文本chunk分块分片的策略。

2. 对pdf的加载 

不过我们的任务可能会更复杂一点,因为我们有一些易学书籍是图片形式的(保存在pdf中),因此先需要进行ocr识别,然后再作为文本加载。

这里我注意到这些图片可能都是人为拍摄的(因为图片有些略有倾斜,不是规整的),因此就需要制定一套解决方案: 

  1. 先读取pdf中的每一页(这里使用了pyMuPDF里的fitz包)GitHub - pymupdf/PyMuPDF: PyMuPDF is a high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents.PyMuPDF is a high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents. - pymupdf/PyMuPDFicon-default.png?t=N7T8https://github.com/pymupdf/PyMuPDF.git
  2. 对于文本我们可以直接加载
  3. 对于图片
    1. 先把图片转正
    2. 然后利用ocr识别图片
  4. 将识别结果加载到内存,并嵌入转储到向量数据库

2.1. OCR识别实例

这一块我用了一个开源库:PaddleOCR

PaddlePaddle/PaddleOCR: Awesome multilingual OCR toolkits based on PaddlePaddle (practical ultra lightweight OCR system, support 80+ languages recognition, provide data annotation and synthesis tools, support training and deployment among server, mobile, embedded and IoT devices) (github.com)icon-default.png?t=N7T8https://github.com/PaddlePaddle/PaddleOCR可以通过:

pip install rapidocr_paddle

进行下载安装。

或者onnx格式的

pip install rapidocr_onnxruntime 

随后即可通过RapidOCR()进行实例初始化并返回该对象。

 2.2. 图像旋转

图像文本分离

首先我们要把图像和文本分离开来,因为文本可以直接加载,不需要ocr识别:

            for i, page in enumerate(doc):
                b_unit.set_description("RapidOCRPDFLoader context page index: {}".format(i))
                b_unit.refresh()
                text = page.get_text("")
                resp += text + "\n"

                img_list = page.get_image_info(xrefs=True)
                for img in img_list:

这里b_unit是tqdm这个进度条库的组件,用来显示pdf文件的文本加载到哪一页了。

b_unit = tqdm.tqdm(total=doc.page_count, desc="RapidOCRPDFLoader context page index: 0")

效果类似于下面的:

 图像尺寸检查

在处理图像的过程中,可能会因为一些小图像影响处理效率,因为小图像中包含的信息可能在视觉效果上很模糊,导致ocr难以识别出文字。因此我们要筛选掉那些过小的图像:

                    if xref := img.get("xref"):
                        bbox = img["bbox"]
                        # 检查图片尺寸是否超过设定的阈值
                        if ((bbox[2] - bbox[0]) / (page.rect.width) < PDF_OCR_THRESHOLD[0]
                            or (bbox[3] - bbox[1]) / (page.rect.height) < PDF_OCR_THRESHOLD[1]):
                            continue

这里我们通过图像的包围盒(bounding box)与整个页面的长宽的比值判断这个图片是否“过小"。其中:

bbox = [ x0,y0,x1,y1 ]

即图像矩形边界框的坐标。这里如果长宽比值过小,我们就跳过这个图像,继续处理图像列表中的下一个。

获取图像像素矩阵以及图像对象

剩下的图像就都是当前页面中比较容易识别文字的图像了。不过正如我在上面提到过的,这些图片可能因为人为拍摄时的角度问题,不够”正“。为了让他们够正,我们要对图像的像素矩阵进行旋转。(注意是对像素进行旋转,包围盒是正的,里面的图像像素可能是歪的)第一步就是要先获取像素矩阵。

pix = fitz.Pixmap(doc, xref)
  1. 从打开的PDF文档(doc)中,使用给定的交叉引用(xref)来定位页面或页面的一部分。
  2. 创建一个 Pixmap 对象,它包含了定位到的页面区域的图像数据。

Pixmap 对象可以用于各种操作,比如图像处理、提取文本、转换格式等。例如,你可以对 pix 对象调用方法来获取图像的宽度、高度、像素数据,旋转角度等。

然后我们从Pixmap中获取这个图像的像素矩阵(先用numpy库获取所有像素采样点,然后把buffer重塑为原始图像宽高的像素矩阵):

if int(page.rotation)!=0:  #如果Page有旋转角度,则旋转图片
     img_array = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width, -1)

随后我们利用Python Image Library(PIL)库,把这个矩阵转换为一个python图像(可能会疑惑怎么获取了像素矩阵还要转为Python图像,因为Pixmap那个里面的sample点不能直接用来做ocr识别,他接收的就是ndArray,然后ocr接收的是BGR三通道的图像,但我们获取的像素矩阵是RGB的,所以还得用PIL和OpenCV库转一下三通道背景,然后才能传到ocr函数里面去):

tmp_img = Image.fromarray(img_array)
ori_img = cv2.cvtColor(np.array(tmp_img),cv2.COLOR_RGB2BGR)

图像旋转 

接下来就是比较重要的一环,旋转图像:

        def rotate_img(img, angle):
            
            h, w = img.shape[:2]
            rotate_center = (w/2, h/2)
            #获取旋转矩阵
            # 参数1为旋转中心点;
            # 参数2为旋转角度,正值-逆时针旋转;负值-顺时针旋转
            # 参数3为各向同性的比例因子,1.0原图,2.0变成原来的2倍,0.5变成原来的0.5倍
            M = cv2.getRotationMatrix2D(rotate_center, angle, 1.0)
            #计算图像新边界
            new_w = int(h * np.abs(M[0, 1]) + w * np.abs(M[0, 0]))
            new_h = int(h * np.abs(M[0, 0]) + w * np.abs(M[0, 1]))
            #调整旋转矩阵以考虑平移
            M[0, 2] += (new_w - w) / 2
            M[1, 2] += (new_h - h) / 2

            rotated_img = cv2.warpAffine(img, M, (new_w, new_h))
            return rotated_img

首先我们先获取图像对象的宽和高,然后宽高中间的位置就是旋转中心(还是再强调一遍,图像本身不是歪的,是图像内容歪了!我们要旋转的是图像的像素点!)

OpenCV提供了一个矩阵旋转的函数,getRotationMatrix2D,第一个参数接收旋转中心,第二个参数接收旋转角度,第三个参数接收scale缩放因子,这里我们不缩放。

随后我们要用到计算机图形学/线性代数中的仿射变换

y = Ax+p \Leftrightarrow y = Mx

首先,我们先来推导一下旋转矩阵:假设二维向量(x,y)绕原点逆时针旋转了角度\Theta,求旋转后的二维向量。 

我们可以利用极坐标法来求解这个问题:

设:

r = \sqrt{x^2+y^2}

同时,向量(x,y)与x正半轴的夹角为\alpha。则

(x,y) = (r\cdot cos(\alpha ),r\cdot sin(\alpha )) \cdot \cdot \cdot \cdot \cdot \cdot (1)

则旋转后的坐标

(x',y') = (r\cdot cos(\alpha +\theta ),r\cdot sin(\alpha +\theta ))\cdot \cdot \cdot \cdot \cdot \cdot (2)

根据三角函数的和差公式:

cos(\alpha-\theta) = cos(\alpha)\cdot cos(\theta) - sin(\alpha)\cdot sin(\theta) \cdot \cdot \cdot \cdot \cdot \cdot (3)\\ sin(\alpha-\theta) = sin(\alpha)\cdot cos(\theta) + sin(\theta)\cdot cos(\alpha)\cdot \cdot \cdot \cdot \cdot \cdot (4)

 结合上述(1) (2) (3) (4)式可得: 

(x',y') = (x\cdot cos(\theta)-y\cdot sin(\theta),y\cdot cos(\theta)+x\cdot cos(\theta))

整理为仿射矩阵可得: 

M = \begin{bmatrix} \cos(\theta) & -\sin(\theta) & t_x \\ \sin(\theta) & \cos(\theta) & t_y \\ \end{bmatrix}

其中:

  • M 是仿射变换矩阵。
  • \Theta是旋转角度。
  • t_xty是旋转后的图像中心点在原始图像中的平移量。

则将M作用于任意一个像素点(x,y,1)得到(x',y'):(注意这里是齐次坐标)

x' = \cos(\theta) \cdot x - \sin(\theta) \cdot y + t_x \\ y' = \sin(\theta) \cdot x + \cos(\theta) \cdot y + t_y

同样,我们可以将原先的(width,height)视作(x,y)元组,计算旋转后图像的新边界。

#计算图像新边界
new_w = int(h * np.abs(M[0, 1]) + w * np.abs(M[0, 0]))
new_h = int(h * np.abs(M[0, 0]) + w * np.abs(M[0, 1]))

 随后将旋转得到的图像像素平移至包围盒中心。

#调整旋转矩阵以考虑平移
M[0, 2] += (new_w - w) / 2
M[1, 2] += (new_h - h) / 2

 最后我们通过OpenCV提供的仿射变换函数,将该矩阵作用到图像上:

rotated_img = cv2.warpAffine(img, M, (new_w, new_h))

至此,图像旋转完毕。

在具体使用这个rotate_img时,传入的旋转角为:  

angle = 360 - page.rotation 

例如,一开始这个图像顺时针旋转了45°,那么我们转360-45相当于让他转了一圈,再往回倒45°,就转正了。

rot_img = rotate_img(img=ori_img, angle=360-page.rotation)

 2.3. 案例

这里可以看一个例子,展示一下ocr识别的效果。(这里我就不用那个易学书籍的例子了,因为之前跑的时候没截图,跑一次要很久很久,一本书200页)

 识别效果如下:

可以看到都是可以识别出来的。而且结果也很精准(只要图片不是太糊)。 

  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值