YOLO 目标检测 API 调用示例代码发布(Python/Java)

0 阅读11分钟

在日常的计算机视觉开发工作中,经常遇到业务侧同学的诉求:“只想快速用 YOLO 做目标检测,不想本地搭环境、配依赖、加载模型,有没有更轻量化的方式?”。确实,对于非算法岗位的开发人员来说,本地部署 YOLO 的环境成本、模型加载成本过高,而 API 调用是将目标检测能力快速集成到业务系统的最优解 —— 只需关注请求 / 响应逻辑,无需关心底层的模型推理细节。

本文基于 YOLOv8(社区生态最完善、易封装 API 的版本),先搭建轻量且高性能的 YOLO 检测 API 服务,再提供可直接运行的 Python/Java 调用示例,覆盖单张图片检测、批量检测、异常处理等核心场景。所有代码均经过生产环境简化验证,无冗余逻辑,可直接复制复用。

一、前置准备:搭建 YOLOv8 API 服务(FastAPI 版)

要实现 API 调用,首先需要一个可提供检测能力的服务端。这里选择 FastAPI(轻量、高性能、自带接口文档)作为服务框架,是新手和生产环境的双重优选。

1.1 服务端环境安装

bash

运行

# 安装FastAPI和服务运行依赖
pip install fastapi uvicorn
# 安装YOLOv8核心库(封装了完整的检测逻辑)
pip install ultralytics
# 安装图片处理依赖
pip install pillow python-multipart

1.2 编写 API 服务代码(yolo_api_server.py)

python

运行

from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
from ultralytics import YOLO
from PIL import Image
import io
import os

# 初始化FastAPI应用
app = FastAPI(title="YOLOv8目标检测API", version="1.0")

# 加载YOLOv8预训练模型(按需选择:n=轻量 s=平衡 m=高精度,新手优先选s)
# 模型会自动下载,若网络问题可手动从https://github.com/ultralytics/assets下载
model = YOLO("yolov8s.pt")

# 单张图片检测接口(核心接口)
@app.post("/detect/single", summary="单张图片目标检测")
async def detect_single(file: UploadFile = File(...)):
    try:
        # 1. 校验文件类型(避免非图片文件上传)
        allowed_ext = {"jpg", "jpeg", "png", "bmp"}
        file_ext = file.filename.split(".")[-1].lower()
        if file_ext not in allowed_ext:
            raise HTTPException(status_code=400, detail="仅支持jpg/jpeg/png/bmp格式图片")
        
        # 2. 读取并解析图片
        img_content = await file.read()
        img = Image.open(io.BytesIO(img_content)).convert("RGB")  # 统一转为RGB格式
        
        # 3. 执行目标检测
        results = model(img)
        
        # 4. 解析检测结果(适配业务侧易读格式)
        detect_res = []
        for r in results:
            boxes = r.boxes  # 检测框信息
            for box in boxes:
                # 提取目标框坐标、置信度、类别
                x1, y1, x2, y2 = [round(v, 2) for v in box.xyxy[0].tolist()]
                conf = round(box.conf[0].item(), 4)  # 置信度保留4位小数
                cls_id = int(box.cls[0].item())
                cls_name = model.names[cls_id]  # 类别名称(如person、car)
                
                detect_res.append({
                    "class_name": cls_name,
                    "confidence": conf,
                    "bbox": [x1, y1, x2, y2]  # 目标框左上角/右下角坐标
                })
        
        # 5. 返回标准化响应
        return JSONResponse(content={
            "code": 200,
            "msg": "检测成功",
            "data": detect_res
        })
    
    except HTTPException as e:
        # 已知异常(如文件类型错误)
        raise e
    except Exception as e:
        # 未知异常(如图片损坏、模型推理失败)
        raise HTTPException(status_code=500, detail=f"检测失败:{str(e)}")

