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

关于前端页面上传图片检测

  1. 依赖于前文,linux系统上部署yolo识别图片,远程宿主机访问docker全流程(https://blog.csdn.net/yanzhuang521967/article/details/148777650?spm=1001.2014.3001.5501)
    fastapi把端口暴露出来

  2. 后端代码

from fastapi import FastAPI, UploadFile, File, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, StreamingResponse, Response
from starlette.responses import RedirectResponse
from urllib.parse import urlparse
from ultralytics import YOLO
import os
import json
from pathlib import Path
from fastapi.staticfiles import StaticFiles
import logging
import io
from PIL import Image# 初始化应用
app = FastAPI()# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)# 静态文件服务
app.mount("/output-predict", StaticFiles(directory="/usr/src/output/predict"), name="output-predict")
app.mount("/output-labels", StaticFiles(directory="/usr/src/output/predict/labels"), name="output-labels")# CORS配置
app.add_middleware(CORSMiddleware,allow_origins=["*"],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],expose_headers=["*"]
)# 修复后的协议转换中间件
@app.middleware("http")
async def protocol_converter(request: Request, call_next):try:# 处理HTTPS转HTTP(如果需要)if request.url.scheme == "https":url = str(request.url).replace("https://", "http://", 1)logger.info(f"Converting HTTPS to HTTP: {url}")scope = request.scopescope["scheme"] = "http"headers = []for k, v in scope["headers"]:if k == b"referer":headers.append((k, v.replace(b"https://", b"http://")))else:headers.append((k, v))scope["headers"] = headersresponse = await call_next(request)# 不处理流式响应和重定向if isinstance(response, (StreamingResponse, RedirectResponse)):return response# 获取响应体(兼容新旧版本)if hasattr(response, "body_iterator"):# 处理StreamingResponsebody = b"".join([chunk async for chunk in response.body_iterator])else:# 普通响应body = await response.body()# 替换内容中的HTTPS链接(如果需要)if body and b"https://" in body:body = body.replace(b"https://", b"http://")logger.debug("Replaced HTTPS links in response body")return Response(content=body,status_code=response.status_code,media_type=response.media_type,headers=dict(response.headers))except Exception as e:logger.error(f"Protocol converter error: {str(e)}", exc_info=True)raise HTTPException(status_code=500, detail="Internal server error")# 安全头中间件
@app.middleware("http")
async def security_headers(request: Request, call_next):response = await call_next(request)response.headers.update({"Access-Control-Allow-Private-Network": "true","Cross-Origin-Resource-Policy": "cross-origin","X-Content-Type-Options": "nosniff"})return response# 初始化模型和目录
model = YOLO("/ultralytics/yolo11n.pt")
output_base = Path("/usr/src/output")
predict_dir = output_base / "predict"
(predict_dir / "labels").mkdir(parents=True, exist_ok=True)# 辅助函数
async def save_upload_file(file: UploadFile) -> str:"""保存上传文件到临时位置"""temp_path = f"/tmp/{file.filename}"try:with open(temp_path, "wb") as f:content = await file.read()f.write(content)return temp_pathexcept Exception as e:logger.error(f"File save failed: {str(e)}")raise HTTPException(500, "File upload failed")# API端点
@app.post("/predict")
async def predict(request: Request, file: UploadFile = File(...)):temp_path = Nonetry:# 1. 保存文件temp_path = await save_upload_file(file)# 2. 运行预测results = model.predict(source=temp_path,project=str(predict_dir),name="",save=True,save_txt=True,save_conf=True,exist_ok=True)# 3. 准备结果(使用新的to_json()方法)file_stem = Path(file.filename).stembase_url = str(request.base_url).replace("https://", "http://")json_result = {"filename": file.filename,"detections": json.loads(results[0].to_json()),  # 使用to_json()替代tojson()"image_path": f"{base_url}output-predict/{file.filename}","label_path": f"{base_url}output-labels/{file_stem}.txt","speed": {"preprocess": results[0].speed["preprocess"],"inference": results[0].speed["inference"],"postprocess": results[0].speed["postprocess"]}}# 4. 保存JSONjson_path = predict_dir / "labels" / f"{file_stem}.json"with open(json_path, "w") as f:json.dump(json_result, f, indent=2)return JSONResponse({"status": "success","data": json_result,"debug": {"original_protocol": request.url.scheme,"processed_protocol": "http"}})except Exception as e:logger.error(f"Prediction failed: {str(e)}", exc_info=True)raise HTTPException(status_code=500, detail=str(e))finally:if temp_path and os.path.exists(temp_path):os.remove(temp_path)# 健康检查端点
@app.get("/health")
async def health_check():return {"status": "healthy", "protocol": "http"}# 协议检查端点
@app.get("/check-protocol")
async def check_protocol(request: Request):return {"client_protocol": request.url.scheme,"server_protocol": "http","headers": dict(request.headers)}
  1. 前端代码
   <!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- 在HTML的<head>中添加 --><title>YOLOv8 图像检测系统</title><style>body {font-family: Arial, sans-serif;max-width: 1200px;margin: 0 auto;padding: 20px;line-height: 1.6;}.upload-container {text-align: center;margin-bottom: 30px;}.upload-area {border: 2px dashed #ccc;border-radius: 8px;padding: 40px;margin: 20px 0;cursor: pointer;transition: all 0.3s;}.upload-area:hover {border-color: #4CAF50;background-color: #f9f9f9;}#fileInput {display: none;}button {background-color: #4CAF50;color: white;border: none;padding: 10px 20px;border-radius: 4px;cursor: pointer;font-size: 16px;transition: background 0.3s;}button:hover {background-color: #45a049;}button:disabled {background-color: #cccccc;cursor: not-allowed;}.status {margin: 20px 0;padding: 15px;border-radius: 4px;display: none;}.loading {background-color: #e8f5e9;color: #2e7d32;}.error {background-color: #ffebee;color: #f44336;}.image-container {display: flex;flex-wrap: wrap;gap: 20px;margin-top: 30px;}.image-box {flex: 1;min-width: 300px;margin-bottom: 20px;}.image-box img {max-width: 100%;border-radius: 4px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);}.results-panel {margin-top: 30px;padding: 20px;background-color: #f5f5f5;border-radius: 8px;}pre {background-color: #eee;padding: 15px;border-radius: 4px;overflow-x: auto;}.spinner {border: 4px solid rgba(0,0,0,0.1);border-radius: 50%;border-top: 4px solid #4CAF50;width: 40px;height: 40px;animation: spin 1s linear infinite;margin: 20px auto;}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}</style>
</head>
<body><div class="upload-container"><h1>YOLOv8 图像检测系统</h1><div class="upload-area" id="dropZone"><p>点击或拖拽图片到此处上传</p><input type="file" id="fileInput" accept="image/*"></div><button id="detectBtn" disabled>开始检测</button><div id="loadingStatus" class="status loading"><div class="spinner"></div><p>正在处理图像,请稍候...</p></div><div id="errorStatus" class="status error"><h3>检测失败</h3><p id="errorMessage"></p></div></div><div class="image-container"><div class="image-box"><h3>原始图片</h3><img id="preview" style="display: none;"></div><div class="image-box"><h3>检测结果</h3><img id="result" style="display: none;"></div></div><div class="results-panel" id="resultsPanel" style="display: none;"><h3>检测结果数据</h3><pre id="jsonData"></pre></div><script>// DOM元素const fileInput = document.getElementById('fileInput');const dropZone = document.getElementById('dropZone');const detectBtn = document.getElementById('detectBtn');const preview = document.getElementById('preview');const result = document.getElementById('result');const loadingStatus = document.getElementById('loadingStatus');const errorStatus = document.getElementById('errorStatus');const errorMessage = document.getElementById('errorMessage');const resultsPanel = document.getElementById('resultsPanel');const jsonData = document.getElementById('jsonData');// 当前处理的文件let currentFile = null;// 文件选择处理fileInput.addEventListener('change', handleFileSelect);dropZone.addEventListener('click', () => fileInput.click());// 拖放功能dropZone.addEventListener('dragover', (e) => {e.preventDefault();dropZone.style.borderColor = '#4CAF50';dropZone.style.backgroundColor = '#f0fff0';});dropZone.addEventListener('dragleave', () => {dropZone.style.borderColor = '#ccc';dropZone.style.backgroundColor = '';});dropZone.addEventListener('drop', (e) => {e.preventDefault();dropZone.style.borderColor = '#ccc';dropZone.style.backgroundColor = '';if (e.dataTransfer.files.length) {fileInput.files = e.dataTransfer.files;handleFileSelect({ target: fileInput });}});// 检测按钮点击detectBtn.addEventListener('click', startDetection);function handleFileSelect(event) {const file = event.target.files[0];if (file && file.type.match('image.*')) {currentFile = file;const reader = new FileReader();reader.onload = (e) => {preview.src = e.target.result;preview.style.display = 'block';detectBtn.disabled = false;// 重置状态result.style.display = 'none';resultsPanel.style.display = 'none';errorStatus.style.display = 'none';};reader.readAsDataURL(file);}}async function startDetection() {if (!currentFile) return;// 显示加载状态loadingStatus.style.display = 'block';errorStatus.style.display = 'none';detectBtn.disabled = true;try {// 1. 上传图片进行预测const formData = new FormData();formData.append('file', currentFile);const response = await fetch('http://192.168.0.100:34567/predict', {method: 'POST',mode: 'cors', // 确保这是cors而不是no-corsbody: formData});if (!response.ok) {const error = await response.text();throw new Error(error || `服务器错误: ${response.status}`);}const resultData = await response.json();// 2. 显示处理后的图片const filename = currentFile.name.split('.')[0];result.src = `http://192.168.0.100:34567/output-predict/predict/${filename}.jpg?t=${Date.now()}`;result.style.display = 'block';// 3. 显示JSON数据jsonData.textContent = JSON.stringify(resultData, null, 2);resultsPanel.style.display = 'block';} catch (error) {console.error('检测失败:', error);errorMessage.textContent = error.message;errorStatus.style.display = 'block';} finally {loadingStatus.style.display = 'none';detectBtn.disabled = false;}}</script>
</body>
</html>

在这里插入图片描述
在这里插入图片描述

http://www.lqws.cn/news/557299.html

相关文章:

  • 暑假复习篇之运算与逻辑
  • UI前端大数据可视化创新:利用AR/VR技术提升用户沉浸感
  • 什么是集中刷新,分散刷新,和异步刷新
  • 从 AJAX 到 axios:前端与服务器通信实战指南
  • 2023国赛linux的应急响应-wp
  • Re--攻防世界-基础android
  • C++ vector 完全指南:从入门到精通
  • 源码运行效果图(六)
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(三十八) -> 构建HAR
  • 基于springboot的海产品交易系统
  • 【数据标注师】3D标注
  • JWT认证性能优化实战指南
  • 《从 0 到 1 掌握正则表达式:解析串口数据的万能钥匙》
  • springboot+Vue逍遥大药房管理系统
  • 创建套接字时和填充地址时指定类型的异同
  • C++泛型编程2 - 类模板
  • 【数论】P11169 「CMOI R1」Bismuth / Linear Sieve|普及+
  • 嵌入式硬件与应用篇---寄存器GPIO控制
  • 进阶向:Flask框架详解,从零开始理解Web开发利器
  • Odoo邮箱别名使用指南:从配置到业务流程自动化
  • C# 委托(为委托添加方法和从委托移除方法)
  • docker部署后端服务的脚本
  • Golang JSON 标准库用法详解
  • Foundry测试实战:解锁区块链测试新姿势
  • Java 大视界 -- Java 大数据机器学习模型在金融市场高频交易策略优化与风险控制中的应用(327)
  • 单调栈一文深度解析
  • NLP——文本预处理(下)
  • 翻译服务器
  • Redis高级数据结构深度解析:BitMap、布隆过滤器、HyperLogLog与Geo应用实践
  • 趣味数据结构之——数组