当前位置: 首页 > article >正文

DAMOYOLO-S集成JavaScript前端:打造交互式Web目标检测Demo

DAMOYOLO-S集成JavaScript前端打造交互式Web目标检测Demo1. 引言你有没有想过把一个强大的目标检测模型变成一个在浏览器里就能直接玩的工具比如上传一张街景照片网页上立刻就能框出所有的车辆和行人或者打开摄像头实时视频里就能看到动态的检测框跟着物体移动。这听起来像是专业软件才能做的事但其实用我们手头的DAMOYOLO-S模型和一些基础的Web技术就能轻松实现。对于很多开发者来说模型训练和部署是一回事但怎么把它变成一个用户能直观感受和使用的产品又是另一道坎。后端模型推理的代码写好了但用户总不能对着命令行窗口操作。这时候一个轻量、交互友好的Web前端就显得至关重要。它不仅是模型的“脸面”更是连接复杂AI能力与普通用户之间的桥梁。本文将带你一步步走通这个流程从搭建一个提供检测API的Python后端开始到用纯JavaScript构建一个能够上传图片、处理视频流并实时渲染检测结果的前端页面。整个过程我们聚焦于“可用”和“易懂”用最直接的代码展示如何将DAMOYOLO-S的检测能力无缝集成到一个现代化的Web应用中。无论你是想快速验证模型效果还是希望构建一个AI应用的原型这个方案都能提供一个坚实的起点。2. 项目整体架构与思路在动手写代码之前我们先理清整个Demo要做什么以及各个部分如何协同工作。这样在开发时思路会更清晰。我们的目标是构建一个B/S浏览器/服务器架构的Web应用。核心工作流很简单用户在浏览器前端选择图片或开启摄像头前端将图像数据发送到我们的后端服务器后端调用DAMOYOLO-S模型进行推理然后将检测结果包括物体类别、位置坐标返回给前端最后由前端在原始图像上绘制出检测框和标签展示给用户。2.1 技术栈选型为了实现上述流程我们需要选择合适的技术后端 (Server-side):框架: 选用Flask。它足够轻量、灵活对于构建RESTful API快速原型来说非常合适。当然如果你追求更高的性能FastAPI是更现代的替代品其异步特性和自动API文档生成也很吸引人。本文以Flask为例进行讲解其思想同样适用于FastAPI。核心模型:DAMOYOLO-S。这是一个在速度和精度上平衡得较好的目标检测模型适合需要实时反馈的Web应用场景。推理库:PyTorch或ONNX Runtime。取决于你最终部署的模型格式。前端 (Client-side):核心语言:Vanilla JavaScript (原生JS)。我们不依赖React、Vue等重型框架以最纯粹的方式展示如何通过JavaScript的Fetch API、Canvas和Video元素与后端交互这使得代码更易于理解和移植。UI 与 样式: 简单的HTML5和CSS3。构建一个包含文件上传、实时视频和结果显示区域的基础界面。通信桥梁:API 接口: 遵循RESTful风格设计。前端通过HTTPPOST请求将图像数据发送到后端的某个接口例如/detect。数据格式: 使用JSON作为前后端数据交换的标准格式。前端发送Base64编码的图片或帧数据后端返回结构化的检测结果。2.2 工作流程拆解整个应用跑起来就像一场精心安排的接力赛用户交互: 用户在网页上点击“选择图片”按钮或者点击“开启摄像头”。数据采集与预处理:如果是图片前端读取文件并将其转换为Base64字符串或FormData。如果是摄像头前端使用getUserMedia获取视频流并定期例如每秒10帧将当前视频帧捕获到Canvas上再转换为图像数据。网络请求: 前端使用JavaScript的fetch函数将图像数据打包发送到后端的检测API如http://your-server-address:5000/detect。模型推理: 后端Flask应用接收到请求。从请求中解析出图像数据并解码为OpenCV或PIL可以处理的格式。调用预先加载好的DAMOYOLO-S模型对图像进行推理。对模型的原始输出进行后处理包括非极大值抑制NMS得到最终的边界框、置信度和类别ID。结果返回: 后端将处理后的结果一个包含多个检测框信息的列表封装成JSON格式返回给前端。结果渲染: 前端收到JSON响应后解析数据。在另一个Canvas画布上先绘制原始图像。然后遍历每一个检测结果根据其坐标信息在画布上绘制矩形框。在框的附近用文字标注出检测到的物体类别和置信度。实时循环(针对视频): 如果是视频流模式则第2至第6步会在一个requestAnimationFrame或setInterval循环中不断执行从而实现实时检测的效果。理清了这些我们就可以开始分步实现了。接下来我们先从后端服务搭建做起。3. 后端服务搭建Flask与DAMOYOLO-S模型API后端是整个应用的大脑它负责加载模型、处理请求、运行推理。我们首先来构建这个“大脑”。3.1 环境准备与依赖安装建议使用Python虚拟环境来管理项目依赖避免包冲突。创建一个新的项目目录并在其中初始化环境。# 创建项目目录 mkdir damoyolo-web-demo cd damoyolo-web-demo # 创建虚拟环境 (以venv为例) python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate激活虚拟环境后安装必要的Python包。我们主要需要Flask来处理Web请求以及PyTorch和OpenCV来处理图像和模型推理。# 安装核心依赖 pip install flask torch torchvision opencv-python pillow numpy # 如果需要使用DAMOYOLO官方仓库可能还需要安装其依赖 # pip install damo-yolo # 请根据官方仓库说明安装3.2 构建Flask应用与检测API首先我们创建一个名为app.py的文件这是后端应用的入口。# app.py import io import base64 import json from flask import Flask, request, jsonify import cv2 import numpy as np from PIL import Image import torch from torchvision import transforms # 假设我们已经有一个加载好的DAMOYOLO-S模型和预处理、后处理函数 # 这里我们先定义它们的接口具体实现取决于你如何获取和使用模型 def load_model(): 加载DAMOYOLO-S模型 # 示例从本地文件加载 # model torch.hub.load(path/to/damoyolo, damoyolo_s, pretrainedTrue, sourcelocal) # model.eval() # return model print(模型加载函数被调用。在实际应用中请在此处实现模型加载逻辑。) return None def preprocess_image(image_np): 将numpy图像数组预处理为模型输入张量 # 示例调整大小、归一化、转换为Tensor # transform transforms.Compose([...]) # input_tensor transform(Image.fromarray(image_np)).unsqueeze(0) # return input_tensor print(图像预处理函数被调用。) return torch.randn(1, 3, 640, 640) # 返回一个模拟的张量 def postprocess_output(model_output, orig_img_shape): 将模型输出后处理为边界框、置信度、类别列表 # 示例应用NMS过滤低置信度框将坐标映射回原图尺寸 # boxes [...] # scores [...] # class_ids [...] # return boxes, scores, class_ids print(输出后处理函数被调用。) # 返回一些模拟数据用于测试前端 boxes [[100, 100, 200, 200], [300, 150, 400, 300]] scores [0.95, 0.87] class_ids [0, 2] # 假设0代表人2代表车 class_names {0: person, 2: car} # 类别映射 return boxes, scores, class_ids, class_names app Flask(__name__) # 全局加载模型 (在实际应用中注意线程安全) model load_model() app.route(/) def index(): return DAMOYOLO-S Detection API is running. app.route(/detect, methods[POST]) def detect(): 检测API接口。 接收JSON格式请求其body中应包含一个image字段值为Base64编码的图片字符串不含data:image前缀。 返回JSON格式的检测结果。 if not request.is_json: return jsonify({error: Request must be JSON}), 400 data request.get_json() image_b64 data.get(image) if not image_b64: return jsonify({error: No image data provided}), 400 try: # 1. 解码Base64图像 image_data base64.b64decode(image_b64) image_np np.frombuffer(image_data, np.uint8) image_np cv2.imdecode(image_np, cv2.IMREAD_COLOR) if image_np is None: return jsonify({error: Invalid image data}), 400 # 可选将BGR转换为RGB取决于模型训练时的通道顺序 # image_rgb cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB) orig_h, orig_w image_np.shape[:2] # 2. 预处理 input_tensor preprocess_image(image_np) # 3. 模型推理 (这里用模拟推理代替) # with torch.no_grad(): # outputs model(input_tensor) print(执行模型推理模拟...) # 4. 后处理 boxes, scores, class_ids, class_names postprocess_output(None, (orig_h, orig_w)) # 5. 格式化结果 detections [] for box, score, cls_id in zip(boxes, scores, class_ids): x1, y1, x2, y2 box detections.append({ bbox: [float(x1), float(y1), float(x2), float(y2)], # 转为float便于JSON序列化 score: float(score), class_id: int(cls_id), class_name: class_names.get(cls_id, fclass_{cls_id}) }) # 6. 返回结果 return jsonify({ success: True, detections: detections, image_shape: {height: orig_h, width: orig_w} }) except Exception as e: print(fDetection error: {e}) return jsonify({error: str(e)}), 500 if __name__ __main__: # 在本地开发时运行host0.0.0.0允许局域网访问debugTrue便于调试 app.run(host0.0.0.0, port5000, debugTrue)代码要点解释模型加载与处理函数(load_model,preprocess_image,postprocess_output): 这些是核心的AI部分。你需要根据DAMOYOLO-S模型的具体使用方式来实现它们。示例中使用了打印语句和模拟数据确保API流程能跑通方便我们先测试前端。Flask应用与路由:app.route(/): 根路径用于简单验证服务是否启动。app.route(/detect, methods[POST]): 核心的检测接口。它只接受POST请求和JSON格式的数据。请求处理流程:验证: 检查请求是否为JSON并包含image字段。解码: 将Base64字符串解码为原始的字节数据然后用OpenCV解码成NumPy数组。推理: 调用预处理、模型推理、后处理函数。注意示例中推理是模拟的你需要替换为真实的模型调用。格式化: 将检测结果组织成一个字典列表每个字典代表一个检测到的物体。返回: 将结果以JSON格式返回包含成功标志、检测列表和原图尺寸。3.3 运行与测试后端API保存好app.py后在终端运行它python app.py如果看到类似* Running on http://0.0.0.0:5000的输出说明后端服务已经启动。现在我们可以用任何HTTP客户端如curl、Postman来测试这个API。这里提供一个简单的Python测试脚本# test_api.py import requests import base64 import json # 1. 读取一张测试图片并编码为Base64 with open(test.jpg, rb) as f: # 请准备一张名为test.jpg的图片 img_b64 base64.b64encode(f.read()).decode(utf-8) # 2. 构造请求数据 url http://127.0.0.1:5000/detect headers {Content-Type: application/json} data {image: img_b64} # 3. 发送POST请求 response requests.post(url, headersheaders, datajson.dumps(data)) # 4. 打印响应 print(fStatus Code: {response.status_code}) print(fResponse JSON: {response.json()})运行这个测试脚本你应该会收到一个包含模拟检测结果的JSON响应。这表明后端API已经就绪可以接受前端的调用了。4. 前端开发使用JavaScript构建交互界面后端准备好了现在我们来打造用户直接看到和操作的“脸面”。我们将创建一个简单的HTML页面并用原生JavaScript实现所有交互逻辑。4.1 基础HTML结构与样式创建一个index.html文件构建页面的骨架和基础样式。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleDAMOYOLO-S 交互式目标检测 Demo/title style * { box-sizing: border-box; margin: 0; padding: 0; font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f5f7fa; color: #333; line-height: 1.6; padding: 20px; max-width: 1200px; margin: 0 auto; } header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #e1e4e8; } h1 { color: #2c3e50; margin-bottom: 10px; } .subtitle { color: #7f8c8d; font-size: 1.1em; } .container { display: flex; flex-wrap: wrap; gap: 30px; margin-bottom: 30px; } .panel { background: white; border-radius: 10px; padding: 25px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); flex: 1; min-width: 300px; } .panel h2 { color: #3498db; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .controls { display: flex; flex-direction: column; gap: 20px; } .button-group { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; } button, input[typefile] { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.3s ease; } button { background-color: #3498db; color: white; flex: 1; min-width: 140px; } button:hover { background-color: #2980b9; transform: translateY(-2px); } button:active { transform: translateY(0); } button#stopCam { background-color: #e74c3c; } button#stopCam:hover { background-color: #c0392b; } input[typefile] { background-color: #ecf0f1; padding: 10px; width: 100%; } .canvas-container { text-align: center; margin-top: 15px; } canvas { max-width: 100%; border-radius: 8px; box-shadow: 0 3px 10px rgba(0,0,0,0.1); border: 1px solid #ddd; } #resultCanvas { border-color: #3498db; } .status { margin-top: 20px; padding: 15px; border-radius: 6px; background-color: #f8f9fa; border-left: 4px solid #3498db; font-family: monospace; white-space: pre-wrap; word-break: break-all; min-height: 60px; max-height: 200px; overflow-y: auto; } .status.error { border-left-color: #e74c3c; background-color: #fdf2f2; } .status.success { border-left-color: #2ecc71; background-color: #f0f9f4; } footer { text-align: center; margin-top: 40px; color: #95a5a6; font-size: 0.9em; padding-top: 20px; border-top: 1px solid #e1e4e8; } media (max-width: 768px) { .container { flex-direction: column; } } /style /head body header h1 DAMOYOLO-S 交互式目标检测 Demo/h1 p classsubtitle上传图片或开启摄像头体验实时目标检测/p /header div classcontainer div classpanel h2 图片检测/h2 div classcontrols input typefile idfileInput acceptimage/* div classbutton-group button iddetectImage检测图片/button button idclearImage清空画布/button /div div classcanvas-container pstrong检测结果/strong/p canvas idresultCanvas/canvas /div /div /div div classpanel h2 实时视频检测/h2 div classcontrols div classbutton-group button idstartCam开启摄像头/button button idstopCam disabled停止检测/button /div div classcanvas-container pstrong实时画面/strong/p video idvideoElement autoplay playsinline styledisplay:none; width:100%; border-radius:8px;/video canvas idvideoCanvas/canvas /div div label forfpsSlider检测频率 (FPS): /label input typerange idfpsSlider min1 max30 value10 span idfpsValue10/span /div /div /div /div div classpanel h2 检测结果与状态/h2 div idstatusBox classstatus 等待操作...请选择图片或开启摄像头。 /div /div footer p本Demo展示了DAMOYOLO-S模型与JavaScript前端的集成。后端API运行于 code idapiEndpointhttp://localhost:5000/code/p p你可以修改下方配置以连接到不同的后端服务器。/p div stylemargin-top: 10px; label forapiUrlInput后端API地址: /label input typetext idapiUrlInput valuehttp://localhost:5000/detect stylewidth: 300px; padding: 5px; button idupdateApiBtn stylepadding: 5px 15px; font-size: 14px;更新/button /div /footer script srcmain.js/script /body /html这个HTML页面创建了一个双栏布局左侧用于图片上传和检测右侧用于实时视频检测。它包含了所有必要的UI元素文件输入、按钮、视频元素、画布以及状态显示区域。4.2 JavaScript核心逻辑实现现在创建main.js文件这是前端所有交互逻辑的核心。我们将实现图片处理、视频流捕获、与后端API通信以及结果绘制。// main.js document.addEventListener(DOMContentLoaded, function() { // --- 全局变量与DOM元素 --- const fileInput document.getElementById(fileInput); const detectImageBtn document.getElementById(detectImage); const clearImageBtn document.getElementById(clearImage); const startCamBtn document.getElementById(startCam); const stopCamBtn document.getElementById(stopCam); const videoElement document.getElementById(videoElement); const videoCanvas document.getElementById(videoCanvas); const resultCanvas document.getElementById(resultCanvas); const statusBox document.getElementById(statusBox); const fpsSlider document.getElementById(fpsSlider); const fpsValue document.getElementById(fpsValue); const apiUrlInput document.getElementById(apiUrlInput); const updateApiBtn document.getElementById(updateApiBtn); let videoStream null; let detectionInterval null; let currentFps 10; let apiEndpoint apiUrlInput.value; // 获取画布上下文 const resultCtx resultCanvas.getContext(2d); const videoCtx videoCanvas.getContext(2d); // --- 工具函数 --- function updateStatus(message, isError false, isSuccess false) { statusBox.textContent message; statusBox.className status; if (isError) { statusBox.classList.add(error); } else if (isSuccess) { statusBox.classList.add(success); } console.log(message); } function drawDetection(ctx, detections, imgWidth, imgHeight, canvasWidth, canvasHeight) { // 计算缩放比例使图像适应画布 const scaleX canvasWidth / imgWidth; const scaleY canvasHeight / imgHeight; const scale Math.min(scaleX, scaleY); // 保持宽高比 // 计算在画布上居中的偏移量 const offsetX (canvasWidth - imgWidth * scale) / 2; const offsetY (canvasHeight - imgHeight * scale) / 2; // 绘制检测框和标签 detections.forEach(det { const [x1, y1, x2, y2] det.bbox; const score det.score; const className det.class_name; // 映射坐标到画布 const drawX1 offsetX x1 * scale; const drawY1 offsetY y1 * scale; const drawX2 offsetX x2 * scale; const drawY2 offsetY y2 * scale; const width drawX2 - drawX1; const height drawY2 - drawY1; // 绘制矩形框 ctx.strokeStyle #00FF00; // 绿色框 ctx.lineWidth 2; ctx.strokeRect(drawX1, drawY1, width, height); // 绘制标签背景 const label ${className} ${(score * 100).toFixed(1)}%; ctx.font 16px Arial; const textWidth ctx.measureText(label).width; ctx.fillStyle #00FF00; ctx.fillRect(drawX1, drawY1 - 20, textWidth 10, 20); // 绘制标签文字 ctx.fillStyle #000; ctx.fillText(label, drawX1 5, drawY1 - 5); }); } // --- 图片检测相关函数 --- function handleImageDetection(imageDataUrl) { updateStatus(正在发送图片进行检测...); // 移除Base64 URL前缀 const base64Data imageDataUrl.replace(/^data:image\/(png|jpeg|jpg);base64,/, ); fetch(apiEndpoint, { method: POST, headers: { Content-Type: application/json, }, body: JSON.stringify({ image: base64Data }) }) .then(response { if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } return response.json(); }) .then(data { if (data.success) { updateStatus(检测成功共发现 ${data.detections.length} 个目标。, false, true); // 在结果画布上绘制 const img new Image(); img.onload () { // 设置画布尺寸与图片原始尺寸一致或按需缩放 resultCanvas.width img.width; resultCanvas.height img.height; resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height); resultCtx.drawImage(img, 0, 0); drawDetection(resultCtx, data.detections, img.width, img.height, resultCanvas.width, resultCanvas.height); }; img.src imageDataUrl; // 使用原始DataURL加载图片 } else { updateStatus(检测失败: ${data.error || 未知错误}, true); } }) .catch(error { updateStatus(请求失败: ${error.message}, true); console.error(Detection error:, error); }); } // --- 视频检测相关函数 --- function startVideoDetection() { if (!videoStream) { updateStatus(请先开启摄像头。, true); return; } if (detectionInterval) { clearInterval(detectionInterval); } updateStatus(开始实时检测 (${currentFps} FPS)..., false, true); // 设置定时器按指定FPS发送帧进行检测 detectionInterval setInterval(captureAndDetectFrame, 1000 / currentFps); } function stopVideoDetection() { if (detectionInterval) { clearInterval(detectionInterval); detectionInterval null; updateStatus(已停止实时检测。); } } function captureAndDetectFrame() { if (videoElement.readyState ! videoElement.HAVE_ENOUGH_DATA) { return; } // 将当前视频帧绘制到videoCanvas上 videoCanvas.width videoElement.videoWidth; videoCanvas.height videoElement.videoHeight; videoCtx.drawImage(videoElement, 0, 0, videoCanvas.width, videoCanvas.height); // 将canvas内容转换为Base64 const imageDataUrl videoCanvas.toDataURL(image/jpeg, 0.8); // 使用JPEG压缩以减少数据量 const base64Data imageDataUrl.replace(/^data:image\/(png|jpeg|jpg);base64,/, ); // 发送到后端API (注意这里为了性能没有等待响应就继续下一帧实际可根据需求调整) fetch(apiEndpoint, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ image: base64Data }) }) .then(response response.json()) .then(data { if (data.success) { // 在videoCanvas上绘制检测结果 const tempCtx videoCanvas.getContext(2d); // 先清除上一帧的绘制但保留视频图像 tempCtx.clearRect(0, 0, videoCanvas.width, videoCanvas.height); tempCtx.drawImage(videoElement, 0, 0, videoCanvas.width, videoCanvas.height); drawDetection(tempCtx, data.detections, data.image_shape.width, data.image_shape.height, videoCanvas.width, videoCanvas.height); } }) .catch(error { console.error(Frame detection error:, error); }); } // --- 事件监听器 --- // 更新API端点 updateApiBtn.addEventListener(click, () { const newUrl apiUrlInput.value.trim(); if (newUrl) { apiEndpoint newUrl; updateStatus(API端点已更新为: ${apiEndpoint}, false, true); document.querySelector(footer code).textContent newUrl.split(/detect)[0] || newUrl; } }); // 图片文件选择 fileInput.addEventListener(change, function(e) { const file e.target.files[0]; if (!file) return; const reader new FileReader(); reader.onload function(event) { const img new Image(); img.onload function() { // 可选在检测前预览图片到结果画布 resultCanvas.width img.width; resultCanvas.height img.height; resultCtx.drawImage(img, 0, 0); updateStatus(已加载图片: ${file.name} (${img.width}x${img.height})); }; img.src event.target.result; // 存储DataURL供检测按钮使用 resultCanvas.dataset.currentImage event.target.result; }; reader.readAsDataURL(file); }); // 检测图片按钮 detectImageBtn.addEventListener(click, function() { const currentImage resultCanvas.dataset.currentImage; if (!currentImage) { updateStatus(请先选择一张图片。, true); return; } handleImageDetection(currentImage); }); // 清空图片画布 clearImageBtn.addEventListener(click, function() { resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height); resultCanvas.removeAttribute(data-current-image); fileInput.value ; updateStatus(画布已清空。); }); // 开启摄像头 startCamBtn.addEventListener(click, function() { if (navigator.mediaDevices navigator.mediaDevices.getUserMedia) { const constraints { video: { facingMode: user } }; // 使用前置摄像头 navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { videoStream stream; videoElement.srcObject stream; videoElement.style.display block; startCamBtn.disabled true; stopCamBtn.disabled false; updateStatus(摄像头已开启。点击“停止检测”以结束。); // 视频元数据加载后开始检测 videoElement.onloadedmetadata () { startVideoDetection(); }; }) .catch(function(err) { updateStatus(无法访问摄像头: ${err.name}, true); console.error(Camera error:, err); }); } else { updateStatus(您的浏览器不支持摄像头API。, true); } }); // 停止摄像头检测 stopCamBtn.addEventListener(click, function() { stopVideoDetection(); if (videoStream) { videoStream.getTracks().forEach(track track.stop()); videoStream null; } videoElement.srcObject null; videoElement.style.display none; videoCtx.clearRect(0, 0, videoCanvas.width, videoCanvas.height); startCamBtn.disabled false; stopCamBtn.disabled true; updateStatus(摄像头已关闭。); }); // FPS滑块 fpsSlider.addEventListener(input, function() { currentFps parseInt(this.value); fpsValue.textContent currentFps; if (detectionInterval) { // 如果正在检测则重启间隔以应用新的FPS stopVideoDetection(); startVideoDetection(); } }); // 初始化状态 updateStatus(Demo已加载。请选择图片或开启摄像头开始体验。); });JavaScript代码核心逻辑解析状态管理使用updateStatus函数统一更新页面底部的状态栏提供操作反馈。绘制函数drawDetection函数负责在画布上绘制检测框和标签。它处理了图像缩放和居中确保检测框能正确对应到画布上的位置。图片检测流程handleImageDetection: 将图片的Base64数据发送到后端API收到结果后在resultCanvas上绘制原图和检测框。视频检测流程startVideoDetection/stopVideoDetection: 控制定时器的启动和停止实现按固定频率FPS捕获帧并检测。captureAndDetectFrame: 核心循环函数。它将当前视频帧绘制到videoCanvas转换为Base64发送给后端并在收到结果后立即在同一个画布上绘制检测框实现“实时”覆盖的效果。事件绑定将所有按钮、滑块、输入框的点击和变化事件与对应的函数绑定起来。API端点配置允许用户在前端界面直接修改后端API的URL提高了Demo的灵活性。5. 前后端联调与效果展示代码都写好了现在是让它们“握手”并跑起来的时候了。5.1 启动与连接启动后端确保你的Flask后端服务正在运行python app.py。终端应显示服务运行在http://0.0.0.0:5000。启动前端由于前端使用了Fetch API直接通过浏览器打开index.html文件file://协议可能会因跨域问题CORS导致请求失败。最简单的方式是使用一个简单的HTTP服务器来托管前端文件。在项目根目录下打开另一个终端。使用Python快速启动一个HTTP服务器# Python 3 python -m http.server 8080或者使用Node.js的http-server需提前安装npm install -g http-serverhttp-server -p 8080访问页面打开浏览器访问http://localhost:8080或你指定的端口。你应该能看到我们设计的界面。5.2 功能测试现在让我们逐一测试所有功能图片检测在左侧“图片检测”面板点击“选择文件”按钮上传一张包含物体如人、车、动物的图片。图片会预览在下方画布中。点击“检测图片”按钮。观察状态栏会显示“正在发送图片进行检测...”稍后会变为“检测成功共发现 X 个目标。”。同时画布上会在检测到的物体周围绘制出绿色的边界框和标签。实时视频检测在右侧“实时视频检测”面板点击“开启摄像头”按钮。浏览器会请求摄像头权限请点击“允许”。你的摄像头画面会显示在下方画布中。检测会自动开始。你会看到画面上实时出现绿色的检测框跟随物体移动。拖动“检测频率”滑块可以调整每秒发送多少帧去检测。调低可以减轻后端压力调高可以获得更流畅的检测体验。点击“停止检测”按钮会停止检测并关闭摄像头。切换后端API如果你将后端部署到了另一台服务器例如云服务器你可以在页面底部的输入框中修改API地址例如http://your-server-ip:5000/detect然后点击“更新”按钮。之后的所有检测请求都会发送到新的地址。5.3 效果展示与体验当这一切顺利运行起来你会获得一个功能完整的交互式目标检测Web应用。它的价值在于直观性用户无需理解命令行或Python脚本通过直观的网页界面即可操作。实时性视频检测功能提供了近乎实时的反馈让模型的动态检测能力一目了然。可交互性用户可以自由切换图片/视频模式调整检测频率体验不同的交互。可移植性前端是纯静态文件HTML, JS, CSS可以轻松部署到任何Web服务器或对象存储。后端API也可以独立部署和扩展。这个Demo本身已经是一个可用的产品原型。你可以在此基础上继续添加更多功能比如选择不同的模型例如切换DAMOYOLO的不同尺寸版本。增加检测结果的统计信息面板。允许用户下载带检测框的图片。添加更多预处理或后处理选项如置信度阈值调整。使用WebSocket替代HTTP轮询实现更低延迟的视频流检测。6. 总结通过这个完整的项目我们实践了将AI模型能力产品化的关键一步构建一个用户友好的交互界面。我们从零开始搭建了一个基于Flask的轻量级检测API后端并用原生JavaScript开发了一个功能丰富的前端应用实现了图片上传检测和实时视频流检测。整个过程的核心思路非常清晰前端负责采集和展示后端负责计算和返回JSON作为沟通的语言。这种解耦的架构使得前后端可以独立开发和优化。即使你之后想用更强大的前端框架如Vue、React重构界面或者用性能更高的后端框架如FastAPI重写服务整体的通信协议和业务流程都无需改变。对于初学者来说这个项目涵盖了现代Web AI应用的基础要素RESTful API设计、异步网络请求Fetch、Canvas绘图、媒体设备访问getUserMedia以及基础的UI/UX设计。它不仅仅是一个Demo更是一个可以延伸和扩展的模板。将AI模型从实验室的Jupyter Notebook搬到真实的Web环境中让其能够被更多人直接使用和体验是AI技术产生实际价值的重要一环。希望这个结合了DAMOYOLO-S与JavaScript的实践能为你打开一扇门启发你创造出更多有趣、有用的AI应用。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