# 批量图片检测接口(扩展接口)
@app.post("/detect/batch", summary="批量图片目标检测")
async def detect_batch(files: list[UploadFile] = File(...)):
    try:
        batch_res = []
        for file in files:
            # 单文件处理逻辑(复用单张检测的校验/解析逻辑)
            file_res = {"filename": file.filename}
            try:
                allowed_ext = {"jpg", "jpeg", "png", "bmp"}
                file_ext = file.filename.split(".")[-1].lower()
                if file_ext not in allowed_ext:
                    file_res.update({"code": 400, "msg": "文件格式不支持", "data": None})
                    batch_res.append(file_res)
                    continue
                
                img_content = await file.read()
                img = Image.open(io.BytesIO(img_content)).convert("RGB")
                results = model(img)
                
                detect_res = []
                for r in results:
                    boxes = r.boxes
                    for box in boxes:
                        x1, y1, x2, y2 = [round(v, 2) for v in box.xyxy[0].tolist()]
                        conf = round(box.conf[0].item(), 4)
                        cls_id = int(box.cls[0].item())
                        cls_name = model.names[cls_id]
                        detect_res.append({
                            "class_name": cls_name,
                            "confidence": conf,
                            "bbox": [x1, y1, x2, y2]
                        })
                
                file_res.update({"code": 200, "msg": "检测成功", "data": detect_res})
            except Exception as e:
                file_res.update({"code": 500, "msg": f"检测失败:{str(e)}", "data": None})
            finally:
                batch_res.append(file_res)
        
        return JSONResponse(content={
            "code": 200,
            "msg": "批量检测完成",
            "data": batch_res
        })
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"批量检测失败:{str(e)}")

# 启动服务(本地测试用)
if __name__ == "__main__":
    import uvicorn
    # host=0.0.0.0 允许局域网访问,port=8000 可自定义(避免端口冲突)
    uvicorn.run(app, host="0.0.0.0", port=8000)

1.3 启动 API 服务

bash

运行

python yolo_api_server.py

启动成功后,访问 http://localhost:8000/docs 可查看自动生成的 Swagger 接口文档,支持在线上传图片调试,新手友好度拉满。

二、Python 版 API 调用示例

Python 调用选择最主流的requests库,覆盖单张 / 批量检测场景,包含完整的异常处理和超时控制,可直接嵌入业务代码。

2.1 安装调用依赖

bash

运行

pip install requests

2.2 Python 调用代码(yolo_api_client.py)

python

运行

import requests
import os
from typing import List, Dict

# API基础配置(根据实际部署地址修改)
API_BASE_URL = "http://localhost:8000"
TIMEOUT = 30  # 请求超时时间(秒),避免长时间阻塞

def detect_single_image(image_path: str) -> Dict:
    """
    调用单张图片检测接口
    :param image_path: 本地图片绝对/相对路径
    :return: 标准化检测结果(字典格式)
    """
    # 前置校验:文件是否存在
    if not os.path.exists(image_path):
        return {"code": 404, "msg": f"图片文件不存在:{image_path}", "data": None}
    
    # 构造请求
    url = f"{API_BASE_URL}/detect/single"
    try:
        # 以二进制方式读取图片
        with open(image_path, "rb") as f:
            files = {"file": (os.path.basename(image_path), f)}
            # 发送POST请求
            response = requests.post(url, files=files, timeout=TIMEOUT)
        
        # 解析响应
        if response.status_code == 200:
            return response.json()
        else:
            return {"code": response.status_code, "msg": f"接口返回错误:{response.text}", "data": None}
    
    # 异常捕获(覆盖90%的调用问题)
    except requests.exceptions.Timeout:
        return {"code": 408, "msg": "请求超时(服务端响应过慢或网络不稳定)", "data": None}
    except requests.exceptions.ConnectionError:
        return {"code": 503, "msg": "连接失败(检查API服务是否启动、端口是否正确)", "data": None}
    except Exception as e:
        return {"code": 500, "msg": f"调用异常:{str(e)}", "data": None}

def detect_batch_images(image_paths: List[str]) -> Dict:
    """
    调用批量图片检测接口
    :param image_paths: 图片路径列表
    :return: 批量检测结果(字典格式)
    """
    # 过滤无效文件,避免批量请求整体失败
    valid_files = []
    for path in image_paths:
        if os.path.exists(path):
            # 构造批量上传的文件元组(key固定为files,与服务端对应)
            valid_files.append(("files", (os.path.basename(path), open(path, "rb"))))
        else:
            print(f"警告:跳过不存在的文件:{path}")
    
    if not valid_files:
        return {"code": 400, "msg": "无有效图片文件", "data": None}
    
    # 构造请求
    url = f"{API_BASE_URL}/detect/batch"
    try:
        response = requests.post(url, files=valid_files, timeout=TIMEOUT)
        
        if response.status_code == 200:
            return response.json()
        else:
            return {"code": response.status_code, "msg": f"接口返回错误:{response.text}", "data": None}
    
    except requests.exceptions.Timeout:
        return {"code": 408, "msg": "批量请求超时", "data": None}
    except requests.exceptions.ConnectionError:
        return {"code": 503, "msg": "连接失败(检查API服务状态)", "data": None}
    except Exception as e:
        return {"code": 500, "msg": f"批量调用异常:{str(e)}", "data": None}
    finally:
        # 关键:关闭所有打开的文件句柄,避免资源泄露
        for _, (_, f) in valid_files:
            f.close()

