likes
comments
collection
share

NestJS小技巧09-使用NestJS和Tesseract进行文件上传和图像解析

作者站长头像
站长
· 阅读数 28
by 雪隐 from https://juejin.cn/user/1433418895994094
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

原文链接

嘿,嘿,欢迎回来!在这篇文章中,我将快速讨论NestJS中的文件上传,为什么不呢?使用一种可以从图像中读取文本的工具,称为Tesseract OCR

你不需要了解OCR和如何从图像中读取文本,这只是一个很好的工具,希望能让这篇文章读起来更有趣。

在本文结束时,您应该能够配置一个端点来将文件(在我们的情况下是图像)发送到您的API,并了解如何使用tesseract进行图像识别。

让我们开始吧!


NestJS脚手架和配置文件上传

为了使本文与我以前的文章(特别是关于Github的文章)更加隔离,让我们使用NestJS创建一个快速API:

  • 确保您拥有最新的nestjs/cli
npm i -g @nestjs/cli
  • 现在创建项目并选择您喜欢的包管理工具:
nest new file-upload-ocr

我们现在有一个名为文件上传ocr的文件夹,其中包含所有基本的API集

默认情况下,NestJS已经构建了用于创建RESTAPI的API,并具有用于ExpressJS的multer的内置模块

根据他们的文档,让我们添加multer的类型

pnpm i -D @types/multer

了这些类型,您的IDE在使用multer时可以为您的工作提供便利。

要创建一个可以接收文件的端点,以下是要添加到app.controller.ts中的接收:

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    // 在这里做我们想做的一些事情.
  }

UseInterceptors的魔力在于它将为您提供一个包含以下内容的文件对象:

  • 字段名称
  • 原始名称
  • 编码
  • 模拟型
  • 缓冲器
  • 大小

对于本文来说,重要的是我们将发送给tesseract的缓冲区。

大小对于在后端验证您想要接收的最大大小非常重要。

我们已经有办法接收文件了。让我们现在配置OCR!

配置tesseract

在编写和安装npm软件包之前,在操作系统中安装tesseract是至关重要的。请根据您使用的操作系统遵循指南。我是MacOS系统所以我直接运行下面的命令进行安装

brew install tesseract

安装后,我们现在可以使用以下组件安装npm软件包:

pnpm i --save node-tesseract-ocr

现在,让我们为tesseract创建一个服务:

nest g module ocr
nest g service ocr

这将在名为ocr的文件夹中创建ocr.service.ts文件,以及需要导出服务的ocr.module.ts:

import { Module } from '@nestjs/common';
import { OcrService } from './ocr.service';

@Module({
  providers: [OcrService],
  exports: [OcrService],
})
export class OcrModule {}

我们需要一个图像和我们想做的事情的目的。对于我们的场景,挑战是从超市获得收据并将其解析给用户。你可以改进它来控制你的开支和类似的事情。让我们使用以下收据图像作为测试:

NestJS小技巧09-使用NestJS和Tesseract进行文件上传和图像解析

请不要介意语言,因为我参照的文章是住在意大利,他提供的东西就是意大利语

从中我们可以得到价格(欧元)和“IMPORTO PAGATO”的线型,这意味着支付的总金额。我决定使用这个,因为我们在使用欧元时总是会遇到一些问题,我相信这对你来说是一个很好的挑战。一旦你完成了文章,你可以更改图片并测试一些你自己的收据。

现在,回到ocr.services.ts,我们可以开始玩它了。让我们从以下内容开始:

import { Injectable } from '@nestjs/common';
import * as tesseract from 'node-tesseract-ocr';

@Injectable()
export class OcrService {
  config = {
    lang: 'eng',
    oem: 1,
    psm: 4,
  };

  parseImage(imageBuffer) {
    return tesseract
      .recognize(imageBuffer, this.config)
      .then((text) => {
        return text.split('\n');
      })
      .catch((error) => {
        throw new Error(error.message);
      });
  }
}

这种情况下的配置变量仍然可以使用'eng'作为语言和oem,psm,它们是

  • oem:引擎模式
  • psm:页面分割模式。在我们的案例中,我们使用的是单列。

您可以在他们的文档中看到所有的oem/psm选项。

recision方法将返回一个字符串,在本文中,我们将分解为一个数组以供将来使用。

现在,回到app.controller.ts,让我们在构造函数中导入服务器:

  constructor(
    private readonly appService: AppService,
    private readonly ocrService: OcrService,
  ) {}

并更新我们的uploadFile功能:

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    return this.ocrService.parseImage(file.buffer);
  }

现在使用以下功能运行应用程序:

npm run start

打开Postman进行请求或者用其他类似的工具

NestJS小技巧09-使用NestJS和Tesseract进行文件上传和图像解析

太酷了!我们已经提取了数据,但…这似乎有点奇怪。。在这种情况下,仅仅拥有数据并不是100%有价值的……如果我们在返回数据之前解析数据呢?

为了解析数据,我们可以使用一些正则表达式来匹配“值字母”(例如,0,02 D),还可以使用“IMPORTO PAGATO”来确认项目的总和将与其匹配。

这两个正则表达式应该类似于:

const regexForAnyCurrency = /\d+(?:[.,]\d{0,2})?/;  
const regexWithLetter = /\d+(?:[.,]\d{0,2})?\x20[DBO0]/;
免责声明:我匆忙制作了regex。请随时为您自己的场景升级

