Web应用全栈开发:部署DAMOYOLO-S模型并提供前端交互界面

张开发
2026/5/25 18:13:20 15 分钟阅读
Web应用全栈开发:部署DAMOYOLO-S模型并提供前端交互界面
Web应用全栈开发部署DAMOYOLO-S模型并提供前端交互界面最近在做一个智能安防相关的项目需要把目标检测能力集成到一个Web应用里让非技术同事也能方便地上传图片、查看检测结果。我选择了DAMOYOLO-S这个轻量级但性能不错的模型搭配FastAPI和Vue.js折腾了一周多终于把整个流程跑通了。今天就把这个完整的全栈项目实战分享出来。从模型部署、API搭建到前端界面开发、前后端联调我会把每个关键步骤都讲清楚。如果你也想把AI模型包装成一个可交互的Web应用这篇文章应该能给你不少参考。1. 项目整体架构与准备我们先来看看整个项目是怎么搭起来的。简单说就是用户在前端页面上传一张图片前端把图片发给后端后端调用DAMOYOLO-S模型进行目标检测然后把检测结果比如框出了哪些物体、置信度多少返回给前端前端再把结果画在图片上展示给用户。整个技术栈是这样的后端Python FastAPI。FastAPI特别适合做这种API服务异步支持好自动生成文档写起来也快。AI模型DAMOYOLO-S。一个兼顾速度和精度的目标检测模型用ONNX格式部署推理速度快。前端Vue 3 Element Plus。Vue的响应式开发体验很好Element Plus提供了丰富的UI组件能快速搭出漂亮的界面。通信HTTP JSON。前端通过axios库调用后端API图片以FormData形式上传。在开始写代码之前你得先准备好环境。我假设你本地已经装好了Python建议3.8以上和Node.js建议16以上。然后我们创建一个项目文件夹比如叫damoyolo-web-demo里面再建两个子文件夹backend和frontend。2. 后端服务用FastAPI搭建模型API后端是整个应用的大脑它负责接收图片、运行模型、返回结果。我们先来处理这一块。2.1 环境搭建与模型准备进入backend文件夹创建一个虚拟环境并安装依赖。你的requirements.txt文件大概长这样fastapi0.104.1 uvicorn[standard]0.24.0 python-multipart0.0.6 pillow10.1.0 onnxruntime1.16.3 opencv-python4.8.1.78 numpy1.24.3用pip install -r requirements.txt安装它们。接下来是模型你需要先去DAMOYOLO的官方仓库找到DAMOYOLO-S的ONNX格式模型文件通常叫damoyolo_s.onnx和对应的类别标签文件coco.names。把它们下载下来放在backend目录下一个叫models的文件夹里。2.2 核心推理引擎的编写模型不会自己工作我们需要写一个类来加载它、预处理图片、运行推理、再处理输出结果。在backend下创建一个inference.py文件。import cv2 import numpy as np import onnxruntime as ort from PIL import Image import time from typing import List, Tuple, Dict class DamoyoloInference: def __init__(self, model_path: str, class_names_path: str): 初始化推理引擎 :param model_path: ONNX模型文件路径 :param class_names_path: 类别名称文件路径 # 加载类别名称 with open(class_names_path, r) as f: self.class_names [line.strip() for line in f.readlines()] # 创建ONNX Runtime会话 self.session ort.InferenceSession(model_path) # 获取输入输出信息 self.input_name self.session.get_inputs()[0].name input_shape self.session.get_inputs()[0].shape self.input_height, self.input_width input_shape[2], input_shape[3] print(f模型加载成功输入尺寸: {self.input_height}x{self.input_width}) def preprocess(self, image: np.ndarray) - np.ndarray: 将输入图片预处理为模型需要的格式 # 调整尺寸到模型输入大小 img_resized cv2.resize(image, (self.input_width, self.input_height)) # 转换颜色通道 BGR - RGB img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) # 归一化到 [0, 1] img_normalized img_rgb / 255.0 # 调整维度顺序为 (1, C, H, W) 并转换为float32 img_input img_normalized.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32) return img_input def postprocess(self, outputs: List[np.ndarray], original_shape: Tuple[int, int], conf_threshold: float 0.5, iou_threshold: float 0.5) - List[Dict]: 处理模型输出进行非极大值抑制返回检测结果 # 这里简化处理实际需要根据DAMOYOLO的输出格式解析 # 假设outputs[0]形状为 [1, 8400, 85] predictions outputs[0][0] # shape: [8400, 85] detections [] orig_h, orig_w original_shape for pred in predictions: # 前4个是框坐标 (cx, cy, w, h)需要转换 # 第5个是置信度后面80个是类别概率 scores pred[5:] class_id np.argmax(scores) confidence pred[4] * scores[class_id] if confidence conf_threshold: # 将中心点坐标和宽高转换为左上角和右下角坐标 cx, cy, w, h pred[:4] x1 (cx - w/2) * (orig_w / self.input_width) y1 (cy - h/2) * (orig_h / self.input_height) x2 (cx w/2) * (orig_w / self.input_width) y2 (cy h/2) * (orig_h / self.input_height) detections.append({ bbox: [float(x1), float(y1), float(x2), float(y2)], confidence: float(confidence), class_id: int(class_id), class_name: self.class_names[class_id] }) # 简单的非极大值抑制 if detections: detections.sort(keylambda x: x[confidence], reverseTrue) filtered_detections [] while detections: best detections.pop(0) filtered_detections.append(best) detections [det for det in detections if self._iou(best[bbox], det[bbox]) iou_threshold] return filtered_detections return [] def _iou(self, box1: List[float], box2: List[float]) - float: 计算两个框的交并比 x1 max(box1[0], box2[0]) y1 max(box1[1], box2[1]) x2 min(box1[2], box2[2]) y2 min(box1[3], box2[3]) inter_area max(0, x2 - x1) * max(0, y2 - y1) box1_area (box1[2] - box1[0]) * (box1[3] - box1[1]) box2_area (box2[2] - box2[0]) * (box2[3] - box2[1]) return inter_area / (box1_area box2_area - inter_area) def predict(self, image: np.ndarray) - Tuple[List[Dict], float]: 执行完整的预测流程 start_time time.time() # 预处理 img_input self.preprocess(image) # 推理 outputs self.session.run(None, {self.input_name: img_input}) # 后处理 detections self.postprocess(outputs, image.shape[:2]) inference_time time.time() - start_time return detections, inference_time这个类把加载模型、处理图片、运行推理、解析结果这些脏活累活都封装好了。注意实际的postprocess函数需要根据你下载的DAMOYOLO-S模型的具体输出格式来调整上面的代码是个示意。2.3 构建FastAPI应用与接口有了推理引擎我们就可以用FastAPI把它包装成HTTP接口了。创建main.py文件。from fastapi import FastAPI, File, UploadFile, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import cv2 import numpy as np from inference import DamoyoloInference import os from typing import List import json # 初始化FastAPI应用 app FastAPI(titleDAMOYOLO-S目标检测API, description提供基于DAMOYOLO-S模型的图片目标检测服务) # 配置CORS允许前端跨域访问 app.add_middleware( CORSMiddleware, allow_origins[*], # 生产环境应指定具体域名 allow_credentialsTrue, allow_methods[*], allow_headers[*], ) # 全局模型实例 MODEL_PATH models/damoyolo_s.onnx CLASS_NAMES_PATH models/coco.names model None app.on_event(startup) async def startup_event(): 应用启动时加载模型 global model try: model DamoyoloInference(MODEL_PATH, CLASS_NAMES_PATH) print(✅ DAMOYOLO-S模型加载完成API服务已就绪) except Exception as e: print(f❌ 模型加载失败: {e}) raise app.get(/) async def root(): 根路径返回服务状态 return { service: DAMOYOLO-S Object Detection API, status: running, model: DAMOYOLO-S (ONNX), endpoints: { 检测图片: POST /detect, 健康检查: GET /health } } app.get(/health) async def health_check(): 健康检查端点 return {status: healthy, model_loaded: model is not None} app.post(/detect) async def detect_objects( file: UploadFile File(..., description上传的图片文件), confidence_threshold: float 0.5, return_image: bool False ): 目标检测接口 - file: 图片文件 (支持jpg, png, jpeg) - confidence_threshold: 置信度阈值默认0.5 - return_image: 是否返回带标注的图片Base64格式 # 检查文件类型 allowed_types [image/jpeg, image/png, image/jpg] if file.content_type not in allowed_types: raise HTTPException(status_code400, detail仅支持JPEG或PNG格式的图片) try: # 读取图片数据 contents await file.read() nparr np.frombuffer(contents, np.uint8) image cv2.imdecode(nparr, cv2.IMREAD_COLOR) if image is None: raise HTTPException(status_code400, detail无法解码图片文件) # 执行检测 detections, inference_time model.predict(image) # 过滤低于阈值的检测结果 filtered_detections [ det for det in detections if det[confidence] confidence_threshold ] # 准备响应数据 response_data { filename: file.filename, detections_count: len(filtered_detections), inference_time_ms: round(inference_time * 1000, 2), detections: filtered_detections } # 如果需要返回带标注的图片 if return_image and filtered_detections: annotated_image image.copy() for det in filtered_detections: x1, y1, x2, y2 map(int, det[bbox]) label f{det[class_name]} {det[confidence]:.2f} # 画框 cv2.rectangle(annotated_image, (x1, y1), (x2, y2), (0, 255, 0), 2) # 画标签背景 cv2.rectangle(annotated_image, (x1, y1-20), (x1len(label)*10, y1), (0, 255, 0), -1) # 写标签文字 cv2.putText(annotated_image, label, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2) # 转换为Base64 _, buffer cv2.imencode(.jpg, annotated_image) import base64 img_base64 base64.b64encode(buffer).decode(utf-8) response_data[annotated_image] fdata:image/jpeg;base64,{img_base64} return JSONResponse(contentresponse_data) except Exception as e: raise HTTPException(status_code500, detailf处理图片时发生错误: {str(e)}) if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)这个API提供了两个主要端点/detect用于检测/health用于健康检查。代码里加了详细的错误处理和日志实际运行起来会更稳定。现在在backend目录下运行python main.py如果看到“模型加载成功”的提示说明后端服务已经跑起来了。你可以用浏览器打开http://localhost:8000/docsFastAPI会自动生成一个交互式API文档页面很方便测试。3. 前端界面用Vue.js构建交互页面后端搞定后我们来做一个好看又好用的前端界面。用户在这里上传图片、查看结果、下载标注后的图片。3.1 初始化Vue项目与组件结构进入frontend文件夹我们用Vite快速创建一个Vue项目npm create vuelatest # 项目名填 frontend # 选择 Vue 3, TypeScript, Router, Pinia # 其他选项按需选择创建完成后安装我们需要的额外依赖npm install axios element-plus npm install --save-dev types/node然后我们规划一下页面组件。主要就一个页面但可以拆成几个部分上传区域拖拽或点击上传图片参数控制区调整置信度阈值等参数结果展示区并列显示原图和带检测框的结果图检测结果列表以表格形式列出所有检测到的物体在src/views下创建DetectionPage.vue作为我们的主页面。3.2 核心页面组件开发先写页面的模板部分也就是HTML结构template div classdetection-page div classheader h1DAMOYOLO-S 目标检测演示/h1 p classsubtitle上传图片实时检测其中的物体/p /div div classmain-content !-- 左侧上传与控制面板 -- div classleft-panel el-card classupload-card template #header div classcard-header span图片上传/span /div /template el-upload classupload-area drag action# :auto-uploadfalse :show-file-listfalse :on-changehandleFileChange accept.jpg,.jpeg,.png div classupload-content el-icon classupload-iconUploadFilled //el-icon div classupload-text p点击或拖拽图片到此处/p p classupload-hint支持 JPG、PNG 格式/p /div /div /el-upload div classpreview v-iforiginalImageUrl p原图预览/p img :srcoriginalImageUrl alt原图预览 classpreview-image / /div /el-card el-card classcontrol-card template #header div classcard-header span检测参数/span /div /template div classcontrol-item span classcontrol-label置信度阈值/span el-slider v-modelconfidenceThreshold :min0.1 :max0.9 :step0.05 show-input input-sizesmall / span classthreshold-value{{ confidenceThreshold.toFixed(2) }}/span /div div classcontrol-item el-checkbox v-modelshowAnnotatedImage 显示标注结果图 /el-checkbox /div div classaction-buttons el-button typeprimary :loadingisDetecting :disabled!selectedFile clickhandleDetect classdetect-btn el-icon v-if!isDetectingSearch //el-icon {{ isDetecting ? 检测中... : 开始检测 }} /el-button el-button :disabled!detectionResult clickhandleReset el-iconRefresh //el-icon 重置 /el-button /div /el-card /div !-- 右侧结果展示 -- div classright-panel el-card classresult-card template #header div classcard-header span检测结果/span div classresult-stats v-ifdetectionResult el-tag typesuccess 检测到 {{ detectionResult.detections_count }} 个物体 /el-tag el-tag typeinfo 耗时 {{ detectionResult.inference_time_ms }} ms /el-tag /div /div /template div classimage-comparison v-iforiginalImageUrl div classimage-container p classimage-title原图/p img :srcoriginalImageUrl alt原图 classresult-image / /div div classimage-container v-ifshowAnnotatedImage annotatedImageUrl p classimage-title检测结果/p img :srcannotatedImageUrl alt检测结果 classresult-image / div classdownload-btn el-button typesuccess sizesmall clickdownloadAnnotatedImage el-iconDownload //el-icon 下载结果图 /el-button /div /div /div div classno-result v-else el-empty description暂无检测结果 / /div /el-card !-- 检测结果表格 -- el-card classtable-card v-ifdetectionResult detectionResult.detections.length 0 template #header div classcard-header span检测详情/span /div /template el-table :datadetectionResult.detections stripe stylewidth: 100% :default-sort{ prop: confidence, order: descending } el-table-column propclass_name label类别 width120 / el-table-column propconfidence label置信度 width120 template #default{ row } el-progress :percentageMath.round(row.confidence * 100) :colorgetConfidenceColor(row.confidence) :show-textfalse / span classconfidence-text{{ (row.confidence * 100).toFixed(1) }}%/span /template /el-table-column el-table-column label边界框 width200 template #default{ row } [{{ Math.round(row.bbox[0]) }}, {{ Math.round(row.bbox[1]) }}, {{ Math.round(row.bbox[2]) }}, {{ Math.round(row.bbox[3]) }}] /template /el-table-column el-table-column label操作 width100 template #default{ row, $index } el-button typeprimary link clickhighlightDetection(row, $index) 高亮 /el-button /template /el-table-column /el-table /el-card /div /div /div /template页面布局分左右两栏左边上传和控制右边展示结果。用了Element Plus的卡片、上传、滑块、表格这些组件看起来比较专业。接下来是脚本部分处理所有的交互逻辑script setup langts import { ref, computed } from vue import { UploadFilled, Search, Refresh, Download } from element-plus/icons-vue import { ElMessage, ElMessageBox } from element-plus import axios from axios import type { UploadFile } from element-plus // 定义检测结果类型 interface Detection { bbox: [number, number, number, number] confidence: number class_id: number class_name: string } interface DetectionResult { filename: string detections_count: number inference_time_ms: number detections: Detection[] annotated_image?: string } // 响应式数据 const selectedFile refFile | null(null) const originalImageUrl refstring() const confidenceThreshold refnumber(0.5) const showAnnotatedImage refboolean(true) const isDetecting refboolean(false) const detectionResult refDetectionResult | null(null) const annotatedImageUrl refstring() // 计算属性根据置信度获取颜色 const getConfidenceColor (confidence: number) { if (confidence 0.8) return #67c23a // 绿色 if (confidence 0.5) return #e6a23c // 黄色 return #f56c6c // 红色 } // 文件选择处理 const handleFileChange (uploadFile: UploadFile) { if (!uploadFile.raw) return selectedFile.value uploadFile.raw // 创建本地URL用于预览 if (originalImageUrl.value) { URL.revokeObjectURL(originalImageUrl.value) } originalImageUrl.value URL.createObjectURL(uploadFile.raw) // 重置之前的检测结果 detectionResult.value null annotatedImageUrl.value } // 执行检测 const handleDetect async () { if (!selectedFile.value) { ElMessage.warning(请先选择图片) return } isDetecting.value true try { const formData new FormData() formData.append(file, selectedFile.value) const response await axios.post(http://localhost:8000/detect, formData, { params: { confidence_threshold: confidenceThreshold.value, return_image: true }, headers: { Content-Type: multipart/form-data } }) detectionResult.value response.data if (response.data.annotated_image) { annotatedImageUrl.value response.data.annotated_image } ElMessage.success(检测完成发现 ${response.data.detections_count} 个物体) } catch (error: any) { console.error(检测失败:, error) ElMessage.error(检测失败: ${error.response?.data?.detail || error.message}) } finally { isDetecting.value false } } // 高亮特定检测结果 const highlightDetection (detection: Detection, index: number) { ElMessageBox.alert( 类别: ${detection.class_name}br 置信度: ${(detection.confidence * 100).toFixed(1)}%br 位置: [${detection.bbox.map(Math.round).join(, )}], 检测结果 ${index 1}, { dangerouslyUseHTMLString: true, confirmButtonText: 确定 } ) } // 下载标注后的图片 const downloadAnnotatedImage () { if (!annotatedImageUrl.value) return const link document.createElement(a) link.href annotatedImageUrl.value link.download detected_${selectedFile.value?.name || result.jpg} document.body.appendChild(link) link.click() document.body.removeChild(link) } // 重置所有状态 const handleReset () { selectedFile.value null if (originalImageUrl.value) { URL.revokeObjectURL(originalImageUrl.value) originalImageUrl.value } detectionResult.value null annotatedImageUrl.value confidenceThreshold.value 0.5 ElMessage.info(已重置) } /script最后加点样式让页面更好看style scoped .detection-page { padding: 20px; max-width: 1400px; margin: 0 auto; } .header { text-align: center; margin-bottom: 30px; } .header h1 { color: #303133; margin-bottom: 10px; } .subtitle { color: #909399; font-size: 16px; } .main-content { display: flex; gap: 20px; } .left-panel { flex: 1; max-width: 400px; } .right-panel { flex: 2; min-width: 0; /* 防止flex item溢出 */ } .upload-card, .control-card, .result-card, .table-card { margin-bottom: 20px; } .card-header { display: flex; justify-content: space-between; align-items: center; } .upload-area { width: 100%; } .upload-content { padding: 40px 0; text-align: center; } .upload-icon { font-size: 48px; color: #409eff; margin-bottom: 16px; } .upload-text { color: #606266; } .upload-hint { font-size: 12px; color: #909399; margin-top: 8px; } .preview { margin-top: 20px; } .preview-image { width: 100%; max-height: 200px; object-fit: contain; border-radius: 4px; border: 1px solid #ebeef5; } .control-item { margin-bottom: 20px; } .control-label { display: block; margin-bottom: 8px; color: #606266; font-size: 14px; } .threshold-value { display: inline-block; margin-left: 10px; min-width: 40px; text-align: right; color: #409eff; font-weight: bold; } .action-buttons { display: flex; gap: 10px; margin-top: 24px; } .detect-btn { flex: 1; } .result-stats { display: flex; gap: 10px; } .image-comparison { display: flex; gap: 20px; margin-bottom: 20px; } .image-container { flex: 1; text-align: center; } .image-title { margin-bottom: 10px; color: #606266; font-weight: 500; } .result-image { width: 100%; max-height: 400px; object-fit: contain; border-radius: 4px; border: 1px solid #ebeef5; background: #f5f7fa; } .download-btn { margin-top: 10px; } .confidence-text { display: inline-block; margin-left: 10px; min-width: 50px; text-align: right; font-size: 12px; color: #909399; } .no-result { padding: 40px 0; } /* 响应式设计 */ media (max-width: 992px) { .main-content { flex-direction: column; } .left-panel { max-width: 100%; } .image-comparison { flex-direction: column; } } /style3.3 配置路由与启动应用修改src/router/index.ts把我们的检测页面设为首页import { createRouter, createWebHistory } from vue-router import DetectionPage from ../views/DetectionPage.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: detection, component: DetectionPage } ] }) export default router在src/App.vue中引入Element Plus的样式template router-view / /template script setup langts import element-plus/dist/index.css /script最后在frontend目录下运行npm run dev前端应用就会在http://localhost:5173启动。4. 前后端联调与工程化考虑现在前后端都跑起来了但要让它们完美配合还需要处理一些工程化的问题。4.1 解决跨域与网络通信虽然我们在后端配了CORS但实际开发中可能还会遇到问题。我建议在前端创建一个专门的API客户端统一处理请求// src/api/client.ts import axios from axios const apiClient axios.create({ baseURL: http://localhost:8000, timeout: 30000, // 30秒超时图片上传可能较慢 headers: { Content-Type: application/json } }) // 请求拦截器 apiClient.interceptors.request.use( (config) { // 可以在这里添加token等 return config }, (error) { return Promise.reject(error) } ) // 响应拦截器 apiClient.interceptors.response.use( (response) { return response }, (error) { // 统一错误处理 if (error.response) { switch (error.response.status) { case 400: console.error(请求参数错误:, error.response.data) break case 500: console.error(服务器内部错误:, error.response.data) break case 504: console.error(请求超时请重试) break } } else if (error.request) { console.error(网络错误请检查连接) } return Promise.reject(error) } ) export default apiClient然后在页面组件里导入这个客户端替换掉直接使用axios的地方。4.2 性能优化与用户体验上传大图片时我们可以做一些优化script setup langts // 在组件中添加图片大小检查 const MAX_FILE_SIZE 10 * 1024 * 1024 // 10MB const handleFileChange (uploadFile: UploadFile) { if (!uploadFile.raw) return // 检查文件大小 if (uploadFile.raw.size MAX_FILE_SIZE) { ElMessage.warning(图片大小不能超过 ${MAX_FILE_SIZE / 1024 / 1024}MB) return } // 检查文件类型 const validTypes [image/jpeg, image/png, image/jpg] if (!validTypes.includes(uploadFile.raw.type)) { ElMessage.warning(仅支持JPG和PNG格式的图片) return } // ... 其余代码 } // 添加加载状态 const uploadProgress refnumber(0) const handleDetect async () { // ... 之前代码 try { // 显示进度模拟 const progressInterval setInterval(() { if (uploadProgress.value 90) { uploadProgress.value 10 } }, 200) const response await apiClient.post(/detect, formData, { params: { confidence_threshold: confidenceThreshold.value, return_image: true }, headers: { Content-Type: multipart/form-data }, onUploadProgress: (progressEvent) { if (progressEvent.total) { const percent Math.round((progressEvent.loaded * 100) / progressEvent.total) uploadProgress.value percent } } }) clearInterval(progressInterval) uploadProgress.value 100 // ... 处理响应 } catch (error) { // ... 错误处理 uploadProgress.value 0 } } /script4.3 错误处理与用户反馈好的错误处理能让用户体验提升不少。我们已经在拦截器里做了基础处理但在UI层面可以更友好template !-- 在上传区域添加进度条 -- div v-ifisDetecting classprogress-container el-progress :percentageuploadProgress :statusuploadProgress 100 ? success : undefined :show-textfalse / p classprogress-text上传与检测中... {{ uploadProgress }}%/p /div /template script setup langts // 添加重试逻辑 const retryCount refnumber(0) const MAX_RETRIES 3 const handleDetect async () { // ... 之前代码 try { // ... 请求代码 retryCount.value 0 // 成功时重置重试计数 } catch (error: any) { if (error.code ECONNABORTED retryCount.value MAX_RETRIES) { // 超时重试 retryCount.value ElMessage.warning(请求超时正在重试 (${retryCount.value}/${MAX_RETRIES})) setTimeout(() { handleDetect() }, 1000) return } // ... 其他错误处理 } } /script5. 部署与后续优化建议项目在本地跑通后你可能想把它部署到服务器上。这里有几个方向可以考虑。5.1 后端部署方案对于FastAPI后端我推荐用Docker容器化部署这样环境一致迁移也方便。创建一个Dockerfile# backend/Dockerfile FROM python:3.9-slim WORKDIR /app # 安装系统依赖 RUN apt-get update apt-get install -y \ libgl1-mesa-glx \ libglib2.0-0 \ rm -rf /var/lib/apt/lists/* # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 创建模型目录确保模型文件已存在 RUN mkdir -p models # 暴露端口 EXPOSE 8000 # 启动命令 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]然后构建并运行docker build -t damoyolo-backend . docker run -p 8000:8000 damoyolo-backend5.2 前端部署方案前端可以用Nginx来服务。先构建生产版本npm run build然后配置Nginx# nginx.conf server { listen 80; server_name your-domain.com; location / { root /path/to/frontend/dist; index index.html; try_files $uri $uri/ /index.html; } # 代理后端API请求 location /api/ { proxy_pass http://localhost:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }5.3 性能优化建议实际使用中你可能会遇到一些性能问题这里有几个优化方向模型优化考虑将ONNX模型转换为TensorRT能获得更好的GPU推理性能。异步处理对于大图片或批量处理可以引入消息队列如Celery Redis实现异步处理。缓存机制对相同的图片请求可以缓存检测结果减少重复计算。前端懒加载如果检测结果很多可以考虑分页或虚拟滚动展示。WebSocket支持对于需要实时反馈的场景可以考虑用WebSocket替代HTTP。5.4 功能扩展想法这个基础版本跑通后你还可以加入更多实用功能批量处理支持一次上传多张图片批量检测历史记录把检测记录保存到数据库方便查看历史导出功能支持将检测结果导出为JSON、CSV或COCO格式模型切换集成多个不同模型让用户可以选择API密钥添加简单的认证机制保护你的服务整个项目做下来感觉最难的不是写代码而是前后端的配合和错误处理。特别是文件上传、图片预览、结果展示这些环节有很多细节要注意。不过一旦跑通看到图片上传后能实时显示出检测框那种成就感还是挺足的。这个项目虽然是个demo但已经具备了生产应用的基本骨架。你可以基于它继续扩展加入用户系统、计费功能、更复杂的业务逻辑等等。希望这个实战分享对你有帮助如果在实现过程中遇到问题欢迎一起讨论。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章