相关文章:

DAMOYOLO-S集成JavaScript前端:打造交互式Web目标检测Demo

DAMOYOLO-S集成JavaScript前端:打造交互式Web目标检测Demo 1. 引言 你有没有想过,把一个强大的目标检测模型,变成一个在浏览器里就能直接玩的工具?比如上传一张街景照片,网页上立刻就能框出所有的车辆和行人&#xf…...

我让AI开发一个完整项目,结果离谱了(全流程实测)

最近我做了一个“有点离谱”的实验:👉 不写一行代码,让AI帮我开发一个完整项目。结果是:项目真的跑起来了功能基本完整甚至代码结构还不错但同时也出现了一些“很真实的问题”。这篇文章,我把整个过程完整复盘给你看&a…...

含电转气和碳捕集耦合的综合能源系统多时间尺度优化调度探索

【文章复现】含电转气和碳捕集耦合的综合能源系统多时间尺度优化调度。 代码为本人自己编写 碳;mpc;多时间尺度优化;综合能源:碳捕集 运行平台:matlabyalmipcplex在能源领域不断探索可持续发展道路的当下,含…...

避开这些坑!BurpSuite时间盲注爆破的正确配置指南(含线程优化技巧)

避开这些坑!BurpSuite时间盲注爆破的正确配置指南(含线程优化技巧) 时间盲注作为SQL注入的高级技术,对渗透测试工具的配置提出了严苛要求。许多中级用户在BurpSuite实操中常陷入"明明payload正确却无法识别延迟响应"的困…...

