My avatar, a blue cat cartoon picture

如何 3 分钟搭建图片转文本工具



AI

0. 为什么会有这篇博客

最近 ChatGPT、Stable Diffusion 等 AI 工具在圈外热度下降,但在圈内热度不减。LLM 工具主要玩的是问答和文本生成文本,Stable Diffusion、Midjourney 等工具玩的主要是文本生成图片和图片生成图片,似乎缺少一个开源的识别图片后生成文本描述的工具,但这显然是一个普遍的需求。New Bing 最近推出了这个功能,可以甩给它一张图片,然后对图片提问,非常强大!但并不是开源的。今天我为大家带来一个开源的方案,仅需 3 分钟即可构建好。

图 1 - New Bing 识别图片
图 1 - New Bing 识别图片
图 2 - 我的图片转文本工具
图 2 - 我的图片转文本工具

1. 在 HuggingFace 上选择合适的模型

因为我无业,暂时没有钱购买服务器资源,我无法自己部署模型,所以选择白嫖 HuggingFace 的 API。如果您有充足的钱购买 GPU 资源,您完全可以将模型部署到您自己的服务器上。下面我展示的是调用 HuggingFace API 的方式,如果您需要自己部署的方式,可以等我有钱买 GPU 之后再写博客介绍😢

首先,你需要注册一个 hugging face 账号。hugging face 地址为:https://huggingface.co/

注册完之后登录,然后点击右上角头像,按照下图操作步骤,进入 Access Tokens 页面,生成一个 Token 并复制。

图 3 - 获取 Access Token
图 3 - 获取 Access Token

然后我们选择一个合适的图片转文本的模型,这里我选择了 Salesforce 的 blip-image-captioning-large 模型:https://huggingface.co/Salesforce/blip-image-captioning-large

进入这个页面,点击 Deploy,然后点击 Inference API:

图 4 - Inference API
图 4 - Inference API

在弹出的模态框中,选择 JavaScript,然后直接复制调用的示例代码:

图 5 - 复制示例代码
图 5 - 复制示例代码

前面我说过了,我没有服务器资源。所以我打算白嫖 Laf。Laf 是一个集函数、数据库、存储为一体的云开发平台,可以随时随地,发布上线。这里我用到了它的 JavaScript 云函数功能,所以我选择了复制 JavaScript 的示例代码,您如果想使用 Python 或者 cURL 等语言和工具来调用,可以选择复制对应的示例代码。

我的 Laf 账号上有一定的 Laf 官方免费赠送的额度可以供我白嫖,非常棒🎉

Laf 在大陆和海外都提供服务,大陆的域名为 https://laf.run/,海外的域名为 https://laf.dev/。因为我要调用 HuggingFace 的 API,所以我选择使用海外版本。但我并未测试过大陆版本是否可以访问 HuggingFace 的 API,说不定也能调的到。

2. 创建图片转文本的 Laf 云函数

首先进入 https://laf.dev/ 注册 Laf 账号。注册完之后,进入 dashbord 新建一个 Laf 应用:

图 6 - 新建 Laf 应用
图 6 - 新建 Laf 应用

随便取一个名字,然后选择规格。这里我们不需要很高的配置,因为只是中转调用一下 HuggingFace 的接口。

创建应用后,就可以在这个页面上看到刚刚创建的新应用了。点击右边的三个点,选择运行应用。然后点击右侧操作栏里的“开发”即可进入云函数开发的页面。

图 7 - 设置 Token 环境变量
图 7 - 设置 Token 环境变量

如上图,首先点击左下角设置按钮,选择环境变量,添加一个 HUGGINGFACE_TOKEN 环境变量,把前面我们复制的 HuggingFace Access Token 作为字符串粘贴进来。

图 8 - 创建 img2text 云函数
图 8 - 创建 img2text 云函数

如上图所示,点击加号创建云函数,我这里命名为 img2text,只勾选 POST 方法。

以下是我的云函数的代码。我 JavaScript 玩的一般,我是搞 .NET 后端的,所以大家凑合看一下。比较关键的地方我加了注释。

其中我们从 process.env 中获取我们设置的 HUGGINGFACE_TOKEN 环境变量,从 ctx 中获取上传的 files,取第 0 个作为 file,也就是我们计划转为文字的图片,保存到 file 变量中。然后做了一些简单的名称、类型、大小校验。

img2text 函数是我们之前在 HuggingFace 中复制的 JavaScript 调用示例代码,我稍做了调整。

import cloud from '@lafjs/cloud'

const fs = require("fs")