# 测试示例(替换为自己的图片路径)
if __name__ == "__main__":
    # 1. 单张图片检测测试
    single_img_path = "test_car.jpg"  # 本地图片路径
    single_result = detect_single_image(single_img_path)
    print("=== 单张图片检测结果 ===")
    print(single_result)
    
    # 2. 批量图片检测测试
    batch_img_paths = ["test_person.jpg", "test_dog.png", "invalid_img.jpg"]  # 包含无效文件示例
    batch_result = detect_batch_images(batch_img_paths)
    print("\n=== 批量图片检测结果 ===")
    print(batch_result)

三、Java 版 API 调用示例

Java 调用选择工业级的OkHttp客户端(性能优、稳定性强),搭配 FastJSON2 解析响应,适配 Java 后端开发场景。

3.1 Maven 依赖配置(pom.xml)

xml

<dependencies>
    <!-- OkHttp核心依赖(HTTP客户端) -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>
    <!-- FastJSON2(JSON解析,轻量高效) -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.45</version>
    </dependency>
    <!-- 日志依赖(可选,方便调试) -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.9</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.9</version>
    </dependency>
</dependencies>

3.2 Java 调用代码(YoloApiClient.java)

java

运行

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * YOLOv8目标检测API Java客户端
 * 适配生产环境调用规范:单例客户端、超时控制、异常分类处理
 */
public class YoloApiClient {
    // API基础配置(根据实际部署修改)
    private static final String API_BASE_URL = "http://localhost:8000";
    private static final int TIMEOUT_SECONDS = 30;
    // 单例OkHttpClient(避免重复创建连接池,提升性能)
    private static final OkHttpClient OK_HTTP_CLIENT;

    // 静态初始化客户端(配置超时、连接池)
    static {
        OK_HTTP_CLIENT = new OkHttpClient.Builder()
                .connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)  // 连接超时
                .readTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)     // 读取超时
                .writeTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)    // 写入超时
                .retryOnConnectionFailure(true)                     // 连接失败自动重试
                .build();
    }

    /**
     * 调用单张图片检测接口
     * @param imagePath 本地图片绝对路径
     * @return 标准化检测结果(JSON字符串)
     */
    public static String detectSingleImage(String imagePath) {
        // 1. 校验文件是否存在
        File imageFile = new File(imagePath);
        if (!imageFile.exists()) {
            JSONObject errorObj = new JSONObject();
            errorObj.put("code", 404);
            errorObj.put("msg", "图片文件不存在:" + imagePath);
            errorObj.put("data", null);
            return errorObj.toJSONString();
        }

        // 2. 校验文件格式(避免无效请求)
        String fileName = imageFile.getName();
        if (!fileName.contains(".")) {
            JSONObject errorObj = new JSONObject();
            errorObj.put("code", 400);
            errorObj.put("msg", "文件无后缀,无法识别格式");
            errorObj.put("data", null);
            return errorObj.toJSONString();
        }
        String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        if (!"jpg".equals(fileExt) && !"jpeg".equals(fileExt) && !"png".equals(fileExt) && !"bmp".equals(fileExt)) {
            JSONObject errorObj = new JSONObject();
            errorObj.put("code", 400);
            errorObj.put("msg", "仅支持jpg/jpeg/png/bmp格式图片");
            errorObj.put("data", null);
            return errorObj.toJSONString();
        }

        // 3. 构造multipart/form-data请求体(与服务端接口参数对应)
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart(
                        "file",  // 参数名,必须与服务端的UploadFile参数名一致
                        fileName,
                        RequestBody.create(MediaType.parse("image/" + fileExt), imageFile)
                )
                .build();

        // 4. 构造请求
        Request request = new Request.Builder()
                .url(API_BASE_URL + "/detect/single")
                .post(requestBody)
                .build();

        // 5. 发送请求并处理响应
        try (Response response = OK_HTTP_CLIENT.newCall(request).execute()) {
            // 处理非200响应
            if (!response.isSuccessful()) {
                JSONObject errorObj = new JSONObject();
                errorObj.put("code", response.code());
                errorObj.put("msg", "接口调用失败:" + response.message());
                errorObj.put("data", null);
                return errorObj.toJSONString();
            }
            // 解析成功响应
            String responseBody = response.body().string();
            return responseBody;
        } catch (IOException e) {
            // 分类处理异常,方便业务侧排查
            JSONObject errorObj = new JSONObject();
            if (e.getMessage().contains("timeout")) {
                errorObj.put("code", 408);
                errorObj.put("msg", "请求超时(服务端未及时响应)");
            } else if (e.getMessage().contains("Connection refused")) {
                errorObj.put("code", 503);
                errorObj.put("msg", "连接被拒绝(检查API服务是否启动)");
            } else {
                errorObj.put("code", 500);
                errorObj.put("msg", "调用异常:" + e.getMessage());
            }
            errorObj.put("data", null);
            return errorObj.toJSONString();
        }
    }

    // 测试主方法(替换为实际图片路径)
    public static void main(String[] args) {
        // 本地图片绝对路径(避免相对路径歧义)
        String imagePath = "D:/test_images/test_person.jpg";
        String detectResult = detectSingleImage(imagePath);
        
        // 格式化输出结果,便于阅读
        JSONObject resultObj = JSON.parseObject(detectResult);
        System.out.println("=== Java调用YOLO API检测结果 ===");
        System.out.println(JSON.toJSONString(resultObj, true));
    }
}