基于 MIPS 架构的跨境充电桩链路检测与底层自愈实现

摘要: 在跨境新能源充电架构中,海外基站的 NAT 映射老化及弱网环境常导致通信隧道假死。单机默认网络协议栈已无法满足高频交易的防掉线需求。本文分享一种在存储受限(4MB 用户 Flash)环境下实现的 C 语言守护进程。该方案通过底层…...

【Dify评估系统成本控制白皮书】:20年LLM工程实战总结的7大降本杠杆与ROI测算模型

第一章:Dify自动化评估系统成本控制的战略定位与核心挑战Dify自动化评估系统在企业AI应用落地过程中,已逐步从“能力验证平台”演进为支撑规模化模型迭代与业务闭环的核心基础设施。其战略定位不再局限于低代码编排与快速原型验证,而是承担起…...

告别复杂配置!LingBot-Depth Docker镜像10分钟快速部署指南

告别复杂配置!LingBot-Depth Docker镜像10分钟快速部署指南 你是不是曾经被复杂的AI模型部署搞得头大?各种依赖包冲突、环境配置问题、版本不兼容……光是安装配置就要花上大半天时间。今天我要介绍的LingBot-Depth Docker镜像,就是来解决这…...

DAY33MLP神经网络的训练

一、 核心知识点回顾 1. 环境配置基础 核心操作:PyTorch 与 CUDA 的安装、验证及环境排查。关键命令: 查看显卡信息:nvidia-smi(CMD 中使用)。CUDA 检查:验证 PyTorch 是否能调用 GPU 加速(.c…...

毕业设计救星:手把手教你用KF-GINS搞定GNSS/INS松组合导航(附代码避坑)

毕业设计实战:从零搭建GNSS/INS松组合导航系统 第一次接触KF-GINS时,我被那些复杂的矩阵运算和坐标系转换搞得晕头转向。作为导航专业的毕业生,我完全理解那种面对开源代码手足无措的感觉——明明知道卡尔曼滤波很重要,但看到满屏…...

欧姆龙CP1H脉冲程序案例及新手入门指南

A1欧姆龙CP1H程序 姆龙标准程序 欧姆龙PLC标准案例模板 本产品适用于新手或者在校生 本程序主要写了欧姆龙CP1H脉冲程序案例, 包含以下: 威纶通触摸屏程序; word详细说明文档 ; 欧姆龙CP1H程序; 里面的文档有详细介绍…...

Turtlebot3+Nav2实战:手把手教你用RVIZ实现室内SLAM建图(避坑指南)

Turtlebot3Nav2实战:从零实现室内SLAM建图的避坑指南 当第一次看到Turtlebot3在未知环境中自主构建地图时,那种科技带来的震撼感至今难忘。作为ROS2生态中最受欢迎的入门级机器人平台,Turtlebot3配合Nav2导航栈能够实现令人惊艳的SLAM建图效果…...

RRT+人工势场法路径规划与APF应用

融合RRT和人工势场法 路径规划 rrt apf 具有开关设置路径规划领域有个经典难题:如何在复杂环境中快速找到安全路径?RRT(快速扩展随机树)和人工势场法这对CP最近被我玩出了新花样。咱们今天不聊理论公式,直接上代码说人…...

别再自己造轮子了!用Three.js的TubeGeometry在Cesium里画空心管道(附完整Vue3代码)

跨引擎三维可视化:用Three.js几何体增强Cesium场景渲染 在三维地理信息系统开发中,Cesium和Three.js都是不可或缺的技术栈。Cesium擅长全球尺度的地理空间可视化,而Three.js则提供了丰富的几何体生成能力。当我们需要在Cesium中实现复杂几何…...

Comsol仿真超表面复现:多级分解通用适用于各种形状,六面体阵列与圆柱体阵列复现相吻合,多物...

comsol仿真超表面复现:多级分解通用,适用各种形状,以下是两篇文献(六面体阵列、圆柱体阵列)的复现都相吻合 多物理场仿真耦合有限元模拟comsol,提供建模思路,包括流体、力学、传热、电磁等 玩C…...

Qwen2-VL-2B-Instruct模型压缩与量化教程:在边缘设备部署视觉语言模型

Qwen2-VL-2B-Instruct模型压缩与量化教程:在边缘设备部署视觉语言模型 想让一个能看懂图片、还能跟你聊天的AI模型,在你的树莓派或者开发板上跑起来吗?听起来有点天方夜谭,毕竟这类视觉语言模型通常都是“大块头”,对…...

OpenClaw - Personal AI Assistant (个人 AI 助理)

OpenClaw - Personal AI Assistant {个人 AI 助理} 1. OpenClaw - Personal AI Assistant2. OpenClaw2.1. Docs2.2. Mattermost 3. ConclusionsReferences OpenClaw (formerly Clawdbot, Moltbot, and Molty) is a free and open-source autonomous artificial intelligence ag…...

带隙基准Bandgap与低压差稳压器Ldo电路

带隙基准Bandgap,低压差稳压器Ldo电路在模拟电路设计中,稳定的电压源是许多系统的基石。带隙基准(Bandgap)和低压差稳压器(LDO)这对黄金搭档,一个负责生成精准电压,另一个负责在恶劣…...

RT-Thread实战:STM32硬件看门狗配置与多任务喂狗策略详解

RT-Thread实战:STM32硬件看门狗配置与多任务喂狗策略详解 在嵌入式系统开发中,系统稳定性是至关重要的考量因素。当系统运行在复杂电磁环境或长时间无人值守的场景时,硬件看门狗(Watchdog)成为保障系统可靠性的最后一道…...

做了一个 AI 鸿蒙 App,我发现逻辑变了

子玥酱 (掘金 / 知乎 / CSDN / 简书 同名) 大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩‍💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚…...

【暖洋葱家庭教育有效果吗】用数据说话:暖洋葱发布年度服务报告,家长满意度高达96.3%

“孩子沉迷手机,说了不听,打又没用,暖洋葱真的能帮我吗?”这是许多家长在咨询时最关心的问题。面对家长的期待,暖洋葱家庭教育坚信:教育不能仅靠口号,效果必须经得起检验。近日,暖洋…...

基于深度学习预测+MPC的车辆轨迹跟踪自动驾驶汽车预测控制Matlab仿真(带参考文献)

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎 往期回顾关注个人主页:Matlab科研工作室 👇 关注我领取海量matlab电子…...

现代智能汽车系统——照明系统0

摘要:车辆灯具按功能分为四大类:1)外部照明灯(远近光灯、雾灯等),用于道路照明;2)外部信号灯(转向灯、刹车灯等),用于车辆间通信;3&am…...