export default async function (ctx: FunctionContext) {
  // 获取到所有 file
  const _files = ctx.files;
  // 从环境变量中获取到 HUGGINGFACE_TOKEN
  const apiKey = process.env['HUGGINGFACE_TOKEN'];

  // 校验 start
  console.log('uploadFile->files', _files);

  // 取 _files 中的第 0 个作为我们要处理的文件
  const file = _files[0];
  if (!_files || _files.length == 0) {
    return '未上传文件!';
  }
  const fileInfo = _files[0];
  if (!fileInfo.filename) {
    return '文件名称为空!';
  }
  if (!fileInfo.mimetype) {
    return '文件类型为空!';
  }
  const mimetype = file.mimetype;
  console.log(mimetype);
  if (!mimetype.startsWith("image/")) {
    return '不合法的图片文件!';
  }
  if (!fileInfo.size || fileInfo.size > 5 * 1024 * 1024) {
    return '文件大小不能超过5M!';
  }
  // 校验 end

  // 获取上传文件的对象
  let fileData = await fs.readFileSync(fileInfo.path);

  // 调用 HuggingFace 接口获取图片转文本的结果
  const img2textResp = await img2text(fileData, apiKey);

  // 取到 Response 中的 generated_text 字段,即图片转文字后的字符串
  const imgText = img2textResp[0].generated_text
  return imgText;
}

// 调用 HuggingFace API 实现图片转文本
async function img2text(fileData, apiKey) {
  const response = await fetch(
    "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-large",
    {
      headers: { Authorization: `Bearer ${apiKey}` },
      method: "POST",
      body: fileData,
    }
  );

  // 将得到的结果反序列化并返回后
  const result = await response.json();
  return result;
}

调用 HuggingFace 接口返回的结果格式为:

[{"generated_text": "a close up of a small black and yellow animal wearing a bee costume"}]

所以我在 img2text 函数中使用 const result = await response.json(); 将得到的结果反序列化并返回后,在主函数中使用 const imgText = img2textResp[0].generated_text 来把 generated_text 字段取到 imgText 变量中并返回。最终我们返回的就是一个简单的图片转文字后的字符串。如果用户上传的图片未通过校验,则会返回报错信息。

我们可以使用 laf 右侧的调试部分进行调试:

图 9 - 调试云函数
图 9 - 调试云函数

依次选择接口调试,POST 请求方法,Body 传参方式,form data,点击上传按钮,即可选择图片上传,然后点击运行即可在下方运行结果窗口看到运行结果,在 Console 中可以查看日志。

调试完之后,点击右上方“发布”即可把云函数发布。旁边是云函数的地址,可以复制下来。

图 10 - 发布云函数
图 10 - 发布云函数

3. 使用 Laf 云函数实现简单的前端

我打算把白嫖贯彻到底,直接使用云函数返回前端代码组成的字符串,这样就不需要单独的服务器来放前端代码了。

所以我根据前面创建云函数的步骤,创建了一个 GET 方法的云函数,命名为 do。简单写了一些样式和前端代码,作为该云函数的返回值字符串,直接返回:

import cloud from '@lafjs/cloud'

export default async function (ctx: FunctionContext) {
  console.log('Hello World')
  return `
  <style>
    body {
      height: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }

    input {
      width: 300px;
      border: 1px solid gray;
      padding: 5px;
      margin: 10px;
    }

    button {
      width: 100px;
      height: 30px;
      background-color: blue;
      color: white;
      border: none;
      padding: 5px;
      margin: 10px;
    }

    button:hover {
      background-color: darkblue;
    }

    div {
      width: 300px;
      height: 100px;
      border: 1px solid gray;
      padding: 5px;
      margin: 10px;
      text-align: center;
    }

    img {
      max-width: 500px;
    }
  </style>
  <input type="file" id="imageInput" accept="image/*" onchange="showImage()">
  <button onclick="uploadImage()">图片转文字</button>
  <div id="result"></div>
  <script>
    function uploadImage() {
      var input = document.getElementById("imageInput");
      var file = input.files[0];
      var formData = new FormData();
      formData.append("image", file);
      var xhr = new XMLHttpRequest();
      // open 的第二个参数填入前面复制的第一个云函数的地址
      xhr.open("POST", "https://i6giyd.laf.dev/img2text");
      xhr.onload = function() {
        if (xhr.status === 200) {
          var responseText = xhr.responseText;
          var result = document.getElementById("result");
          result.textContent = responseText;
        } else {
          alert("Upload failed: " + xhr.statusText);
        }
      };
      xhr.send(formData);
    }
    function showImage() {
      var input = document.getElementById("imageInput");
      var file = input.files[0];
      var reader = new FileReader();
      reader.onload = function() {
        var dataURL = reader.result;
        var img = document.createElement("img");
        img.src = dataURL;
        var body = document.body;
        body.insertBefore(img, body.firstChild);
      };
      reader.readAsDataURL(file);
    }
  </script>
  `;
}

其中 open 的第二个参数填入前面复制的第一个云函数的地址。我在代码中已经使用注释标出。这都是最基础的前端代码,不做详细解释。

然后发布该云函数,复制下地址,直接在浏览器中访问即可:

图 11 - 识图结果展示
图 11 - 识图结果展示

识图结果为英文,可以再接入翻译接口翻译为中文返回给用户,也可以找一个支持中文的识图模型来替换掉 Salesforce 的 blip-image-captioning-large 模型。您现在已经掌握了基本的开发方法,往后的一些特性,可以任凭您的想像来添加。