⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7139739779519905806 「CodeDevMaster」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

OpenCV

效果预览

概述

OpenCV(开源计算机视觉库)是在BSD(开源协议)许可下发布的。它是一个高度优化的库,专注于实时应用程序。它具有C ++,Python和Java接口,支持Windows,Linux,Mac OS,iOS和Android。

下载与安装

下载地址:https://opencv.org/releases/

下载到本地后,双击进行安装即可

目录说明

安装目录如下

build :基于window构建

sources:开源,提供源码

build目录说明

这里是Java开发关注java目录即可

x64与x86代表给不同的系统使用

opencv-460.jar给java操作openvc的程序包

由于是64位系统,所以关注x64目录

DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。DLL文件,放置于系统中。当执行某一个程序时,相应的DLL文件就会被调用

OpenCV的基本使用

官网文档地址: https://docs.opencv.org/4.6.0/df/d65/tutorial_table_of_content_introduction.html

中文文档: http://wiki.opencv.org.cn/index.php

教程参考: https://www.w3cschool.cn/opencv/

教程参考: https://www.yiibai.com/opencv/opencv_adding_text.html

项目集成

这里使用IDEA进行开发,导入opencv-460.jar库

使用快捷键 Ctrl+Shift+Alt+S打开

选择库项,导入Java库。

除了上述方式,还可以将opencv-460.jar安装到本地仓库或私有仓库,然后在pom.xml中引入依赖。

图片人脸检测

public static void main(String[] args) {
imageFaceDetection();
}


