上一文,我们讲到的是使用JavaCV拉取笔记本摄像头画面,这次,我们基于上一次的基础,加工人脸识别功能。
原理很简单,拉取摄像头每一帧,然后对每一帧的画面进行识别,看里面是否有人脸在里面,有的话就对人脸进行绘框,把红框绘制在画面上,然后返回给前面进行展示。
人脸识别功能,需要借助一个叫haarcascade_frontalface_alt.xml的文件,它是OpenCV中已经训练好的人脸分类器文件,也叫正脸识别分类器,我们把它下载到本地,放在resource下,然后使用CascadeClassifier进行加载。
然后各种崩溃的问题来了,不是xml文件加载问题,就是chatgpt给的方法包装有问题。
有haarcascade_frontalface_alt.xml加载错误问题
也有Mat转换问题
好在我这么问,它也能自己去检测问题
各种兜兜转转,花了一天的时间,终于把它矫正了,如果是去百度找一份别人写好的案例,或许五分钟都不需要,但是那样很难的去真正学到什么。
接下来讲一下人脸识别的步骤吧!
使用OpenCVFrameGrabber
对象获取本机摄像头,指定分辨率
//获取本地摄像头,一般是0
OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);
// 摄像头有可能有多个分辨率,这里指定
// 可以指定宽高,也可以不指定反而调用grabber.getImageWidth去获取,
grabber.setImageWidth(1280);
grabber.setImageHeight(720);
// 开启抓取器
grabber.start();
使用CanvasFrame
对象创建一个窗口用来展示视频流
//创建一个Frame窗口
CanvasFrame previewCanvas = new CanvasFrame("摄像头预览", CanvasFrame.getDefaultGamma() / grabber.getGamma());
previewCanvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
previewCanvas.setAlwaysOnTop(true);
使用CascadeClassifier
对象加载人脸识别模型
// 下载模型文件
CascadeClassifier cascadeClassifier = new CascadeClassifier("D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\haarcascade_frontalface_alt.xml");
遍历每一帧,将帧对象转为IplImage对象,然后转成Mat对象
// 遍历每一帧
while ((captureFrame = grabber.grab()) != null) {
// 将帧对象转为IplImage对象
IplImage img = openCVConverter.convert(captureFrame);
// 镜像翻转
cvFlip(img, img, 1);
// IplImage转mat
Mat mat1 = new Mat(img);
......
}
将图片转成灰度图片,原因是灰度图片更适合识别人脸,所消耗的性能比较低
// 将图像转为灰度图像
Mat grayMat = new Mat();
// 当前图片转为灰度图片
cvtColor(mat1, grayMat, CV_BGR2GRAY);
创建检测结果的容器,然后开始检测
// 存放检测结果的容器
RectVector objects = new RectVector();
// 开始检测
if (cascadeClassifier.empty()) {
System.out.println("Failed to load cascade classifier.");
}
cascadeClassifier.detectMultiScale(grayMat, objects);
获取检测结果,并且为其绘画红框
// 检测结果总数
long total = objects.size();
// 如果有检测结果,就根据结果的数据构造矩形框,画在原图上
for (long i = 0; i < total; i++) {
Rect r = objects.get(i);
int x = r.x(), y = r.y(), w = r.width(), h = r.height();
rectangle(mat1, new Point(x, y), new Point(x + w, y + h), Scalar.RED, 1, CV_AA, 0);
}
释放资源,将绘好红框的画面返回给前面
// 释放检测结果资源
objects.close();
// 将标注过的图片转为帧,返回
Frame convert = openCVConverter.convert(mat1);
// 显示图像
previewCanvas.showImage(convert);
人脸识别就到此结束了。
第一步肯定是引入maven依赖
一个是JavaCV依赖,一个是lombok依赖,因为打算使用@Slf4j
<!-- javacv相关依赖,一个就够了 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
然后是人脸识别代码
package com.gateway.link.cv.local;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import javax.swing.*;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import static cn.hutool.core.util.ClassUtil.getClassLoader;
import static org.bytedeco.opencv.global.opencv_core.cvFlip;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
/**
* @author YESIJIE
* @date 2023-07-16 16:16
*/
@Slf4j
public class localVideFace {
/**
* 输出帧率
*/
private static final double frameRate = 30;
/**
* 摄像头视频的宽
*/
private static final int cameraImageWidth = 1280;
/**
* 摄像头视频的高
*/
private static final int cameraImageHeight = 720;
/**
* 转换器
*/
private static final OpenCVFrameConverter.ToIplImage openCVConverter = new OpenCVFrameConverter.ToIplImage();
public static void main(String[] args) throws Exception
{
long startTime = System.currentTimeMillis();
// 设置ffmepg日志级别
avutil.av_log_set_level(avutil.AV_LOG_INFO);
FFmpegLogCallback.set();
//获取本地摄像头,一般是0
OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);
// 摄像头有可能有多个分辨率,这里指定
// 可以指定宽高,也可以不指定反而调用grabber.getImageWidth去获取,
grabber.setImageWidth(1280);
grabber.setImageHeight(720);
// 开启抓取器
grabber.start();
//创建一个Frame窗口
CanvasFrame previewCanvas = new CanvasFrame("摄像头预览", CanvasFrame.getDefaultGamma() / grabber.getGamma());
previewCanvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
previewCanvas.setAlwaysOnTop(true);
log.info("初始化完成,耗时[{}]毫秒,帧率[{}],图像宽度[{}],图像高度[{}]",
System.currentTimeMillis()-startTime,
frameRate,
cameraImageWidth,
cameraImageHeight);
// 添加水印时用到的时间工具
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 水印在图片上的位置
org.bytedeco.opencv.opencv_core.Point point = new org.bytedeco.opencv.opencv_core.Point(15, 35);
Frame captureFrame;
// 下载模型文件
CascadeClassifier cascadeClassifier = new CascadeClassifier("D:\\IDEA_Work\\LinkCV\\src\\main\\resources\\haarcascade_frontalface_alt.xml");
// 遍历每一帧
while ((captureFrame = grabber.grab()) != null) {
// 将帧对象转为IplImage对象
IplImage img = openCVConverter.convert(captureFrame);
// 镜像翻转
cvFlip(img, img, 1);
// IplImage转mat
Mat mat1 = new Mat(img);
// 在图片上添加水印,水印内容是当前时间,位置是左上角
opencv_imgproc.putText(mat1,
simpleDateFormat.format(new Date()),
point,
opencv_imgproc.CV_FONT_VECTOR0,
0.8,
new Scalar(0, 200, 255, 0),
1,
0,
false);
// 将图像转为灰度图像
Mat grayMat = new Mat();
// 当前图片转为灰度图片
cvtColor(mat1, grayMat, CV_BGR2GRAY);
// 存放检测结果的容器
RectVector objects = new RectVector();
// 开始检测
if (cascadeClassifier.empty()) {
System.out.println("Failed to load cascade classifier.");
}
cascadeClassifier.detectMultiScale(grayMat, objects);
// 检测结果总数
long total = objects.size();
// 如果有检测结果,就根据结果的数据构造矩形框,画在原图上
for (long i = 0; i < total; i++) {
Rect r = objects.get(i);
int x = r.x(), y = r.y(), w = r.width(), h = r.height();
rectangle(mat1, new Point(x, y), new Point(x + w, y + h), Scalar.RED, 1, CV_AA, 0);
}
// 释放检测结果资源
objects.close();
// 将标注过的图片转为帧,返回
Frame convert = openCVConverter.convert(mat1);
// 显示图像
previewCanvas.showImage(convert);
}
log.info("输出结束");
if (null!= previewCanvas) {
previewCanvas.dispose();
}
if (null!=grabber) {
try {
grabber.close();
} catch (Exception exception) {
log.error("close grabber error", exception);
}
}
}
}
效果大概是下面这样的,会在视频帧上面对于识别到的脸部部位绘画一个框,然后推送出去展示。由于本人没颜值,所以给自己打码了。
关于haarcascade_frontalface_alt模型,可以去github直接下载
https://github.com/opencv/opencv/tree/master/data/haarcascades