写点什么

Java + opencv 实现年龄识别

用户头像
张音乐
关注
发布于: 刚刚
Java + opencv 实现年龄识别

一、效果展示

还是同样的图片, 先来一张效果图压压惊. 看过上一篇文章的同学都知道, 前面已经实现了 人脸的性别识别, 所以这篇文章主要讲的是年龄识别. 大部分代码都是一样的, 主要是调用的 dnn 网络模型不一样, 预测的结果集合不一样. 其他的都一样. 不懂的往下看. 如果觉得本文对你有用, 请一键三连吧.听说点赞关注加收藏的人都是帅哥.(ps: 图片来源于 github 项目 faceai, 顺手就拿来用啦)


二、技术实现思路

1、人脸检测与画框

1.1 图片转换成灰色(降低为一维的灰度,减低计算强度)

1.2 图片上画矩形

1.3 使用训练分类器查找人脸

1.4 如果对于人脸检测不是很熟悉的话可以参考往期文章, 传送门 Java + opencv 实现图片人脸检测

2、人脸年龄识别

2.1 加载年龄识别网络模型

2.2 遍历图片上检测到的人脸, 逐一检测年龄

2.3 在人脸矩形框上方显示年龄

3、显示年龄

因为 opencv 原生的 Imgproc.putText 方法在图片上显示中文会乱码, 也是参考了网上的一些文章, 才找到了解决方案, 具体做法是, 需要将性别文字制作成水印添加到图片上, 这个过程中需要先将图片二位矩阵 Mat 转化成 Image , 添加完水印后再将 Image 转回成二位矩阵 Mat

4、模型讲解

上一章的性别预测返回的是一个二分类结果

本章的年龄预测返回的是 8 个年龄的阶段

这个结果实际上是算出性别检测值在二维矩阵 Mat 的第 1 行中最大值的索引.

定义两个结果集合:

性别集合: 

    /**     * 性别预测返回的是一个二分类结果 Male, Female     */    private final static List<String> genderList = new ArrayList<>(Arrays.asList("男", "女"));
复制代码

年龄集合: 

    /**     * 年龄预测返回的是8个年龄的阶段!     */    private final static List<String> ageList = new ArrayList<>(Arrays.asList("(0-2)", "(4-6)", "(8-12)", "(15-20)", "(25-32)", "(38-43)", "(48-53)", "(60-100)"));
复制代码

三、核心代码