/**
* 图片人脸检测
*/
public static void imageFaceDetection() {
// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
CascadeClassifier faceDetector = new CascadeClassifier("D:\\Development\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
// 读取测试图片
String imgPath = "D:\\user\\test.png";
Mat image = Imgcodecs.imread(imgPath);
if (image.empty()) {
throw new RuntimeException("图片内存为空");
}

// 检测脸部
MatOfRect face = new MatOfRect();
// 检测图像中的人脸
faceDetector.detectMultiScale(image, face);
// 匹配Rect矩阵
Rect[] rects = face.toArray();
System.out.println("识别人脸个数: " + rects.length);

// 识别图片中的所以人脸并分别保存
int i = 1;
for (Rect rect : face.toArray()) {
Imgproc.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
// 进行图片裁剪
imageCut(imgPath, "D:\\user\\" + i + ".jpg", rect.x, rect.y, rect.width, rect.height);
i++;
}
// 图片中人脸画框保存到本地
Imgcodecs.imwrite("D:\\user\\test1.png", image);

// 展示图片
HighGui.imshow("人脸识别", image);
HighGui.waitKey(0);
}

/**
* 裁剪人脸
*
* @param readPath 读取文件路径
* @param outPath 写出文件路径
* @param x 坐标X
* @param y 坐标Y
* @param width 截图宽度
* @param height 截图长度
*/
public static void imageCut(String readPath, String outPath, int x, int y, int width, int height) {
// 原始图像
Mat image = Imgcodecs.imread(readPath);
// 截取的区域
Rect rect = new Rect(x, y, width, height);
// Mat sub = new Mat(image,rect);
Mat sub = image.submat(rect);
Mat mat = new Mat();
Size size = new Size(width, height);
// 人脸进行截图并保存
Imgproc.resize(sub, mat, size);
Imgcodecs.imwrite(outPath, mat);
}

人脸对比相似度

对比1.jpg与1-1.jpg

对比1.jpg与3.jpg

// 初始化人脸探测器
static CascadeClassifier faceDetector;

static {
// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
faceDetector = new CascadeClassifier("D:\\Development\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
}

public static void main(String[] args) {
double comparison = faceRecognitionComparison("D:\\user\\1.jpg", "D:\\user\\1-1.jpg");
System.out.println("对比结果:" + comparison);
if (comparison > 0.85) {
System.out.println("人脸匹配成功");
} else {
System.out.println("人脸不匹配识别");
}

double comparison2 = faceRecognitionComparison("D:\\user\\1.jpg", "D:\\user\\3.jpg");
System.out.println("对比结果:" + comparison2);
if (comparison2 > 0.85) {
System.out.println("人脸匹配成功");
} else {
System.out.println("人脸不匹配识别");
}
// 终止当前运行的 Java 虚拟机。
System.exit(0);
}


/**
* 人脸识别比对
*/
public static double faceRecognitionComparison(String image1, String image2) {
Mat mat1 = conv_Mat(image1);
Mat mat2 = conv_Mat(image2);
Mat mat3 = new Mat();
Mat mat4 = new Mat();
// 颜色范围
MatOfFloat ranges = new MatOfFloat(0f, 256f);
// 直方图大小, 越大匹配越精确 (越慢)
MatOfInt histSize = new MatOfInt(1000);

Imgproc.calcHist(Arrays.asList(mat1), new MatOfInt(0), new Mat(), mat3, histSize, ranges);
Imgproc.calcHist(Arrays.asList(mat2), new MatOfInt(0), new Mat(), mat4, histSize, ranges);

// 比较两个密集或两个稀疏直方图
return Imgproc.compareHist(mat3, mat4, Imgproc.CV_COMP_CORREL);
}

/**
* 灰度化人脸
*/
public static Mat conv_Mat(String img) {
// 读取图像
Mat mat1 = Imgcodecs.imread(img);
Mat mat2 = new Mat();
// 灰度化:将图像从一种颜色空间转换为另一种颜色空间
Imgproc.cvtColor(mat1, mat2, Imgproc.COLOR_BGR2GRAY);
// 探测人脸:检测到的对象作为矩形列表返回
MatOfRect faceDetections = new MatOfRect();
faceDetector.detectMultiScale(mat1, faceDetections);
// rect中人脸图片的范围
for (Rect rect : faceDetections.toArray()) {
Mat face = new Mat(mat1, rect);
return face;
}
return null;
}

对比结果如下

对比结果:1.0
人脸匹配成功
对比结果:0.2501351968792374
人脸不匹配识别

识别视频中的人脸

// 初始化人脸探测器
static CascadeClassifier faceDetector;

static {
// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
faceDetector = new CascadeClassifier("D:\\Development\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
}

public static void main(String[] args) {
videoFaceRecognition();
// 终止当前运行的 Java 虚拟机。
System.exit(0);
}

/**
* 从视频中识别人脸
*/
public static void videoFaceRecognition() {
// 读取视频文件
VideoCapture capture = new VideoCapture();
capture.open("D:\\user\\test.mp4");
if (!capture.isOpened()) {
throw new RuntimeException("读取视频文件失败");
}

Mat video = new Mat();
int index = 0;
while (capture.isOpened()) {
// 抓取、解码并返回下一个视频帧写入Mat对象中
capture.read(video);
// 显示从视频中识别的人脸图像
HighGui.imshow("视频识别人脸", getFace(video));
// 获取键盘输入
index = HighGui.waitKey(100);
// 如果是 Esc 则退出
if (index == 27) {
capture.release();
return;
}
}
}

/**
* 从视频帧中识别人脸
*
* @param image 待处理Mat图片,即视频中的某一帧
* @return 处理后的图片
*/
public static Mat getFace(Mat image) {
MatOfRect face = new MatOfRect();
// 检测输入图像中不同大小的对象。检测到的对象作为矩形列表返回。
faceDetector.detectMultiScale(image, face);
Rect[] rects = face.toArray();
System.out.println("识别人脸个数: " + rects.length);
if (rects.length > 0 && Math.random() * 10 > 8) {
Imgcodecs.imwrite("D:\\user\\" + UUID.randomUUID() + ".png", image);
}

if (rects != null && rects.length >= 1) {
// 为每张识别到的人脸画一个圈
for (int i = 0; i < rects.length; i++) {
/**
* 绘制一个简单的、粗的或填充的直角矩形
*
* img 图像
* pt1 - 矩形的顶点
* pt2 - 与 pt1 相对的矩形的顶点
* color – 矩形颜色或亮度(灰度图像)意味着该函数必须绘制一个填充的矩形。
*/
Imgproc.rectangle(image, new Point(rects[i].x, rects[i].y), new Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height), new Scalar(0, 255, 0));
/**
* 绘制一个文本字符串,放在识别人脸框上
*
* img -- 图像
* text -- 要绘制的文本字符串
* org – 图像中文本字符串的左下角
* fontFace – 字体类型,请参阅#HersheyFonts
* fontScale – 字体比例因子乘以特定字体的基本大小
* color - 文本颜色
* thickness ——用于绘制文本的线条粗细
* lineType – 线型
* bottomLeftOrigin – 当为 true 时,图像数据原点位于左下角。否则,它位于左上角
*/
Imgproc.putText(image, "test", new Point(rects[i].x, rects[i].y), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA, false);
}
}
return image;
}

摄像头识别人脸

// 初始化人脸探测器
static CascadeClassifier faceDetector;

static {
// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
faceDetector = new CascadeClassifier("D:\\Development\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
}

public static void main(String[] args) throws Exception {
cameraFaceRecognition();
// 终止当前运行的 Java 虚拟机。
System.exit(0);
}

/**
* 摄像头实时人脸识别
*
* @throws Exception
*/
public static void cameraFaceRecognition() throws Exception {
// 打开摄像头获取视频流,0 打开默认摄像头
VideoCapture videoCapture = new VideoCapture(0);
// 检查是否支持摄像头 true:代表摄像头可以打开 false:不可以打开
System.out.println(videoCapture.isOpened());
// 获取摄像头高度
int height = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
// 获取摄像头宽度
int width = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH);
if (height == 0 || width == 0) {
throw new Exception("摄像头不存在");
}

Mat video = new Mat();
int index = 0;
if (videoCapture.isOpened()) {
while (true) {
videoCapture.read(video);
HighGui.imshow("实时人脸识别", getFace(video));
// 键盘输入
index = HighGui.waitKey(50);
// 是Esc则退出,比强制退出好
if (index == 27) {
// 写入人脸
Imgcodecs.imwrite("D:\\user\\" + "face.png", video);
videoCapture.release();
return;
}
}
}
}

/**
* 从视频帧中识别人脸
*
* @param image 待处理Mat图片,即视频中的某一帧
* @return 处理后的图片
*/
public static Mat getFace(Mat image) {
MatOfRect face = new MatOfRect();
// 检测输入图像中不同大小的对象。检测到的对象作为矩形列表返回。
faceDetector.detectMultiScale(image, face);
Rect[] rects = face.toArray();
System.out.println("识别人脸个数: " + rects.length);

if (rects != null && rects.length >= 1) {
// 为每张识别到的人脸画一个圈
for (int i = 0; i < rects.length; i++) {
// 绘制一个简单的、粗的或填充的直角矩形
Imgproc.rectangle(image, new Point(rects[i].x, rects[i].y), new Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height), new Scalar(0, 255, 0));
// 绘制一个文本字符串,放在识别人脸框上
Imgproc.putText(image, "test", new Point(rects[i].x, rects[i].y), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA, false);
}
}
return image;
}

自定义窗口

OpenCV带的HighGUI图形用户界面感觉可配置参数太少,因此可自定义窗口用于代替。

import org.opencv.core.Point;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public class MyJPanel extends JPanel {
private BufferedImage mImg;
// 初始化人脸探测器
static CascadeClassifier faceDetector;

static VideoCapture videoCapture;
static JFrame frame;

static {
// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
faceDetector = new CascadeClassifier("D:\\Development\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
}


public void paintComponent(Graphics g) {
if (mImg != null) {
g.drawImage(mImg, 0, 0, mImg.getWidth(), mImg.getHeight(), this);
}
}


/**
* 摄像头识别人脸
*/
public static void cameraFaceRecognition() throws Exception {
try {
// 打开摄像头获取视频流,0 打开默认摄像头
videoCapture = new VideoCapture(0);
// 检查是否支持摄像头 true:代表摄像头可以打开 false:不可以打开
System.out.println(videoCapture.isOpened());
// 获取摄像头高度
int height = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
// 获取摄像头宽度
int width = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH);
if (height == 0 || width == 0) {
throw new Exception("摄像头不存在");
}

//使用Swing生成GUI
frame = new JFrame("人脸识别");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
MyJPanel panel = new MyJPanel();
//设置中心显示
frame.setContentPane(panel);
frame.setVisible(true);
frame.setSize(width + frame.getInsets().left + frame.getInsets().right, height + frame.getInsets().top + frame.getInsets().bottom);
frame.setLocationRelativeTo(null);

// 创建矩阵
Mat capImg = new Mat();
// 创建一个临时矩阵
Mat temp = new Mat();
while (frame.isShowing()) {
//从摄像头读取一帧数据,保存到capImg矩阵中。
videoCapture.read(capImg);
//转换为彩色图
Imgproc.cvtColor(capImg, temp, Imgproc.COLOR_RGBA2BGRA);
// 人脸识别
capImg = getFace(capImg);
// 本地图片保存
Imgcodecs.imwrite("D:\\user\\1.jpg", capImg);
//转为图像显示
panel.mImg = panel.matToImage(capImg);
// 重绘此组件
panel.repaint();
}
} finally {
// 关闭摄像头
videoCapture.release();
frame.dispose();
}

}


/**
* 从视频帧中识别人脸
*
* @param image 待处理Mat图片,即视频中的某一帧
* @return 处理后的图片
*/
public static Mat getFace(Mat image) {
MatOfRect face = new MatOfRect();
// 检测输入图像中不同大小的对象。检测到的对象作为矩形列表返回。
faceDetector.detectMultiScale(image, face);
Rect[] rects = face.toArray();
System.out.println("识别人脸个数: " + rects.length);

if (rects != null && rects.length >= 1) {
// 为每张识别到的人脸画一个圈
for (int i = 0; i < rects.length; i++) {
// 绘制一个简单的、粗的或填充的直角矩形
Imgproc.rectangle(image, new org.opencv.core.Point(rects[i].x, rects[i].y), new org.opencv.core.Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height), new Scalar(0, 255, 0));
// 绘制一个文本字符串,放在识别人脸框上
Imgproc.putText(image, "test", new Point(rects[i].x, rects[i].y), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA, false);
}
}
return image;
}

/**
* 转换图像
*/
private BufferedImage matToImage(Mat mat) {
int dataSize = mat.cols() * mat.rows() * (int) mat.elemSize();
byte[] data = new byte[dataSize];
mat.get(0, 0, data);
int type = mat.channels() == 1 ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_3BYTE_BGR;
if (type == BufferedImage.TYPE_3BYTE_BGR) {
for (int i = 0; i < dataSize; i += 3) {
byte blue = data[i + 0];
data[i + 0] = data[i + 2];
data[i + 2] = blue;
}
}
BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);
image.getRaster().setDataElements(0, 0, mat.cols(), mat.rows(), data);
return image;
}
}