四、调用过程中的高频踩坑点 & 解决方案

日常开发中,80% 的 API 调用问题集中在以下场景,提前规避可大幅提升开发效率:

踩坑现象核心原因解决方案
400 错误:“仅支持 jpg/jpeg/png/bmp 格式图片”图片后缀大小写(如 JPG 写成 jpg)、文件实际格式与后缀不符1. 统一将后缀转为小写;2. 校验文件魔数(而非仅靠后缀),确保格式真实
500 错误:“cannot identify image file”图片文件损坏、上传时流读取不完整1. 校验图片完整性(可通过 PIL/ImageIO 预校验);2. 上传时确保文件流完整关闭
连接拒绝 / 超时API 服务未启动、端口被占用、防火墙拦截1. 确认服务已启动(查看 uvicorn 日志);2. 检查端口占用(Windows:netstat -anofindstr 8000);3. 放行 8000 端口
Java 调用报 “no such file or directory”相对路径歧义(工作目录与预期不一致)强制使用绝对路径(如 D:/test.jpg),避免相对路径
响应结果为空图片尺寸过大(超过 10MB)、服务端处理超时1. 服务端增加图片大小限制配置;2. 调用端压缩图片后上传;3. 调大超时时间

五、生产环境进阶优化建议

若需将该 API 用于生产环境,需补充以下能力,保障稳定性和安全性:

  1. 接口鉴权:添加 API Key/Token 校验(如在请求头中携带 X-API-Key),避免接口被恶意调用;
  2. 限流控制:服务端集成 SlowAPI 实现接口限流(如单 IP 每分钟最多调用 100 次),防止高频请求压垮服务;
  3. 异步处理:批量检测接口改为异步模式(返回任务 ID,轮询获取结果),避免长连接超时;
  4. 部署优化:将 API 服务打包为 Docker 镜像,方便跨环境部署;使用 Gunicorn+Uvicorn 多进程运行,提升并发能力;
  5. 监控告警:添加接口调用量、失败率、响应时间监控,异常时触发告警(如钉钉 / 企业微信通知)。

六、总结

  1. YOLO API 调用的核心是 “服务端封装模型 + 客户端标准化请求”,Python 用 FastAPI 搭服务、requests 调接口,Java 用 OkHttp 调接口,都是低成本、易落地的方案;
  2. 调用时重点关注文件校验、超时控制、异常分类处理,可避开 90% 的调用问题;
  3. 测试阶段优先使用 FastAPI 的/docs在线调试接口,快速定位请求参数、格式问题;
  4. 生产环境需补充鉴权、限流、监控能力,避免接口被滥用或故障无感知。

这套示例代码可直接复用,无论是 Python 还是 Java 开发者,都能在 10 分钟内完成 YOLO 目标检测能力的集成,无需关注底层的环境搭建和模型推理细节,聚焦业务逻辑即可。