用Flask和YOLOv11快速搭建一个工业零件检测Web应用(附完整源码)

张开发
2026/4/16 0:42:44 15 分钟阅读

分享文章

用Flask和YOLOv11快速搭建一个工业零件检测Web应用(附完整源码)
工业级零件检测Web应用实战从YOLOv11模型到Flask部署全流程当我们需要将训练好的目标检测模型转化为可交互的Web应用时技术选型和工程实现往往成为开发者面临的首要挑战。本文将手把手带你完成一个工业零件检测系统的完整搭建过程涵盖从模型加载到前端交互的每个技术细节。1. 环境准备与项目初始化在开始编码前我们需要搭建一个稳定的开发环境。这个环节往往被很多教程忽略但却是项目能否顺利运行的关键基础。首先创建一个干净的Python虚拟环境推荐使用Python 3.8python -m venv yolo_env source yolo_env/bin/activate # Linux/Mac # 或者 yolo_env\Scripts\activate # Windows接着安装核心依赖库。这里我们使用Ultralytics官方实现的YOLOv11pip install torch torchvision --extra-index-url https://download.pytorch.org/whl/cu118 # GPU版本 pip install ultralytics flask pillow opencv-python项目目录结构应该保持清晰这对后期维护至关重要yolo_flask_app/ ├── app.py # Flask主程序 ├── model/ │ └── best.pt # 训练好的YOLOv11模型 ├── static/ │ ├── js/ # 前端JavaScript │ ├── css/ # 样式表 │ └── uploads/ # 用户上传图片临时存储 ├── templates/ # HTML模板 └── requirements.txt # 依赖清单提示模型文件建议放在单独目录中避免与代码文件混放。生产环境中应考虑将模型存储在云存储服务中按需加载。2. Flask后端核心实现2.1 模型加载与初始化在app.py中我们首先实现模型的加载逻辑。YOLOv11的接口与早期版本有所不同需要特别注意from ultralytics import YOLO import os class Detector: def __init__(self, model_path): self.model YOLO(model_path) self.class_names { 0: 电容, 1: 电阻, 2: 稳压管 } # 根据实际训练类别修改 def predict(self, img_path, conf0.5): results self.model.predict( sourceimg_path, confconf, imgsz640, devicecuda if torch.cuda.is_available() else cpu ) return self._format_results(results[0]) def _format_results(self, result): boxes result.boxes.xyxy.tolist() confs result.boxes.conf.tolist() class_ids result.boxes.cls.tolist() return [{ class: self.class_names[int(cls_id)], confidence: float(conf), bbox: [int(x) for x in box] } for box, conf, cls_id in zip(boxes, confs, class_ids)]2.2 文件上传与结果返回Flask需要处理图片上传和结果返回两个主要端点from flask import Flask, request, jsonify, render_template import uuid import cv2 app Flask(__name__) detector Detector(model/best.pt) app.route(/) def index(): return render_template(index.html) app.route(/api/detect, methods[POST]) def detect(): if file not in request.files: return jsonify({error: No file uploaded}), 400 file request.files[file] if file.filename : return jsonify({error: Empty filename}), 400 # 保存上传文件 upload_dir static/uploads os.makedirs(upload_dir, exist_okTrue) filename str(uuid.uuid4()) os.path.splitext(file.filename)[1] filepath os.path.join(upload_dir, filename) file.save(filepath) # 获取置信度阈值 conf float(request.form.get(confidence, 0.5)) # 执行检测 detections detector.predict(filepath, conf) # 绘制检测框 img cv2.imread(filepath) for det in detections: x1, y1, x2, y2 det[bbox] cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, f{det[class]} {det[confidence]:.2f}, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) # 保存结果图 result_filename fresult_{filename} result_path os.path.join(upload_dir, result_filename) cv2.imwrite(result_path, img) return jsonify({ detections: detections, result_img: f/static/uploads/{result_filename} })3. 前端交互实现3.1 基础HTML结构在templates/index.html中构建基本界面!DOCTYPE html html head title工业零件检测系统/title link relstylesheet href/static/css/style.css /head body div classcontainer h1工业零件检测系统/h1 div classupload-area iddropZone p拖放图片到此处或点击上传/p input typefile idfileInput acceptimage/* /div div classcontrols label置信度阈值: span idconfValue0.5/span/label input typerange idconfSlider min0.1 max1.0 step0.1 value0.5 /div div classresults div classimage-container img idoriginalImg styledisplay:none; canvas idresultCanvas/canvas /div div classstats h3检测结果/h3 div iddetectionResults/div /div /div /div script src/static/js/main.js/script /body /html3.2 JavaScript交互逻辑前端交互主要处理文件上传、结果显示和用户操作// static/js/main.js document.addEventListener(DOMContentLoaded, () { const dropZone document.getElementById(dropZone); const fileInput document.getElementById(fileInput); const confSlider document.getElementById(confSlider); const confValue document.getElementById(confValue); const originalImg document.getElementById(originalImg); const resultCanvas document.getElementById(resultCanvas); const ctx resultCanvas.getContext(2d); const detectionResults document.getElementById(detectionResults); // 拖拽上传处理 dropZone.addEventListener(dragover, (e) { e.preventDefault(); dropZone.classList.add(dragover); }); dropZone.addEventListener(dragleave, () { dropZone.classList.remove(dragover); }); dropZone.addEventListener(drop, (e) { e.preventDefault(); dropZone.classList.remove(dragover); if (e.dataTransfer.files.length) { fileInput.files e.dataTransfer.files; handleFileUpload(e.dataTransfer.files[0]); } }); // 点击上传处理 dropZone.addEventListener(click, () fileInput.click()); fileInput.addEventListener(change, () { if (fileInput.files.length) { handleFileUpload(fileInput.files[0]); } }); // 置信度滑块 confSlider.addEventListener(input, () { confValue.textContent confSlider.value; }); // 处理文件上传 async function handleFileUpload(file) { const formData new FormData(); formData.append(file, file); formData.append(confidence, confSlider.value); try { const response await fetch(/api/detect, { method: POST, body: formData }); const data await response.json(); if (response.ok) { displayResults(data); } else { alert(检测失败: ${data.error}); } } catch (error) { alert(请求错误: ${error.message}); } } // 显示检测结果 function displayResults(data) { // 加载原始图片 originalImg.src /static/uploads/${fileInput.files[0].name}; originalImg.onload () { // 设置canvas尺寸 resultCanvas.width originalImg.width; resultCanvas.height originalImg.height; // 绘制原始图片 ctx.drawImage(originalImg, 0, 0); // 显示统计信息 let html p检测到 strong${data.detections.length}/strong 个零件/pul; const classCount {}; data.detections.forEach(det { classCount[det.class] (classCount[det.class] || 0) 1; }); for (const cls in classCount) { html li${cls}: ${classCount[cls]}个/li; } html /ul; detectionResults.innerHTML html; // 加载结果图片带检测框 const resultImg new Image(); resultImg.src data.result_img; resultImg.onload () { ctx.drawImage(resultImg, 0, 0); }; }; } });4. 部署优化与性能调优4.1 生产环境部署开发完成后我们需要考虑如何将应用部署到生产环境。使用Waitress作为WSGI服务器是个不错的选择pip install waitress创建启动脚本run_prod.pyfrom waitress import serve from app import app serve(app, host0.0.0.0, port5000)4.2 模型性能优化YOLOv11提供了多种优化选项可以显著提升推理速度# 在Detector类中添加优化方法 def optimize_model(self): # 半精度推理 self.model self.model.half() # 预热模型 dummy_input torch.randn(1, 3, 640, 640).half() if torch.cuda.is_available(): dummy_input dummy_input.cuda() self.model(dummy_input) # 开启推理模式 self.model self.model.eval() torch.set_grad_enabled(False)4.3 常见问题解决方案在实际部署中你可能会遇到以下典型问题问题1首次请求响应慢原因模型初次加载需要时间解决添加预热逻辑如上optimize_model方法问题2内存占用过高原因默认会缓存多个推理会话解决在predict方法中添加内存清理def predict(self, img_path, conf0.5): torch.cuda.empty_cache() # 清理GPU缓存 with torch.no_grad(): # 禁用梯度计算 results self.model.predict(...) return self._format_results(results[0])问题3跨域访问问题解决在Flask中启用CORS支持from flask_cors import CORS CORS(app)5. 功能扩展与进阶方向基础功能实现后可以考虑以下扩展方向5.1 批量处理功能添加批量上传和处理接口app.route(/api/batch_detect, methods[POST]) def batch_detect(): if files not in request.files: return jsonify({error: No files uploaded}), 400 files request.files.getlist(files) conf float(request.form.get(confidence, 0.5)) results [] for file in files: if file.filename : continue filename str(uuid.uuid4()) os.path.splitext(file.filename)[1] filepath os.path.join(static/uploads, filename) file.save(filepath) detections detector.predict(filepath, conf) results.append({ filename: file.filename, detections: detections, result_img: f/static/uploads/result_{filename} }) return jsonify({results: results})5.2 历史记录功能使用SQLite存储检测记录import sqlite3 from datetime import datetime def init_db(): conn sqlite3.connect(detections.db) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS detections (id TEXT PRIMARY KEY, filename TEXT, timestamp TEXT, detections TEXT)) conn.commit() conn.close() app.route(/api/history, methods[GET]) def get_history(): conn sqlite3.connect(detections.db) c conn.cursor() c.execute(SELECT * FROM detections ORDER BY timestamp DESC LIMIT 10) rows c.fetchall() conn.close() return jsonify([{ id: row[0], filename: row[1], timestamp: row[2], detections: json.loads(row[3]) } for row in rows])5.3 模型版本管理实现多模型版本切换功能class ModelManager: def __init__(self): self.models {} self.current_model None def load_model(self, model_name, model_path): if model_name not in self.models: self.models[model_name] YOLO(model_path) self.current_model model_name def get_model(self): return self.models[self.current_model] # 使用方式 model_manager ModelManager() model_manager.load_model(v11, models/yolov11.pt) model_manager.load_model(v8, models/yolov8.pt) app.route(/api/switch_model, methods[POST]) def switch_model(): model_name request.json.get(model) if model_name in model_manager.models: model_manager.current_model model_name return jsonify({status: success}) return jsonify({error: Invalid model name}), 400

更多文章