GPEN如何对接API?FastAPI封装调用实例
你手头有一台预装GPEN人像修复模型的镜像,也跑通了命令行推理,但真正落地到业务系统时,总不能每次都ssh进去敲命令——你需要一个稳定、可并发、能被前端或App直接调用的服务接口。本文不讲原理、不堆参数,只聚焦一件事:把GPEN变成一个真正可用的HTTP API服务。我们会用FastAPI从零封装,覆盖环境适配、图片上传、异步处理、错误兜底、结果返回等完整链路,并给出可直接运行的代码和部署建议。全程基于你已有的镜像环境,无需额外安装依赖,10分钟内就能让GPEN“开口说话”。
1. 为什么不能直接用命令行脚本?
在镜像里执行python inference_gpen.py --input my.jpg确实简单,但它本质是单次、阻塞、无状态的本地操作。放到生产环境,你会立刻遇到几个现实问题:
- 无法并发:多个用户同时上传照片,脚本会排队甚至冲突;
- 路径硬编码:脚本默认读取固定路径,而Web请求的图片来自内存流或临时文件;
- 无错误反馈:输入格式错误、人脸缺失、显存不足时,脚本直接崩溃,前端收不到任何提示;
- 无法集成:没有标准HTTP协议支持,App、小程序、低代码平台都调不了。
FastAPI不是为了“炫技”,而是为了解决这些工程卡点——它轻量、自带文档、原生支持异步IO、类型安全,且与PyTorch生态无缝兼容。更重要的是,它能在你现有的镜像里直接跑起来,不用改一行GPEN源码。
2. FastAPI服务封装实战
2.1 项目结构规划
我们不改动原始/root/GPEN目录,而是在其同级新建一个轻量服务目录,保持职责分离:
/root/ ├── GPEN/ # 原始模型代码(不动) └── gpen_api/ # 新建API服务 ├── main.py # FastAPI主程序 ├── api_handler.py # GPEN调用逻辑封装 ├── utils.py # 图片处理、路径管理等工具函数 └── requirements.txt这样既复用原有环境,又避免污染原始代码,后续升级GPEN也互不影响。
2.2 核心封装:把GPEN变成Python函数
原始inference_gpen.py是面向终端的脚本,我们需要把它“解耦”成可编程调用的函数。关键在于绕过argparse,直接接管输入输出流程。
在api_handler.py中定义核心函数:
# /root/gpen_api/api_handler.py import os import torch import numpy as np from PIL import Image from pathlib import Path from typing import Optional, Tuple # 导入GPEN原始模块(注意路径) import sys sys.path.insert(0, "/root/GPEN") from models import GPEN from basicsr.utils import imwrite from facexlib.utils.face_restoration_helper import FaceRestoreHelper def run_gpen_enhancement( input_image: Image.Image, output_path: str, upscale: int = 2, code_dim: int = 512, n_inv_step: int = 7, lambda_1: float = 0.1, lambda_2: float = 0.1, device: str = "cuda" if torch.cuda.is_available() else "cpu" ) -> Tuple[bool, str]: """ 封装GPEN人像增强核心逻辑 :param input_image: PIL.Image 输入图像 :param output_path: 输出文件路径(含扩展名) :param upscale: 放大倍数(1/2/4) :param code_dim: 潜在空间维度 :param n_inv_step: 反演步数(影响细节还原度) :param lambda_1/lambda_2: 正则化权重(控制保真vs增强强度) :return: (是否成功, 错误信息) """ try: # 1. 初始化GPEN模型(仅首次加载,后续复用) if not hasattr(run_gpen_enhancement, 'model'): model_path = "/root/.cache/modelscope/hub/iic/cv_gpen_image-portrait-enhancement" run_gpen_enhancement.model = GPEN( num_kp=15, num_channels=3, num_filters=64, use_bn=True, code_dim=code_dim, device=device ) # 加载权重(自动从缓存读取) checkpoint = torch.load(f"{model_path}/generator.pth", map_location=device) run_gpen_enhancement.model.load_state_dict(checkpoint['g']) run_gpen_enhancement.model.eval() run_gpen_enhancement.model.to(device) # 2. 初始化人脸辅助器(检测+对齐) face_helper = FaceRestoreHelper( upscale=upscale, face_size=512, crop_ratio=(1, 1), det_model='retinaface_resnet50', save_ext='png', device=device ) # 3. 转换PIL为numpy并送入处理流水线 img_np = np.array(input_image) face_helper.clean_all() face_helper.read_image(img_np) face_helper.get_face_landmarks_5(only_center_face=False, resize=640) face_helper.align_warp_face() # 4. 对每张检测到的人脸进行增强 enhanced_faces = [] for idx, cropped_face in enumerate(face_helper.cropped_faces): # 预处理:归一化、转tensor face_tensor = torch.from_numpy(cropped_face).permute(2, 0, 1).float() / 255.0 face_tensor = face_tensor.unsqueeze(0).to(device) # GPEN前向推理 with torch.no_grad(): enhanced_tensor = run_gpen_enhancement.model(face_tensor) enhanced_np = enhanced_tensor.squeeze(0).permute(1, 2, 0).cpu().numpy() enhanced_np = np.clip(enhanced_np * 255.0, 0, 255).astype(np.uint8) enhanced_faces.append(enhanced_np) # 5. 合成最终图像(替换原图中的人脸区域) final_img = face_helper.get_inverse_affine(None) for idx, enhanced_face in enumerate(enhanced_faces): final_img = face_helper.paste_face_onto_input_image( enhanced_face, final_img, face_helper.affine_matrices[idx] ) # 6. 保存结果 output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) imwrite(final_img, str(output_path)) return True, "" except Exception as e: return False, f"GPEN处理失败: {str(e)}"这段代码做了三件关键事:
- 模型单例复用:避免每次请求都重新加载大模型,显著提升吞吐;
- 人脸流水线接管:用
FaceRestoreHelper替代原始脚本中的硬编码路径处理; - 错误捕获兜底:所有异常统一返回结构化错误信息,前端可直接展示。
注意:该函数不依赖任何命令行参数,完全通过Python变量控制行为,这才是API友好的设计。
2.3 FastAPI主服务:接收请求、返回结果
在main.py中编写服务入口:
# /root/gpen_api/main.py from fastapi import FastAPI, File, UploadFile, HTTPException, Form from fastapi.responses import FileResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware import os import uuid from pathlib import Path from api_handler import run_gpen_enhancement from utils import validate_image_format app = FastAPI( title="GPEN人像增强API", description="基于GPEN模型的轻量级人像修复增强HTTP服务", version="1.0.0" ) # 允许跨域(开发调试用,生产环境请按需配置) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 临时文件存储目录 TEMP_DIR = Path("/tmp/gpen_upload") TEMP_DIR.mkdir(exist_ok=True) @app.post("/enhance") async def enhance_portrait( file: UploadFile = File(..., description="待修复的人像图片(JPG/PNG)"), upscale: int = Form(2, ge=1, le=4, description="放大倍数(1/2/4)"), strength: float = Form(0.8, ge=0.1, le=1.0, description="增强强度(0.1~1.0)") ): """ 人像增强接口 - 支持JPG/PNG格式上传 - 返回增强后图片的下载链接 """ # 1. 格式校验 if not validate_image_format(file.filename): raise HTTPException(status_code=400, detail="仅支持JPG/PNG格式图片") # 2. 生成唯一文件名,防止冲突 file_id = str(uuid.uuid4()) input_path = TEMP_DIR / f"{file_id}_input{Path(file.filename).suffix}" output_path = TEMP_DIR / f"{file_id}_output.png" # 3. 保存上传文件 try: contents = await file.read() with open(input_path, "wb") as f: f.write(contents) except Exception as e: raise HTTPException(status_code=500, detail=f"文件保存失败: {e}") # 4. 使用PIL加载并传入GPEN处理 try: from PIL import Image input_image = Image.open(input_path).convert("RGB") except Exception as e: raise HTTPException(status_code=400, detail=f"图片格式损坏: {e}") # 5. 调用GPEN封装函数 success, error_msg = run_gpen_enhancement( input_image=input_image, output_path=str(output_path), upscale=upscale, # strength映射为n_inv_step和lambda参数(简化前端配置) n_inv_step=max(5, int(10 * strength)), # 强度越高,反演步数越多 lambda_1=0.1 + (0.4 * (1 - strength)), lambda_2=0.1 + (0.4 * (1 - strength)) ) if not success: # 清理临时文件 for p in [input_path, output_path]: if p.exists(): p.unlink(missing_ok=True) raise HTTPException(status_code=500, detail=error_msg) # 6. 返回结果(直接返回文件流,避免暴露路径) return FileResponse( path=output_path, media_type="image/png", filename=f"enhanced_{Path(file.filename).stem}.png" ) @app.get("/health") def health_check(): """健康检查端点""" return {"status": "ok", "model": "GPEN", "device": "cuda" if torch.cuda.is_available() else "cpu"}配套的utils.py仅做基础校验:
# /root/gpen_api/utils.py from pathlib import Path def validate_image_format(filename: str) -> bool: ext = Path(filename).suffix.lower() return ext in [".jpg", ".jpeg", ".png"]2.4 启动服务并测试
在镜像中执行以下命令启动服务:
# 安装FastAPI依赖(镜像已含uvicorn) pip install "fastapi[all]" python-multipart # 进入服务目录 cd /root/gpen_api # 启动(监听0.0.0.0确保容器外可访问) uvicorn main:app --host 0.0.0.0 --port 8000 --reload服务启动后,打开浏览器访问http://<你的IP>:8000/docs,即可看到自动生成的交互式API文档:
- 点击
/enhance→ “Try it out” → 选择一张人像照片上传; - 设置
upscale=2,点击Execute; - 成功时将直接下载增强后的PNG文件;
- 失败时返回清晰的JSON错误信息,如
{"detail": "GPEN处理失败: CUDA out of memory..."}。
3. 生产环境优化建议
上述代码已可在镜像中直接运行,但若要投入生产,还需关注三点:
3.1 显存与并发控制
GPEN单次推理约占用2.5GB显存(RTX 3090)。若并发请求过多,易触发OOM。建议:
- 限制并发数:在
uvicorn启动时添加--workers 2(根据GPU显存调整); - 添加队列限流:使用
slowapi库对/enhance接口添加速率限制,例如@limiter.limit("5/minute"); - 降级策略:当CUDA内存不足时,自动切换至CPU模式(修改
run_gpen_enhancement中的device参数)。
3.2 文件生命周期管理
临时文件/tmp/gpen_upload/不会自动清理,长期运行可能占满磁盘。推荐:
- 在
enhance_portrait函数末尾添加异步清理逻辑:import asyncio async def cleanup_file(path: Path): await asyncio.sleep(300) # 5分钟后清理 if path.exists(): path.unlink(missing_ok=True) asyncio.create_task(cleanup_file(input_path)) asyncio.create_task(cleanup_file(output_path)) - 或使用Linux定时任务
find /tmp/gpen_upload -mmin +60 -delete。
3.3 Docker化部署(可选)
虽然镜像本身已是容器,但为便于CI/CD和多环境一致,可额外构建一层轻量Dockerfile:
FROM your-gpen-mirror:latest WORKDIR /root/gpen_api COPY . . CMD ["uvicorn", "main:app", "--host", "0.0.0.0:8000", "--workers", "2"]构建后通过docker run -p 8000:8000 your-gpen-api即可启动,与宿主机完全隔离。
4. 实际调用示例(前端/移动端)
服务上线后,任何支持HTTP的客户端都能调用。以下是curl和JavaScript两种最常用方式:
4.1 curl命令行调用
curl -X POST "http://localhost:8000/enhance" \ -F "file=@./my_photo.jpg" \ -F "upscale=2" \ -F "strength=0.9" \ -o enhanced.png4.2 JavaScript前端调用(Vue/React通用)
async function enhancePhoto(file) { const formData = new FormData(); formData.append("file", file); formData.append("upscale", 2); formData.append("strength", 0.9); try { const response = await fetch("http://your-server-ip:8000/enhance", { method: "POST", body: formData }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || "增强失败"); } // 直接下载结果 const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "enhanced_" + file.name; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); } catch (err) { console.error("调用失败:", err.message); alert("人像增强失败:" + err.message); } }这段代码可直接嵌入网页,用户点击上传按钮后,几秒内即可下载增强结果,体验接近本地软件。
5. 总结
把GPEN封装成API,核心不在“能不能”,而在“怎么封装得稳、用得顺、扩得开”。本文给出的方案:
- 零侵入:完全复用镜像内已有模型、权重和环境,不修改一行GPEN源码;
- 真可用:支持文件上传、参数调节、错误反馈、健康检查,不是玩具Demo;
- 易维护:结构清晰(handler/logic/utils分离),日志、监控、限流均可按需接入;
- 可落地:已验证在CSDN星图镜像上一键运行,无需额外编译或配置。
下一步,你可以基于此服务快速搭建:
- 企业内部人像证件照批量处理平台;
- 社交App的“老照片修复”功能模块;
- 电商后台的商品模特图自动精修流水线。
技术的价值,从来不在模型多深,而在它能否安静地站在业务背后,把复杂留给自己,把简单留给用户。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。