Práctica de cámara doce de JavaCV: detección de género

¡Acostúmbrate a escribir juntos! Este es el séptimo día de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

Bienvenido a mi GitHub

Todos los trabajos originales de Xinchen (incluido el código fuente de soporte) se clasifican y resumen aquí: github.com/zq2599/blog…

Resumen de este artículo

  • Este artículo es el duodécimo de la serie "JavaCV Camera Actual". Desarrollemos una función práctica: identifique el género y muéstrelo en la página de vista previa, como se muestra a continuación:

inserte la descripción de la imagen aquí

  • El código de hoy, la función principal se muestra en la siguiente figura:

inserte la descripción de la imagen aquí

  • Si ha leído otros artículos en la serie "JavaCV Camera Actual Combat", encontrará que solo la parte azul en la imagen de arriba es contenido nuevo, y el resto de los pasos son rutinas fijas. Cada aplicación en "JavaCV Camera Actual Combat" La serie "Combate" puede jugar Todos son la misma rutina: independientemente de la cantidad de pasos, en realidad es el mismo proceso

Acerca de la detección de sexo y edad

  • Más detalles técnicos sobre la inferencia de género y edad mediante redes neuronales convolucionales se explican con más detalle aquí:

talhassner.github.io/home/public…

  • Este artículo utilizará un modelo de Caffe entrenado, los datos para entrenar el modelo provienen de álbumes de Flickr, ensamblados mediante carga automática desde dispositivos de teléfonos inteligentes iPhone5 (o posteriores) y publicados por sus autores bajo una licencia Creative Commons (CC). un total de 26580 fotos, involucrando a 2284 personas, las edades de estas personas fueron identificadas en ocho grupos: (0-2, 4-6, 8-13, 15-20, 25-32, 38-43, 48-53, 60 -)
  • Para obtener más detalles sobre la fuente de datos, consulte: talhassner.github.io/home/projec…
  • Dirección en papel: talhassner.github.io/home/projec…

Descarga del código fuente

  • El código fuente completo de "JavaCV Face Recognition Trilogy” se puede descargar desde GitHub. La dirección y la información del enlace se muestran en la siguiente tabla ( github.com/zq2599/blog…
nombre Enlace Observación
página de inicio del proyecto github.com/zq2599/blog… 该项目在GitHub上的主页
git仓库地址(https) github.com/zq2599/blog… 该项目源码的仓库地址,https协议
git仓库地址(ssh) [email protected]:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在javacv-tutorials文件夹下,如下图红框所示:

inserte la descripción de la imagen aquí

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

inserte la descripción de la imagen aquí

准备:文件下载

  • 本次实战需要三个文件:
  1. 人脸检测的模型文件:raw.github.com/opencv/open…
  2. 性别识别的配置文件:raw.githubusercontent.com/GilLevi/Age…
  3. 性别识别的模型文件:raw.githubusercontent.com/GilLevi/Age…

准备:代码接口简介

  • 编码前,先把涉及到的所有java文件说明一下:
  1. AbstractCameraApplication.java:主程序的抽象类,这里面定义了打开摄像头、抓取每一帧、处理每一帧的基本框架,避免每个应用都把这些事情重复做一遍
  2. PreviewCameraWithGenderAge.java:主程序,是AbstractCameraApplication的实现类,本次实战的核心功能人脸检测和性别检测,都委托给它的成员变量detectService去完成
  3. DetectService.java:检测服务的接口,里面定义了几个重要的api,例如初始化、处理每一帧、释放资源等
  4. GenderDetectService.java:是DetectService接口的实现类,本次实战的核心功能都写在这个类中
  • 介绍完毕,可以开始编码了,先从最简单的主程序开始

编码:主程序

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

在这里插入图片描述

  • 新建文件PreviewCameraWithGenderAge.java,这是AbstractCameraApplication的子类,其代码很简单,接下来按上图顺序依次说明
  • 先定义CanvasFrame类型的成员变量previewCanvas,这是展示视频帧的本地窗口:
protected CanvasFrame previewCanvas
复制代码
  • 把前面创建的DetectService作为成员变量,后面检测的时候会用到:
    /**
     * 检测工具接口
     */
    private DetectService detectService;
