JavaCV人脸识别三部曲之三:识别和预览

打印 上一主题 下一主题

主题 915|帖子 915|积分 2745

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
《JavaCV人脸识别三部曲》链接

本篇概览


  • 作为《JavaCV人脸识别三部曲》的终篇,今天咱们要开发一个实用的功能:有人出现在摄像头中时,应用程序在预览窗口标注出此人的身份,效果如下图所示:

  • 简单来说,本篇要做的事情如下:

  • 理解重点概念:confidence
  • 理解重点概念:threshold
  • 编码
  • 验证


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

理解重点概念:confidence


  • confidence和threshold是OpenCV的人脸识别中非常重要的两个概念,咱们先把这两个概念搞清楚,再去编码就非常容易了
  • 假设,咱们用下面六张照片训练出包含两个类别的模型:

  • 用一张新的照片去训练好的模型中做识别,如下图,识别结果有两部分内容:label和confidence

  • 先说lable,这个好理解,与训练时的lable一致(回顾上一篇的代码,lable如下图红框所示),前面图中lable等于2,表示被判定为郭富城:

  • 按照上面的说法,lable等于2就能确定照片中的人像是郭富城吗?
  • 当然不能!!!此时confidence字段就非常重要了,先看JavaCV源码中对confidence的解释,如下图红框所示,我的理解是:与lable值相关联的置信度,或者说这张脸是郭富城的可能性

  • 如果理解为可能性,那么问题来了,这是个double型的值,这个值越大,表示可能性越大还是越小?
  • 上图并没有明说,但是那一句e.g. distance,让我想起了机器学习中的K-means,此时我脑海中的画面如下:

    -若真如上图所示,那么显然confidence越小,是郭富城的可能性就越大了,接下来再去找一些权威的说法:
  • OpenCV的官方论坛有个帖子的说法如下图:代码中的confidence变量属于命名不当,其含义不是可信度,而是与模型中的类别的距离:

  • 再看第二个解释,如下图红框,说得很清楚了,值越小,与模型中类别的相似度越高,0表示完全匹配:

  • 再看一个Stack Overflow的解释

  • 至此,相信您对confidence已经足够理解了,lable等于2,confidence=30.01,意思是:被识别照片与郭富城最相似,距离为30.01,距离越小,是郭富城的可能性越大
理解重点概念:threshold


  • 在聊threshold之前,咱们先看一个场景,还是刘德华郭富城的模型,这次咱们拿喜洋洋的照片给模型识别,识别结果如下:

  • 显然,模型不会告诉你照片里是谁,只会告诉你:和郭富城的距离是3000.01
  • 看到这里,聪明的您可能会这么想:那我就写一段代码吧,识别结果的confidence如果太大(例如超过100),就判定用于识别的人不属于训练模型的任何一个类别
  • 上述功能,OpenCV已经帮咱们想到了,那就是:threshold,翻译过来即门限,如果咱们设置了threshold等于100,那么,一旦距离超过100,OpenCV的lable返回值就是-1
  • 理解了confidence和threshold,接下来可以写人脸识别的代码了,感谢咱们的充分准备,接下来是丝般顺滑的编码过程...
源码下载

名称链接备注项目主页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工程下:

编码:人脸识别服务


  • 开始正式编码,今天咱们不会新建工程,而是继续使用《JavaCV的摄像头实战之一:基础》中创建的simple-grab-push工程
  • 先定义一个Bean类PredictRlt.java,用来保存识别结果(lable和confidence字段):
  1. package com.bolingcavalry.grabpush.extend;
  2. import lombok.Data;
  3. @Data
  4. public class PredictRlt {
  5.     private int lable;
  6.     private double confidence;
  7. }
