TensorFlow.js圖片分類應(yīng)用開(kāi)發(fā)項(xiàng)目實(shí)戰(zhàn)記錄
一個(gè)基于深度學(xué)習(xí)的Web圖像識(shí)別應(yīng)用開(kāi)發(fā)全記錄
前言
在這個(gè)人工智能日益普及的時(shí)代,將機(jī)器學(xué)習(xí)能力融入Web應(yīng)用已成為前端開(kāi)發(fā)的重要趨勢(shì)。今天,我要和大家分享一個(gè)基于TensorFlow.js開(kāi)發(fā)的圖片分類應(yīng)用項(xiàng)目,通過(guò)這個(gè)項(xiàng)目,我們可以深入了解如何在瀏覽器端實(shí)現(xiàn)圖像識(shí)別功能。
這個(gè)項(xiàng)目不僅是一個(gè)實(shí)用的工具,更是一個(gè)學(xué)習(xí)前端AI應(yīng)用的絕佳案例。讓我們一起探索如何將復(fù)雜的機(jī)器學(xué)習(xí)模型優(yōu)雅地集成到Web應(yīng)用中!
項(xiàng)目概述
這個(gè)應(yīng)用實(shí)現(xiàn)了以下核心功能:
主要功能
- 圖片上傳:支持點(diǎn)擊選擇和拖拽上傳兩種方式
- 攝像頭拍攝:實(shí)時(shí)獲取設(shè)備攝像頭畫面進(jìn)行識(shí)別
- 智能分類:利用預(yù)訓(xùn)練的MobileNet模型進(jìn)行圖像識(shí)別
- 結(jié)果展示:清晰展示識(shí)別結(jié)果及置信度百分比
項(xiàng)目特點(diǎn)
| 特點(diǎn) | 說(shuō)明 |
|---|---|
| 純前端實(shí)現(xiàn) | 無(wú)需后端服務(wù)器,所有處理在瀏覽器完成 |
| 輕量級(jí)模型 | 使用MobileNet,模型體積小加載快 |
| 響應(yīng)式設(shè)計(jì) | 完美適配桌面端和移動(dòng)端 |
| 良好的用戶體驗(yàn) | 加載動(dòng)畫、錯(cuò)誤提示、使用引導(dǎo)一應(yīng)俱全 |
技術(shù)棧選擇
在技術(shù)棧的選擇上,我考慮了以下幾個(gè)關(guān)鍵因素:
TensorFlow.js + MobileNet
為什么選擇這個(gè)組合呢?TensorFlow.js是Google推出的JavaScript機(jī)器學(xué)習(xí)庫(kù),它讓我們可以直接在瀏覽器中運(yùn)行機(jī)器學(xué)習(xí)模型,無(wú)需后端服務(wù)。而MobileNet是一個(gè)輕量級(jí)的卷積神經(jīng)網(wǎng)絡(luò),專為移動(dòng)端和嵌入式設(shè)備設(shè)計(jì),非常適合Web應(yīng)用場(chǎng)景。
技術(shù)優(yōu)勢(shì):
- 無(wú)需服務(wù)器:所有計(jì)算在客戶端完成,減少服務(wù)器壓力
- 隱私保護(hù):圖片不需要上傳到服務(wù)器
- 實(shí)時(shí)響應(yīng):網(wǎng)絡(luò)延遲降到最低
- 離線可用:可配合Service Worker實(shí)現(xiàn)離線功能
代碼示例:
// 模型加載代碼示例
async function loadModel() {
try {
// 加載MobileNet V2模型,alpha=1.0在速度和準(zhǔn)確率之間取得平衡
model = await mobilenet.load({
version: 2,
alpha: 1.0
});
isModelReady = true;
console.log('模型加載成功!');
} catch (error) {
handleError('模型加載失敗', error);
}
}
響應(yīng)式設(shè)計(jì)技術(shù)
采用現(xiàn)代CSS技術(shù)實(shí)現(xiàn)響應(yīng)式布局:
- CSS Grid:實(shí)現(xiàn)雙欄主布局
- Flexbox:處理組件對(duì)齊和導(dǎo)航
- Media Queries:適配不同屏幕尺寸
- CSS動(dòng)畫:增強(qiáng)用戶體驗(yàn)
核心實(shí)現(xiàn)解析
模型加載與狀態(tài)管理
模型加載是整個(gè)應(yīng)用的起點(diǎn),我們需要處理好加載狀態(tài)、進(jìn)度反饋和錯(cuò)誤處理。
/**
* 加載預(yù)訓(xùn)練的MobileNet模型
* 使用TensorFlow.js和MobileNet庫(kù)
*/
async function loadModel() {
try {
updateModelStatus('loading', '正在下載模型文件...');
simulateProgress(0, 30);
// 加載MobileNet模型
// 使用version: 2, alpha: 1.0的版本,在準(zhǔn)確率和速度之間取得平衡
model = await mobilenet.load({
version: 2,
alpha: 1.0
});
simulateProgress(30, 60);
updateModelStatus('loading', '模型加載中...');
// 驗(yàn)證模型是否正確加載
if (!model) {
throw new Error('模型加載失敗,返回值為空');
}
simulateProgress(60, 100);
isModelReady = true;
updateModelStatus('ready', '模型已就緒,可以開(kāi)始分類');
console.log('? MobileNet模型加載成功');
} catch (error) {
console.error('? 模型加載失敗:', error);
handleError('模型加載失敗,請(qǐng)刷新頁(yè)面重試。', error);
}
}
關(guān)鍵點(diǎn)解析:
- 進(jìn)度模擬:由于TensorFlow.js不提供加載進(jìn)度,我們使用setInterval模擬進(jìn)度動(dòng)畫
- 狀態(tài)更新:根據(jù)加載進(jìn)度更新UI狀態(tài),給用戶即時(shí)反饋
- 錯(cuò)誤處理:捕獲并處理加載過(guò)程中的異常
圖片預(yù)處理
為了讓模型能夠正確識(shí)別圖片,我們需要在分類前對(duì)圖片進(jìn)行預(yù)處理。MobileNet模型要求的輸入尺寸是224x224像素。
/**
* 圖像預(yù)處理
* 將圖像調(diào)整為模型要求的輸入尺寸
* @param {HTMLImageElement} img - 原始圖像
* @param {number} width - 目標(biāo)寬度(默認(rèn)224)
* @param {number} height - 目標(biāo)高度(默認(rèn)224)
* @returns {HTMLCanvasElement} 處理后的圖像
*/
function preprocessImage(img, width = 224, height = 224) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 保持寬高比的縮放
const scale = Math.min(width / img.width, height / img.height);
const newWidth = img.width * scale;
const newHeight = img.height * scale;
const x = (width - newWidth) / 2;
const y = (height - newHeight) / 2;
// 繪制調(diào)整后的圖像
ctx.drawImage(img, x, y, newWidth, newHeight);
return canvas;
}
技術(shù)要點(diǎn):
- 保持原始圖片的寬高比,避免變形
- 使用Canvas API進(jìn)行圖像處理
- 將圖片縮放到224x224以匹配模型輸入
圖像分類
這是應(yīng)用的核心功能,使用加載好的模型對(duì)圖片進(jìn)行分類。
/**
* 對(duì)圖像進(jìn)行分類
* @param {HTMLImageElement} img - 要分類的圖像元素
*/
async function classifyImage(img) {
if (!model || !isModelReady) {
showError('模型尚未就緒,請(qǐng)稍候...');
return;
}
showLoading(true);
try {
// 使用TensorFlow.js進(jìn)行圖像分類
// MobileNet模型的輸入尺寸為224x224
// 獲取前5個(gè)預(yù)測(cè)結(jié)果
const predictions = await model.classify(img, 5);
displayResults(predictions);
} catch (error) {
console.error('? 分類失敗:', error);
handleError('圖像分類失敗,請(qǐng)重試。', error);
} finally {
showLoading(false);
}
}
返回結(jié)果格式:
// predictions數(shù)組示例
[
{
className: "golden retriever",
probability: 0.98
},
{
className: "Labrador retriever",
probability: 0.01
}
// ...更多結(jié)果
]
攝像頭功能實(shí)現(xiàn)
使用MediaDevices API訪問(wèn)設(shè)備攝像頭,這是現(xiàn)代瀏覽器提供的標(biāo)準(zhǔn)API。
/**
* 啟動(dòng)攝像頭
*/
async function startCamera() {
if (!isModelReady) {
showError('模型尚未加載完成,請(qǐng)稍候...');
return;
}
try {
// 請(qǐng)求攝像頭權(quán)限并獲取流
stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment', // 優(yōu)先使用后置攝像頭
width: { ideal: 1280 },
height: { ideal: 720 }
},
audio: false
});
// 顯示視頻流
elements.cameraVideo.srcObject = stream;
elements.cameraPlaceholder.classList.add('hidden');
elements.cameraVideo.classList.remove('hidden');
// 更新按鈕狀態(tài)
elements.startCameraBtn.classList.add('hidden');
elements.captureBtn.disabled = false;
elements.stopCameraBtn.classList.remove('hidden');
console.log('? 攝像頭已啟動(dòng)');
} catch (error) {
console.error('? 攝像頭啟動(dòng)失敗:', error);
// 處理常見(jiàn)錯(cuò)誤
if (error.name === 'NotAllowedError') {
showError('無(wú)法訪問(wèn)攝像頭,請(qǐng)確保已授予攝像頭權(quán)限。');
} else if (error.name === 'NotFoundError') {
showError('未檢測(cè)到攝像頭設(shè)備,請(qǐng)連接攝像頭后重試。');
} else {
showError(`攝像頭啟動(dòng)失敗: ${error.message}`);
}
}
}
注意事項(xiàng):
- 需要用戶明確授權(quán)才能訪問(wèn)攝像頭
- 優(yōu)先請(qǐng)求后置攝像頭(移動(dòng)設(shè)備場(chǎng)景)
- 處理各種可能的錯(cuò)誤情況
拖拽上傳實(shí)現(xiàn)
支持用戶將圖片直接拖拽到上傳區(qū)域,提供更好的用戶體驗(yàn)。
/**
* 設(shè)置拖拽上傳功能
*/
function setupDragAndDrop() {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
elements.uploadArea.addEventListener(eventName, preventDefaults);
});
// 拖拽視覺(jué)效果
['dragenter', 'dragover'].forEach(eventName => {
elements.uploadArea.addEventListener(eventName, () => {
elements.uploadArea.classList.add('dragover');
});
});
['dragleave', 'drop'].forEach(eventName => {
elements.uploadArea.addEventListener(eventName, () => {
elements.uploadArea.classList.remove('dragover');
});
});
// 處理拖拽釋放
elements.uploadArea.addEventListener('drop', handleDrop);
}
界面設(shè)計(jì)亮點(diǎn)
動(dòng)態(tài)進(jìn)度指示器
使用CSS動(dòng)畫模擬模型加載進(jìn)度,提供視覺(jué)反饋:
.progress-bar {
width: 100%;
height: 6px;
background: var(--border-color);
border-radius: 3px;
overflow: hidden;
margin-top: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
border-radius: 3px;
transition: width 0.5s ease;
width: 0%;
}
置信度環(huán)形進(jìn)度
使用SVG實(shí)現(xiàn)優(yōu)雅的置信度展示:
.confidence-ring {
fill: none;
stroke: var(--success-color);
stroke-width: 8;
stroke-linecap: round;
stroke-dasharray: 283;
stroke-dashoffset: 283;
transition: stroke-dashoffset 1s ease, stroke 0.3s ease;
}SVG圓環(huán)計(jì)算:
const circumference = 2 * Math.PI * 45; // 半徑45的圓周長(zhǎng) const offset = circumference - (percent / 100) * circumference;
結(jié)果動(dòng)畫效果
/**
* 數(shù)字遞增動(dòng)畫
*/
function animateNumber(element, start, end, duration) {
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// 使用緩動(dòng)函數(shù)實(shí)現(xiàn)平滑動(dòng)畫
const easeOut = 1 - Math.pow(1 - progress, 3);
const current = Math.round(start + (end - start) * easeOut);
element.textContent = current;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
響應(yīng)式布局
/* 桌面端雙欄布局 */
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
}
/* 平板及以下單欄布局 */
@media (max-width: 1024px) {
.main-content {
grid-template-columns: 1fr;
}
.right-panel {
order: -1; /* 結(jié)果區(qū)域置頂 */
}
}
/* 移動(dòng)端適配 */
@media (max-width: 768px) {
.header h1 {
font-size: 1.8rem;
}
.confidence-circle {
width: 120px;
height: 120px;
}
}
視覺(jué)設(shè)計(jì)規(guī)范
:root {
--primary-color: #4f46e5; /* 主色調(diào) */
--secondary-color: #06b6d4; /* 輔助色 */
--success-color: #10b981; /* 成功色 */
--danger-color: #ef4444; /* 危險(xiǎn)色 */
--warning-color: #f59e0b; /* 警告色 */
--bg-gradient-start: #f0f4ff; /* 背景漸變起始 */
--bg-gradient-end: #e0e7ff; /* 背景漸變結(jié)束 */
--card-bg: #ffffff; /* 卡片背景 */
--text-primary: #1f2937; /* 主文字色 */
--text-secondary: #6b7280; /* 次要文字色 */
--border-color: #e5e7eb; /* 邊框色 */
--radius-md: 10px; /* 中等圓角 */
--radius-lg: 16px; /* 大圓角 */
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}錯(cuò)誤處理機(jī)制
完善的錯(cuò)誤處理是提升用戶體驗(yàn)的關(guān)鍵,我們實(shí)現(xiàn)了多層次的錯(cuò)誤處理機(jī)制。
模型加載錯(cuò)誤
try {
model = await mobilenet.load({...});
} catch (error) {
console.error('? 模型加載失敗:', error);
updateModelStatus('error', '模型加載失敗,請(qǐng)刷新頁(yè)面重試。');
showError('模型加載失敗,請(qǐng)檢查網(wǎng)絡(luò)連接后刷新頁(yè)面。');
}
攝像頭訪問(wèn)錯(cuò)誤
try {
stream = await navigator.mediaDevices.getUserMedia({...});
} catch (error) {
if (error.name === 'NotAllowedError') {
showError('無(wú)法訪問(wèn)攝像頭,請(qǐng)確保已授予攝像頭權(quán)限。');
} else if (error.name === 'NotFoundError') {
showError('未檢測(cè)到攝像頭設(shè)備,請(qǐng)連接攝像頭后重試。');
} else if (error.name === 'NotReadableError') {
showError('攝像頭已被其他應(yīng)用占用。');
} else {
showError(`攝像頭啟動(dòng)失敗: ${error.message}`);
}
}
圖片處理錯(cuò)誤
const reader = new FileReader();
reader.onerror = () => {
showError('文件讀取失敗,請(qǐng)重試');
};
img.onerror = () => {
showError('圖片加載失敗,請(qǐng)重試');
};
統(tǒng)一錯(cuò)誤展示
/**
* 顯示錯(cuò)誤信息
* @param {string} message - 錯(cuò)誤消息
*/
function showError(message) {
elements.resultsSection.classList.add('hidden');
elements.errorSection.classList.remove('hidden');
elements.errorMessage.textContent = message;
}
性能優(yōu)化實(shí)踐
DOM元素緩存
在應(yīng)用初始化時(shí)緩存所有DOM元素引用,避免頻繁查詢。
/**
* 緩存DOM元素引用,提高性能
*/
function cacheElements() {
elements.modelStatus = document.getElementById('model-status');
elements.statusText = document.getElementById('status-text');
elements.progressFill = document.getElementById('progress-fill');
elements.uploadArea = document.getElementById('upload-area');
elements.fileInput = document.getElementById('file-input');
elements.previewImage = document.getElementById('preview-image');
// ...更多元素
}
CSS動(dòng)畫優(yōu)先
盡量使用CSS動(dòng)畫而非JavaScript動(dòng)畫,減少主線程負(fù)擔(dān)。
/* CSS動(dòng)畫 */
.progress-fill {
transition: width 0.5s ease;
}
.confidence-ring {
transition: stroke-dashoffset 1s ease, stroke 0.3s ease;
}
圖片尺寸限制
在上傳前驗(yàn)證圖片大小,避免處理過(guò)大的圖片。
// 驗(yàn)證文件大小(限制為10MB)
if (file.size > 10 * 1024 * 1024) {
showError('圖片文件過(guò)大,請(qǐng)選擇小于10MB的圖片');
return;
}
事件委托
使用事件委托處理動(dòng)態(tài)添加的元素。
// 事件委托示例
document.querySelector('.results-list').addEventListener('click', (e) => {
// 處理列表項(xiàng)點(diǎn)擊
});
資源清理
在頁(yè)面卸載時(shí)釋放攝像頭等資源。
window.addEventListener('beforeunload', () => {
if (stream) {
stream.getTracks().forEach(track => track.stop());
}
});
使用指南
環(huán)境要求
| 要求 | 說(shuō)明 |
|---|---|
| 瀏覽器 | 現(xiàn)代瀏覽器(Chrome、Firefox、Safari、Edge) |
| 網(wǎng)絡(luò) | 穩(wěn)定的網(wǎng)絡(luò)連接(首次加載需要下載模型,約20MB) |
| 攝像頭 | 設(shè)備攝像頭(使用攝像頭功能時(shí)需要) |
| 權(quán)限 | 攝像頭訪問(wèn)權(quán)限(首次使用時(shí)需要授權(quán)) |
使用步驟
第一步:打開(kāi)應(yīng)用
在瀏覽器中打開(kāi)index.html文件。
第二步:等待模型加載
頁(yè)面會(huì)自動(dòng)下載MobileNet模型,首次加載可能需要幾秒鐘(取決于網(wǎng)絡(luò)速度)。狀態(tài)欄會(huì)顯示加載進(jìn)度。
第三步:選擇圖片方式
- 圖片上傳:點(diǎn)擊"圖片上傳"標(biāo)簽
- 攝像頭拍攝:點(diǎn)擊"攝像頭拍攝"標(biāo)簽
第四步:獲取圖片
上傳模式:
- 點(diǎn)擊上傳區(qū)域選擇圖片
- 或直接將圖片拖拽到上傳區(qū)域
攝像頭模式:
- 點(diǎn)擊"開(kāi)啟攝像頭"按鈕
- 允許攝像頭權(quán)限請(qǐng)求
- 調(diào)整角度準(zhǔn)備拍攝
第五步:開(kāi)始分類
- 點(diǎn)擊"開(kāi)始分類"或"拍照并分類"按鈕
第六步:查看結(jié)果
- 右側(cè)會(huì)顯示識(shí)別結(jié)果
- 置信度以環(huán)形進(jìn)度展示
- 下方列出Top 5預(yù)測(cè)結(jié)果
常見(jiàn)問(wèn)題
Q: 模型加載很慢怎么辦?
A: 首次加載需要下載約20MB的模型文件。請(qǐng)確保網(wǎng)絡(luò)連接穩(wěn)定。后續(xù)訪問(wèn)會(huì)使用瀏覽器緩存,加載會(huì)快很多。
Q: 攝像頭無(wú)法啟動(dòng)?
A: 請(qǐng)檢查以下幾點(diǎn):
- 瀏覽器是否有攝像頭訪問(wèn)權(quán)限
- 攝像頭是否被其他應(yīng)用占用
- 設(shè)備是否連接了攝像頭
Q: 識(shí)別結(jié)果不準(zhǔn)確?
A: 嘗試以下方法:
- 使用清晰、光線充足的照片
- 確保識(shí)別主體在圖片中清晰可見(jiàn)
- 避免背景過(guò)于復(fù)雜
技術(shù)要點(diǎn)總結(jié)
| 技術(shù)點(diǎn) | 實(shí)現(xiàn)方式 | 說(shuō)明 |
|---|---|---|
| 模型加載 | TensorFlow.js + MobileNet | V2版本,alpha=1.0 |
| 圖片處理 | HTML5 Canvas API | 尺寸調(diào)整和預(yù)處理 |
| 攝像頭訪問(wèn) | MediaDevices API | getUserMedia |
| 拖拽上傳 | Drag and Drop API | 原生拖拽支持 |
| 動(dòng)畫效果 | CSS Transitions + SVG | 圓環(huán)進(jìn)度動(dòng)畫 |
| 響應(yīng)式設(shè)計(jì) | CSS Grid + Media Queries | 三端適配 |
| 狀態(tài)管理 | 原生JavaScript變量 | 簡(jiǎn)單有效 |
| 錯(cuò)誤處理 | try-catch + 錯(cuò)誤類型判斷 | 完善的異常處理 |
擴(kuò)展建議
如果你想進(jìn)一步擴(kuò)展這個(gè)應(yīng)用,可以考慮以下方向:
多模型支持
集成不同的預(yù)訓(xùn)練模型供用戶選擇:
// 支持多種模型
const models = {
mobilenet: () => mobilenet.load({ version: 2, alpha: 1.0 }),
resnet: () => tf.loadLayersModel('https://.../model.json'),
inception: () => tf.loadLayersModel('https://.../model.json')
};
圖像標(biāo)注
在圖片上直接顯示識(shí)別結(jié)果:
function annotateImage(img, predictions) {
const ctx = img.getContext('2d');
predictions.forEach(p => {
// 繪制邊框和標(biāo)簽
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeRect(p.bbox.x, p.bbox.y, p.bbox.width, p.bbox.height);
});
}
歷史記錄
保存用戶的識(shí)別歷史:
// 使用localStorage保存歷史
function saveToHistory(prediction) {
const history = JSON.parse(localStorage.getItem('recognitionHistory') || '[]');
history.unshift({
timestamp: new Date(),
image: capturedImage,
result: prediction
});
// 只保留最近20條記錄
localStorage.setItem('recognitionHistory', JSON.stringify(history.slice(0, 20)));
}
批量處理
支持一次處理多張圖片:
async function batchClassify(images) {
const results = await Promise.all(
images.map(img => model.classify(img))
);
return results;
}
離線支持
使用Service Worker實(shí)現(xiàn)離線功能:
// service-worker.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('tfjs-classifier-v1').then((cache) => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/app.js',
'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest/dist/tf.min.js',
'https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet@latest/dist/mobilenet.min.js'
]);
})
);
});
主題切換
添加深色/淺色主題支持:
function toggleTheme() {
document.body.classList.toggle('dark-theme');
const isDark = document.body.classList.contains('dark-theme');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
項(xiàng)目文件結(jié)構(gòu)
TensorFlowDemo/ ├── index.html # 主頁(yè)面結(jié)構(gòu) ├── styles.css # 樣式文件 ├── app.js # 核心邏輯 └── README.md # 項(xiàng)目說(shuō)明
結(jié)語(yǔ)
通過(guò)這個(gè)項(xiàng)目,我深刻體會(huì)到TensorFlow.js為前端開(kāi)發(fā)者打開(kāi)了一扇通往機(jī)器學(xué)習(xí)世界的大門。無(wú)需深厚的數(shù)據(jù)科學(xué)背景,我們也能在Web應(yīng)用中集成智能識(shí)別能力。
這個(gè)項(xiàng)目教會(huì)了我:
- 前沿技術(shù)的力量:TensorFlow.js讓機(jī)器學(xué)習(xí)觸手可及
- 用戶體驗(yàn)的重要性:即使是AI應(yīng)用,也需要良好的UI/UX設(shè)計(jì)
- 錯(cuò)誤處理的關(guān)鍵:完善的錯(cuò)誤處理能大幅提升用戶滿意度
- 性能優(yōu)化的必要:在前端,性能永遠(yuǎn)不能被忽視
前端與AI的結(jié)合一定是未來(lái)發(fā)展的重要方向。從智能推薦到圖像識(shí)別,從自然語(yǔ)言處理到實(shí)時(shí)翻譯,AI正在改變我們構(gòu)建Web應(yīng)用的方式。
希望這個(gè)項(xiàng)目能給大家一些啟發(fā),也期待看到更多有趣的前端AI應(yīng)用!
參考資源
到此這篇關(guān)于TensorFlow.js圖片分類應(yīng)用開(kāi)發(fā)項(xiàng)目的文章就介紹到這了,更多相關(guān)TensorFlow.js圖片分類應(yīng)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
結(jié)合ES6?編寫?JavaScript?設(shè)計(jì)模式中的結(jié)構(gòu)型模式
這篇文章主要介紹了結(jié)合ES6編寫JavaScript?設(shè)計(jì)模式中的結(jié)構(gòu)型模式,設(shè)計(jì)模式是軟件設(shè)計(jì)中常見(jiàn)問(wèn)題的解決方案,這些模式很容易重復(fù)使用并且富有表現(xiàn)力2022-07-07
uniapp中安裝axios并解決跨域問(wèn)題小結(jié)
跨域(Cross-Origin)是指在瀏覽器中,當(dāng)前網(wǎng)頁(yè)的協(xié)議、域名或端口與請(qǐng)求目標(biāo)的協(xié)議、域名或端口不相同,即存在跨域請(qǐng)求的情況,這篇文章主要介紹了uniapp中安裝axios并解決跨域問(wèn)題小結(jié),需要的朋友可以參考下2024-05-05
微信小程序使用自定義組件導(dǎo)航實(shí)現(xiàn)當(dāng)前頁(yè)面高亮
這篇文章主要介紹了微信小程序使用自定義組件導(dǎo)航實(shí)現(xiàn)當(dāng)前頁(yè)面高亮,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
JavaScript實(shí)現(xiàn)鼠標(biāo)懸浮頁(yè)面切換效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)鼠標(biāo)懸浮頁(yè)面切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

