超详细的编码实战,让你的springboot应用识别图片中的行人汽车狗子喵星人(JavaCV+YOLO4)
Posted 程序员欣宸
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了超详细的编码实战,让你的springboot应用识别图片中的行人汽车狗子喵星人(JavaCV+YOLO4)相关的知识,希望对你有一定的参考价值。
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 在《三分钟:极速体验JAVA版目标检测(YOLO4)》一文中,咱们体验了YOLO4强大的物体识别能力,如下图,原图中的狗子、人、马都被识别并标注出来了:
- 如果您之前对深度学习和YOLO、darknet等有过了解,相信您会产生疑问:Java能实现这些?
- 没错,今天咱们就从零开始,开发一个SpringBoot应用实现上述功能,该应用名为yolo-demo
- 让SpringBoot应用识别图片中的物体,其关键在如何使用已经训练好的神经网络模型,好在OpenCV集成的DNN模块可以加载和使用YOLO4模型,我们只要找到使用OpenCV的办法即可
- 我这里的方法是使用JavaCV库,因为JavaCV本身封装了OpenCV,最终可以使用YOLO4模型进行推理,依赖情况如下图所示:
关键技术
- 本篇涉及到JavaCV、OpenCV、YOLO4等,从上图可以看出JavaCV已将这些做了封装,包括最终推理时所用的模型也是YOLO4官方提前训练好的,咱们只要知道如何使用JavaCV的API即可
- YOVO4的paper在此:https://arxiv.org/pdf/2004.10934v1.pdf
版本信息
- 这里给出我的开发环境供您参考:
- 操作系统:Ubuntu 16(MacBook Pro也可以,版本是11.2.3,macOS Big Sur)
- docker:20.10.2 Community
- java:1.8.0_211
- springboot:2.4.8
- javacv:1.5.6
- opencv:4.5.3
实战步骤
-
在正式动手前,先把本次实战的步骤梳理清楚,后面按部就班执行即可;
-
为了减少环境和软件差异的影响,让程序的运行调试更简单,这里会把SpringBoot应用制作成docker镜像,然后在docker环境运行,所以,整个实战简单来说分为三步 :制做基础镜像、开发SpringBoot应用、把应用做成镜像,如下图:
-
上述流程中的第一步制做基础镜像,已经在《制作JavaCV应用依赖的基础Docker镜像(CentOS7+JDK8+OpenCV4)》一文中详细介绍,咱们直接使用镜像bolingcavalry/opencv4.5.3:0.0.1即可,接下来的内容将会聚焦SpringBoot应用的开发;
-
这个SpringBoot应用的功能很单一,如下图所示:
-
整个开发过程涉及到这些步骤:提交照片的网页、神经网络初始化、文件处理、图片检测、处理检测结果、在图片上标准识别结果、前端展示图片等,完整步骤已经整理如下图:
-
内容很丰富,收获也不会少,更何况前文已确保可以成功运行,那么,别犹豫啦,咱们开始吧!
源码下载
- 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
- 这个git项目中有多个文件夹,本篇的源码在javacv-tutorials文件夹下,如下图红框所示:
- javacv-tutorials里面有多个子工程,今天的代码在yolo-demo工程下:
新建SpringBoot应用
- 新建名为yolo-demo的maven工程,首先这是个标准的SpringBoot工程,其次添加了javacv的依赖库,pom.xml内容如下,重点是javacv、opencv等库的依赖和准确的版本匹配:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>yolo-demo</artifactId>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
<springboot.version>2.4.8</springboot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--FreeMarker模板视图依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv-platform-gpu</artifactId>
<version>4.5.3-1.5.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bolingcavalry.yolodemo.YoloDemoApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- 接下来的重点是配置文件application.properties,如下可见,除了常见的spring配置,还有几个文件路径配置,实际运行时,这些路径都要存放对应的文件给程序使用,这些文件如何获取稍后会讲到:
### FreeMarker 配置
spring.freemarker.allow-request-override=false
#Enable template caching.启用模板缓存。
spring.freemarker.cache=false
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
#设置面板后缀
spring.freemarker.suffix=.ftl
# 设置单个文件最大内存
spring.servlet.multipart.max-file-size=100MB
# 设置所有文件最大内存
spring.servlet.multipart.max-request-size=1000MB
# 自定义文件上传路径
web.upload-path=/app/images
# 模型路径
# yolo的配置文件所在位置
opencv.yolo-cfg-path=/app/model/yolov4.cfg
# yolo的模型文件所在位置
opencv.yolo-weights-path=/app/model/yolov4.weights
# yolo的分类文件所在位置
opencv.yolo-coconames-path=/app/model/coco.names
# yolo模型推理时的图片宽度
opencv.yolo-width=608
# yolo模型推理时的图片高度
opencv.yolo-height=608
- 启动类YoloDemoApplication.java:
package com.bolingcavalry.yolodemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class YoloDemoApplication {
public static void main(String[] args) {
SpringApplication.run(YoloDemoApplication.class, args);
}
}
- 工程已建好,接下来开始编码,先从前端页面开始
前端页面
- 只要涉及到前端,欣宸一般都会发个自保声明:请大家原谅欣宸不入流的前端水平,页面做得我自己都不忍直视,但为了功能的完整,请您忍忍,也不是不能用,咱们总要有个地方提交照片并且展示识别结果不是?
- 新增名为index.ftl的前端模板文件,位置如下图红框:
- index.ftl的内容如下,可见很简单,有选择和提交文件的表单,也有展示结果的脚本,还能展示后台返回的提示信息,嗯嗯,这就够用了:
<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<title>图片上传Demo</title>
</head>
<body>
<h1 >图片上传Demo</h1>
<form action="fileUpload" method="post" enctype="multipart/form-data">
<p>选择检测文件: <input type="file" name="fileName"/></p>
<p><input type="submit" value="提交"/></p>
</form>
<#--判断是否上传文件-->
<#if msg??>
<span>${msg}</span><br><br>
<#else >
<span>${msg!("文件未上传")}</span><br>
</#if>
<#--显示图片,一定要在img中的src发请求给controller,否则直接跳转是乱码-->
<#if fileName??>
<#--<img src="/show?fileName=${fileName}" style="width: 100px"/>-->
<img src="/show?fileName=${fileName}"/>
<#else>
<#--<img src="/show" style="width: 200px"/>-->
</#if>
</body>
</html>
- 页面的效果,就像下面这样:
后端逻辑:初始化
- 为了保持简单,所有后端逻辑放在一个java文件中:YoloServiceController.java,按照前面梳理的流程,咱们先看初始化部分
- 首先是成员变量和依赖
private final ResourceLoader resourceLoader;
@Autowired
public YoloServiceController(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Value("${web.upload-path}")
private String uploadPath;
@Value("${opencv.yolo-cfg-path}")
private String cfgPath;
@Value("${opencv.yolo-weights-path}")
private String weightsPath;
@Value("${opencv.yolo-coconames-path}")
private String namesPath;
@Value("${opencv.yolo-width}")
private int width;
@Value("${opencv.yolo-height}")
private int height;
/**
* 置信度门限(超过这个值才认为是可信的推理结果)
*/
private float confidenceThreshold = 0.5f;
private float nmsThreshold = 0.4f;
// 神经网络
private Net net;
// 输出层
private StringVector outNames;
// 分类名称
private List<String> names;
- 接下来是初始化方法init,可见会从之前配置的几个文件路径中加载神经网络所需的配置、训练模型等文件,关键方法是readNetFromDarknet的调用,还有就是检查是否有支持CUDA的设备,如果有就在神经网络中做好设置:
@PostConstruct
private void init() throws Exception {
// 初始化打印一下,确保编码正常,否则日志输出会是乱码
log.error("file.encoding is " + System.getProperty("file.encoding"));
// 神经网络初始化
net = readNetFromDarknet(cfgPath, weightsPath);
// 检查网络是否为空
if (net.empty()) {
log.error("神经网络初始化失败");
throw new Exception("神经网络初始化失败");
}
// 输出层
outNames = net.getUnconnectedOutLayersNames();
// 检查GPU
if (getCudaEnabledDeviceCount() > 0) {
net.setPreferableBackend(opencv_dnn.DNN_BACKEND_CUDA);
net.setPreferableTarget(opencv_dnn.DNN_TARGET_CUDA);
}
// 分类名称
try {
names = Files.readAllLines(Paths.get(namesPath));
} catch (IOException e) {
log.error("获取分类名称失败,文件路径[{}]", namesPath, e);
}
}
处理上传文件
- 前端将二进制格式的图片文件提交上来后如何处理?这里整理了一个简单的文件处理方法upload,会将文件保存在服务器的指定位置,后面会调用:
/**
* 上传文件到指定目录
* @param file 文件
* @param path 文件存放路径
* @param fileName 源文件名
* @return
*/
private static boolean upload(MultipartFile file, String path, String fileName){
//使用原文件名
String realPath = path + "/" + fileName;
File dest = new File(realPath);
//判断文件父目录是否存在
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdir();
}
try {
//保存文件
file.transferTo(dest);
return true;
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
物体检测
- 准备工作都完成了,来写最核心的物体检测代码,这些代码放在yolo-demo应用处理web请求的方法中,如下所示,可见这里只是个大纲,将推理、结果处理、图片标注等功能串起来形成完整流程,但是不涉及每个具体功能的细节:
@RequestMapping("fileUpload")
public String upload(@RequestParam("fileName") MultipartFile file, Map<String, Object> map){
log.info("文件 [{}], 大小 [{}]", file.getOriginalFilename(), file.getSize());
// 文件名称
String originalFileName = file.getOriginalFilename();
if (!upload(file, uploadPath, originalFileName)){
map.put("msg", "上传失败!");
return "forward:/index";
}
// 读取文件到Mat
Mat src = imread(uploadPath + "/" + originalFileName);
// 执行推理
MatVector outs = doPredict(src);
// 处理原始的推理结果,
// 对检测到的每个目标,找出置信度最高的类别作为改目标的类别,
// 还要找出每个目标的位置,这些信息都保存在ObjectDetectionResult对象中
List<ObjectDetectionResult> results = postprocess(src, outs);
// 释放资源
outs.releaseReference();
// 检测到的目标总数
int detectNum = results.size();
log.info("一共检测到{}个目标", detectNum);
// 没检测到
if (detectNum<1) {
// 显示图片
map.put("msg", "未检测到目标");
// 文件名
map.put("fileName", originalFileName);
return "forward:/index";
} else {
// 检测结果页面的提示信息
map.put("msg", "检测到" + results.size() + "个目标");
}
以上是关于超详细的编码实战,让你的springboot应用识别图片中的行人汽车狗子喵星人(JavaCV+YOLO4)的主要内容,如果未能解决你的问题,请参考以下文章
手把手带你搞定C语言实现三子棋游戏,让你的代码有趣起来(超详细教程,从思路到代码,快码起来!)
Python可视化超详细Pyecharts 1.x教程,让你的图表动起来~
超详细,新手都能看懂 !SpringBoot高级笔记(全彩版)让阿里老爸带你飞~
Android App实战项目之使用OpenCV人脸识别实现找人功能(附源码和演示 超详细)