摄像头拍摄视频写入本地

public static void main(String[] args) throws Exception {
MyJPanel.cameraFaceRecognition();
// 终止当前运行的 Java 虚拟机。
System.exit(0);
}
// 初始化人脸探测器
static CascadeClassifier faceDetector;

static BufferedImage mImg;

static {
// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
// 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
faceDetector = new CascadeClassifier("D:\\Development\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml");
}

public static void main(String[] args) throws Exception {
writeVideo();
// 终止当前运行的 Java 虚拟机。
System.exit(0);
}

/**
* 摄像头拍摄视频写入本地
*/
public static void writeVideo() throws Exception {
// 打开摄像头获取视频流,0 打开默认摄像头
VideoCapture videoCapture = new VideoCapture(0);
// 检查是否支持摄像头 true:代表摄像头可以打开 false:不可以打开
System.out.println(videoCapture.isOpened());
// 获取摄像头高度
int height = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
// 获取摄像头宽度
int width = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH);
if (height == 0 || width == 0) {
throw new Exception("摄像头不存在");
}

Mat video = new Mat();
int index = 0;
Size size = new Size(videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH), videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT));
VideoWriter writer = new VideoWriter("D:\\user\\1.mp4", VideoWriter.fourcc('D', 'I', 'V', 'X'), 30.0, size, true);
while (videoCapture.isOpened()) {
//从摄像头读取一帧数据,保存到capImg矩阵中。
videoCapture.read(video);
writer.write(video);
HighGui.imshow("视频人脸识别", video);
// 获取键盘输入
index = HighGui.waitKey(100);
// 是Esc则退出,若强制退出将导致录制视频无法播放
if (index == 27) {
videoCapture.release();
writer.release();
return;
}
}
}

