One of the JavaCV face recognition trilogy: save the face in the video as a picture

Get into the habit of writing together! This is the 4th day of my participation in the "Nuggets Daily New Plan·April Update Challenge", click to view the details of the event

Welcome to my GitHub

All original works of Xinchen (including supporting source code) are classified and summarized here: github.com/zq2599/blog…

About face recognition

  • This article is the first of the "JavaCV Face Recognition Trilogy". In the article "JavaCV's Camera Practice Eight: Face Detection" , the detection of the face is realized and marked with a box, but only the role of the face is framed. It's not big, it's best to identify the person's identity, so that you can do a lot of things, such as sign-in, alarm, etc. This is the face recognition we will challenge next.
  • Face recognition involves two steps: training and recognition, and then briefly explain
  • Let's first look at what training is, as shown in the figure below, using six photos of the two kings for training, a total of two categories, after the training is completed, the model file faceRecognizer.xml is obtained:

insert image description here

  • After the training is successful, we take a new photo for the model to identify, and the result obtained is the category during training. After the identification is completed, we have determined the identity of the new photo:

insert image description here

  • The training and recognition are described in more detail below with a flowchart:

insert image description here

About "JavaCV Face Recognition Trilogy"

"JavaCV Face Recognition Trilogy" consists of three articles, the contents are as follows:

  1. "Save the face in the video as a picture": This article introduces how to detect each face in the camera through JavaCV, and save each face as a picture, this method allows us to quickly obtain a large number of face photos for use in training
  2. "Training": how to use the classified photos to train the model
  3. "Recognition and Preview": Get the trained model, identify the face in each frame of the video, and mark the result on the picture for preview
  • The entire trilogy is also part of the "JavaCV Camera Actual Combat" series, which are the ninth, tenth, and eleventh chapters of the "JavaCV Camera Actual Combat" series.

Overview of this article

  • The thing to do in this article is to prepare the photos for training
  • 您可能会疑惑:我自己去找一些照片不就行了吗?去网上搜、去相册搜、去拍照不都可以吗?没错,只要找到您想识别的人脸即可,而本篇介绍的是另一种方法:借助摄像头检测人脸,然后将人脸大小的照片保存在硬盘,用这些照片来训练,实测多张照片训练处的模型在检测新照片时效果更好
  • 具体做法如下:
  1. 写个程序,对摄像头的照片做人脸检测,每个检测到的人脸,都作一张图片保存,注意不是摄像头视频帧的完整图片,而是检测出每张人脸,把这个人脸的矩形作为图片保存,而且保存的是灰度图片,不是彩色图片(训练和检测只需要灰度图片)
  2. 然后找个没人的地方运行程序,一个人对着摄像头,开始......搔首弄姿,各种光线明暗、各种角度、各种表情都用上,作为图片保存
  • 用这些图片训练出的模型,由于覆盖了各种亮度、角度、表情,最终的识别效果会更好
  • 接下来我们就来写这段程序吧

