前言
在信息时代,Web 页面成为我们与世界交互的重要窗口。如今,AI 程序的出现,为 Web 页面带来了新的变革。通过在 Web 页面上实现图片识别,我们即将迈入一个更加智能与便捷的时代。它将赋予网页全新的能力,使其能够理解图片的内容,为用户带来前所未有的体验。让我们一同踏上这充满无限可能的探索之旅。
具体步骤
html部分
如果你觉得你在编写代码的速度很慢或者是速度有待提升,你可以看看我的另外一篇文章掌握 emmet 语法,小白也能高效编码,我们可以通过掌握emmet提高编码速度。
我们可以试试通过输入:
main.container>(label.custom-file-upload>input#file-upload)+#image-container+p#status
再按tab键就可以快速生成以下的html框架。
html
<main class="container">
<label for="file-upload" class="custom-file-upload">
<input type="file" accept="image/*" id="file-upload">
上传图片
</label>
<div id="image-container"></div>
<p id="status"></p>
</main>
-
我们选择使用main标签而不是选择div,是因为main比div更具有语义化。main标签可以表示页面的主体内容。
-
label标签用于将表单中的输入元素(如文本输入框、单选按钮、复选框等)与相关的文本描述关联起来。label标签通过将 for 属性值设置为输入元素的 id 属性值,建立与输入元素的关联。
-
input的type属性值为file,表示是文件输入框;accept属性值为image/*,表示该文件输入框只接收图像类型的文件。
JavaScript部分
这个部分是这篇文章的重点。
第一部分
首先我们通过JavaScript在远程的transformers库中引入pipleline和env模块。我们需要禁止使用本地的模块,而使用远程的模块。
JavaScript
import { pipeline, env } from "https://cdn.jsdelivr.net/npm/@xenova/transformers@2.6.0"
env.allowLocalModels = false;
接下来我们要读取用户输入的文件,并且输出在页面上。我们可以这样做:
JavaScript
const fileUpload = document.getElementById('file-upload');
const imageContainer = document.getElementById('image-container')
//添加事件监听器来响应元素上的特定事件
//在fileUpload元素上添加一个change事件监听。当用户更改上传文件时,触发事件处理函数。
fileUpload.addEventListener('change', function (e) {
//e是事件对象
//e.target是触发事件的文件上传元素。
//e.target.files[0]是获取选中的第一个文件
const file = e.target.files[0];
// 新建一个FileReader 对象用于读取文件(异步)
//FileReader 是 JavaScript 中的一个对象,用于异步读取用户选择的文件或输入的内容。
const reader = new FileReader();
//reader.onload就相当于FileReader。onload;FileReader。onload是一个事件,在读取操作完成时触发function(e2)
reader.onload = function (e2) {
//创建一个新的html元素,也就是创建一个img标签
const image = document.createElement('img');
//将image的scr属性的url值改为e2.target.result
image.src = e2.target.result;
//将 image 元素添加作为子节点到 imageContainer 元素中
imageContainer.appendChild(image)
detect(image) // 启动AI任务。是功能模块化,要封装出去
}
//FileReader.readAsDataURL():开始读取指定的 Blob 中的内容。一旦完成,result 属性中将包含一个 data: URL 格式的 Base64 字符串以表示所读取文件的内容。
//用于将指定的文件读取为 DataURL 格式的数据。
reader.readAsDataURL(file)
})
我们先梳理一下我们的思路。
-
我们要对文件输入框里元素进行操作,所以我们要获取那个元素。我们可以创建一个fileUpload对象获取这个元素。
-
我们可以对fileUpload对象添加一个change事件监听。这个监听器实现当用户更改上传文件时,触发事件处理函数function(e)实现读取文件并且在页面输出文件的功能。
-
function(e)的功能是用于异步读取用户选择的文件,并且在页面输出它。
- 因为我们要读取整个图片文件,所以我们可以创建一个FileReader对象读取图片文件。在文件读取完成时我们可以用reader.onload实现在文件读取完毕时在id为mage-container的div标签内添加一个img标签输出图片。
- 因为要输出图片就要先知道图片的URL,于是我们用e2.target.result获取图片的URL。
- 因为要在div里添加一个img标签,于是创建一个imageContainer对象获取ID 为 image-container 的元素后用imageContainer.appendChild(image)实现将img标签添加为div的子标签。
- 因为我们要读取整个图片文件,所以我们可以创建一个FileReader对象读取图片文件。在文件读取完成时我们可以用reader.onload实现在文件读取完毕时在id为mage-container的div标签内添加一个img标签输出图片。
-
我们用detect函数封装AI图片识别任务。
第二部分
接下来我们要完成ai任务---------检测图片。
我们要通过ai进行对象检测并且获得检测到的元素的参数。
JavaScript
const status = document.getElementById('status');
const detect = async (image) => {
//在id为status的p标签中添加"分析中...请稍等一会"的文本内容
status.textContent = "分析中...请稍等一会"
//object-detection是对象检查任务;Xenova/detr-resnet-50是指定用于对象检测的模型
const detector = await pipeline("object-detection","Xenova/detr-resnet-50") // model 实例化了detector对象
//使用检测器对象对指定的图像源进行检测,并传入了一些参数:
const output = await detector(image.src, {
threshold: 0.1,
percentage: true
})
//forEach 方法用于遍历数组或类似数组的对象,并对每个元素执行指定的函数
output.forEach(renderBox)
}
分析检测图片和获取检测元素的参数的思路。
- 首先,对图片进行分析需要一些时间,为了让用户知道分析需要时间并且让用户等待,我们可以用我们html中id="status"的p标签输入一段文字。
- 首先我们用document.getElementById('status')获取id="status"的元素,并且用textContent方法动态添加id="status"的p标签的文本内容,然后输出在页面上。
detect异步函数
- 为了实现对象检测我们采用了async/await编写异步代码的方法。使用async关键字来标记一个函数为异步函数,并在其中使用await关键字来等待一个Promise对象的结果。await表达式会暂停当前async function的执行,等待Promise处理完成。
- 创建一个detector对象接收对象分析结果。再创建output对象接收对象分析结果的参数
output.forEach(renderBox)是让output的所有元素遍历进行渲染。
第三部分
我们用renderBox函数将框出图片中的物品的区域,并且在区域内输出ai识别得出的物品名称。
javascript
function renderBox({ box, label }) {
console.log(box, label);
const { xmax, xmin, ymax, ymin } = box
//创建一个盒子
const boxElement = document.createElement("div");
//动态添加div的类名
boxElement.className = "bounding-box"
//通过执行 Object.assign(boxElement.style, {}),可以将一个空对象的属性合并到 boxElement.style 对象中
//准备为 boxElement 设置新的样式属性
Object.assign(boxElement.style, {
borderColor: '#123123',
borderWidth: '1px',
borderStyle: 'solid',
//最左侧
left: 100 * xmin + '%',
//最上侧
top: 100 * ymin + '%',
//宽度
width: 100 * (xmax - xmin) + "%",
//高度
height: 100 * (ymax - ymin) + "%"
})
const labelElement = document.createElement('span');
labelElement.textContent = label;
labelElement.className = "bounding-box-label"
labelElement.style.backgroundColor = '#000000'
//在图片上增加的物品区域范围内添加物品的种类名称的文本内容
boxElement.appendChild(labelElement);
//在图片上增加物品的区域范围
imageContainer.appendChild(boxElement);
}
分析renderBox函数思路。
- output元素中有许多参数,我们只需要label参数(检测得出的物品名称)和box参数(图片物品的区域参数,分别为xmax、xmin、ymax、ymin)。
- 实现框出图片中的物品的区域(每一个新建的div都表示一个图片物体)
- 首先我们创建{ xmax, xmin, ymax, ymin }对象获取位置信息。
- 通过document.createElement("div")创建一个新的div标签,再用className方法动态得给新建的div标签添加一个bounding-box类。
- 通过
- Object.assign(boxElement.style, { })对新建的div添加css样式,并且通过测量图片物体的最左侧和最上侧以及高度和宽度框出图片物体的区域。
- 再通过imageContainer.appendChild(boxElement)代码添加到id="image-container"的div标签中。
- 实现在图片物体区域添加ai识别得出的label参数文本
- 通过document.createElement('span')创建span标签,再用textContent方法将label内容动态输入新建的span标签
- 再通过 boxElement.appendChild(labelElement)添加到刚刚创建的div标签中,也就是添加到图片物体区域中。
JavaScript部分总结
我们将图片识别功能分别分成三个部分实现。首先通过第一部分读取图片,通过第二部分进行ai分析,再通过第三部分输出结果。
我们要有灵活的封装思想。
css部分
该部分不做过度解释,直接上代码。
css
.container {
margin: 40px auto;
width: max(50vw, 400px);
display: flex;
flex-direction: column;
align-items: center;
}
.custom-file-upload {
display: flex;
align-items: center;
cursor: pointer;
gap: 10px;
border: 2px solid black;
padding: 8px 16px;
border-radius: 6px;
}
#file-upload {
display: none;
}
#image-container {
width: 100%;
margin-top: 20px;
position: relative;
}
#image-container>img {
width: 100%;
}
.bounding-box {
position: absolute;
box-sizing: border-box;
}
.bounding-box-label {
position: absolute;
color: white;
font-size: 12px;
}
效果展示
- 选择图片上传
- 选择图片后进行分析的过程
控制台输出的是e2.target.result的内容。
- 分析结果
代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>nlp之图片识别</title>
<style>
.container {
margin: 40px auto;
width: max(50vw, 400px);
display: flex;
flex-direction: column;
align-items: center;
}
.custom-file-upload {
display: flex;
align-items: center;
cursor: pointer;
gap: 10px;
border: 2px solid black;
padding: 8px 16px;
border-radius: 6px;
}
#file-upload {
display: none;
}
#image-container {
width: 100%;
margin-top: 20px;
position: relative;
}
#image-container>img {
width: 100%;
}
.bounding-box {
position: absolute;
box-sizing: border-box;
}
.bounding-box-label {
position: absolute;
color: white;
font-size: 12px;
}
</style>
</head>
<body>
<!-- 语义化 main就比div更好,更有语义化,是这个页面的主体内容 -->
<main class="container">
<!-- label标签用于将表单中的输入元素(如文本输入框、单选按钮、复选框等)与相关的文本描述关联起来。 -->
<label for="file-upload" class="custom-file-upload">
<!-- label标签通过将 for 属性值设置为输入元素的 id 属性值,建立与输入元素的关联 -->
<input type="file" accept="image/*" id="file-upload">
上传图片
</label>
<div id="image-container"></div>
<p id="status"></p>
</main>
<script type="module">
// transformers提供js库(https://cdn.jsdelivr.net/npm/@xenova/transformers@2.6.0)
//从 https://cdn.jsdelivr.net/npm/@xenova/transformers@2.6.0 导入 pipeline 和 env
import { pipeline, env } from "https://cdn.jsdelivr.net/npm/@xenova/transformers@2.6.0"
//禁止加载本地模型,加载远程模型
env.allowLocalModels = false;
//document.getElementById 是 js 中用于通过元素的 ID 来获取特定 HTML 元素的方法
const fileUpload = document.getElementById('file-upload');
const imageContainer = document.getElementById('image-container')
//添加事件监听器来响应元素上的特定事件
//在fileUpload元素上添加一个change事件监听。当用户更改上传文件时,触发事件处理函数。
fileUpload.addEventListener('change', function (e) {
//e是事件对象
//e.target是触发事件的文件上传元素。
//e.target.files[0]是获取选中的第一个文件
const file = e.target.files[0];
// 新建一个FileReader 对象用于读取文件(异步)
//FileReader 是 JavaScript 中的一个对象,用于异步读取用户选择的文件或输入的内容。
const reader = new FileReader();
//reader.onload是一个事件,在读取操作完成时触发
reader.onload = function (e2) {
//用createElement创建一个新的html元素
const image = document.createElement('img');
console.log(e2.target.result);
//将image的scr属性的url值改为e2.target.result
image.src = e2.target.result;
//将 image 元素添加作为子节点到 imageContainer 元素中
imageContainer.appendChild(image)
detect(image) // 启动AI任务 功能模块化,封装出去
}
//用于将指定的文件读取为 DataURL 格式的数据。
reader.readAsDataURL(file)
})
//ai任务
const status = document.getElementById('status');
const detect = async (image) => {
//在id为status的p标签中添加"分析中...请稍等一会"的文本内容
status.textContent = "分析中...请稍等一会"
//object-detection是对象检查任务;Xenova/detr-resnet-50是指定用于对象检测的模型
const detector = await pipeline("object-detection","Xenova/detr-resnet-50") // model 实例化了detector对象
//使用检测器对象对指定的图像源进行检测,并传入了一些参数:
const output = await detector(image.src, {
threshold: 0.1,
percentage: true
})
//forEach 方法用于遍历数组或类似数组的对象,并对每个元素执行指定的函数
output.forEach(renderBox)
}
//封装;渲染盒子
function renderBox({ box, label }) {
console.log(box, label);
const { xmax, xmin, ymax, ymin } = box
//创建一个盒子
const boxElement = document.createElement("div");
//动态添加div的类名
boxElement.className = "bounding-box"
//通过执行 Object.assign(boxElement.style, {}),可以将一个空对象的属性合并到 boxElement.style 对象中
//准备为 boxElement 设置新的样式属性
Object.assign(boxElement.style, {
borderColor: '#123123',
borderWidth: '1px',
borderStyle: 'solid',
//最左侧
left: 100 * xmin + '%',
//最上侧
top: 100 * ymin + '%',
//宽度
width: 100 * (xmax - xmin) + "%",
//高度
height: 100 * (ymax - ymin) + "%"
})
const labelElement = document.createElement('span');
labelElement.textContent = label;
labelElement.className = "bounding-box-label"
labelElement.style.backgroundColor = '#000000'
//在图片上增加的物品区域范围内添加物品的种类名称的文本内容
boxElement.appendChild(labelElement);
//在图片上增加物品的区域范围
imageContainer.appendChild(boxElement);
}
</script>
</body>
</html>
结尾
整个代码还存在一些不足之处,还需要不断完善。希望我的文章可以帮助到你们。欢迎点赞评论加关注。