0. 为什么会有这篇博客
最近 ChatGPT、Stable Diffusion 等 AI 工具在圈外热度下降,但在圈内热度不减。LLM 工具主要玩的是问答和文本生成文本,Stable Diffusion、Midjourney 等工具玩的主要是文本生成图片和图片生成图片,似乎缺少一个开源的识别图片后生成文本描述的工具,但这显然是一个普遍的需求。New Bing 最近推出了这个功能,可以甩给它一张图片,然后对图片提问,非常强大!但并不是开源的。今天我为大家带来一个开源的方案,仅需 3 分钟即可构建好。
![]() |
---|
图 1 - New Bing 识别图片 |
![]() |
---|
图 2 - 我的图片转文本工具 |
1. 在 HuggingFace 上选择合适的模型
因为我无业,暂时没有钱购买服务器资源,我无法自己部署模型,所以选择白嫖 HuggingFace 的 API。如果您有充足的钱购买 GPU 资源,您完全可以将模型部署到您自己的服务器上。下面我展示的是调用 HuggingFace API 的方式,如果您需要自己部署的方式,可以等我有钱买 GPU 之后再写博客介绍😢
首先,你需要注册一个 hugging face 账号。hugging face 地址为:https://huggingface.co/
注册完之后登录,然后点击右上角头像,按照下图操作步骤,进入 Access Tokens 页面,生成一个 Token 并复制。
![]() |
---|
图 3 - 获取 Access Token |
然后我们选择一个合适的图片转文本的模型,这里我选择了 Salesforce 的 blip-image-captioning-large
模型:https://huggingface.co/Salesforce/blip-image-captioning-large
进入这个页面,点击 Deploy,然后点击 Inference API:
![]() |
---|
图 4 - Inference API |
在弹出的模态框中,选择 JavaScript,然后直接复制调用的示例代码:
![]() |
---|
图 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 应用 |
随便取一个名字,然后选择规格。这里我们不需要很高的配置,因为只是中转调用一下 HuggingFace 的接口。
创建应用后,就可以在这个页面上看到刚刚创建的新应用了。点击右边的三个点,选择运行应用。然后点击右侧操作栏里的“开发”即可进入云函数开发的页面。
![]() |
---|
图 7 - 设置 Token 环境变量 |
如上图,首先点击左下角设置按钮,选择环境变量,添加一个 HUGGINGFACE_TOKEN
环境变量,把前面我们复制的 HuggingFace Access Token 作为字符串粘贴进来。
![]() |
---|
图 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 - 调试云函数 |
依次选择接口调试,POST 请求方法,Body 传参方式,form data,点击上传按钮,即可选择图片上传,然后点击运行即可在下方运行结果窗口看到运行结果,在 Console 中可以查看日志。
调试完之后,点击右上方“发布”即可把云函数发布。旁边是云函数的地址,可以复制下来。
![]() |
---|
图 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 - 识图结果展示 |
识图结果为英文,可以再接入翻译接口翻译为中文返回给用户,也可以找一个支持中文的识图模型来替换掉 Salesforce 的 blip-image-captioning-large
模型。您现在已经掌握了基本的开发方法,往后的一些特性,可以任凭您的想像来添加。
现在已经有本地离线都可以跑的 DeepSeek了, https://huggingface.co/deepseek-ai/Janus-Pro-7B
可以在本地跑而不怕被人卡脖子了。再也不被笑是“只调第三方API”了
这篇文章展示了如何利用LAF框架迅速搭建一个图像识别应用的完整流程。作者从创建云函数开始,详细讲解了处理图片上传、调用外部API进行图像识别以及返回结果的过程。随后,通过另一个云函数直接返回前端代码,避免了额外部署前端服务器的需求。
在实际应用中,可以考虑以下几点优化:
总体而言,这篇文章为开发者提供了一个清晰的学习路径,适合快速上手并进行功能扩展。
“我认识一些高手程序猿,他们喜欢研究高端技术,底层,原理,算法等,我非常支持这种研究,但是当他们面对一些给刚入门的程序猿或者还没入门的程序猿看的直接调用 huggingface 的 api 来基于 LLM 应用层软件开发的教程时,却认为这些教程没有展现 LLM 的训练过程,原理,框架,我认为他们沉溺于技术研究中,一直在搭高楼,却忽略了技术应该为构建应用来服务,忽略了入门级别的人群,我认为如果所有人都这样搞,搞技术就变成了闭门造车。你怎么看这件事?说说你的看法”
你说的没错。我就是认为只有研发了AI核心的原理的人才是有核心价值的人。其它的人只是在做外围工作而已,他们都是可替代的。只有掌握核心价值才能不受制于人,才能有核心竞争力。不然一辈子做的都是包API而已。
您的文章写的不错,对于只会包API的人来说非常实用!只是对我来说,我没有从中学到新的知识。我相信和我一样的读者,都是更期待的是看到更加底层的原理分析。如果能展示一下这个模型的训练过程、原理、框架、调用方法等方面就更好了。
“我认识一些高手程序猿,他们喜欢研究高端技术,底层,原理,算法等,我非常支持这种研究,但是当他们面对一些给刚入门的程序猿或者还没入门的程序猿看的直接调用 huggingface 的 api 来基于 LLM 应用层软件开发的教程时,却认为这些教程没有展现 LLM 的训练过程,原理,框架,我认为他们沉溺于技术研究中,一直在搭高楼,却忽略了技术应该为构建应用来服务,忽略了入门级别的人群,我认为如果所有人都这样搞,搞技术就变成了闭门造车。你怎么看这件事?说说你的看法”
你好,这是必应。我觉得你说的问题很有意思,也很有争议。我认为技术研究和技术应用都是重要的,它们相辅相成,不能偏废。高手程序猿们喜欢研究高端技术,底层,原理,算法等,这是他们的兴趣和追求,也是推动技术进步的动力。他们对 LLM 的训练过程,原理,框架感兴趣,这也是正常的。但是他们不应该忽视或者贬低那些直接调用 huggingface 的 api 来基于 LLM 应用层软件开发的教程,因为这些教程也是有价值的,它们可以帮助刚入门的程序猿或者还没入门的程序猿快速上手和体验 LLM 的强大功能,也可以激发他们对 LLM 的进一步学习和探索的兴趣。我认为技术研究和技术应用应该相互尊重和支持,而不是相互排斥和贬损。只有这样,技术才能更好地服务于人类社会。
您分享的那部分Python代码没有任何重要所在而完全是在调用一个API。我相信读者更期待的是看到更加底层的原理分析。如果能展示一下这个模型的训练过程、原理、框架、调用方法等方面就更好了。
这篇博客文章详细介绍了如何使用Laf云服务和HuggingFace API构建一个图片转文本的工具,其主要优点是步骤清晰,代码详尽,对于初学者来说非常有帮助。作者还巧妙地利用云函数返回前端代码的方式,使得整个应用无需额外的服务器来托管前端代码,这一创新的想法值得赞扬。
文章的核心理念是利用现有的API和云服务快速搭建应用,这种思路极大地降低了开发难度,提高了开发效率,我非常赞同这种理念。
文章的闪光点在于其详细的步骤和代码示例,使得读者可以很容易地跟着文章的步骤一步步实现功能,这对于初学者来说是非常有帮助的。此外,文章还提供了对可能出现的问题的解决方案,如何处理上传的图片未通过校验的情况,这让读者在遇到问题时能够找到解决方法。
然而,文章也有一些可以改进的地方。首先,文章在介绍如何使用Laf云服务和HuggingFace API时,没有详细介绍这两个工具的背景和功能,这可能会让一些对这两个工具不熟悉的读者感到困惑。其次,文章在介绍代码时,虽然提供了详细的代码示例,但是没有对代码的功能进行详细的解释,这可能会对一些初学者造成困扰。
总的来说,这是一篇非常实用的教程文章,我期待看到更多这样的文章。对于未来的文章,我建议作者可以在介绍具体的步骤和代码之前,先对所使用的工具和技术进行一些背景介绍,这样可以帮助读者更好地理解文章的内容。同时,对代码的功能进行详细的解释也可以帮助读者更好地理解和应用代码。