在 AI 目标检测领域,YOLO(You Only Look Once)系列模型以其高效、精准的特点成为主流选择,而 Spring Boot 作为 Java 生态最流行的 Web 开发框架,能轻松将 YOLO 的检测能力封装为可复用的接口。本文将全程还原 Windows 环境下,通过 Docker 部署 YOLOv8 模型,并集成到 Spring Boot 项目的完整流程,包含环境准备、问题排查、代码集成、接口测试,适合有一定 Java 和 Docker 基础的开发者参考。
一、前言:为什么选择 Docker + Spring Boot + YOLOv8?
在本地部署 YOLO 模型时,常常会遇到环境依赖复杂、版本冲突、跨平台兼容性差等问题,而 Docker 能完美解决这些痛点------通过容器化封装 YOLO 运行所需的所有依赖,实现"一次构建,到处运行"。
结合 Spring Boot 的优势,我们可以将 YOLO 的图片检测能力封装为 HTTP 接口,支持前端上传图片、后端返回检测结果,轻松集成到小程序、APP、管理系统等各类业务场景中,实现"检测能力接口化、业务集成轻量化"。
本文核心目标:在 Windows 环境下,完成 Docker 部署 YOLOv8 → 解决部署过程中的常见问题 → 将 YOLO 检测能力集成到 Spring Boot 项目 → 提供可直接调用的图片检测接口。
二、环境准备(必看)
2.1 基础环境清单
- 操作系统:Windows 10/11(建议开启 WSL2,提升 Docker 性能)
- Docker:Docker Desktop(版本 20.0 以上)
- JDK:17(Spring Boot 3.2.x 要求)
- Maven:3.6+(项目构建)
- Spring Boot:3.2.4
- YOLO 模型:YOLOv8n(轻量版,适合 CPU 运行,无需 GPU 加速)
2.2 前置环境配置
2.2.1 Docker Desktop 配置(关键)
-
安装 Docker Desktop 后,开启 WSL2 后端(性能优于 Hyper-V):
- 打开 Docker Desktop → Settings → General → 勾选 "Use the WSL 2 based engine"
- 重启 Docker Desktop,确保服务正常启动(右下角 Docker 图标显示绿色)
-
配置文件共享(避免挂载目录权限问题):
- 打开 Docker Desktop → Settings → Resources → File Sharing
- 点击 "+" 按钮,添加本地目录
D:\yolo_test(用于挂载 Docker 容器,存储图片和检测结果),保存后重启 Docker
2.2.2 项目基础准备
本文基于已有的 Spring Boot 项目(原有功能包含 Ollama 大模型集成、PGvector 向量存储、阿里云语音识别等),核心是在原有项目基础上新增 YOLO 图片检测模块,无需从零搭建 Spring Boot 项目。
三、Docker 部署 YOLOv8 模型(全程实操+问题排查)
这一步是核心基础,我们将通过 Docker 拉取 YOLOv8 官方镜像,运行容器并完成图片检测,解决部署过程中最常见的"文件找不到""挂载失败"等问题。
3.1 拉取 YOLOv8 官方 Docker 镜像
打开 PowerShell(管理员模式),执行以下命令拉取官方镜像(内置 YOLOv8 所有依赖,无需手动配置):
bash
docker pull ultralytics/ultralytics:latest
镜像拉取完成后,执行 docker images 可查看镜像是否存在。
3.2 运行 Docker 容器并挂载本地目录
核心命令 (挂载本地 D:\yolo_test 到容器内 /yolo_data,实现本地与容器文件互通):
bash
docker run -it --rm -v /d/yolo_test:/yolo_data ultralytics/ultralytics:latest
命令说明:
-it:交互式终端,方便在容器内执行命令--rm:容器退出后自动删除,避免残留-v /d/yolo_test:/yolo_data:目录挂载(Windows 路径需用/d/替代D:\,Docker 才能识别)
3.3 执行 YOLOv8 图片检测(首次实操)
3.3.1 准备测试图片
在本地 D:\yolo_test 目录下,放入一张测试图片,命名为 test.jpg(注意 :文件名和后缀必须完全匹配,避免大小写错误,如 Test.jpg、test.jpeg 都会导致失败)。
3.3.2 执行检测命令
在容器内(命令行显示 root@xxx:/ultralytics#),执行以下命令:
bash
yolo detect predict model=yolov8n.pt source=/yolo_data/test.jpg save=True
命令说明:
model=yolov8n.pt:使用 YOLOv8 轻量版模型(n=纳米版,适合 CPU 运行,体积小、速度快)source=/yolo_data/test.jpg:检测源为容器内挂载目录下的 test.jpgsave=True:保存检测结果(带检测框的图片)
3.4 常见问题排查(重点!)
首次运行大概率会遇到以下问题,本文全程还原排查过程,帮你快速解决。
问题1:FileNotFoundError: /yolo_data/test.jpg does not exist
报错原因:容器内找不到指定图片,核心是两个原因之一:
- 本地
D:\yolo_test目录下没有 test.jpg,或文件名/后缀错误 - 目录挂载失败,容器内
/yolo_data目录为空
解决步骤:
-
先在容器内执行
ls /yolo_data,查看挂载目录是否有文件:- 如果输出为空:说明挂载失败,重新执行容器运行命令(确保路径是
/d/yolo_test,而非D:\yolo_test) - 如果输出
yolo_data(子目录):说明图片放在了D:\yolo_test\yolo_data下,需修改命令为source=/yolo_data/yolo_data/test.jpg
- 如果输出为空:说明挂载失败,重新执行容器运行命令(确保路径是
-
确认本地
D:\yolo_test下有 test.jpg,重新执行检测命令
问题2:检测结果保存到容器内,本地看不到
报错现象 :检测成功,但本地 D:\yolo_test 目录下没有检测结果
原因 :YOLO 默认将结果保存到容器内 /ultralytics/runs/detect/predict,而非挂载目录
解决方法:
-
手动复制结果到挂载目录(容器内执行):
bashcp -r /ultralytics/runs/detect/predict /yolo_data/ -
下次检测直接指定保存路径(推荐),修改命令为:
bashyolo detect predict model=yolov8n.pt source=/yolo_data/test.jpg save=True project=/yolo_data/runs name=detect这样结果会直接保存到
/yolo_data/runs/detect,对应本地D:\yolo_test\runs\detect
3.5 检测成功验证
检测成功后,容器内会输出类似以下日志:
image 1/1 /yolo_data/test.jpg: 640x480 1 cup, 1 tv, 1 mouse, 1 refrigerator, 1 book, 63.4ms
Speed: 4.6ms preprocess, 63.4ms inference, 8.6ms postprocess per image at shape (1, 3, 640, 480)
Results saved to /yolo_data/runs/detect
此时打开本地 D:\yolo_test\runs\detect 目录,会看到带检测框的 test.jpg,图片上会标注出识别到的物体(如杯子、电视、鼠标等),说明 Docker 部署 YOLOv8 成功!
四、Spring Boot 集成 YOLOv8 检测能力(完整代码)
Docker 部署 YOLOv8 成功后,我们将其集成到 Spring Boot 项目,提供 HTTP 接口,支持前端上传图片、后端返回检测结果(识别的物体、数量、耗时、结果图片路径)。
4.1 项目改造:pom.xml 依赖新增
在原有 pom.xml 基础上,新增文件操作、命令执行相关依赖(用于处理图片上传、调用 Docker 命令):
xml
<!-- 新增依赖版本定义 -->
<properties>
<commons-io.version>2.15.1</commons-io.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
</properties>
<!-- 新增依赖 -->
<dependencies>
<!-- 文件操作工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!-- 字符串/日期工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- 命令执行相关 -->
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>262</version>
</dependency>
</dependencies>
注:原有依赖(Spring Boot Web、Ollama、PGvector、阿里云语音等)全部保留,无需修改。
4.2 项目改造:application.yml 配置新增
在原有配置基础上,新增 YOLO 和 Docker 相关配置,同时补充文件上传限制:
yaml
# 新增 YOLO 配置
yolo:
# Docker 本地挂载目录(对应本地 D:\yolo_test)
docker-mount-path: D:\yolo_test
# 容器内挂载路径
container-mount-path: /yolo_data
# YOLO 模型名称(默认 yolov8n.pt)
model-name: yolov8n.pt
# 检测结果保存目录(容器内)
container-result-path: /yolo_data/runs/detect
# 本地临时文件存储目录
local-temp-path: D:\yolo_test\temp
# Docker 镜像名称
docker-image: ultralytics/ultralytics:latest
# 原有配置保留,补充文件上传配置
spring:
servlet:
multipart:
max-file-size: 10MB # 单文件最大10MB
max-request-size: 50MB # 总请求最大50MB
4.3 新增核心代码(可直接复制使用)
新增 4 个核心类,实现图片上传、Docker 命令调用、检测结果解析、接口提供,全部放在 com.ruoyi 包下(与原有项目包结构一致)。
4.3.1 配置类:YoloConfig.java(读取 YOLO 配置)
java
package com.ruoyi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* YOLO 配置类,读取 application.yml 中的 yolo 相关配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "yolo")
public class YoloConfig {
/**
* Docker 本地挂载目录
*/
private String dockerMountPath;
/**
* 容器内挂载路径
*/
private String containerMountPath;
/**
* YOLO 模型名称
*/
private String modelName;
/**
* 容器内检测结果保存路径
*/
private String containerResultPath;
/**
* 本地临时文件存储目录
*/
private String localTempPath;
/**
* Docker 镜像名称
*/
private String dockerImage;
}
4.3.2 实体类:YoloDetectResult.java(封装检测结果)
java
package com.ruoyi.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* YOLO 检测结果返回实体,用于接口返回 JSON 格式数据
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class YoloDetectResult {
/**
* 检测是否成功
*/
private boolean success;
/**
* 错误信息(失败时返回)
*/
private String errorMsg;
/**
* 检测到的目标列表
*/
private List<DetectObject> detectObjects;
/**
* 检测耗时(毫秒)
*/
private long costTime;
/**
* 检测结果图片路径(本地)
*/
private String resultImagePath;
/**
* 检测目标实体(单个物体的信息)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class DetectObject {
/**
* 目标类别(如 cup、tv、mouse)
*/
private String className;
/**
* 目标数量
*/
private int count;
/**
* 置信度(0-1),越大越精准
*/
private float confidence;
}
}
4.3.3 工具类:YoloDetectUtil.java(核心检测逻辑)
java
package com.ruoyi.util;
import com.ruoyi.config.YoloConfig;
import com.ruoyi.entity.YoloDetectResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* YOLO 图片检测工具类,封装 Docker 命令调用、结果解析逻辑
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class YoloDetectUtil {
private final YoloConfig yoloConfig;
// 匹配检测结果的正则表达式(如:1 cup, 1 tv, 1 mouse)
private static final Pattern DETECT_PATTERN = Pattern.compile("(\\d+)\\s+([a-zA-Z_]+)");
/**
* 上传图片并执行 YOLO 检测
* @param file 上传的图片文件
* @return 检测结果(包含识别的物体、耗时、结果路径)
*/
public YoloDetectResult detectImage(MultipartFile file) {
long startTime = System.currentTimeMillis();
try {
// 1. 校验文件是否为空
if (file.isEmpty()) {
return YoloDetectResult.builder()
.success(false)
.errorMsg("上传的图片文件为空")
.build();
}
// 2. 校验图片格式(仅支持 jpg/jpeg/png/bmp)
String originalFilename = file.getOriginalFilename();
if (originalFilename == null || !originalFilename.matches(".*\\.(jpg|jpeg|png|bmp)$")) {
return YoloDetectResult.builder()
.success(false)
.errorMsg("仅支持 jpg/jpeg/png/bmp 格式的图片")
.build();
}
// 3. 创建临时目录(用于存储上传的图片)
File tempDir = new File(yoloConfig.getLocalTempPath());
if (!tempDir.exists()) {
boolean mkdirs = tempDir.mkdirs();
if (!mkdirs) {
log.error("创建临时目录失败:{}", yoloConfig.getLocalTempPath());
return YoloDetectResult.builder()
.success(false)
.errorMsg("创建临时目录失败")
.build();
}
}
// 4. 保存上传的图片到本地挂载目录(Docker 可访问)
String fileName = UUID.randomUUID() + "_" + originalFilename;
String localImagePath = yoloConfig.getDockerMountPath() + File.separator + fileName;
File localImageFile = new File(localImagePath);
file.transferTo(localImageFile);
log.info("上传的图片已保存到:{}", localImagePath);
// 5. 构建 Docker 命令(调用 YOLO 检测)
String containerImagePath = yoloConfig.getContainerMountPath() + "/" + fileName;
String dockerCmd = String.format(
"docker run --rm -v %s:%s %s yolo detect predict model=%s source=%s save=True project=%s name=detect",
yoloConfig.getDockerMountPath().replace("\\", "/"), // Windows 路径转换为 Docker 可识别格式
yoloConfig.getContainerMountPath(),
yoloConfig.getDockerImage(),
yoloConfig.getModelName(),
containerImagePath,
yoloConfig.getContainerResultPath()
);
log.info("执行 Docker 命令:{}", dockerCmd);
// 6. 执行 Docker 命令,获取输出结果
Process process = Runtime.getRuntime().exec(new String[]{"cmd", "/c", dockerCmd});
int exitCode = process.waitFor();
if (exitCode != 0) {
// 读取错误信息,返回失败结果
String errorMsg = new String(process.getErrorStream().readAllBytes());
log.error("执行 YOLO 检测失败,错误码:{},错误信息:{}", exitCode, errorMsg);
return YoloDetectResult.builder()
.success(false)
.errorMsg("执行检测失败:" + errorMsg)
.build();
}
// 7. 解析命令输出,提取检测到的物体信息
String output = new String(process.getInputStream().readAllBytes());
log.info("YOLO 检测命令输出:{}", output);
List<YoloDetectResult.DetectObject> detectObjects = parseDetectResult(output);
// 8. 拼接检测结果图片路径(本地可访问)
String resultImagePath = yoloConfig.getDockerMountPath() + File.separator + "runs" + File.separator + "detect" + File.separator + "detect" + File.separator + fileName;
long costTime = System.currentTimeMillis() - startTime;
// 9. 返回成功结果
return YoloDetectResult.builder()
.success(true)
.detectObjects(detectObjects)
.costTime(costTime)
.resultImagePath(resultImagePath)
.build();
} catch (IOException e) {
log.error("图片检测 IO 异常", e);
return YoloDetectResult.builder()
.success(false)
.errorMsg("IO 异常:" + e.getMessage())
.build();
} catch (InterruptedException e) {
log.error("检测命令执行被中断", e);
Thread.currentThread().interrupt();
return YoloDetectResult.builder()
.success(false)
.errorMsg("检测被中断:" + e.getMessage())
.build();
} catch (Exception e) {
log.error("图片检测异常", e);
return YoloDetectResult.builder()
.success(false)
.errorMsg("检测异常:" + e.getMessage())
.build();
}
}
/**
* 解析 YOLO 检测命令输出,提取目标信息(类别、数量)
* @param output 命令输出字符串
* @return 检测目标列表
*/
private List<YoloDetectResult.DetectObject> parseDetectResult(String output) {
List<YoloDetectResult.DetectObject> result = new ArrayList<>();
if (output == null || output.isEmpty()) {
return result;
}
// 匹配 "1 cup, 1 tv, 1 mouse" 格式的字符串
Matcher matcher = DETECT_PATTERN.matcher(output);
Map<String, Integer> countMap = new HashMap<>();
while (matcher.find()) {
int count = Integer.parseInt(matcher.group(1));
String className = matcher.group(2);
countMap.put(className, countMap.getOrDefault(className, 0) + count);
}
// 转换为 DetectObject 列表(置信度默认 0.9,可根据实际输出优化)
for (Map.Entry<String, Integer> entry : countMap.entrySet()) {
result.add(YoloDetectResult.DetectObject.builder()
.className(entry.getKey())
.count(entry.getValue())
.confidence(0.9f)
.build());
}
return result;
}
}
4.3.4 控制器:YoloController.java(提供 HTTP 接口)
java
package com.ruoyi.controller;
import com.ruoyi.entity.YoloDetectResult;
import com.ruoyi.util.YoloDetectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* YOLO 图片检测控制器,提供 HTTP 接口供前端调用
*/
@Slf4j
@RestController
@RequestMapping("/api/yolo")
@RequiredArgsConstructor
public class YoloController {
private final YoloDetectUtil yoloDetectUtil;
/**
* 图片目标检测接口
* @param file 上传的图片文件
* @return 检测结果(JSON 格式)
*/
@PostMapping("/detect")
public ResponseEntity<YoloDetectResult> detectImage(@RequestParam("file") MultipartFile file) {
try {
YoloDetectResult result = yoloDetectUtil.detectImage(file);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("图片检测接口异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(YoloDetectResult.builder()
.success(false)
.errorMsg("接口异常:" + e.getMessage())
.build());
}
}
}
4.4 启动类验证(确保组件扫描)
确保 Spring Boot 启动类扫描到新增的配置类、控制器等组件,修改启动类(如已有则无需新增):
java
package com.ruoyi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@Slf4j
@SpringBootApplication(scanBasePackages = "com.ruoyi") // 扫描 com.ruoyi 下所有组件
@EnableConfigurationProperties
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
log.info("Spring Boot 应用启动成功,端口:8889");
}
}
五、接口测试(验证集成效果)
项目启动成功后,我们通过 Postman 测试图片检测接口,验证集成效果。
5.1 测试准备
- 启动 Docker Desktop(确保 Docker 服务正常运行)
- 启动 Spring Boot 应用(端口 8889)
- 准备一张测试图片(jpg/png 格式)
5.2 Postman 测试步骤
- 新建 POST 请求,地址:
http://localhost:8889/api/yolo/detect - 请求类型选择
form-data,key 填写file,value 选择"文件",上传测试图片 - 点击发送请求,查看返回结果
5.3 成功返回示例
json
{
"success": true,
"errorMsg": null,
"detectObjects": [
{
"className": "cup",
"count": 1,
"confidence": 0.9
},
{
"className": "tv",
"count": 1,
"confidence": 0.9
},
{
"className": "mouse",
"count": 1,
"confidence": 0.9
}
],
"costTime": 1200,
"resultImagePath": "D:\\yolo_test\\runs\\detect\\detect\\xxx_test.jpg"
}
返回结果说明:
success: true:检测成功detectObjects:识别到的物体列表(类别、数量、置信度)costTime:检测耗时(毫秒)resultImagePath:本地带检测框的图片路径,可直接打开查看结果
六、常见问题与优化建议
6.1 常见问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 接口调用失败,提示"Docker 命令执行失败" | Docker 未启动 | 检查 Docker Desktop 是否正常运行 |
| 接口调用失败,提示"执行检测失败" | 挂载目录权限问题 | 检查 Docker Desktop 文件共享配置是否正确 |
| 检测速度慢 | 模型较大或 CPU 性能不足 | 使用更轻量的 YOLOv8n 模型,或考虑开启 GPU 加速 |
| 结果图片无法打开 | 结果图片路径不正确 | 检查 resultImagePath 是否正确,确认 Docker 检测结果已保存到挂载目录 |
| 图片上传失败,提示文件过大 | 文件大小超出限制 | 调整 application.yml 中的 max-file-size 配置 |
6.2 优化建议
1. 优化检测速度
- 开启 Docker GPU 加速(需安装 NVIDIA 显卡驱动和 Docker GPU 插件)
- 使用更轻量的 YOLO 模型(如 YOLOv8n 已是最轻量,可考虑模型量化压缩)
- 图片预处理:上传前对图片进行压缩,减小分辨率
2. 增强接口安全性
- 添加 API Key 权限校验,防止匿名调用
- 添加接口限流,避免 Docker 容器被压垮
- 文件类型严格校验,防止恶意文件上传
3. 拓展功能
- 支持批量图片检测(上传压缩包,异步处理)
- 检测结果持久化:将检测结果存入数据库(如 PostgreSQL),支持历史记录查询
- 前端可视化:新增前端页面,实现图片上传、检测结果可视化
- 视频流检测:支持 RTSP 视频流实时目标检测
七、总结
本文全程还原了 Windows 环境下,通过 Docker 部署 YOLOv8 模型,并集成到 Spring Boot 项目的完整流程,从环境准备、问题排查到代码集成、接口测试,每一步都提供了详细的实操步骤和解决方案。
核心亮点
- 通过 Docker 容器化部署 YOLOv8,避免环境依赖冲突,实现快速部署
- 完整解决了 Windows 下 Docker 挂载目录、文件找不到等常见问题
- 将 YOLO 检测能力封装为 Spring Boot 接口,可直接集成到各类业务系统
- 代码可直接复用,适配原有 Spring Boot 项目(无需从零搭建)
后续拓展方向
基于本文的基础,可以进一步拓展:
- 视频流实时目标检测
- 批量图片异步处理
- 检测结果可视化 Dashboard
- 模型训练与迭代优化
让 YOLO 目标检测能力更好地服务于实际业务场景。如果在实操过程中遇到问题,可参考本文的问题排查部分,或留言交流。