本文介绍windows系统下springboot+opencv进行对图片中的人脸进行提取,适合入门学习
参考:springboot,opencv
下载windows下opencv开发包4.5.1点击下载,下载完成后打开exe提取资源到本地
在springboot项目resources目录下新建lib包,将上一步提取资源目录下的\build\java\opencv-451.jar复制到lib包下
修改pom文件,将该jar包添加到项目依赖中
org.opencv
opencv
4.5.1
system
${project.basedir}/src/main/resources/lib/opencv-451.jar
添加动态库
在项目根目录新建文件夹dll,将opencv资源目录下的\build\java x64,x86复制到dll下
添加opencv分类器
在项目根目录新建文件夹haarcascades,将opencv资源目录下\sources\data\haarcascades中的xml文件复制到该目录
注: 也可以使用其他分类器,如haarcascades_cuda、lbpcascades下的,效果对比参考此处
最终的项目结构如下:
java
package com.demo.face;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import java.io.File;
import java.util.Comparator;
public class FaceUtils {
static {
String dir = System.getProperty("user.dir").replace("/", "\\");
String filePath = "\\dll\\" + (System.getProperty("java.vm.name").contains("64") ? "x64" : "x86") + "\\opencv_java451.dll";
String path = dir.concat(filePath);
File file = new File(path);
if (!file.exists()) {
throw new RuntimeException("找不到动态库".concat(path));
}
System.load(path);
System.out.println(path.concat(" load success"));
}
/**
* 检测人脸数量
*
* @param base64
* @return
*/
public static int checkFaceNo(String base64) {
String path = System.getProperty("user.dir").concat("/haarcascades/haarcascade_frontalface_alt.xml");
CascadeClassifier faceDetector = new CascadeClassifier(path);
MatOfRect faceDetections = new MatOfRect();
faceDetector.detectMultiScale(StreamUtils.base642Mat(base64), faceDetections);
return faceDetections.toArray().length;
}
public static String extractMaxFace(String base64) {
String path = System.getProperty("user.dir").concat("/haarcascades/haarcascade_frontalface_alt.xml");
CascadeClassifier faceDetector = new CascadeClassifier(path);
MatOfRect faceDetections = new MatOfRect();
Mat mat = StreamUtils.base642Mat(base64);
faceDetector.detectMultiScale(mat, faceDetections);
if (faceDetections.toArray().length > 0) {
Rect rect = faceDetections.toList().parallelStream().max(Comparator.comparingInt(o -> o.height * o.width)).get();
Mat face = imageCut(mat, rect.x, rect.y, rect.width, rect.height);
return StreamUtils.catToBase64(face);
}
return null;
}
/**
* @param image 原始图像
* @param posX x坐标
* @param posY y坐标
* @param width 宽度
* @param height 高度
* @return
*/
private static Mat imageCut(Mat image, int posX, int posY, int width, int height) {
// 截取的区域:参数,坐标X,坐标Y,截图宽度,截图长度
Rect rect = new Rect(posX, posY, width, height);
// 两句效果一样
Mat sub = image.submat(rect); // Mat sub = new Mat(image,rect);
Mat mat = new Mat();
Size size = new Size(width, height);
Imgproc.resize(sub, mat, size);// 将人脸进行截图并保存
return mat;
}
public static String markFace(String base64Images) {
String path = System.getProperty("user.dir").concat("/haarcascades/haarcascade_frontalface_alt.xml");
CascadeClassifier faceDetector = new CascadeClassifier(path);
MatOfRect faceDetections = new MatOfRect();
Mat mat = StreamUtils.base642Mat(base64Images);
faceDetector.detectMultiScale(mat, faceDetections);
if (faceDetections.toArray().length > 0) {
for (Rect rect : faceDetections.toList()) {
Imgproc.rectangle(mat, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
}
}
return StreamUtils.catToBase64(mat);
}
}
用到的StreamUtils.java源码
java
package com.demo.face;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.imgcodecs.Imgcodecs;
import org.springframework.util.Base64Utils;
import sun.misc.BASE64Decoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class StreamUtils {
/**
* 装换回编码
*
* @param correctMat
* @return
*/
public static String catToBase64(Mat correctMat) {
return bufferToBase64(toByteArray(correctMat));
}
/**
* 转换成base64编码
*
* @param buffer
* @return
*/
public static String bufferToBase64(byte[] buffer) {
return Base64Utils.encodeToString(buffer);
}
/**
* base64编码转换成字节数组
*
* @param base64Str
* @return
*/
public static byte[] base64ToByteArray(String base64Str) {
return Base64Utils.decodeFromString(base64Str);
}
/**
* base64 编码转换为 BufferedImage
*
* @param base64
* @return
*/
public static BufferedImage base64ToBufferedImage(String base64) {
BASE64Decoder Base64 = new BASE64Decoder();
try {
byte[] bytes1 = Base64.decodeBuffer(base64);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes1);
return ImageIO.read(bais);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* mat转换成bufferedImage
*
* @param matrix
* @return
*/
public static byte[] toByteArray(Mat matrix) {
MatOfByte mob = new MatOfByte();
Imgcodecs.imencode(".jpg", matrix, mob);
return mob.toArray();
}
/**
* mat转换成bufferedImage
*
* @param matrix
* @return
*/
public static BufferedImage toBufferedImage(Mat matrix) throws IOException {
byte[] buffer = toByteArray(matrix);
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
return ImageIO.read(bais);
}
/**
* base64转Mat
*
* @param base64
* @return
* @throws IOException
*/
public static Mat base642Mat(String base64) {
return bufImg2Mat(base64ToBufferedImage(base64), BufferedImage.TYPE_3BYTE_BGR, CvType.CV_8UC3);
}
/**
* BufferedImage转换成Mat
*
* @param original 要转换的BufferedImage
* @param imgType bufferedImage的类型 如 BufferedImage.TYPE_3BYTE_BGR
* @param matType 转换成mat的type 如 CvType.CV_8UC3
*/
public static Mat bufImg2Mat(BufferedImage original, int imgType, int matType) {
if (original == null) {
throw new IllegalArgumentException("original == null");
}
// Don't convert if it already has correct type
if (original.getType() != imgType) {
// Create a buffered image
BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);
// Draw the image onto the new buffer
Graphics2D g = image.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(original, 0, 0, null);
original = image;
} catch (Exception e) {
e.printStackTrace();
} finally {
g.dispose();
}
}
byte[] pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();
Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);
mat.put(0, 0, pixels);
return mat;
}
}
java
package com.demo.face;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@RestController
@RequestMapping
public class FaceController {
/**
* 获取图片中人脸个数
*
* @param base64Images base64图片,不包含逗号之前的部分
* @return
*/
@PostMapping("checkFaceNumber")
public int checkFaceNumber(String base64Images) {
return FaceUtils.checkFaceNo(base64Images);
}
/**
* 提取最大脸
*
* @param response
* @param base64Images
* @throws IOException
*/
@PostMapping("extractMaxFace")
public void extractMaxFace(HttpServletResponse response, String base64Images) throws IOException {
int indexOf = base64Images.indexOf(",");
if (indexOf > 0) {
base64Images = base64Images.substring(indexOf + 1);
}
response.setContentType("application/octet-stream");
PrintWriter writer = response.getWriter();
base64Images = FaceUtils.extractMaxFace(base64Images);
if (!StringUtils.hasLength(base64Images)) {
throw new RuntimeException("没有人脸");
}
writer.write("data:image/png;base64,".concat(base64Images));
writer.flush();
}
/**
* 标记脸
*
* @param response
* @param base64Images
* @throws IOException
*/
@PostMapping("markFace")
public void markFace(HttpServletResponse response, String base64Images) throws IOException {
int indexOf = base64Images.indexOf(",");
if (indexOf > 0) {
base64Images = base64Images.substring(indexOf + 1);
}
response.setContentType("application/octet-stream");
PrintWriter writer = response.getWriter();
base64Images = FaceUtils.markFace(base64Images);
writer.write("data:image/png;base64,".concat(base64Images));
writer.flush();
}
}
标记脸的效果如下:有框的是识别后的
找了一段视频
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4
居然也能识别,好神奇
绘制文字,绘制线
java
public static void main(String[] args) throws IOException {
Mat mat = Imgcodecs.imread("d:\\face\\1.jpg");
String path = System.getProperty("user.dir").concat("/haarcascades/haarcascade_frontalface_alt.xml");
CascadeClassifier faceDetector = new CascadeClassifier(path);
MatOfRect faceDetections = new MatOfRect();
faceDetector.detectMultiScale(mat, faceDetections);
if (faceDetections.toArray().length > 0) {
for (Rect rect : faceDetections.toList()) {
//绘制长方形
// Imgproc.rectangle(mat, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
//绘制文字
// Imgproc.putText(mat,"Face", new Point(rect.x, rect.y), 0,0.5,new Scalar(0, 255, 0),3);
//绘制线
Imgproc.line(mat, new Point(rect.x, rect.y), new Point(rect.x, rect.y + 20), new Scalar(0, 255, 0), 3);
Imgproc.line(mat, new Point(rect.x, rect.y), new Point(rect.x + 20, rect.y), new Scalar(0, 255, 0), 3);
Imgproc.line(mat, new Point(rect.x + rect.width, rect.y), new Point(rect.x + rect.width - 20, rect.y), new Scalar(0, 255, 0), 3);
Imgproc.line(mat, new Point(rect.x + rect.width, rect.y), new Point(rect.x + rect.width, rect.y + 20), new Scalar(0, 255, 0), 3);
Imgproc.line(mat, new Point(rect.x, rect.y + rect.height), new Point(rect.x, rect.y + rect.height - 20), new Scalar(0, 255, 0), 3);
Imgproc.line(mat, new Point(rect.x, rect.y + rect.height), new Point(rect.x + 20, rect.y + rect.height), new Scalar(0, 255, 0), 3);
Imgproc.line(mat, new Point(rect.x + rect.width, rect.y + rect.height), new Point(rect.x + rect.width, rect.y + rect.height - 20), new Scalar(0, 255, 0), 3);
Imgproc.line(mat, new Point(rect.x + rect.width, rect.y + rect.height), new Point(rect.x + rect.width - 20, rect.y + rect.height), new Scalar(0, 255, 0), 3);
}
}
Imgcodecs.imwrite("D:\\face\\1-new.jpg", mat);
}