我们将面临的另一个问题是,货币是欧元,使用不同的模式而不是浮动。为此,我将使用stackoverflow的建议:

  parseLocaleNumber(stringNumber, locale) {
    const thousandSeparator = Intl.NumberFormat(locale)
      .format(11111)
      .replace(/\p{Number}/gu, '');
    const decimalSeparator = Intl.NumberFormat(locale)
      .format(1.1)
      .replace(/\p{Number}/gu, '');

    return parseFloat(
      stringNumber
        .replace(new RegExp('\\' + thousandSeparator, 'g'), '')
        .replace(new RegExp('\\' + decimalSeparator), '.'),
    );
  }

最后,让我们使用它。我们将在ocr.service.ts中进行操作,但请记住,最好的做法是有一个专门的服务来解析字符串数组,维护ocr.services.ts只是为了提取数据并返回它。为了本文的简单性,我们使用相同的文件。最后的方法是:

import { Injectable } from '@nestjs/common';
import * as tesseract from 'node-tesseract-ocr';

@Injectable()
export class OcrService {
  config = {
    lang: 'eng',
    oem: 1,
    psm: 4,
  };
  regexForAnyCurrency = /\d+(?:[.,]\d{0,2})?/;
  regexWithLetter = /\d+(?:[.,]\d{0,2})?\x20[DBO0]/;
  TOTAL_AMOUNT_PAID = 'IMPORTO PAGATO';

  parseLocaleNumber(stringNumber, locale) {
    const thousandSeparator = Intl.NumberFormat(locale)
      .format(11111)
      .replace(/\p{Number}/gu, '');
    const decimalSeparator = Intl.NumberFormat(locale)
      .format(1.1)
      .replace(/\p{Number}/gu, '');

    return parseFloat(
      stringNumber
        .replace(new RegExp('\\' + thousandSeparator, 'g'), '')
        .replace(new RegExp('\\' + decimalSeparator), '.'),
    );
  }

  parseReceipt(arrayText: Array<string>) {
    const itemsFound = [];
    const totalAmountPaid = [];
    for (const value of arrayText) {
      if (value.match(this.regexWithLetter)) {
        itemsFound.push({
          item: value.split(this.regexWithLetter)[0].trim(),
          value: this.parseLocaleNumber(
            value.match(this.regexWithLetter)[0].split(' ')[0],
            'de',
          ),
        });
      }
      if (value.indexOf(this.TOTAL_AMOUNT_PAID) > -1) {
        totalAmountPaid.push({
          item: this.TOTAL_AMOUNT_PAID,
          value: this.parseLocaleNumber(
            value.match(this.regexForAnyCurrency)[0],
            'it',
          ),
        });
      }
    }

    let totalItemsSum = 0;
    itemsFound.forEach(({ value }) => {
      totalItemsSum += value;
    });
    return {
      totalItemsSum,
      totalAmountPaid,
      itemsFound,
    };
  }

  parseImage(imageBuffer) {
    return tesseract
      .recognize(imageBuffer, this.config)
      .then((text) => {
        return this.parseReceipt(text.split('\n'));
      })
      .catch((error) => {
        throw new Error(error.message);
      });
  }
}

现在,再调用一个我们的api:

NestJS小技巧09-使用NestJS和Tesseract进行文件上传和图像解析

很酷,我们正在拿到所有的物品,并对其进行汇总,确认最终价值与支付的总金额相同。如果你想超越这一点,那么你可以为商品名称创建一个类似的字典映射,并对其进行分类,然后你可以从你花得更多的东西或你不应该买但仍在买的东西中提取更多有价值的信息。


在本文中,我们简要地使用NestJS添加了一个文件上传端点,并使用tesseract ocr来解析图像中的文本,最后将收据数据解析为一些更有价值的信息。

管理文件上传非常简单,如果你使用后端应用程序,这是你日常生活中必备的技能!OCR是一种你只会根据具体情况使用的东西,它里面有一个完整的宇宙。还要记住,我们测试了一个孤立的场景……根据你使用的收据/照片,结果可能并不理想。

进阶-对中文的支持

如果你用homebrew安装tesseract后的路径不在上面图片位置,请用以下命令查询安装路径:

# 执行命令
brew list tesseract

# 执行结果
>/usr/local/Cellar/tesseract/5.2.0/bin/tesseract
>/usr/local/Cellar/tesseract/5.2.0/include/tesseract/ (12 files)
>/usr/local/Cellar/tesseract/5.2.0/lib/libtesseract.5.dylib
>/usr/local/Cellar/tesseract/5.2.0/lib/pkgconfig/tesseract.pc
>/usr/local/Cellar/tesseract/5.2.0/lib/ (2 other files)
>/usr/local/Cellar/tesseract/5.2.0/share/tessdata/ (35 files)

tessdata的安装路径知道了以后,下载中文语言包,然后复制到tessdata文件夹下。

改造下service

import { Injectable } from '@nestjs/common';
import * as tesseract from 'node-tesseract-ocr';

@Injectable()
export class OcrCnService {
  config = {
    lang: 'chi_sim+eng',
  };

  parseImage(imageBuffer) {
    return tesseract
      .recognize(imageBuffer, this.config)
      .then((text) => {
        return text;
      })
      .catch((error) => {
        throw new Error(error.message);
      });
  }
}

随便截图试验下

NestJS小技巧09-使用NestJS和Tesseract进行文件上传和图像解析

最后,文件上传和OCR只是你可以利用创造力创造更伟大事物的工具。

感谢您到目前为止的阅读,我希望您今天喜欢玩文件上传和tesseract!如果这篇文章对你有用,别忘了点赞/评论

本章代码

代码