DAMOYOLO-S Java后端集成实战SpringBoot构建智能安防API服务最近在做一个智慧园区的项目客户那边提了个需求说能不能把摄像头拍到的画面实时分析一下比如看看有没有人闯入禁区、有没有车辆违停或者统计一下人流量。这需求一听就是目标检测的活儿我们团队评估了几个模型最后选了DAMOYOLO-S主要是看中它在速度和精度上的平衡部署起来也相对友好。但问题来了算法团队给的通常是Python脚本或者一个训练好的模型文件怎么把它无缝对接到我们现有的Java技术栈里特别是SpringBoot这套微服务架构上让它变成一个稳定、可扩展的API服务呢这就是今天想跟大家聊聊的实战经验。我会把从模型封装、并发处理到结果落地的整个流程用大白话拆解一遍如果你也在做类似的事情希望能给你一些参考。1. 为什么选择DAMOYOLO-S与SpringBoot组合在做技术选型的时候我们主要考虑了这么几点。首先是模型本身DAMOYOLO-S算是YOLO家族里比较新的成员它的“S”代表Small模型体积小推理速度快这对于需要处理大量视频帧的后端服务来说至关重要。虽然精度上可能比不过那些巨无霸模型但在安防这种对实时性要求高的场景里够用且高效才是王道。其次是技术栈的匹配。我们后端主力是JavaSpringBoot又是构建微服务API的事实标准生态完善社区活跃。把DAMOYOLO-S集成进来意味着我们可以用熟悉的Java技术栈来管理模型推理的生命周期、处理高并发请求、以及方便地和数据库、消息队列这些中间件打交道。想象一下你写个Python服务单独跑模型然后再和Java主服务通信复杂度一下子就上去了调试和运维都麻烦。简单来说这个组合就是想达到一个目的让专业的模型DAMOYOLO-S干专业的活检测让成熟的后端框架SpringBoot处理专业的服务化问题并发、IO、事务各司其职强强联合。2. 核心思路把模型推理包装成服务我们的目标不是简单地在Java里调一下Python。而是要把DAMOYOLO-S的推理能力变成一个标准的、内部的服务组件。这个组件对外提供清晰的接口比如“检测这张图片”对内则管理好模型加载、资源释放这些脏活累活。2.1 整体架构设计先来看看我们设计的简单架构图心里有个数[HTTP Client] - [SpringBoot Controller] - [推理服务组件] - [DAMOYOLO-S 模型] ^ | | v [JSON Response] - [结果后处理] - [原始检测结果] | v [MySQL 数据库]流程是这样的客户端比如前端页面或者别的服务发送一个HTTP请求过来请求体里带着一张图片。SpringBoot的Controller层接收到请求进行基本的参数校验。Controller调用我们封装好的“推理服务组件”。这个组件负责把图片处理成模型能吃的格式喂给DAMOYOLO-S模型拿到原始的检测框、类别和置信度。组件对原始结果进行加工比如过滤掉低置信度的目标、把坐标转换成更易读的格式。加工后的结果一方面返回给Controller最终变成JSON响应给客户端另一方面也可以选择性地被保存到数据库里用于后续的查询和统计。数据库这里我们用了MySQL主要存一些检测记录比如时间、摄像头ID、检测到了什么、坐标信息等。2.2 关键技术点与依赖要实现这个架构我们需要在SpringBoot项目里引入几个关键的依赖。主要是解决两个问题怎么在Java里跑深度学习模型以及怎么方便地处理图片。对于第一个问题我们选择了Deep Java Library (DJL)。它是AWS开源的专门为Java开发者接入深度学习模型设计的库。好处是API设计得很Java范儿而且底层可以对接PyTorch、TensorFlow、MXNet等多个引擎。DAMOYOLO-S通常是PyTorch格式的所以我们就用DJL的PyTorch后端。对于图片处理Java原生的ImageIO功能有限我们用了更强大的OpenCV的Java版本。虽然需要额外安装本地库但它处理起图片的缩放、色彩空间转换BGR转RGB是常事等操作非常顺手。Maven的pom.xml里关键依赖大概长这样dependency groupIdai.djl/groupId artifactIdapi/artifactId version0.25.0/version /dependency dependency groupIdai.djl.pytorch/groupId artifactIdpytorch-engine/artifactId version0.25.0/version scoperuntime/scope /dependency dependency groupIdai.djl.pytorch/groupId artifactIdpytorch-model-zoo/artifactId version0.25.0/version /dependency !-- OpenCV Java绑定通常需要从官方获取 -- dependency groupIdorg.openpnp/groupId artifactIdopencv/artifactId version4.8.1-1/version /dependency3. 分步实现从模型加载到API暴露理论说完了咱们来点实际的代码。我会把关键步骤的代码贴出来并加上注释说明。3.1 第一步模型服务化封装我们创建一个DetectionService类它的核心任务就是管理DAMOYOLO-S模型并提供检测方法。这里用Spring的Service注解让它成为一个受管理的Bean。import ai.djl.inference.Predictor; import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; import ai.djl.modality.cv.output.DetectedObjects; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelZoo; import ai.djl.repository.zoo.ZooModel; import ai.djl.training.util.ProgressBar; import ai.djl.translate.Translator; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.nio.file.Paths; Service Slf4j public class DetectionService { private ZooModelImage, DetectedObjects model; private PredictorImage, DetectedObjects predictor; /** * 服务启动后自动加载模型 */ PostConstruct public void initModel() throws Exception { log.info(开始加载DAMOYOLO-S模型...); // 定义模型加载标准 CriteriaImage, DetectedObjects criteria Criteria.builder() .setTypes(Image.class, DetectedObjects.class) .optModelPath(Paths.get(models/damoyolo-s.pt)) // 你的模型文件路径 .optTranslator(new MyTranslator()) // 自定义的翻译器处理预处理和后处理 .optProgress(new ProgressBar()) .optEngine(PyTorch) .build(); this.model ModelZoo.loadModel(criteria); this.predictor model.newPredictor(); log.info(DAMOYOLO-S模型加载完毕。); } /** * 对外提供的检测接口 * param image 输入图片 * return 检测结果对象 */ public DetectedObjects detect(Image image) { try { return predictor.predict(image); } catch (Exception e) { log.error(模型推理失败, e); throw new RuntimeException(检测服务内部错误, e); } } /** * 服务关闭前释放模型资源防止内存泄漏 */ PreDestroy public void closeModel() { if (predictor ! null) { predictor.close(); } if (model ! null) { model.close(); } log.info(DAMOYOLO-S模型资源已释放。); } }这里有几个关键点PostConstructSpring Bean初始化完成后自动调用用来加载模型。MyTranslator这是核心它告诉DJL如何把一张Image转换成模型输入张量以及如何把模型输出张量转换成DetectedObjects。你需要根据DAMOYOLO-S模型的具体要求来实现它主要是图片resize、归一化等。PreDestroyBean销毁前调用确保关闭Predictor和Model这是DJL推荐的最佳实践。3.2 第二步处理高并发——推理线程池模型推理是比较耗CPU/GPU的计算任务。如果每个HTTP请求都直接调用predictor.predict()在并发高的时候请求会排队严重拖慢响应速度甚至拖垮服务。解决办法是引入线程池。我们把推理任务提交到一个专门的线程池去执行这样Web服务的IO线程如Tomcat线程就不会被阻塞可以快速响应其他请求。import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; Configuration public class ThreadPoolConfig { Bean(inferenceExecutor) public ExecutorService inferenceExecutor() { // 核心线程数根据你的机器CPU/GPU能力来定这里设为CPU核心数 int corePoolSize Runtime.getRuntime().availableProcessors(); // 最大线程数不宜过大避免过多线程竞争GPU资源反而降低效率 int maxPoolSize corePoolSize * 2; // 任务队列用于存放等待执行的任务 LinkedBlockingQueueRunnable queue new LinkedBlockingQueue(100); return new ThreadPoolExecutor( corePoolSize, maxPoolSize, 60L, TimeUnit.SECONDS, // 空闲线程存活时间 queue, new ThreadPoolExecutor.CallerRunsPolicy() // 队列满后由调用者线程执行 ); } }然后在DetectionService里注入这个线程池并把detect方法改造成异步的Service Slf4j public class DetectionService { Resource(name inferenceExecutor) private ExecutorService executorService; /** * 异步检测接口 * param image 输入图片 * return Future对象可通过它获取结果 */ public CompletableFutureDetectedObjects detectAsync(Image image) { return CompletableFuture.supplyAsync(() - { try { return predictor.predict(image); } catch (Exception e) { log.error(异步推理失败, e); throw new RuntimeException(e); } }, executorService); } }3.3 第三步构建RESTful API现在服务组件准备好了线程池也配好了该对外提供HTTP接口了。我们创建一个DetectionController。import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; import ai.djl.modality.cv.output.DetectedObjects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.concurrent.CompletableFuture; RestController RequestMapping(/api/v1/detect) public class DetectionController { Autowired private DetectionService detectionService; Autowired private DetectionRecordService recordService; // 假设有一个服务用于保存记录 PostMapping(/image) public CompletableFutureDetectionResult detectImage(RequestParam(file) MultipartFile file) { // 1. 将上传的文件转换为DJL的Image对象 Image image; try { BufferedImage bufferedImage ImageIO.read(new ByteArrayInputStream(file.getBytes())); image ImageFactory.getInstance().fromImage(bufferedImage); } catch (IOException e) { throw new RuntimeException(图片读取失败, e); } // 2. 调用异步检测服务 CompletableFutureDetectedObjects future detectionService.detectAsync(image); // 3. 处理结果并可以异步保存到数据库 return future.thenApply(detectedObjects - { DetectionResult result processResult(detectedObjects); // 异步保存检测记录不阻塞本次请求响应 CompletableFuture.runAsync(() - { recordService.saveRecord(file.getOriginalFilename(), result); }); return result; }); } /** * 将DJL的检测结果转换成前端友好的格式 */ private DetectionResult processResult(DetectedObjects detections) { DetectionResult result new DetectionResult(); // 这里进行结果转换比如过滤低置信度目标转换坐标格式等 // ... 转换逻辑 return result; } }这个控制器做了几件事接收一个图片文件上传。将MultipartFile转换成DJL能处理的Image对象。调用我们之前写好的异步检测服务。检测完成后将结果转换成自定义的DetectionResult一个普通的POJO包含检测到的目标列表、图片尺寸等信息。异步地将这次检测记录保存到数据库。注意这里用了CompletableFuture.runAsync意味着保存数据库的操作不会影响API的响应速度。3.4 第四步与数据库联动检测记录存数据库方便以后按时间、按摄像头、按事件类型进行查询和统计。我们设计一张简单的表CREATE TABLE detection_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, camera_id VARCHAR(64) COMMENT 摄像头标识, image_name VARCHAR(255) COMMENT 原始图片名, detection_result JSON COMMENT 检测结果JSON, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, INDEX idx_camera_time (camera_id, create_time) );在SpringBoot里用MyBatis-Plus或者Spring Data JPA来操作这张表就很方便了。DetectionRecordService就是负责saveRecord的那个服务里面就是普通的数据库插入逻辑。4. 部署与优化建议把代码写完、测试通过就可以打包部署了。这里有几个实践中总结的小建议模型文件管理不要把巨大的模型文件.pt打包进Jar。最好通过配置文件指定一个外部路径比如/opt/models/部署时把模型文件放到那里。资源监控一定要监控这个服务的内存和GPU显存如果用GPU的话。DJL的Predictor不是绝对线程安全的通常建议一个线程一个Predictor或者用ThreadLocal包装但这会消耗更多内存。我们的方案是单个Predictor配合线程池在实践中没发现问题但需要监控。性能压测上线前用JMeter等工具模拟一下并发请求看看你的线程池配置是否合理数据库连接池是否够用找到服务的瓶颈在哪里。结果缓存对于智慧园区这类场景连续视频帧之间相似度很高。可以考虑对检测结果进行短期缓存比如用Redis如果下一帧和上一帧在短时间内来自同一个摄像头且图片变化不大可以直接返回缓存的结果大幅减少模型调用。健康检查给SpringBoot Actuator加上健康检查端点特别是可以自定义一个健康指示器检查模型是否加载成功这样运维同学能一眼看出服务状态。5. 写在最后这么一套组合拳打下来一个基于SpringBoot的DAMOYOLO-S智能安防API服务就基本成型了。回过头看核心思想就是分层和解耦Controller管HTTPService管业务逻辑和模型调用数据库管数据持久化线程池管并发。每层各司其职代码清晰也方便以后扩展。当然这只是个起点。在实际项目中你可能还需要考虑更多比如如何对接真实的视频流可以用OpenCV拉RTSP流如何将检测结果实时推送到前端用WebSocket如何做更复杂的事件分析比如徘徊检测、区域入侵。但有了这个可用的API服务作为基础后续的这些功能加进来都会顺畅很多。模型部署和服务化是个细致活总会遇到各种坑比如环境依赖、内存泄漏、性能调优。我的经验是多看官方文档多写测试用例逐步迭代。希望这篇实战分享能帮你少走点弯路。如果你在集成过程中遇到其他问题也欢迎一起交流。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
DAMOYOLO-S Java后端集成实战:SpringBoot构建智能安防API服务
DAMOYOLO-S Java后端集成实战SpringBoot构建智能安防API服务最近在做一个智慧园区的项目客户那边提了个需求说能不能把摄像头拍到的画面实时分析一下比如看看有没有人闯入禁区、有没有车辆违停或者统计一下人流量。这需求一听就是目标检测的活儿我们团队评估了几个模型最后选了DAMOYOLO-S主要是看中它在速度和精度上的平衡部署起来也相对友好。但问题来了算法团队给的通常是Python脚本或者一个训练好的模型文件怎么把它无缝对接到我们现有的Java技术栈里特别是SpringBoot这套微服务架构上让它变成一个稳定、可扩展的API服务呢这就是今天想跟大家聊聊的实战经验。我会把从模型封装、并发处理到结果落地的整个流程用大白话拆解一遍如果你也在做类似的事情希望能给你一些参考。1. 为什么选择DAMOYOLO-S与SpringBoot组合在做技术选型的时候我们主要考虑了这么几点。首先是模型本身DAMOYOLO-S算是YOLO家族里比较新的成员它的“S”代表Small模型体积小推理速度快这对于需要处理大量视频帧的后端服务来说至关重要。虽然精度上可能比不过那些巨无霸模型但在安防这种对实时性要求高的场景里够用且高效才是王道。其次是技术栈的匹配。我们后端主力是JavaSpringBoot又是构建微服务API的事实标准生态完善社区活跃。把DAMOYOLO-S集成进来意味着我们可以用熟悉的Java技术栈来管理模型推理的生命周期、处理高并发请求、以及方便地和数据库、消息队列这些中间件打交道。想象一下你写个Python服务单独跑模型然后再和Java主服务通信复杂度一下子就上去了调试和运维都麻烦。简单来说这个组合就是想达到一个目的让专业的模型DAMOYOLO-S干专业的活检测让成熟的后端框架SpringBoot处理专业的服务化问题并发、IO、事务各司其职强强联合。2. 核心思路把模型推理包装成服务我们的目标不是简单地在Java里调一下Python。而是要把DAMOYOLO-S的推理能力变成一个标准的、内部的服务组件。这个组件对外提供清晰的接口比如“检测这张图片”对内则管理好模型加载、资源释放这些脏活累活。2.1 整体架构设计先来看看我们设计的简单架构图心里有个数[HTTP Client] - [SpringBoot Controller] - [推理服务组件] - [DAMOYOLO-S 模型] ^ | | v [JSON Response] - [结果后处理] - [原始检测结果] | v [MySQL 数据库]流程是这样的客户端比如前端页面或者别的服务发送一个HTTP请求过来请求体里带着一张图片。SpringBoot的Controller层接收到请求进行基本的参数校验。Controller调用我们封装好的“推理服务组件”。这个组件负责把图片处理成模型能吃的格式喂给DAMOYOLO-S模型拿到原始的检测框、类别和置信度。组件对原始结果进行加工比如过滤掉低置信度的目标、把坐标转换成更易读的格式。加工后的结果一方面返回给Controller最终变成JSON响应给客户端另一方面也可以选择性地被保存到数据库里用于后续的查询和统计。数据库这里我们用了MySQL主要存一些检测记录比如时间、摄像头ID、检测到了什么、坐标信息等。2.2 关键技术点与依赖要实现这个架构我们需要在SpringBoot项目里引入几个关键的依赖。主要是解决两个问题怎么在Java里跑深度学习模型以及怎么方便地处理图片。对于第一个问题我们选择了Deep Java Library (DJL)。它是AWS开源的专门为Java开发者接入深度学习模型设计的库。好处是API设计得很Java范儿而且底层可以对接PyTorch、TensorFlow、MXNet等多个引擎。DAMOYOLO-S通常是PyTorch格式的所以我们就用DJL的PyTorch后端。对于图片处理Java原生的ImageIO功能有限我们用了更强大的OpenCV的Java版本。虽然需要额外安装本地库但它处理起图片的缩放、色彩空间转换BGR转RGB是常事等操作非常顺手。Maven的pom.xml里关键依赖大概长这样dependency groupIdai.djl/groupId artifactIdapi/artifactId version0.25.0/version /dependency dependency groupIdai.djl.pytorch/groupId artifactIdpytorch-engine/artifactId version0.25.0/version scoperuntime/scope /dependency dependency groupIdai.djl.pytorch/groupId artifactIdpytorch-model-zoo/artifactId version0.25.0/version /dependency !-- OpenCV Java绑定通常需要从官方获取 -- dependency groupIdorg.openpnp/groupId artifactIdopencv/artifactId version4.8.1-1/version /dependency3. 分步实现从模型加载到API暴露理论说完了咱们来点实际的代码。我会把关键步骤的代码贴出来并加上注释说明。3.1 第一步模型服务化封装我们创建一个DetectionService类它的核心任务就是管理DAMOYOLO-S模型并提供检测方法。这里用Spring的Service注解让它成为一个受管理的Bean。import ai.djl.inference.Predictor; import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; import ai.djl.modality.cv.output.DetectedObjects; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelZoo; import ai.djl.repository.zoo.ZooModel; import ai.djl.training.util.ProgressBar; import ai.djl.translate.Translator; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.nio.file.Paths; Service Slf4j public class DetectionService { private ZooModelImage, DetectedObjects model; private PredictorImage, DetectedObjects predictor; /** * 服务启动后自动加载模型 */ PostConstruct public void initModel() throws Exception { log.info(开始加载DAMOYOLO-S模型...); // 定义模型加载标准 CriteriaImage, DetectedObjects criteria Criteria.builder() .setTypes(Image.class, DetectedObjects.class) .optModelPath(Paths.get(models/damoyolo-s.pt)) // 你的模型文件路径 .optTranslator(new MyTranslator()) // 自定义的翻译器处理预处理和后处理 .optProgress(new ProgressBar()) .optEngine(PyTorch) .build(); this.model ModelZoo.loadModel(criteria); this.predictor model.newPredictor(); log.info(DAMOYOLO-S模型加载完毕。); } /** * 对外提供的检测接口 * param image 输入图片 * return 检测结果对象 */ public DetectedObjects detect(Image image) { try { return predictor.predict(image); } catch (Exception e) { log.error(模型推理失败, e); throw new RuntimeException(检测服务内部错误, e); } } /** * 服务关闭前释放模型资源防止内存泄漏 */ PreDestroy public void closeModel() { if (predictor ! null) { predictor.close(); } if (model ! null) { model.close(); } log.info(DAMOYOLO-S模型资源已释放。); } }这里有几个关键点PostConstructSpring Bean初始化完成后自动调用用来加载模型。MyTranslator这是核心它告诉DJL如何把一张Image转换成模型输入张量以及如何把模型输出张量转换成DetectedObjects。你需要根据DAMOYOLO-S模型的具体要求来实现它主要是图片resize、归一化等。PreDestroyBean销毁前调用确保关闭Predictor和Model这是DJL推荐的最佳实践。3.2 第二步处理高并发——推理线程池模型推理是比较耗CPU/GPU的计算任务。如果每个HTTP请求都直接调用predictor.predict()在并发高的时候请求会排队严重拖慢响应速度甚至拖垮服务。解决办法是引入线程池。我们把推理任务提交到一个专门的线程池去执行这样Web服务的IO线程如Tomcat线程就不会被阻塞可以快速响应其他请求。import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; Configuration public class ThreadPoolConfig { Bean(inferenceExecutor) public ExecutorService inferenceExecutor() { // 核心线程数根据你的机器CPU/GPU能力来定这里设为CPU核心数 int corePoolSize Runtime.getRuntime().availableProcessors(); // 最大线程数不宜过大避免过多线程竞争GPU资源反而降低效率 int maxPoolSize corePoolSize * 2; // 任务队列用于存放等待执行的任务 LinkedBlockingQueueRunnable queue new LinkedBlockingQueue(100); return new ThreadPoolExecutor( corePoolSize, maxPoolSize, 60L, TimeUnit.SECONDS, // 空闲线程存活时间 queue, new ThreadPoolExecutor.CallerRunsPolicy() // 队列满后由调用者线程执行 ); } }然后在DetectionService里注入这个线程池并把detect方法改造成异步的Service Slf4j public class DetectionService { Resource(name inferenceExecutor) private ExecutorService executorService; /** * 异步检测接口 * param image 输入图片 * return Future对象可通过它获取结果 */ public CompletableFutureDetectedObjects detectAsync(Image image) { return CompletableFuture.supplyAsync(() - { try { return predictor.predict(image); } catch (Exception e) { log.error(异步推理失败, e); throw new RuntimeException(e); } }, executorService); } }3.3 第三步构建RESTful API现在服务组件准备好了线程池也配好了该对外提供HTTP接口了。我们创建一个DetectionController。import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; import ai.djl.modality.cv.output.DetectedObjects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.concurrent.CompletableFuture; RestController RequestMapping(/api/v1/detect) public class DetectionController { Autowired private DetectionService detectionService; Autowired private DetectionRecordService recordService; // 假设有一个服务用于保存记录 PostMapping(/image) public CompletableFutureDetectionResult detectImage(RequestParam(file) MultipartFile file) { // 1. 将上传的文件转换为DJL的Image对象 Image image; try { BufferedImage bufferedImage ImageIO.read(new ByteArrayInputStream(file.getBytes())); image ImageFactory.getInstance().fromImage(bufferedImage); } catch (IOException e) { throw new RuntimeException(图片读取失败, e); } // 2. 调用异步检测服务 CompletableFutureDetectedObjects future detectionService.detectAsync(image); // 3. 处理结果并可以异步保存到数据库 return future.thenApply(detectedObjects - { DetectionResult result processResult(detectedObjects); // 异步保存检测记录不阻塞本次请求响应 CompletableFuture.runAsync(() - { recordService.saveRecord(file.getOriginalFilename(), result); }); return result; }); } /** * 将DJL的检测结果转换成前端友好的格式 */ private DetectionResult processResult(DetectedObjects detections) { DetectionResult result new DetectionResult(); // 这里进行结果转换比如过滤低置信度目标转换坐标格式等 // ... 转换逻辑 return result; } }这个控制器做了几件事接收一个图片文件上传。将MultipartFile转换成DJL能处理的Image对象。调用我们之前写好的异步检测服务。检测完成后将结果转换成自定义的DetectionResult一个普通的POJO包含检测到的目标列表、图片尺寸等信息。异步地将这次检测记录保存到数据库。注意这里用了CompletableFuture.runAsync意味着保存数据库的操作不会影响API的响应速度。3.4 第四步与数据库联动检测记录存数据库方便以后按时间、按摄像头、按事件类型进行查询和统计。我们设计一张简单的表CREATE TABLE detection_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, camera_id VARCHAR(64) COMMENT 摄像头标识, image_name VARCHAR(255) COMMENT 原始图片名, detection_result JSON COMMENT 检测结果JSON, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, INDEX idx_camera_time (camera_id, create_time) );在SpringBoot里用MyBatis-Plus或者Spring Data JPA来操作这张表就很方便了。DetectionRecordService就是负责saveRecord的那个服务里面就是普通的数据库插入逻辑。4. 部署与优化建议把代码写完、测试通过就可以打包部署了。这里有几个实践中总结的小建议模型文件管理不要把巨大的模型文件.pt打包进Jar。最好通过配置文件指定一个外部路径比如/opt/models/部署时把模型文件放到那里。资源监控一定要监控这个服务的内存和GPU显存如果用GPU的话。DJL的Predictor不是绝对线程安全的通常建议一个线程一个Predictor或者用ThreadLocal包装但这会消耗更多内存。我们的方案是单个Predictor配合线程池在实践中没发现问题但需要监控。性能压测上线前用JMeter等工具模拟一下并发请求看看你的线程池配置是否合理数据库连接池是否够用找到服务的瓶颈在哪里。结果缓存对于智慧园区这类场景连续视频帧之间相似度很高。可以考虑对检测结果进行短期缓存比如用Redis如果下一帧和上一帧在短时间内来自同一个摄像头且图片变化不大可以直接返回缓存的结果大幅减少模型调用。健康检查给SpringBoot Actuator加上健康检查端点特别是可以自定义一个健康指示器检查模型是否加载成功这样运维同学能一眼看出服务状态。5. 写在最后这么一套组合拳打下来一个基于SpringBoot的DAMOYOLO-S智能安防API服务就基本成型了。回过头看核心思想就是分层和解耦Controller管HTTPService管业务逻辑和模型调用数据库管数据持久化线程池管并发。每层各司其职代码清晰也方便以后扩展。当然这只是个起点。在实际项目中你可能还需要考虑更多比如如何对接真实的视频流可以用OpenCV拉RTSP流如何将检测结果实时推送到前端用WebSocket如何做更复杂的事件分析比如徘徊检测、区域入侵。但有了这个可用的API服务作为基础后续的这些功能加进来都会顺畅很多。模型部署和服务化是个细致活总会遇到各种坑比如环境依赖、内存泄漏、性能调优。我的经验是多看官方文档多写测试用例逐步迭代。希望这篇实战分享能帮你少走点弯路。如果你在集成过程中遇到其他问题也欢迎一起交流。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。