源码下载

  • 《JavaCV的摄像头实战》的完整源码可在GitHub下载到,地址和链接信息如下表所示(github.com/zq2599/blog…
名称 链接 备注
项目主页 github.com/zq2599/blog… 该项目在GitHub上的主页
git仓库地址(https) github.com/zq2599/blog… 该项目源码的仓库地址,https协议
git仓库地址(ssh) [email protected]:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在javacv-tutorials文件夹下,如下图红框所示:

insert image description here

  • javacv-tutorials里面有多个子工程,《JavaCV的摄像头实战》系列的代码在simple-grab-push工程下:

insert image description here

编码:检测服务

  • 先定义一个检测有关的接口DetectService.java,如下,主要是定义了三个方法init、convert、releaseOutputResource,其中init用于初始化检测服务,convert负责处理单个帧(本篇就是检测出人脸、把人脸照片保存在硬盘),releaseOutputResource在结束的时候被执行,用于释放资源,另外还有个静态方法buildGrayImage,很简单,生成灰度图片对应的Mat对象:
public interface DetectService {

    /**
     * 根据传入的MAT构造相同尺寸的MAT,存放灰度图片用于以后的检测
     * @param src 原始图片的MAT对象
     * @return 相同尺寸的灰度图片的MAT对象
     */
    static Mat buildGrayImage(Mat src) {
        return new Mat(src.rows(), src.cols(), CV_8UC1);
    }
    
    /**
     * 初始化操作,例如模型下载
     * @throws Exception
     */
    void init() throws Exception;

    /**
     * 得到原始帧,做识别,添加框选
     * @param frame
     * @return
     */
    Frame convert(Frame frame);

    /**
     * 释放资源
     */
    void releaseOutputResource();
}
复制代码
  • 然后就是DetectService的实现类DetectAndSaveService.java,完整代码如下,有几处要注意的地方稍后提到:
@Slf4j
public class DetectAndSaveService implements DetectService {

    /**
     * 每一帧原始图片的对象
     */
    private Mat grabbedImage = null;

    /**
     * 原始图片对应的灰度图片对象
     */
    private Mat grayImage = null;

    /**
     * 分类器
     */
    private CascadeClassifier classifier;

    /**
     * 转换器
     */
    private OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();

    /**
     * 模型文件的下载地址
     */
    private String modelFileUrl;

    /**
     * 存放人脸图片的位置
     */
    private String basePath;

    /**
     * 记录图片总数
     */
    private final AtomicInteger num = new AtomicInteger();

    /**
     * 训练的图片尺寸
     */
    Size size = new Size(Constants.RESIZE_WIDTH, Constants.RESIZE_HEIGHT);

    public DetectAndSaveService(String modelFileUrl, String basePath) {
        this.modelFileUrl = modelFileUrl;
        
        // 图片保存在硬盘的位置,注意文件名的固定前缀是当前的年月日时分秒
        this.basePath = basePath
                      + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
                      + "-";
    }

    /**
     * 音频采样对象的初始化
     * @throws Exception
     */
    @Override
    public void init() throws Exception {
        // 下载模型文件
        URL url = new URL(modelFileUrl);

        File file = Loader.cacheResource(url);

        // 模型文件下载后的完整地址
        String classifierName = file.getAbsolutePath();

        // 根据模型文件实例化分类器
        classifier = new CascadeClassifier(classifierName);

        if (classifier == null) {
            log.error("Error loading classifier file [{}]", classifierName);
            System.exit(1);
        }
    }

    @Override
    public Frame convert(Frame frame) {
        // 由帧转为Mat
        grabbedImage = converter.convert(frame);

        // 灰度Mat,用于检测
        if (null==grayImage) {
            grayImage = DetectService.buildGrayImage(grabbedImage);
        }

        String filePath = basePath + num.incrementAndGet();

        // 进行人脸识别,根据结果做处理得到预览窗口显示的帧
        return detectAndSave(classifier, converter, frame, grabbedImage, grayImage, filePath , size);
    }

    /**
     * 程序结束前,释放人脸识别的资源
     */
    @Override
    public void releaseOutputResource() {
        if (null!=grabbedImage) {
            grabbedImage.release();
        }

        if (null!=grayImage) {
            grayImage.release();
        }

        if (null==classifier) {
            classifier.close();
        }
    }

    static Frame detectAndSave(CascadeClassifier classifier,
                               OpenCVFrameConverter.ToMat converter,
                               Frame rawFrame,
                               Mat grabbedImage,
                               Mat grayImage,
                               String basePath,
                               Size size) {

        // 当前图片转为灰度图片
        cvtColor(grabbedImage, grayImage, CV_BGR2GRAY);

        // 存放检测结果的容器
        RectVector objects = new RectVector();

        // 开始检测
        classifier.detectMultiScale(grayImage, objects);

        // 检测结果总数
        long total = objects.size();

        // 如果没有检测到结果就提前返回
        if (total<1) {
            return rawFrame;
        }

        // 假设现在是一个人对着摄像头,因为此时检测的结果如果大于1,显然是检测有问题
        if (total>1) {
            return rawFrame;
        }

        Mat faceMat;

        // 如果有检测结果,就根据结果的数据构造矩形框,画在原图上
        // 前面的判断确保了此时只有一个人脸
        Rect r = objects.get(0);

        // 从完整的灰度图中取得一个矩形小图的Mat对象
        faceMat = new Mat(grayImage, r);

        // 训练时用的图片尺寸是固定的,因此这里要调整大小
        resize(faceMat, faceMat, size);

        // 图片的保存位置
        String imagePath = basePath + "." + Constants.IMG_TYPE;

        // 保存图片到硬盘
        imwrite(imagePath, faceMat);

        // 人脸的位置信息
        int x = r.x(), y = r.y(), w = r.width(), h = r.height();

        // 在人脸上画矩形
        rectangle(grabbedImage, new Point(x, y), new Point(x + w, y + h), Scalar.RED, 1, CV_AA, 0);

        // 释放检测结果资源
        objects.close();

        // 将标注过的图片转为帧,返回
        return converter.convert(grabbedImage);
    }
}
复制代码
  • 上述代码有几处要注意:
  1. detectAndSave方法中,当前照片检测出的人脸数如果大于1就提前返回不做处理了,这是因为假定运行程序的时候,摄像头前面只有一个人,所以如果检测出超过一张人脸,就认为当前照片的检测不准确,就不再处理当前照片了(实际使用中发现常有检测失误的情况,例如把一个矩形盒子检测为人脸),这个提前返回的逻辑,您可以根据自己的环境去调整
  2. imwrite方法可以将Mat以图片的形式保存到硬盘
  3. 保存文件到磁盘前调用了resize方法,将图片调整为164*164大小,这是因为后面的训练和检测统一使用该尺寸
  • 现在核心代码已经写完,需要再写一些代码来使用DetectAndSaveService

编码:运行框架

  • 《JavaCV的摄像头实战之一:基础》创建的simple-grab-push工程中已经准备好了父类AbstractCameraApplication,所以本篇继续使用该工程,创建子类实现那些抽象方法即可
  • 编码前先回顾父类的基础结构,如下图,粗体是父类定义的各个方法,红色块都是需要子类来实现抽象方法,所以接下来,咱们以本地窗口预览为目标实现这三个红色方法即可:

insert image description here

  • 新建文件PreviewCameraWithDetectAndSave.java,这是AbstractCameraApplication的子类,其代码很简单,接下来按上图顺序依次说明
  • 先定义CanvasFrame类型的成员变量previewCanvas,这是展示视频帧的本地窗口:
protected CanvasFrame previewCanvas
复制代码
  • 把前面创建的DetectService作为成员变量,后面检测的时候会用到:
    /**
     * 检测工具接口
     */
    private DetectService detectService;
复制代码
  • PreviewCameraWithDetectAndSave的构造方法,接受DetectService的实例:
    /**
     * 不同的检测工具,可以通过构造方法传入
     * @param detectService
     */
    public PreviewCameraWithDetectAndSave(DetectService detectService) {
        this.detectService = detectService;
    }
复制代码
  • 然后是初始化操作,可见是previewCanvas的实例化和参数设置:
    @Override
    protected void initOutput() throws Exception {
        previewCanvas = new CanvasFrame("摄像头预览,检测人脸并保存在硬盘", CanvasFrame.getDefaultGamma() / grabber.getGamma());
        previewCanvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        previewCanvas.setAlwaysOnTop(true);

        // 检测服务的初始化操作
        detectService.init();
    }
复制代码
  • 接下来是output方法,定义了拿到每一帧视频数据后做什么事情,这里调用了detectService.convert检测人脸并保存图片,然后在本地窗口显示:
    @Override
    protected void output(Frame frame) {
        // 原始帧先交给检测服务处理,这个处理包括物体检测,再将检测结果标注在原始图片上,
        // 然后转换为帧返回
        Frame detectedFrame = detectService.convert(frame);
        // 预览窗口上显示的帧是标注了检测结果的帧
        previewCanvas.showImage(detectedFrame);
    }
复制代码
  • 最后是处理视频的循环结束后,程序退出前要做的事情,先关闭本地窗口,再释放检测服务的资源:
    @Override
    protected void releaseOutputResource() {
        if (null!= previewCanvas) {
            previewCanvas.dispose();
        }

        // 检测工具也要释放资源
        detectService.releaseOutputResource();
    }
复制代码
  • 由于检测有些耗时,所以两帧之间的间隔时间要低于普通预览:
    @Override
    protected int getInterval() {
        return super.getInterval()/8;
    }
复制代码
  • 至此,功能已开发完成,再写上main方法,在实例化DetectAndSaveService的时候注意入参有两个,第一个是人脸检测模型的下载地址,第二个是人脸照片保存在本地的位置,还有action方法的参数1000表示预览持续时间是1000秒:
    public static void main(String[] args) {
        String modelFileUrl = "https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml";
        new PreviewCameraWithDetectAndSave(
                new DetectAndSaveService(
                        modelFileUrl, 
                        "E:\\temp\\202112\\18\\001\\man"))
                .action(1000);
    }
复制代码

抓取第一个人的照片

  • 运行main方法,然后请群众演员A登场,看着他一个人对着摄像头,开始......搔首弄姿,各种光线明暗、各种角度、各种表情都用上吧,哎,不忍直视...
  • 由于开启了预览窗口,因此可以看到摄像头拍摄的效果,出现红框的矩形最终都会被保存为图片,请注意调整角度和表情,群众演员A好像很热衷于自拍,玩得不亦乐乎,好吧,让他放飞自我:

insert image description here

  • 检测的图片到了一定数量就可以结束了,我这里保存了259张,如下图:

insert image description here

  • For the above photos, it is recommended to check all the photos with the naked eye, and delete all the non-human faces. I found more than ten photos that are not human faces. For example, the following photo identifies a part of the face as a human face. If there is a problem, delete such photos, do not use them for training:

insert image description here

  • All the above photos are saved in the directory E:\temp\202112\18\001\man

Grab a photo of the second person

  • Modify the code, change the directory where the pictures are stored in the main method to E:\temp\202112\18\001\woman, then run the program again, invite extra actor B to appear, and beg her to be alone like the previous extra actor Camera, start... Scratch your head and pose , use all kinds of light and shade, various angles, and various expressions.
  • Therefore, we successfully obtained a large number of face pictures of the second extra, remember to observe each picture with the naked eye, and delete all inaccurate pictures.
  • So far, with the help of the program written above, we have easily obtained a large number of face photos of the two extras. The photos of A are saved in E:\temp\202112\18\001\ man , and the photos of B are saved in E:\temp \202112\18\001\ woman :

insert image description here

  • So far, the task of this article has been completed, and the next article will use these photos for training to prepare for the final recognition;

Welcome to the Nuggets: Programmer Xin Chen

On the road of learning, you are not alone, Xinchen Original will accompany you all the way...

Guess you like

Origin juejin.im/post/7082797687921180679