复制代码

  • 然后把人脸识别有关的服务集中在RecognizeService.java中,方便主程序使用,代码如下,有几处要注意的地方稍后提到:
  1. package com.bolingcavalry.grabpush.extend;
  2. import com.bolingcavalry.grabpush.Constants;
  3. import org.bytedeco.opencv.global.opencv_imgcodecs;
  4. import org.bytedeco.opencv.opencv_core.Mat;
  5. import org.bytedeco.opencv.opencv_core.Size;
  6. import org.bytedeco.opencv.opencv_face.FaceRecognizer;
  7. import org.bytedeco.opencv.opencv_face.FisherFaceRecognizer;
  8. import static org.bytedeco.opencv.global.opencv_imgcodecs.IMREAD_GRAYSCALE;
  9. import static org.bytedeco.opencv.global.opencv_imgproc.resize;
  10. /**
  11. * @author willzhao
  12. * @version 1.0
  13. * @description 把人脸识别的服务集中在这里
  14. * @date 2021/12/12 21:32
  15. */
  16. public class RecognizeService {
  17.     private FaceRecognizer faceRecognizer;
  18.     // 推理结果的标签
  19.     private int[] plabel;
  20.     // 推理结果的置信度
  21.     private double[] pconfidence;
  22.     // 推理结果
  23.     private PredictRlt predictRlt;
  24.     // 用于推理的图片尺寸,要和训练时的尺寸保持一致
  25.     private Size size= new Size(Constants.RESIZE_WIDTH, Constants.RESIZE_HEIGHT);
  26.     public RecognizeService(String modelPath) {
  27.         plabel = new int[1];
  28.         pconfidence = new double[1];
  29.         predictRlt = new PredictRlt();
  30.         
  31.         // 识别类的实例化,与训练时相同
  32.         faceRecognizer = FisherFaceRecognizer.create();
  33.         // 加载的是训练时生成的模型
  34.         faceRecognizer.read(modelPath);
  35.         // 设置门限,这个可以根据您自身的情况不断调整
  36.         faceRecognizer.setThreshold(Constants.MAX_CONFIDENCE);
  37.     }
  38.     /**
  39.      * 将Mat实例给模型去推理
  40.      * @param mat
  41.      * @return
  42.      */
  43.     public PredictRlt predict(Mat mat) {
  44.         // 调整到和训练一致的尺寸
  45.         resize(mat, mat, size);
  46.         boolean isFinish = false;
  47.         try {
  48.             // 推理(这一行可能抛出RuntimeException异常,因此要补货,否则会导致程序退出)
  49.             faceRecognizer.predict(mat, plabel, pconfidence);
  50.             isFinish = true;
  51.         } catch (RuntimeException runtimeException) {
  52.             runtimeException.printStackTrace();
  53.         }
  54.         // 如果发生过异常,就提前返回
  55.         if (!isFinish) {
  56.             return null;
  57.         }
  58.         // 将推理结果写入返回对象中
  59.         predictRlt.setLable(plabel[0]);
  60.         predictRlt.setConfidence(pconfidence[0]);
  61.         return predictRlt;
  62.     }
  63. }
复制代码

  • 上述代码有以下几处需要注意:

  • 构造方法中,通过faceRecognizer.setThreshold设置门限,我在实际使用中发现50比较合适,您可以根据自己的情况不断调整
  • predict方法中,用于识别的图片要用resize方法调整大小,尺寸要和训练时的尺寸一致
  • 实测发现,在一张照片中出现多个人脸时,faceRecognizer.predict可能抛出RuntimeException异常,因此这里要捕获异常,避免程序崩溃退出
编码:检测和识别

  1. package com.bolingcavalry.grabpush.extend;
  2. import com.bolingcavalry.grabpush.Constants;
  3. import org.bytedeco.javacv.Frame;
  4. import org.bytedeco.javacv.OpenCVFrameConverter;
  5. import org.bytedeco.opencv.opencv_core.*;
  6. import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
  7. import static org.bytedeco.opencv.global.opencv_core.CV_8UC1;
  8. import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
  9. import static org.bytedeco.opencv.global.opencv_imgproc.*;
  10. /**
  11. * @author willzhao
  12. * @version 1.0
  13. * @description 检测工具的通用接口
  14. * @date 2021/12/5 10:57
  15. */
  16. public interface DetectService {
  17.     /**
  18.      * 根据传入的MAT构造相同尺寸的MAT,存放灰度图片用于以后的检测
  19.      * @param src 原始图片的MAT对象
  20.      * @return 相同尺寸的灰度图片的MAT对象
  21.      */
  22.     static Mat buildGrayImage(Mat src) {
  23.         return new Mat(src.rows(), src.cols(), CV_8UC1);
  24.     }
  25.    
  26.     /**
  27.      * 初始化操作,例如模型下载
  28.      * @throws Exception
  29.      */
  30.     void init() throws Exception;
  31.     /**
  32.      * 得到原始帧,做识别,添加框选
  33.      * @param frame
  34.      * @return
  35.      */
  36.     Frame convert(Frame frame);
  37.     /**
  38.      * 释放资源
  39.      */
  40.     void releaseOutputResource();
  41. }
复制代码

  • 然后就是DetectService的实现类DetectAndRecognizeService .java,功能是用摄像头的一帧图片检测人脸,再拿检测到的人脸给RecognizeService做识别,完整代码如下,有几处要注意的地方稍后提到:
[code]package com.bolingcavalry.grabpush.extend;import lombok.extern.slf4j.Slf4j;import org.bytedeco.javacpp.Loader;import org.bytedeco.javacv.Frame;import org.bytedeco.javacv.OpenCVFrameConverter;import org.bytedeco.opencv.opencv_core.*;import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;import java.io.File;import java.net.URL;import java.util.Map;import static org.bytedeco.opencv.global.opencv_imgproc.*;/** * @author willzhao * @version 1.0 * @description 音频相关的服务 * @date 2021/12/3 8:09 */@Slf4jpublic class DetectAndRecognizeService implements DetectService {    /**     * 每一帧原始图片的对象     */    private Mat grabbedImage = null;    /**     * 原始图片对应的灰度图片对象     */    private Mat grayImage = null;    /**     * 分类器     */    private CascadeClassifier classifier;    /**     * 转换器     */    private OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();    /**     * 检测模型文件的下载地址     */    private String detectModelFileUrl;    /**     * 处理每一帧的服务     */    private RecognizeService recognizeService;    /**     * 为了显示的时候更加友好,给每个分类对应一个名称     */    private Map kindNameMap;    /**     * 构造方法     * @param detectModelFileUrl     * @param recognizeModelFilePath     * @param kindNameMap     */    public DetectAndRecognizeService(String detectModelFileUrl, String recognizeModelFilePath, Map kindNameMap) {        this.detectModelFileUrl = detectModelFileUrl;        this.recognizeService = new RecognizeService(recognizeModelFilePath);        this.kindNameMap = kindNameMap;    }    /**     * 音频采样对象的初始化     * @throws Exception     */    @Override    public void init() throws Exception {        // 下载模型文件        URL url = new URL(detectModelFileUrl);        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);        }        // 进行人脸识别,根据结果做处理得到预览窗口显示的帧        return detectAndRecoginze(classifier, converter, frame, grabbedImage, grayImage, recognizeService, kindNameMap);    }    /**     * 程序结束前,释放人脸识别的资源     */    @Override    public void releaseOutputResource() {        if (null!=grabbedImage) {            grabbedImage.release();        }        if (null!=grayImage) {            grayImage.release();        }        if (null==classifier) {            classifier.close();        }    }    /**     * 检测图片,将检测结果用矩形标注在原始图片上     * @param classifier 分类器     * @param converter Frame和mat的转换器     * @param rawFrame 原始视频帧     * @param grabbedImage 原始视频帧对应的mat     * @param grayImage 存放灰度图片的mat     * @param kindNameMap 每个分类编号对应的名称     * @return 标注了识别结果的视频帧     */    static Frame detectAndRecoginze(CascadeClassifier classifier,                                    OpenCVFrameConverter.ToMat converter,                                    Frame rawFrame,                                    Mat grabbedImage,                                    Mat grayImage,                                    RecognizeService recognizeService,                                    Map kindNameMap) {        // 当前图片转为灰度图片        cvtColor(grabbedImage, grayImage, CV_BGR2GRAY);        // 存放检测结果的容器        RectVector objects = new RectVector();        // 开始检测        classifier.detectMultiScale(grayImage, objects);        // 检测结果总数        long total = objects.size();        // 如果没有检测到结果,就用原始帧返回        if (total

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

愛在花開的季節

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表