UI-TARS-desktop完整指南:vLLM高性能推理+Qwen3-4B-Instruct多模态任务闭环实践

UI-TARS-desktop完整指南:vLLM高性能推理Qwen3-4B-Instruct多模态任务闭环实践 想找一个开箱即用、能看能说、还能帮你操作电脑的AI助手吗?今天要介绍的UI-TARS-desktop,就是一个集成了高性能vLLM推理引擎和强大Qwen3-4B-Instruct多模态模型…...

Laravel7.x十大核心特性解析

Laravel 7.x 版本引入了多项重要特性与优化,以下是核心特性概述: 1. 路由签名语法优化 新增 Route::signed() 和 Route::temporarySigned() 方法,简化签名 URL 的生成与验证: // 生成签名路由 Route::signed(verify, Verificati…...

无速度传感器DTC实战:让电机自己“报“转速

基于MRAS的异步电机直接转矩控制/基于转子磁链模型的MRAS转速辨识/基于反电动势模型的MRAS转速辨识/基于无功功率模型的MRAS转速辨识 在simulink搭建的异步电机模型预测转矩控制模型之上进行改进,把转速环中实际转速从测量值更换为MARS观测器的转速估计值&#xff0…...

保姆级教程:JCG Q30 Pro免拆刷OpenWrt 24.10(附常见问题排查)