Spring Boot集成OpenCV

添加依赖

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>

项目集成OpenCV

项目集成OpenCV参考上述OpenCV的基本使用中的项目集成

请求接口

@Controller
@RequestMapping("/user")
public class UserFaceLogin {

@Autowired
private MyJPanel myJPanel;

@RequestMapping("/login")
public String login() throws Exception {
// 调用摄像头显示
boolean result = myJPanel.cameraFaceRecognition();
if (result) {
return "/success.html";
} else {
return "/error.html";
}
}
}

配置application.yml

开发环境与生产环境需区分

opencv:
lib:
linuxxmlpath: /usr/local/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml
windowxmlpath: D:\Development\opencv\sources\data\haarcascades\haarcascade_frontalface_alt.xml

指定虚拟机参数

-Djava.library.path=D:\Development\opencv\build\java\x64
或
-Djava.library.path=D:\Development\opencv\build\java\x64;D:\Development\opencv\build\x64\vc15\bin

OpenCvUtil

完成初始化工作以及添加人脸匹配功能,更多功能扩展此工具类即可。

@Component
public class OpenCvUtil implements CommandLineRunner {
// 初始化人脸探测器
static CascadeClassifier faceDetector;

@Value("${opencv.lib.linuxxmlpath}")
private String linuxXmlPath;
@Value("${opencv.lib.windowxmlpath}")
private String windowXmlPath;

/**
* 判断是否是Windows系统
*/
private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("win");


@Override
public void run(String... args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
String path = "";
if (IS_WINDOWS) {
path = windowXmlPath;
} else {
path = linuxXmlPath;
}
/**
* 初始化人脸探测器
*/
faceDetector = new CascadeClassifier(path);
}

public static int match(String loginImagePath, String comparedImagePath) {
Mat mat1 = conv_Mat(loginImagePath);
if (mat1 == null) {
return 0;
}

Mat mat2 = conv_Mat(comparedImagePath);
Mat mat3 = new Mat();
Mat mat4 = new Mat();
// 颜色范围
MatOfFloat ranges = new MatOfFloat(0f, 256f);
// 直方图大小, 越大匹配越精确 (越慢)
MatOfInt histSize = new MatOfInt(1000);

Imgproc.calcHist(Arrays.asList(mat1), new MatOfInt(0), new Mat(), mat3, histSize, ranges);
Imgproc.calcHist(Arrays.asList(mat2), new MatOfInt(0), new Mat(), mat4, histSize, ranges);

// 比较两个密集或两个稀疏直方图
Double score = Imgproc.compareHist(mat3, mat4, Imgproc.CV_COMP_CORREL);
System.out.println("score " + score);
if (score >= 0.8) {
return 1;
}
return 0;
}

public static Mat conv_Mat(String img) {
// 读取图像
Mat mat1 = Imgcodecs.imread(img);
Mat mat2 = new Mat();
// 灰度化:将图像从一种颜色空间转换为另一种颜色空间
Imgproc.cvtColor(mat1, mat2, Imgproc.COLOR_BGR2GRAY);
// 探测人脸:检测到的对象作为矩形列表返回
MatOfRect faceDetections = new MatOfRect();
faceDetector.detectMultiScale(mat1, faceDetections);
// rect中人脸图片的范围
for (Rect rect : faceDetections.toArray()) {
Mat face = new Mat(mat1, rect);
return face;
}
return null;
}
}

