<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”>
javacv-tutorials
com.bolingcavalry
1.0-SNAPSHOT
4.0.0
face-detect-demo
jar
org.springframework.boot
spring-boot-dependencies
${springboot.version}
pom
import
org.springframework.boot
spring-boot-starter-freemarker
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-test
test
org.bytedeco
javacv-platform
org.bytedeco
javacv
org.bytedeco
javacpp
org.bytedeco.javacpp-presets
ffmpeg-platform
org.bytedeco.javacpp-presets
ffmpeg
org.springframework.boot
spring-boot-maven-plugin
com.bolingcavalry.facedetect.FaceDetectApplication
repackage
- 配置文件如下,要重点关注前段模板、文件上传大小、模型文件目录等配置:
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
模型路径
opencv.model-path=/app/model/haarcascade_frontalface_default.xml
- 前端页面文件只有一个index.ftl,请原谅欣宸不入流的前端水平,前端只有一个页面,可以提交页面,同时也是展示处理结果的页面:
图片上传Demo
选择检测文件:
周围检测数量:
<#–判断是否上传文件–>
<#if msg??>
${msg}
<#else >
${msg!(“文件未上传”)}
</#if>
<#–显示图片,一定要在img中的src发请求给controller,否则直接跳转是乱码–>
<#if fileName??>
<#––>
<#else>
<#––>
</#if>
- 再来看后台代码,先是最常见的应用启动类:
package com.bolingcavalry.facedetect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FaceDetectApplication {
public static void main(String[] args) {
SpringApplication.run(FaceDetectApplication.class, args);
}
}
- 前端上传图片后,后端要做哪些处理呢?先不贴代码,咱们把后端要做的事情捋一遍,如下图:

- 接下来是最核心的业务类UploadController.java,web接口和业务逻辑处理都在这里面,是按照上图的流程顺序执行的,有几处要注意的地方稍后会提到:
package com.bolingcavalry.facedetect.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import java.util.UUID;
import static org.bytedeco.javacpp.opencv_objdetect.CV_HAAR_DO_CANNY_PRUNING;
@Controller
@Slf4j
public class UploadController {
static {
// 加载 动态链接库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
private final ResourceLoader resourceLoader;
@Autowired
public UploadController(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Value("${web.upload-path}")
private String uploadPath;
@Value("${opencv.model-path}")
private String modelPath;
/**
跳转到文件上传页面
@return
*/
@RequestMapping(“index”)
public String toUpload(){
return “index”;
}
/**
上次文件到指定目录
@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;
}
}
/**
@param file 要上传的文件
@return
*/
@RequestMapping(“fileUpload”)
public String upload(@RequestParam(“fileName”) MultipartFile file, @RequestParam(“minneighbors”) int minneighbors, Map<String, Object> map){
log.info(“file [{}], size [{}], minneighbors [{}]”, file.getOriginalFilename(), file.getSize(), minneighbors);
String originalFileName = file.getOriginalFilename();
if (!upload(file, uploadPath, originalFileName)){
map.put(“msg”, “上传失败!”);
return “forward:/index”;
}
String realPath = uploadPath + “/” + originalFileName;
Mat srcImg = Imgcodecs.imread(realPath);
// 目标灰色图像
Mat dstGrayImg = new Mat();
// 转换灰色
Imgproc.c
【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 开源分享
vtColor(srcImg, dstGrayImg, Imgproc.COLOR_BGR2GRAY);
// OpenCv人脸识别分类器
CascadeClassifier classifier = new CascadeClassifier(modelPath);
// 用来存放人脸矩形
MatOfRect faceRect = new MatOfRect();
// 特征检测点的最小尺寸
Size minSize = new Size(32, 32);
// 图像缩放比例,可以理解为相机的X倍镜
double scaleFactor = 1.2;
// 执行人脸检测
classifier.detectMultiScale(dstGrayImg, faceRect, scaleFactor, minneighbors, CV_HAAR_DO_CANNY_PRUNING, minSize);
//遍历矩形,画到原图上面
// 定义绘制颜色
Scalar color = new Scalar(0, 0, 255);
Rect[] rects = faceRect.toArray();
// 没检测到
if (null==rects || rects.length<1) {
// 显示图片
map.put(“msg”, “未检测到人脸”);
// 文件名
map.put(“fileName”, originalFileName);
return “forward:/index”;
}
// 逐个处理
for(Rect rect: rects) {
int x = rect.x;
int y = rect.y;
int w = rect.width;
int h = rect.height;
// 单独框出每一张人脸
Imgproc.rectangle(srcImg, new Point(x, y), new Point(x + w, y + w), color, 2);
}
// 添加人脸框之后的图片的名字
String newFileName = UUID.randomUUID().toString() + “.png”;
// 保存
Imgcodecs.imwrite(uploadPath + “/” + newFileName, srcImg);
// 显示图片
map.put(“msg”, “一共检测到” + rects.length + “个人脸”);
// 文件名
map.put(“fileName”, newFileName);
return “forward:/index”;
}
/**
显示单张图片
@return
*/
@RequestMapping(“show”)
public ResponseEntity showPhotos(String fileName){
if (null==fileName) {
return ResponseEntity.notFound().build();
}
try {
// 由于是读取本机的文件,file是一定要加上的, path是在application配置文件中的路径
return ResponseEntity.ok(resourceLoader.getResource(“file:” + uploadPath + “/” + fileName));
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
}
- UploadController.java的代码,有以下几处要关注:
在静态方法中通过System.loadLibrary加载本地库函,实际开发过程中,这里是最容易报错的地方,一定要确保-Djava.library.path参数配置的路径中的本地库是正常可用的,前文制作的基础镜像中已经准比好了这些本地库,因此只要确保-Djava.library.path参数配置正确即可,这个配置在稍后的Dockerfile中会提到
public String upload方法是处理人脸检测的代码入口,内部按照前面分析的流程顺序执行
new CascadeClassifier(modelPath)是根据指定的模型来实例化分类器,模型文件是从GitHub下载的,opencv官方提前训练好的模型,地址是:https://github.com/opencv/opencv/tree/master/data/haarcascades
看似神奇的人脸检测功能,实际上只需一行代码classifier.detectMultiScale,就能得到每个人脸在原图中的矩形位置,接下来,咱们只要按照位置在原图上添加矩形框即可
- 现在代码已经写完了,接下来将其做成docker镜像
docker镜像制作
- 首先是编写Dockerfile:
基础镜像集成了openjdk8和opencv3.4.3
FROM bolingcavalry/opencv3.4.3:0.0.3