第5章:计算机视觉应用
5.1 图像识别基础
理论讲解
计算机视觉是人工智能的一个分支,旨在让计算机能够理解和解释图像内容。图像识别是计算机视觉的核心任务之一,包括:
- 图像分类:将图像归类到预定义的类别中
- 物体检测:识别图像中的物体并确定其位置
- 图像分割:将图像分割成不同的区域,每个区域对应不同的物体或背景
- 图像生成:根据输入生成新的图像
图像识别的基本流程包括:
- 图像获取:从摄像头、文件或网络获取图像
- 图像预处理:调整大小、归一化、增强等
- 特征提取:提取图像中的关键特征
- 模型推理:使用训练好的模型进行预测
- 结果输出:显示或保存识别结果
代码示例
// 图像识别基础概念的简单演示
function imageRecognitionBasics() {
console.log('=== 图像识别基础 ===');
// 图像数据结构示例
const image = {
width: 28,
height: 28,
channels: 3, // RGB
data: new Array(28 * 28 * 3).fill(0) // 像素数据
};
console.log('图像数据结构:', image);
// 图像预处理步骤
const preprocessingSteps = [
'调整大小',
'归一化像素值',
'数据增强',
'通道转换'
];
console.log('图像预处理步骤:', preprocessingSteps);
// 图像识别结果示例
const recognitionResult = {
className: 'cat',
confidence: 0.95,
boundingBox: { x: 100, y: 50, width: 200, height: 150 }
};
console.log('图像识别结果:', recognitionResult);
}
imageRecognitionBasics();实践练习
- 描述图像识别的基本流程
- 思考:为什么图像预处理是图像识别的重要步骤?
- 列出3个图像识别的实际应用场景
5.2 预训练模型使用
理论讲解
预训练模型是已经在大规模数据集上训练好的模型,可以直接用于特定任务,或者作为迁移学习的基础。使用预训练模型的优势包括:
- 节省训练时间和计算资源
- 无需大量标注数据
- 提供良好的初始性能
- 可以针对特定任务进行微调
在前端AI开发中,常用的预训练模型包括:
- MobileNet:轻量级图像分类模型
- COCO-SSD:物体检测模型
- Facemesh:人脸关键点检测模型
- Handpose:手部关键点检测模型
- PoseNet:人体姿态估计模型
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用预训练模型</title>
<!-- 引入 ml5.js -->
<script src="https://cdn.jsdelivr.net/npm/ml5@0.12.2/dist/ml5.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.container {
display: flex;
gap: 20px;
margin: 20px 0;
}
.image-box {
width: 400px;
border: 1px solid #ddd;
padding: 10px;
}
img {
max-width: 100%;
height: auto;
}
.result-box {
flex: 1;
border: 1px solid #ddd;
padding: 10px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
margin: 10px 0;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>使用预训练模型进行图像分类</h1>
<div class="container">
<div class="image-box">
<input type="file" id="imageInput" accept="image/*">
<img id="image" src="https://picsum.photos/400/300" alt="待识别图像">
<button onclick="classifyImage()">识别图像</button>
</div>
<div class="result-box">
<h3>识别结果</h3>
<div id="result"></div>
</div>
</div>
<script>
// 全局变量
let classifier;
const imageElement = document.getElementById('image');
const resultElement = document.getElementById('result');
const imageInput = document.getElementById('imageInput');
// 初始化分类器
function initClassifier() {
console.log('加载预训练模型 MobileNet...');
classifier = ml5.imageClassifier('MobileNet', modelLoaded);
}
// 模型加载完成后的回调
function modelLoaded() {
console.log('模型加载完成!');
resultElement.innerHTML = '<p>模型已加载,点击"识别图像"按钮开始识别</p>';
}
// 图像分类函数
function classifyImage() {
resultElement.innerHTML = '<p>正在识别中...</p>';
classifier.classify(imageElement, (error, results) => {
if (error) {
console.error(error);
resultElement.innerHTML = `<p>识别错误:${error}</p>`;
return;
}
// 显示结果
let resultHTML = '<ul>';
results.forEach((result, index) => {
resultHTML += `<li>${index + 1}. ${result.label}:${(result.confidence * 100).toFixed(2)}%</li>`;
});
resultHTML += '</ul>';
resultElement.innerHTML = resultHTML;
});
}
// 处理图像上传
imageInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
imageElement.src = e.target.result;
};
reader.readAsDataURL(file);
}
});
// 页面加载完成后初始化
window.addEventListener('load', initClassifier);
</script>
</body>
</html>实践练习
- 运行上面的代码,测试图像分类功能
- 尝试使用不同的预训练模型,如ResNet或Inception
- 调整代码,实现实时摄像头图像分类
- 比较不同预训练模型的性能和速度
5.3 实战:图像分类应用
理论讲解
在这个实战中,我们将使用TensorFlow.js和MobileNet预训练模型开发一个完整的图像分类应用。该应用将具有以下功能:
- 支持上传本地图像
- 支持使用摄像头实时拍摄
- 显示分类结果和置信度
- 支持切换不同的预训练模型
图像分类的核心是使用预训练模型对输入图像进行推理,得到分类结果。我们将使用TensorFlow.js的Layers API加载预训练模型,并进行推理。
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图像分类应用</title>
<!-- 引入 TensorFlow.js -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.10.0/dist/tf.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
}
.container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 20px 0;
}
.box {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
flex: 1;
min-width: 300px;
}
.image-container {
text-align: center;
margin: 10px 0;
}
img, video {
max-width: 100%;
height: auto;
border: 1px solid #ccc;
border-radius: 4px;
}
#webcam {
display: none;
}
.controls {
margin: 10px 0;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 16px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.result {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
select {
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
margin: 5px;
}
</style>
</head>
<body>
<h1>图像分类应用</h1>
<div class="container">
<div class="box">
<h3>图像输入</h3>
<div class="controls">
<select id="modelSelect">
<option value="MobileNet">MobileNet</option>
<option value="ResNet50">ResNet50</option>
</select>
<button onclick="loadModel()">加载模型</button>
</div>
<div class="controls">
<button onclick="useUpload()">上传图像</button>
<button onclick="useWebcam()">使用摄像头</button>
<input type="file" id="fileInput" accept="image/*" style="display: none;">
</div>
<div class="image-container">
<img id="uploadedImage" src="" alt="上传的图像" style="display: none;">
<video id="webcam" autoplay playsinline></video>
<canvas id="webcamCanvas" width="300" height="200" style="display: none;"></canvas>
</div>
</div>
<div class="box">
<h3>分类结果</h3>
<div id="modelStatus" class="result">模型未加载</div>
<div id="classificationResult" class="result">等待图像输入</div>
<div class="controls">
<button onclick="classifyImage()" disabled>分类图像</button>
<button onclick="stopWebcam()" disabled>停止摄像头</button>
</div>
</div>
</div>
<script>
// 全局变量
let model;
let isModelLoaded = false;
let webcamStream = null;
let currentInputType = 'upload';
// DOM 元素
const modelSelect = document.getElementById('modelSelect');
const uploadedImage = document.getElementById('uploadedImage');
const webcam = document.getElementById('webcam');
const webcamCanvas = document.getElementById('webcamCanvas');
const fileInput = document.getElementById('fileInput');
const modelStatus = document.getElementById('modelStatus');
const classificationResult = document.getElementById('classificationResult');
const classifyBtn = document.querySelector('button[onclick="classifyImage()"]');
const stopWebcamBtn = document.querySelector('button[onclick="stopWebcam()"]');
// 加载模型
async function loadModel() {
const modelName = modelSelect.value;
modelStatus.innerHTML = `正在加载 ${modelName} 模型...`;
try {
// 加载预训练模型
if (modelName === 'MobileNet') {
model = await tf.loadLayersModel('https://tfhub.dev/google/tfjs-model/imagenet/mobilenet_v2_100_224/classification/5', {
fromTFHub: true
});
} else if (modelName === 'ResNet50') {
model = await tf.loadLayersModel('https://tfhub.dev/google/tfjs-model/imagenet/resnet_v2_50/classification/4', {
fromTFHub: true
});
}
isModelLoaded = true;
modelStatus.innerHTML = `${modelName} 模型加载成功!`;
classifyBtn.disabled = false;
} catch (error) {
console.error('加载模型失败:', error);
modelStatus.innerHTML = `加载模型失败:${error.message}`;
}
}
// 使用上传图像
function useUpload() {
currentInputType = 'upload';
fileInput.click();
}
// 处理文件上传
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
uploadedImage.src = e.target.result;
uploadedImage.style.display = 'block';
webcam.style.display = 'none';
webcamCanvas.style.display = 'none';
};
reader.readAsDataURL(file);
}
});
// 使用摄像头
async function useWebcam() {
currentInputType = 'webcam';
try {
// 获取摄像头流
webcamStream = await navigator.mediaDevices.getUserMedia({ video: true });
webcam.srcObject = webcamStream;
webcam.style.display = 'block';
uploadedImage.style.display = 'none';
webcamCanvas.style.display = 'none';
stopWebcamBtn.disabled = false;
// 开始实时分类
startRealTimeClassification();
} catch (error) {
console.error('获取摄像头失败:', error);
classificationResult.innerHTML = `获取摄像头失败:${error.message}`;
}
}
// 停止摄像头
function stopWebcam() {
if (webcamStream) {
webcamStream.getTracks().forEach(track => track.stop());
webcamStream = null;
webcam.style.display = 'none';
stopWebcamBtn.disabled = true;
}
}
// 图像预处理
function preprocessImage(imageElement) {
return tf.tidy(() => {
// 转换为张量
let tensor = tf.browser.fromPixels(imageElement)
.resizeBilinear([224, 224]) // 调整大小
.toFloat() // 转换为浮点数
.expandDims(); // 添加批次维度
// 归一化(根据模型要求)
return tensor.div(255.0);
});
}
// 分类图像
async function classifyImage() {
if (!isModelLoaded) {
classificationResult.innerHTML = '请先加载模型';
return;
}
let imageElement;
if (currentInputType === 'upload' && uploadedImage.src) {
imageElement = uploadedImage;
} else if (currentInputType === 'webcam' && webcamStream) {
const ctx = webcamCanvas.getContext('2d');
webcamCanvas.width = webcam.videoWidth;
webcamCanvas.height = webcam.videoHeight;
ctx.drawImage(webcam, 0, 0, webcamCanvas.width, webcamCanvas.height);
imageElement = webcamCanvas;
} else {
classificationResult.innerHTML = '请先上传图像或启动摄像头';
return;
}
classificationResult.innerHTML = '正在分类...';
// 预处理图像
const preprocessedImage = preprocessImage(imageElement);
try {
// 模型推理
const predictions = await model.predict(preprocessedImage).data();
// 解析结果
const top5 = Array.from(predictions)
.map((probability, index) => ({ probability, index }))
.sort((a, b) => b.probability - a.probability)
.slice(0, 5);
// 显示结果
let resultHTML = '<h4>分类结果:</h4><ul>';
top5.forEach((prediction, i) => {
resultHTML += `<li>${i + 1}. 类别 ${prediction.index}:${(prediction.probability * 100).toFixed(2)}%</li>`;
});
resultHTML += '</ul>';
classificationResult.innerHTML = resultHTML;
} catch (error) {
console.error('分类失败:', error);
classificationResult.innerHTML = `分类失败:${error.message}`;
} finally {
// 释放张量资源
preprocessedImage.dispose();
}
}
// 实时分类
function startRealTimeClassification() {
if (!isModelLoaded || !webcamStream) return;
classifyImage();
// 每隔1秒分类一次
setTimeout(startRealTimeClassification, 1000);
}
// 页面加载完成后初始化
window.addEventListener('load', () => {
// 自动加载默认模型
loadModel();
});
</script>
</body>
</html>实践练习
- 运行上面的代码,测试图像分类应用
- 上传不同类型的图像,观察分类结果
- 测试摄像头实时分类功能
- 尝试添加新的预训练模型选项
- 优化界面,显示更友好的分类结果(如类别名称而非索引)
5.4 实战:实时物体检测
理论讲解
实时物体检测是计算机视觉的高级应用,它不仅能够识别图像中的物体类别,还能够确定物体在图像中的位置。物体检测的基本流程包括:
- 图像获取:从摄像头或文件获取图像
- 图像预处理:调整大小、归一化等
- 模型推理:使用物体检测模型进行预测
- 结果后处理:过滤低置信度检测结果,处理重叠边界框
- 结果可视化:在图像上绘制边界框和类别标签
在这个实战中,我们将使用COCO-SSD(Common Objects in Context - Single Shot MultiBox Detector)预训练模型,实现实时物体检测功能。
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时物体检测</title>
<!-- 引入 ml5.js -->
<script src="https://cdn.jsdelivr.net/npm/ml5@0.12.2/dist/ml5.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
}
.container {
display: flex;
gap: 20px;
margin: 20px 0;
}
.box {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
flex: 1;
}
#video {
width: 100%;
height: auto;
border: 1px solid #ccc;
border-radius: 4px;
}
#canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.video-container {
position: relative;
width: 100%;
}
.controls {
margin: 10px 0;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.result {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
max-height: 400px;
overflow-y: auto;
}
.detection-item {
background-color: white;
padding: 8px;
margin: 5px 0;
border-radius: 4px;
border-left: 4px solid #4CAF50;
}
</style>
</head>
<body>
<h1>实时物体检测</h1>
<div class="container">
<div class="box">
<h3>摄像头画面</h3>
<div class="video-container">
<video id="video" autoplay playsinline></video>
<canvas id="canvas"></canvas>
</div>
<div class="controls">
<button onclick="startDetection()" id="startBtn">开始检测</button>
<button onclick="stopDetection()" id="stopBtn" disabled>停止检测</button>
</div>
</div>
<div class="box">
<h3>检测结果</h3>
<div id="modelStatus" class="result">模型加载中...</div>
<div id="detectionResults" class="result"></div>
</div>
</div>
<script>
// 全局变量
let detector;
let video;
let canvas;
let ctx;
let isDetecting = false;
let detectionInterval;
// DOM 元素
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const modelStatus = document.getElementById('modelStatus');
const detectionResults = document.getElementById('detectionResults');
// 初始化
function init() {
video = document.getElementById('video');
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
// 加载物体检测器
loadDetector();
}
// 加载COCO-SSD模型
function loadDetector() {
detector = ml5.objectDetector('cocossd', modelLoaded);
}
// 模型加载完成后的回调
function modelLoaded() {
console.log('COCO-SSD模型加载完成!');
modelStatus.innerHTML = 'COCO-SSD模型加载完成,可以开始检测';
startBtn.disabled = false;
}
// 启动摄像头
async function startWebcam() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
video.play();
// 调整canvas大小以匹配视频
video.addEventListener('loadedmetadata', () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
});
return true;
} catch (error) {
console.error('启动摄像头失败:', error);
modelStatus.innerHTML = `启动摄像头失败:${error.message}`;
return false;
}
}
// 开始检测
async function startDetection() {
if (!detector) {
modelStatus.innerHTML = '模型尚未加载完成';
return;
}
// 启动摄像头
const webcamStarted = await startWebcam();
if (!webcamStarted) {
return;
}
isDetecting = true;
startBtn.disabled = true;
stopBtn.disabled = false;
modelStatus.innerHTML = '正在进行实时物体检测...';
// 开始检测循环
detect();
}
// 停止检测
function stopDetection() {
isDetecting = false;
startBtn.disabled = false;
stopBtn.disabled = true;
modelStatus.innerHTML = '检测已停止';
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 清空结果
detectionResults.innerHTML = '';
// 停止摄像头
if (video.srcObject) {
video.srcObject.getTracks().forEach(track => track.stop());
}
}
// 检测函数
function detect() {
if (!isDetecting) return;
detector.detect(video, (error, results) => {
if (error) {
console.error('检测错误:', error);
modelStatus.innerHTML = `检测错误:${error.message}`;
return;
}
// 绘制结果
drawResults(results);
// 显示结果
displayResults(results);
// 继续检测
requestAnimationFrame(detect);
});
}
// 绘制检测结果
function drawResults(results) {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制边界框和标签
results.forEach(result => {
// 绘制边界框
ctx.strokeStyle = '#4CAF50';
ctx.lineWidth = 2;
ctx.strokeRect(result.x, result.y, result.width, result.height);
// 绘制标签背景
ctx.fillStyle = '#4CAF50';
ctx.fillRect(result.x, result.y - 20, result.label.length * 10 + 10, 20);
// 绘制标签文本
ctx.fillStyle = 'white';
ctx.font = '12px Arial';
ctx.fillText(`${result.label} (${(result.confidence * 100).toFixed(0)}%)`, result.x + 5, result.y - 5);
});
}
// 显示检测结果
function displayResults(results) {
if (results.length === 0) {
detectionResults.innerHTML = '<div class="detection-item">未检测到物体</div>';
return;
}
let resultsHTML = '';
results.forEach((result, index) => {
resultsHTML += `
<div class="detection-item">
<strong>${index + 1}. ${result.label}</strong><br>
置信度:${(result.confidence * 100).toFixed(2)}%<br>
位置:x=${result.x.toFixed(0)}, y=${result.y.toFixed(0)}<br>
大小:宽=${result.width.toFixed(0)}, 高=${result.height.toFixed(0)}
</div>
`;
});
detectionResults.innerHTML = resultsHTML;
}
// 页面加载完成后初始化
window.addEventListener('load', init);
</script>
</body>
</html>实践练习
- 运行上面的代码,测试实时物体检测功能
- 观察不同物体的检测效果和置信度
- 尝试修改检测频率,观察对性能的影响
- 添加一个功能,允许用户调整检测的置信度阈值
- 实现保存检测结果的功能,包括截图和检测数据
章节总结
核心知识点回顾
- 计算机视觉和图像识别的基本概念
- 预训练模型的使用方法和优势
- 使用TensorFlow.js和ml5.js进行图像分类
- 实时物体检测的实现方法
学习收获
- 掌握了计算机视觉的基本原理和应用
- 学会了使用预训练模型进行图像分类
- 实现了基于摄像头的实时物体检测
- 了解了物体检测的基本流程和技术要点
下一步学习
在下一章中,我们将学习自然语言处理(NLP),包括NLP基础概念、文本处理技术,以及实战开发情感分析和聊天机器人应用。
课程分类:前端开发、AI技术开发
学习建议:
- 学习计算机视觉的基本算法和原理
- 了解不同类型的预训练模型及其适用场景
- 实践优化模型性能,如减少推理时间、提高准确率
- 关注计算机视觉的最新发展,如Transformer在计算机视觉中的应用
资源链接: