欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《JavaCV的摄像头实战》系列的第十二篇,咱们来开发一个实用功能:识别性别并显示在预览页面,如下图:

  • 今天的代码,主要功能如下图所示:

  • 如果您看过《JavaCV的摄像头实战》系列的其他文章,就会发现上图中只有蓝色部分是新增内容,其余的步骤都是固定套路,《JavaCV的摄像头实战》系列的每一个应用玩的都是相同套路:别看步骤挺多,其实都是同一个流程

关于性别和年龄检测

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在javacv-tutorials文件夹下,如下图红框所示:

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

准备:文件下载

  • 本次实战需要三个文件:
  1. 人脸检测的模型文件:https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml
  2. 性别识别的配置文件:https://raw.githubusercontent.com/GilLevi/AgeGenderDeepLearning/master/gender_net_definitions/deploy.prototxt
  3. 性别识别的模型文件:https://raw.githubusercontent.com/GilLevi/AgeGenderDeepLearning/master/models/gender_net.caffemodel

准备:代码接口简介

  • 编码前,先把涉及到的所有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中,直接贴出全部源码吧,有几处要注意的地方稍后会提到:
package com.bolingcavalry.grabpush.extend;

import com.bolingcavalry.grabpush.Constants;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.indexer.Indexer;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_dnn.Net;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier; import static org.bytedeco.opencv.global.opencv_core.NORM_MINMAX;
import static org.bytedeco.opencv.global.opencv_core.normalize;
import static org.bytedeco.opencv.global.opencv_dnn.blobFromImage;
import static org.bytedeco.opencv.global.opencv_dnn.readNetFromCaffe;
import static org.bytedeco.opencv.global.opencv_imgproc.*; /**
* @author willzhao
* @version 1.0
* @description 音频相关的服务
* @date 2021/12/3 8:09
*/
@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方法
  • 请群众演员登场,让他站在摄像头前,如下图,性别识别成功,且实时展示:

  • 至此,本地窗口预览集成人脸检测和性别检测的功能就完成了,得益于JavaCV的强大,整个过程是如此的轻松愉快,接下来请继续关注欣宸原创,《JavaCV的摄像头实战》系列还会呈现更多丰富的应用;
  • 得益于本篇所做的扩展准备,下一篇《年龄检测》会更加简单,一起来期待下一段轻松愉快的旅程吧;

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

JavaCV的摄像头实战之十二:性别检测的更多相关文章

  1. JavaCV的摄像头实战之二:本地窗口预览

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. JavaCV的摄像头实战之四:抓图

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<JavaCV的摄像头实战> ...

  3. JavaCV的摄像头实战之七:推流(带声音)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<JavaCV的摄像头实战> ...

  4. JavaCV的摄像头实战之一:基础

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于<JavaCV的摄像头实战>系列 &l ...

  5. JavaCV的摄像头实战之五:推流

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<JavaCV的摄像头实战> ...

  6. JavaCV的摄像头实战之六:保存为mp4文件(有声音)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. spring boot实战(第十二篇)整合RabbitMQ

    前言 最近几篇文章将围绕消息中间件RabbitMQ展开,对于RabbitMQ基本概念这里不阐述,主要讲解RabbitMQ的基本用法.Java客户端API介绍.spring Boot与RabbitMQ整 ...

  8. 【慕课网实战】Spark Streaming实时流处理项目实战笔记十二之铭文升级版

    铭文一级: ======Pull方式整合 Flume Agent的编写: flume_pull_streaming.conf simple-agent.sources = netcat-sources ...

  9. Spring Cloud & Alibaba 实战 | 第十二篇: 微服务整合Sentinel的流控、熔断降级,赋能拥有降级功能的Feign新技能熔断,实现熔断降级双剑合璧(JMeter模拟测试)

    目录 一. Sentinel概念 1. 什么是Sentinel? 2. Sentinel功能特性 3. Sentinel VS Hystrix 二. Docker部署Sentinel Dashboar ...

  10. R实战 第十二篇:随机数

    由R生成的随机数实际上伪随机数,也就是说,随机数是由某种算法而不是真正的随机过程产生的,随机数生成器需要一个初始值来生成数字,该初始值叫做种子.通过把种子设置为特定的值,可以保证每次运行同一段代码时都 ...

随机推荐

  1. w10通过修改注册表实现禁止更新系统

    对于Windows系统更新或驱动更新,并不是越新越好,当然新版本的系统可能带来了许多新的功能.漏洞修补.漂亮的用户界面和流畅的系统优化等,但是新版本的系统和驱动更新有可能会造成CPU占用居高不下,文件 ...

  2. [PKM]阅读的方法

    0 概述 数据 => 信息 => 知识 => 智慧 1 读书的目的 : 先寻求真理,而后实践 => 先博学,而后守约(读透) & 先泛读/速读,再精读 / 知行合一 年 ...

  3. [Java]排序算法>插入排序>【直接插入排序】(O(N*N)/稳定/N较小/有序/顺序存储+链式存储)

    1 直接插入排序 1.1 算法思想 插入排序的基本思想是:每一趟将1个待排序的记录,按其关键字的大小插入到已经排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止. 1.2 算法特征 属于[插 ...

  4. 0002 嵌入式开发带你从小白到大佬系列之——Linux文件系统、常用文件操作命令(一)及用户权限

    1.熟悉Linux的文件系统结构 Linux的文件系统结构其实是一个树形的分层组织结构,如下图: Linux系统目录结构及目录路径: 1.1.文件系统层次结构标准 Linux是开源的操作系统,各个Li ...

  5. 关于微人事中POI导入文件到数据库的异常以及自己的一些技术心得

    前言 在近四个月的时间里面,我的微人事项目才逐渐接近尾声,在昨天的测试接口中出现了两次数组越界以及一次空指针异常,三处异常我都通过吊事bug根据项目实际情况解决了,但是在空指针异常那里还是带有疑问,起 ...

  6. python对图片进行最大边大小缩放

    def split_image_bs4(file, max_len=720): """ 切割图片 :param file: 二进制文件 :param max_len: 最 ...

  7. 【能力提升】SQL Server常见问题介绍及快速解决建议

    前言 本文旨在帮助SQL Server数据库的使用人员了解常见的问题,及快速解决这些问题.这些问题是数据库的常规管理问题,对于很多对数据库没有深入了解的朋友提供一个大概的常见问题框架. 下面一些问题是 ...

  8. Redis 报”OutOfDirectMemoryError“(堆外内存溢出)

    Redis 报错"OutOfDirectMemoryError(堆外内存溢出) "问题如下: 一.报错信息: 使用 Redis 的业务接口 ,产生 OutOfDirectMemor ...

  9. STM32下载ELF文件、最小可执行bin文件测试

    1.STM32能下载ELF格式的文件吗? 答:可以.因为所谓的bin文件就是ELF文件的.text代码段. 当然前提是下载工具能识别ELF文件格式,STM32下载ELF文件并不意味着STM32可以把E ...

  10. 解决VM虚拟机中IP或域名不能ping通

    c4548abb-da65-4f7d-827f-e95dca25a13d 问题 无法ping通域名, 检查事项 确定在同一个子网,能访问DNS服务器. DNS服务器正确设置了正反向解析,且DNS服务器 ...