点击按钮后如何将FastAPI模型预测结果展示到网页?
解决方案
1. 调整FastAPI后端代码
核心修改点
解决原代码中上传图片无法在前端展示的问题,改用Base64编码传递图片内容,同时计算全部分类的预测分数用于展示:
from fastapi import FastAPI, File, UploadFile, Request import uvicorn import numpy as np import tensorflow as tf from keras.applications.vgg16 import preprocess_input import os from fastapi.templating import Jinja2Templates import shutil from pathlib import Path from tempfile import NamedTemporaryFile from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles import base64 # 新增:用于图片Base64编码 app = FastAPI() app.mount("/static", StaticFiles(directory="static"), name="static") origins = [ "http://127.0.0.1", "http://127.0.0.1:80", ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) templates = Jinja2Templates(directory="templates/") MODEL = tf.keras.models.load_model("./Skin Cancer.h5") classes = {4: ('nv', ' melanocytic nevi'), 6: ('mel', 'melanoma'), 2 :('bkl', 'benign keratosis-like lesions'), 1:('bcc' , ' basal cell carcinoma'), 5: ('vasc', ' pyogenic granulomas and hemorrhage'), 0: ('akiec', 'Actinic keratoses and intraepithelial carcinomae'), 3: ('df', 'dermatofibroma')} def model_predict(img_path, MODEL): img = tf.keras.utils.load_img(img_path, target_size = (28,28)) img = tf.keras.utils.img_to_array(img) img = np.expand_dims(img, axis=0) img_data = preprocess_input(img) preds = MODEL.predict(img_data) return preds def save_upload_file_tmp(upload_file: UploadFile) -> Path: try: suffix = Path(upload_file.filename).suffix with NamedTemporaryFile(delete=False, suffix=suffix) as tmp: shutil.copyfileobj(upload_file.file, tmp) tmp_path = Path(tmp.name) finally: upload_file.file.close() return tmp_path @app.get("/ping") def ping(): return "pinging!" @app.get("/") def home(request: Request): return templates.TemplateResponse("index.html", {'request':request}) @app.post("/predict") async def predict( request: Request, file: UploadFile = File(...) ): # 读取图片并转为Base64格式,用于前端展示 img_data = await file.read() img_base64 = base64.b64encode(img_data).decode("utf-8") img_uri = f"data:image/{file.content_type.split('/')[1]};base64,{img_base64}" # 保存临时文件用于模型预测 filepath = save_upload_file_tmp(file) predictions = model_predict(filepath, MODEL) filepath.unlink() # 计算预测结果 class_lab = [0,1,2,3,4,5,6] class_score = tf.nn.softmax(predictions[0]) conf = float(np.max(class_score)) * 100 # 用softmax后的分数计算置信度,结果更合理 cl = class_lab[tf.argmax(class_score)] cancer_name = classes[cl][1] # 整理所有类别的预测分数,用于前端展示 all_predictions = [] for idx, score in enumerate(class_score): all_predictions.append({ "name": classes[idx][1], "score": float(score) * 100 }) return templates.TemplateResponse("pred.html", { "request": request, "img_uri": img_uri, "predicted_class": cancer_name, "confidence": round(conf, 2), "all_predictions": all_predictions }) if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=80)
2. 编写上传页面模板(templates/index.html)
创建带文件上传表单的页面,提交后触发预测接口:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>皮肤癌图像检测</title> <style> .container { max-width: 600px; margin: 2rem auto; padding: 0 1rem; } .upload-form { display: flex; flex-direction: column; gap: 1rem; margin-top: 2rem; } input[type="file"] { padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; } button { padding: 0.75rem; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; } </style> </head> <body> <div class="container"> <h1>皮肤癌图像检测</h1> <form class="upload-form" action="/predict" method="post" enctype="multipart/form-data"> <input type="file" name="file" accept="image/*" required> <button type="submit">开始预测</button> </form> </div> </body> </html>
3. 编写结果展示模板(templates/pred.html)
用网格布局展示上传图片和所有预测分数:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>预测结果</title> <style> .result-container { max-width: 850px; margin: 2rem auto; padding: 0 1rem; display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; } .card { border: 1px solid #eee; border-radius: 8px; padding: 1.5rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .img-card img { max-width: 100%; border-radius: 4px; margin-bottom: 1rem; } .top-pred { font-size: 1.5rem; font-weight: bold; color: #dc3545; margin-bottom: 2rem; } .score-list { display: flex; flex-direction: column; gap: 0.8rem; } .score-item { display: flex; justify-content: space-between; padding-bottom: 0.5rem; border-bottom: 1px solid #eee; } .back-btn { margin-top: 2rem; padding: 0.75rem 1.5rem; background-color: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; } </style> </head> <body> <div class="result-container"> <div class="card img-card"> <h3>上传的图像</h3> <img src="{{ img_uri }}" alt="皮肤检测图像"> </div> <div class="card pred-card"> <h2>预测结果</h2> <div class="top-pred"> {{ predicted_class }}<br> <span style="font-size: 1rem; font-weight: normal; color: #666;">置信度: {{ confidence }}%</span> </div> <h3>全部分类分数</h3> <div class="score-list"> {% for pred in all_predictions %} <div class="score-item"> <span>{{ pred.name }}</span> <span>{{ round(pred.score, 2) }}%</span> </div> {% endfor %} </div> <button class="back-btn" onclick="window.location.href='/'">返回上传页面</button> </div> </div> </body> </html>
4. 运行验证
- 确保项目目录下存在
templates文件夹,放入上述两个HTML文件 - 确保
static文件夹存在(即使为空) - 执行
python main.py启动服务 - 访问
http://127.0.0.1,上传图片后点击预测,将自动跳转到结果页面,以网格形式展示图片和所有预测分数
内容的提问来源于stack exchange,提问作者Jinen Rathore
相关产品推荐
相关产品推荐

