第5章:计算机视觉应用

5.1 图像识别基础

理论讲解

计算机视觉是人工智能的一个分支,旨在让计算机能够理解和解释图像内容。图像识别是计算机视觉的核心任务之一,包括:

  • 图像分类:将图像归类到预定义的类别中
  • 物体检测:识别图像中的物体并确定其位置
  • 图像分割:将图像分割成不同的区域,每个区域对应不同的物体或背景
  • 图像生成:根据输入生成新的图像

图像识别的基本流程包括:

  1. 图像获取:从摄像头、文件或网络获取图像
  2. 图像预处理:调整大小、归一化、增强等
  3. 特征提取:提取图像中的关键特征
  4. 模型推理:使用训练好的模型进行预测
  5. 结果输出:显示或保存识别结果

代码示例

// 图像识别基础概念的简单演示
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();

实践练习

  1. 描述图像识别的基本流程
  2. 思考:为什么图像预处理是图像识别的重要步骤?
  3. 列出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>

实践练习

  1. 运行上面的代码,测试图像分类功能
  2. 尝试使用不同的预训练模型,如ResNet或Inception
  3. 调整代码,实现实时摄像头图像分类
  4. 比较不同预训练模型的性能和速度

5.3 实战:图像分类应用

理论讲解

在这个实战中,我们将使用TensorFlow.js和MobileNet预训练模型开发一个完整的图像分类应用。该应用将具有以下功能:

  1. 支持上传本地图像
  2. 支持使用摄像头实时拍摄
  3. 显示分类结果和置信度
  4. 支持切换不同的预训练模型

图像分类的核心是使用预训练模型对输入图像进行推理,得到分类结果。我们将使用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>

实践练习

  1. 运行上面的代码,测试图像分类应用
  2. 上传不同类型的图像,观察分类结果
  3. 测试摄像头实时分类功能
  4. 尝试添加新的预训练模型选项
  5. 优化界面,显示更友好的分类结果(如类别名称而非索引)

5.4 实战:实时物体检测

理论讲解

实时物体检测是计算机视觉的高级应用,它不仅能够识别图像中的物体类别,还能够确定物体在图像中的位置。物体检测的基本流程包括:

  1. 图像获取:从摄像头或文件获取图像
  2. 图像预处理:调整大小、归一化等
  3. 模型推理:使用物体检测模型进行预测
  4. 结果后处理:过滤低置信度检测结果,处理重叠边界框
  5. 结果可视化:在图像上绘制边界框和类别标签

在这个实战中,我们将使用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>

实践练习

  1. 运行上面的代码,测试实时物体检测功能
  2. 观察不同物体的检测效果和置信度
  3. 尝试修改检测频率,观察对性能的影响
  4. 添加一个功能,允许用户调整检测的置信度阈值
  5. 实现保存检测结果的功能,包括截图和检测数据

章节总结

核心知识点回顾

  1. 计算机视觉和图像识别的基本概念
  2. 预训练模型的使用方法和优势
  3. 使用TensorFlow.js和ml5.js进行图像分类
  4. 实时物体检测的实现方法

学习收获

  • 掌握了计算机视觉的基本原理和应用
  • 学会了使用预训练模型进行图像分类
  • 实现了基于摄像头的实时物体检测
  • 了解了物体检测的基本流程和技术要点

下一步学习

在下一章中,我们将学习自然语言处理(NLP),包括NLP基础概念、文本处理技术,以及实战开发情感分析和聊天机器人应用。


课程分类:前端开发、AI技术开发

学习建议

  • 学习计算机视觉的基本算法和原理
  • 了解不同类型的预训练模型及其适用场景
  • 实践优化模型性能,如减少推理时间、提高准确率
  • 关注计算机视觉的最新发展,如Transformer在计算机视觉中的应用

资源链接

« 上一篇 机器学习基础 下一篇 » 自然语言处理