复制代码
  • PreviewCameraWithGenderAge的构造方法,接受DetectService的实例:
    /**
     * 不同的检测工具,可以通过构造方法传入
     * @param detectService
     */
    public PreviewCameraWithGenderAge(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方法,代码如下,请注意AgeDetectService构造方法的三个入参,分别是前面下载的三个文件在本机的位置:
    public static void main(String[] args) {
        String base = "E:\\temp\\202112\\25\\opencv\\";
  
        DetectService detectService = new GenderDetectService(
                base + "haarcascade_frontalface_alt.xml",
                base + "gender\\deploy.prototxt",
                base + "gender\\gender_net.caffemodel");
                
        new PreviewCameraWithGenderAge(detectService).action(1000);
    }
复制代码
  • 主程序已经写完,接下来是核心功能

编码:服务接口回顾

  • 本篇的核心功能是检测性别,相关代码被封装在DetectService接口的实现类GenderDetectService中,这个DetectService接口是咱们的老朋友了,之前识别相关的实战都有它的身影,再来回顾一下,如下,定义了初始化、处理原始帧、释放资源等关键行为的接口:
package com.bolingcavalry.grabpush.extend;

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接口的实现类,也就是今天实战的核心:GenderDetectService.java

编码:检测服务实现

  • 今天的核心功能都集中在GenderDetectService.java中,直接贴出全部源码吧,有几处要注意的地方稍后会提到:
@Slf4j
public class GenderDetectService implements DetectService {

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

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

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

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

    /**
     * 人脸检测模型文件的下载地址
     */
    private String classifierModelFilePath;

    /**
     * 性别识别proto文件的下载地址
     */
    private String genderProtoFilePath;

    /**
     * 性别识别模型文件的下载地址
     */
    private String genderModelFilePath;

    /**
     * 推理性别的神经网络对象
     */
    private Net cnnNet;

    /**
     * 构造方法,在此指定proto和模型文件的下载地址
     * @param classifierModelFilePath
     * @param cnnProtoFilePath
     * @param cnnModelFilePath
     */
    public GenderDetectService(String classifierModelFilePath,
                               String cnnProtoFilePath,
                               String cnnModelFilePath) {
        this.classifierModelFilePath = classifierModelFilePath;
        this.genderProtoFilePath = cnnProtoFilePath;
        this.genderModelFilePath = cnnModelFilePath;
    }

    /**
     * 初始化操作,主要是创建推理用的神经网络
     * @throws Exception
     */
    @Override
    public void init() throws Exception {
        // 根据模型文件实例化分类器
        classifier = new CascadeClassifier(classifierModelFilePath);
        // 实例化推理性别的神经网络
        cnnNet = readNetFromCaffe(genderProtoFilePath, genderModelFilePath);
    }

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

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

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

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

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

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

        // 如果没有检测到结果,就用原始帧返回
        if (total<1) {
            return frame;
        }

        int pos_x;
        int pos_y;

        Mat faceMat;

        //推理时的入参
        Mat inputBlob;

        // 推理结果
        Mat prob;

        // 如果有检测结果,就根据结果的数据构造矩形框,画在原图上
        for (long i = 0; i < total; i++) {
            Rect r = objects.get(i);

            // 人脸对应的Mat实例(注意:要用彩图,不能用灰度图!!!)
            faceMat = new Mat(grabbedImage, r);
            // 缩放到神经网络所需的尺寸
            resize(faceMat, faceMat, new Size(Constants.CNN_PREIDICT_IMG_WIDTH, Constants.CNN_PREIDICT_IMG_HEIGHT));
            // 归一化
            normalize(faceMat, faceMat, 0, Math.pow(2, frame.imageDepth), NORM_MINMAX, -1, null);
            // 转为推理时所需的的blob类型
            inputBlob = blobFromImage(faceMat);
            // 为神经网络设置入参
            cnnNet.setInput(inputBlob, "data", 1.0, null);      //set the network input
            // 推理
            prob = cnnNet.forward("prob");

            // 根据推理结果得到在人脸上标注的内容
            String lable = getDescriptionFromPredictResult(prob);

            // 人脸标注的横坐标
            pos_x = Math.max(r.tl().x()-10, 0);
            // 人脸标注的纵坐标
            pos_y = Math.max(r.tl().y()-10, 0);

            // 给人脸做标注,标注性别
            putText(grabbedImage, lable, new Point(pos_x, pos_y), FONT_HERSHEY_PLAIN, 1.5, new Scalar(0,255,0,2.0));

            // 给人脸加边框时的边框位置
            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);
    }

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

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

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

        if (null!= cnnNet) {
            cnnNet.close();
        }
    }

    /**
     * 根据推理结果得到在头像上要标注的内容
     * @param prob
     * @return
     */
    protected String getDescriptionFromPredictResult(Mat prob) {
        Indexer indexer = prob.createIndexer();

        // 比较两种性别的概率,概率大的作为当前头像的性别
        return indexer.getDouble(0,0) > indexer.getDouble(0,1)
               ? "male"
               : "female";
    }
}
复制代码
  • 上述代码,有以下几处需要注意的:
  1. 构造方法的三个入参:classifierModelFilePath、cnnProtoFilePath、cnnModelFilePath分别是人脸检测模型、性别检测配置、性别检测模型三个文件的本地存放地址
  2. 检测性别靠的是卷积神经网络的推理,初始化的时候通过readNetFromCaffe方法新建神经网络对象
  3. convert方法被调用时,会收到摄像头捕捉的每一帧,在这里面先检测出每个人脸,再拿每个人脸去神经网络进行推理
  4. 用神经网络的推理结果生成人脸的标注内容,这段逻辑被放入getDescriptionFromPredictResult,下一篇《年龄检测》的实战同样是使用神经网络推理头像的年龄,咱们只要写一个GenderDetectService,并重写getDescriptionFromPredictResult方法,里面的逻辑改成根据推理结果得到年龄,即可轻松完成任务,其他类都可以维持不变
  • 至此,编码完成,接下来开始验证

验证

  • 确保摄像头工作正常,运行PreviewCameraWithGenderAge类的main方法
  • 请群众演员登场,让他站在摄像头前,如下图,性别识别成功,且实时展示:

在这里插入图片描述

  • En este punto, la función de integración de detección de rostros y detección de género en la vista previa de la ventana local se ha completado. Gracias al poder de JavaCV, todo el proceso es tan fácil y agradable. A continuación, continúe prestando atención a Xinchen Original, el La serie "JavaCV Camera Actual Combat" también presentará aplicaciones más ricas;
  • Gracias a los extensos preparativos realizados en este artículo, el próximo artículo "Detección de la edad" será más sencillo, esperemos juntos el próximo viaje relajado y feliz;

Bienvenido a los Nuggets: Programador Xin Chen

En el camino del aprendizaje, no estás solo, Xinchen Original te acompañará en todo el camino...

Supongo que te gusta

Origin juejin.im/post/7083658639344418823
Recomendado
Clasificación