自定义窗口

自定义窗口用于实时获取摄像头拍摄画面

@Component
public class MyJPanel extends JPanel {

@Autowired
private OpenCvUtil openCvUtil;

private BufferedImage mImg;

private VideoCapture videoCapture;

private JFrame frame;

public void paintComponent(Graphics g) {
if (mImg != null) {
g.drawImage(mImg, 0, 0, mImg.getWidth(), mImg.getHeight(), this);
}
}


/**
* 摄像头识别人脸
*/
public Boolean cameraFaceRecognition() throws Exception {
try {
// 打开摄像头获取视频流,0 打开默认摄像头
videoCapture = new VideoCapture(0);
// 检查是否支持摄像头 true:代表摄像头可以打开 false:不可以打开
System.out.println(videoCapture.isOpened());
// 获取摄像头高度
int height = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_HEIGHT);
// 获取摄像头宽度
int width = (int) videoCapture.get(Videoio.CAP_PROP_FRAME_WIDTH);
if (height == 0 || width == 0) {
throw new Exception("摄像头不存在");
}

// 使用Swing生成GUI
frame = new JFrame("人脸识别");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
MyJPanel panel = new MyJPanel();
//设置中心显示
frame.setContentPane(panel);
frame.setVisible(true);
frame.setSize(width + frame.getInsets().left + frame.getInsets().right, height + frame.getInsets().top + frame.getInsets().bottom);
frame.setLocationRelativeTo(null);

// 创建矩阵
Mat capImg = new Mat();
// 创建一个临时矩阵
Mat temp = new Mat();
// 对比图片
String comparedImagePath = "D:\\user\\" + "compared.jpg";
// 摄像头拍摄图片
String loginImagePath = "D:\\user\\" + "login.jpg";
int tag = 0;
while (frame.isShowing() && tag < 5) {
tag++;
//从摄像头读取一帧数据,保存到capImg矩阵中。
videoCapture.read(capImg);
//转换为彩色图
Imgproc.cvtColor(capImg, temp, Imgproc.COLOR_RGBA2BGRA);
// 人脸识别
capImg = this.getFace(capImg);
// 本地图片保存
Imgcodecs.imwrite(loginImagePath, capImg);
//转为图像显示
panel.mImg = panel.matToImage(capImg);
// 重绘组件
panel.repaint();
int result = OpenCvUtil.match(loginImagePath, comparedImagePath);
if (result == 1) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭窗口
if (frame != null) {
frame.dispose();
}
// 关闭摄像头
if (videoCapture != null) {
videoCapture.release();
}
}
return false;
}


/**
* 从视频帧中识别人脸
*
* @param image 待处理Mat图片,即视频中的某一帧
* @return 处理后的图片
*/
public Mat getFace(Mat image) {
MatOfRect face = new MatOfRect();
// 检测输入图像中不同大小的对象。检测到的对象作为矩形列表返回。
openCvUtil.faceDetector.detectMultiScale(image, face);
Rect[] rects = face.toArray();
System.out.println("识别人脸个数: " + rects.length);

if (rects != null && rects.length >= 1) {
// 为每张识别到的人脸画一个圈
for (int i = 0; i < rects.length; i++) {
// 绘制一个简单的、粗的或填充的直角矩形
Imgproc.rectangle(image, new org.opencv.core.Point(rects[i].x, rects[i].y), new org.opencv.core.Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height), new Scalar(0, 255, 0));
// 绘制一个文本字符串,放在识别人脸框上
Imgproc.putText(image, "test", new Point(rects[i].x, rects[i].y), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA, false);
}
}
return image;
}