年龄检测核心代码, 其他代码和上一节的代码完全一样, 如有需要可以去看看, 传送门: Java + opencv 实现性别识别

    /**     * 年龄检测     * @param img     * @param rect     * @param genderNet     * @return     */    private static String getAge(Mat img, Rect rect, Net genderNet) {        Mat face = new Mat(img, rect);        // Resizing pictures to resolution of Caffe model        Imgproc.resize(face, face, new Size(140, 140));        // 灰度化        Imgproc.cvtColor(face, face, Imgproc.COLOR_RGBA2BGR);        // blob输入网络进行年龄的检测        Mat inputBlob = Dnn.blobFromImage(face, 1.0f, new Size(227, 227), MODEL_MEAN_VALUES, false, false);        genderNet.setInput(inputBlob, "data");        // 年龄检测进行前向传播        Mat probs = genderNet.forward("prob").reshape(1, 1);        Core.MinMaxLocResult mm = Core.minMaxLoc(probs);        // Result of gender recognition prediction.        double index = mm.maxLoc.x;        return  ageList.get((int) index);    }
复制代码


四、完整代码

package com.biubiu.example;import java.awt.*;import java.awt.image.BufferedImage;import java.awt.image.DataBufferByte;import java.io.ByteArrayInputStream;import java.io.InputStream;import java.util.ArrayList;import java.util.Arrays;import java.util.List; import javax.imageio.ImageIO; import org.opencv.core.Core;import org.opencv.core.CvType;import org.opencv.core.Mat;import org.opencv.core.MatOfByte;import org.opencv.core.MatOfRect;import org.opencv.core.Point;import org.opencv.core.Rect;import org.opencv.core.Scalar;import org.opencv.core.Size;import org.opencv.dnn.Dnn;import org.opencv.dnn.Net;import org.opencv.highgui.HighGui;import org.opencv.imgcodecs.Imgcodecs;import org.opencv.imgproc.Imgproc;import org.opencv.objdetect.CascadeClassifier;/** * @author :张音乐 * @date :Created in 2021/4/18 下午1:25 * @description:年龄识别 * @email: zhangyule1993@sina.com * @version: 1.0 */public class ImageAgeDetect {    static {        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);    }     /**     * 年龄识别模型     */    private final static String ageProto = "D:/workspace/opencv/data/models/age_deploy.prototxt";    private final static String ageModel = "D:/workspace/opencv/data/models/age_net.caffemodel";     /**     * 年龄预测返回的是8个年龄的阶段!     */    private final static List<String> ageList = new ArrayList<>(Arrays.asList("(0-2)", "(4-6)", "(8-12)", "(15-20)", "(25-32)", "(38-43)", "(48-53)", "(60-100)"));     /**     * 模型均值     */    private final static Scalar MODEL_MEAN_VALUES = new Scalar(78.4263377603, 87.7689143744, 114.895847746);     public static void main(String[] args) {        // 加载网络模型        Net ageNet = Dnn.readNetFromCaffe(ageProto, ageModel);        if (ageNet.empty()) {            System.out.println("无法打开网络模型...\n");            return;        }        // 加载图片矩阵        String filePath = "D:\\upload\\gather.png";        Mat img = Imgcodecs.imread(filePath);        // 人脸检测        MatOfRect faceRects = facePick(img);        // 定义一个颜色        Scalar color = new Scalar(0, 0, 255);        // 遍历检测到的图片        for(Rect rect : faceRects.toArray()) {            // 人脸画矩形框            drawRect(rect, img, color);            // 检测年龄            String gender = getAge(img, rect, ageNet);            // 图片上显示中文的年龄 ,因为原生的 opencv putText 显示中文会乱码, 所以需要特殊处理一下            img = putChineseTxt(img, gender, rect.x + rect.width / 2 - 5, rect.y - 10);            // Imgproc.putText(img, new String(gender.getBytes(StandardCharsets.UTF_8)), new Point(x, y), 2, 2, color);        }        // 显示图像        HighGui.imshow("预览", img);        HighGui.waitKey(0);        // 释放所有的窗体资源        HighGui.destroyAllWindows();    }     /**     * 在图片上的人脸区域画上矩形框     * @param rect     * @param img     * @param color     */    private static void drawRect(Rect rect, Mat img, Scalar color) {        int x = rect.x;        int y = rect.y;        int w = rect.width;        int h = rect.height;        Imgproc.rectangle(img, new Point(x, y), new Point(x + h, y + w), color, 2);    }     /**     * 年龄检测     * @param img     * @param rect     * @param genderNet     * @return     */    private static String getAge(Mat img, Rect rect, Net genderNet) {        Mat face = new Mat(img, rect);        // Resizing pictures to resolution of Caffe model        Imgproc.resize(face, face, new Size(140, 140));        // 颜色空间转换        Imgproc.cvtColor(face, face, Imgproc.COLOR_RGBA2BGR);        // blob输入网络进行年龄的检测        Mat inputBlob = Dnn.blobFromImage(face, 1.0f, new Size(227, 227), MODEL_MEAN_VALUES, false, false);        genderNet.setInput(inputBlob, "data");        // 年龄检测进行前向传播        Mat probs = genderNet.forward("prob").reshape(1, 1);        Core.MinMaxLocResult mm = Core.minMaxLoc(probs);        // Result of gender recognition prediction.        double index = mm.maxLoc.x;        return  ageList.get((int) index);    }     /**     * 图片人脸检测     * @return     */    private static MatOfRect facePick(Mat img) {        // 存放灰度图        Mat tempImg = new Mat();        // 摄像头获取的是彩色图像,所以先灰度化下        Imgproc.cvtColor(img, tempImg, Imgproc.COLOR_BGRA2GRAY);        // OpenCV人脸识别分类器        CascadeClassifier classifier = new CascadeClassifier("D:\\workspace\\opencv\\data\\haarcascades\\haarcascade_frontalface_default.xml");        // # 调用识别人脸        MatOfRect faceRects = new MatOfRect();        // 特征检测点的最小尺寸, 根据实际照片尺寸来选择, 不然测量结果可能不准确。        Size minSize = new Size(140, 140);        // 图像缩放比例,可理解为相机的X倍镜        double scaleFactor = 1.2;        // 对特征检测点周边多少有效点同时检测,这样可避免因选取的特征检测点太小而导致遗漏        int minNeighbors = 3;        // 人脸检测        // CV_HAAR_DO_CANNY_PRUNING        classifier.detectMultiScale(tempImg, faceRects, scaleFactor, minNeighbors, 0, minSize);         return faceRects;    }     /**     * Mat二维矩阵转Image     * @param matrix     * @param fileExtension     * @return     */    public static BufferedImage matToImg(Mat matrix, String fileExtension) {        // convert the matrix into a matrix of bytes appropriate for        // this file extension        MatOfByte mob = new MatOfByte();        Imgcodecs.imencode(fileExtension, matrix, mob);        // convert the "matrix of bytes" into a byte array        byte[] byteArray = mob.toArray();        BufferedImage bufImage = null;        try {            InputStream in = new ByteArrayInputStream(byteArray);            bufImage = ImageIO.read(in);        } catch (Exception e) {            e.printStackTrace();        }        return bufImage;    }     /**     * BufferedImage转换成 Mat     * @param original     * @param imgType     * @param matType     * @return     */    public static Mat imgToMat(BufferedImage original, int imgType, int matType) {        if (original == null) {            throw new IllegalArgumentException("original == null");        }        if (original.getType() != imgType){            // Create a buffered image            BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), imgType);             // Draw the image onto the new buffer            Graphics2D g = image.createGraphics();            try {                g.setComposite(AlphaComposite.Src);                g.drawImage(original, 0, 0, null);            } finally {                g.dispose();            }        }        byte[] pixels = ((DataBufferByte) original.getRaster().getDataBuffer()).getData();        Mat mat = Mat.eye(original.getHeight(), original.getWidth(), matType);        mat.put(0, 0, pixels);        return mat;    }     /**     * 在图片上显示中文     * @param img     * @param gender     * @param x     * @param y     * @return     */    private static Mat putChineseTxt(Mat img, String gender, int x, int y) {        Font font = new Font("微软雅黑", Font.PLAIN, 20);        BufferedImage bufImg = matToImg(img,".png");        Graphics2D g = bufImg.createGraphics();        g.drawImage(bufImg, 0, 0, bufImg.getWidth(), bufImg.getHeight(), null);        // 设置字体        g.setColor(new Color(255, 10, 52));        g.setFont(font);        // 设置水印的坐标        g.drawString(gender, x, y);        g.dispose();        // 加完水印再转换回来        return imgToMat(bufImg, BufferedImage.TYPE_3BYTE_BGR, CvType.CV_8UC3);    }}
复制代码

五、注意事项

需要性别识别网络模型的同学可以评论区域留下邮箱,我看到的话会给你发的, .代码无法调试通过也可以私信或者评论区提问, 如果我能解决都会提供帮助.

建议先读往期文章系统学习一下, 传送门:

Java + opencv 实现图片人脸检测

Java + opencv 实现视频人脸检测

Java + opencv 实现图片修复(去水印)

 Java + opencv 实现性别识别


发布于: 刚刚阅读数: 2
用户头像

张音乐

关注

求你关注我,别不识抬举.别逼我跪下来求你. 2021.03.28 加入

还未添加个人简介

评论

发布
暂无评论
Java + opencv 实现年龄识别