JCG Q30 Pro免拆刷OpenWrt 24.10全流程指南与深度优化 为什么选择OpenWrt与JCG Q30 Pro的完美组合 在智能家居和网络设备高度发达的今天,路由器早已不再是简单的网络连接设备。对于技术爱好者而言,一台能够自由定制、性能强劲的路由器,就像…...

AI简历姬支持上传JD后逐段改写简历吗?

摘要 是的,AI简历姬支持上传JD后逐段改写简历。其核心工作流程是:上传或粘贴JD -> 解析JD关键词 -> 将你的现有经历与岗位要求逐项对齐 -> 提供匹配度评分、缺口清单和具体的改写建议。这不同于简单的文案润色,而是围绕“岗位要求 -…...

基于OpenFast联合仿真的独立变桨与统一变桨风电机组控制模型

openfast与simlink联合仿真模型,风电机组独立变桨控制与统一变桨控制。 独立变桨控制。 OpenFast联合仿真。 基于载荷反馈的独立变桨控制 风机变桨控制基于FAST与MATLAB SIMULINK联合仿真模型的非线性风力发电机的PID独立变桨和统一变桨控制下仿真模型。 5MW非线性风…...

MLX90632红外温度传感器Arduino驱动库详解

1. ProtoCentral MLX90632 非接触式红外温度传感器库深度解析1.1 项目定位与工程价值ProtoCentral MLX90632 库是专为 Melexis MLX90632 红外非接触温度传感器设计的 Arduino 兼容驱动库,面向嵌入式系统工程师、硬件开发者及电子爱好者提供开箱即用的高精度测温能力…...

VMware Workstation Pro 17 安装 VyOS 软路由保姆级教程(附镜像下载)

VMware Workstation Pro 17 安装 VyOS 软路由全流程指南 在家庭网络或小型办公环境中部署软路由正逐渐成为技术爱好者和IT从业者的新选择。VyOS作为一款基于Linux的开源网络操作系统,以其轻量级、高性能和丰富的网络功能吸引了大量用户。本文将详细介绍如何在Window…...