/**
* 转换图像
*/
private BufferedImage matToImage(Mat mat) {
int dataSize = mat.cols() * mat.rows() * (int) mat.elemSize();
byte[] data = new byte[dataSize];
mat.get(0, 0, data);
int type = mat.channels() == 1 ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_3BYTE_BGR;
if (type == BufferedImage.TYPE_3BYTE_BGR) {
for (int i = 0; i < dataSize; i += 3) {
byte blue = data[i + 0];
data[i + 0] = data[i + 2];
data[i + 2] = blue;
}
}
BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);
image.getRaster().setDataElements(0, 0, mat.cols(), mat.rows(), data);
return image;
}
}

创建页面

创建模拟人脸登录的页面Index.html以及人脸登录成功跳转页面success.html和人脸登录失败跳转页面error.html

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="index" class="tab-pane">
<a href="/user/login">人脸登录</a>
</div>
</body>
</html>

success.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<h3>人脸识别登录成功</h3>
</div>
</body>
</html>

error.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<h3>人脸识别登录失败</h3>
</div>
</body>
</html>

启动类配置

在开发过程中遇到一个异常,即使用自定义窗口时,需要修改启动类,设置.setHeadless(false),或添加JVM参数-Djava.awt.headless=false来解决。

@SpringBootApplication
public class FaceOpenCvApplication {

public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(FaceOpenCvApplication.class);
builder.headless(false).run(args);
}
}

常见异常记录

异常1

Exception in thread "main" java.lang.UnsatisfiedLinkError: no opencv_java460 in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
at java.lang.Runtime.loadLibrary0(Runtime.java:871)
at java.lang.System.loadLibrary(System.java:1122)

将D:\Development\opencv\build\java\x64\opencv_java460.dll文件拷贝至下面2个目录,任选其一即可。

异常2

java.lang.Exception: unknown exception
org.opencv.videoio.VideoCapture.VideoCapture_3(Native Method)
org.opencv.videoio.VideoCapture.<init>(VideoCapture.java:62)
com.boxuegu.servlet.UserFaceLogin.doGet(UserFaceLogin.java:25)
javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

配置类库路径

进入D:\Development\opencv\build\x64\vc15\bin,获取该路径

添加JVM运行参数配置

-Djava.library.path=D:\Development\opencv\build\java\x64

或者

-Djava.library.path=D:\Development\opencv\build\java\x64;D:\Development\opencv\build\x64\vc15\bin

异常3

没重启Tomcat,而是让Tomcat自动重启war包导致

java.lang.UnsatisfiedLinkError: Native Library D:\Development\opencv\build\java\x64\opencv_java460.dll already loaded in another classloader
java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1900)
java.lang.ClassLoader.loadLibrary(ClassLoader.java:1850)
java.lang.Runtime.loadLibrary0(Runtime.java:871)
java.lang.System.loadLibrary(System.java:1122)
com.boxuegu.servlet.UserFaceLogin.doGet(UserFaceLogin.java:24)
javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

异常4

Exception in thread "main" java.lang.UnsatisfiedLinkError: org.opencv.videoio.VideoCapture.VideoCapture_5(I)J
at org.opencv.videoio.VideoCapture.VideoCapture_5(Native Method)
at org.opencv.videoio.VideoCapture.<init>(VideoCapture.java:181)

别忘了加载OpenCV本地库

static {
// 加载OpenCV本地库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}

异常5

java.lang.UnsatisfiedLinkError: org.opencv.objdetect.CascadeClassifier.CascadeClassifier_1(Ljava/lang/String;)J
at org.opencv.objdetect.CascadeClassifier.CascadeClassifier_1(Native Method) ~[opencv-460.jar:4.6.0]
at org.opencv.objdetect.CascadeClassifier.<init>(CascadeClassifier.java:48) ~[opencv-460.jar:4.6.0]

受spring-boot-devtools依赖影响,最初排除此依赖,clean项目后正常。后来又加上此依赖,结果又不影响,注意当修改配置后没反应等异常情况还是多clean项目。

异常6

java.awt.HeadlessException
at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)
at java.awt.Window.<init>(Window.java:536)
at java.awt.Frame.<init>(Frame.java:420)
at javax.swing.JFrame.<init>(JFrame.java:233)

修改启动类,设置.setHeadless(false);

@SpringBootApplication
public class FaceOpenCvApplication {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(FaceOpenCvApplication.class);
builder.headless(false).run(args);
}
}

或者设置JVM虚拟机参数

-Djava.awt.headless=false
文章目录
  1. 1. OpenCV
    1. 1.1. 效果预览
    2. 1.2. 概述
    3. 1.3. 下载与安装
    4. 1.4. 目录说明
  2. 2. OpenCV的基本使用
    1. 2.1. 项目集成
    2. 2.2. 图片人脸检测
    3. 2.3. 人脸对比相似度
    4. 2.4. 识别视频中的人脸
    5. 2.5. 摄像头识别人脸
    6. 2.6. 自定义窗口
    7. 2.7. 摄像头拍摄视频写入本地
  3. 3. Spring Boot集成OpenCV
    1. 3.1. 添加依赖
    2. 3.2. 项目集成OpenCV
    3. 3.3. 请求接口
    4. 3.4. 配置application.yml
    5. 3.5. OpenCvUtil
    6. 3.6. 自定义窗口
    7. 3.7. 创建页面
    8. 3.8. 启动类配置
  4. 4. 常见异常记录
    1. 4.1. 异常1
    2. 4.2. 异常2
    3. 4.3. 异常3
    4. 4.4. 异常4
    5. 4.5. 异常5
    6. 4.6. 异常6