基于Opencv-java的人脸分析----后端

之前帮同学做了一个轮廓位点计算的小软件,用到了opencv,看到可以进行人脸识别,感觉挺有趣的 就摸索了一阵子。这里就做一个总结记录,以便日后查阅

1.什么是opencv?

OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库。OpenCV是由英特尔公司发起并参与开发,以BSD许可证授权发行,可以在商业和研究领域中免费使用。OpenCV可用于开发实时的图像处理、计算机视觉以及模式识别程序。该程序库也可以使用英特尔公司的IPP进行加速处理。(该段内容来自于维基百科https://zh.wikipedia.org/wiki/OpenCV)

2.准备

java使用opencv的话 一般来说分为常见的两种,一种是官网下载导入jar包,还有一种就是引用maven依赖

2.1官网下载https://opencv.org/releases/

image-20210419091738911

根据自身使用的环境去进行选择相应的下载,下载完成后是一个exe文件,安装完成后进入如下目录下

image-20210419093204051

其中opencv-451.jar 就是我们所需要的jar包,而x64和x86是对应64位和32位系统所需要的 dll文件。

image-20210419093345094

缺点:

  • 目前该jar包中是缺少OpenCv Contrib的,而人脸识别中的FaceRecognizer 都是来自于它的,如果需要 需自行编译

2.2导入maven依赖

一般选择的依赖有 opencv和opencv-platform

前者的缺点和上面一样,缺少一些类,而后者的缺点在于,打包后,jar包过于臃肿,有400多M

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>opencv</artifactId>
    <version>4.5.1-1.5.5</version>
</dependency>
<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>opencv-platform</artifactId>
    <version>4.5.1-1.5.5</version>
</dependency>

如果是选择jar包导入的 或者 opencv依赖的 需要加载opencv_java451.dll 具体措施 可以自行百度或者在底下评论

3.项目开始(基于opencv的人脸识别)

这里我选择的是通过maven引入依赖,因为需要用到LBPHFaceRecognizer,所以暂定先用opencv-platform,如果用更好的方法,欢迎大家的底下评论

3.1项目开始前,先谈一下之后要选择的FaceRecognizer,目前opencv提供的有三种主要是:

image-20210419095949341

我这里选择用的是LBPH,原因在于 其他两种是不支持更新的,只有LBPH才支持。

3.2大体步骤

一个完整的人脸识别,分为人脸训练+人脸识别,人脸训练的主要目的是,便于机器去识别。

比如一个新人入职,会首先将自己的人脸或者指纹和名字录入进系统,这样当下一次打卡时,就可以根据人脸或者指纹比对当前打卡人是谁,这里的原理是互通。

Tips:进行训练的人脸 需要先转化为大小相同的灰度图,之后再进行训练,具体的方法为opencv的 resize,否则的话可能会抛出一些异常 如: 大小超出32400 这里的size的值,可以自行定义

opencv_imgproc.resize(mat,mat,new Size(180,180));

在进行训练和更新时,如果需要加上

Loader.load(opencv_java.class);

  • train
    • /**
       * LBph 训练
       *
       * @param source source 为样本图片的绝对地址的map  键为对应的样本名  值为绝对地址
       */
      public static void train(Map<String, List<String>> source) {
           //创建一个FaceRecognizer
          LBPHFaceRecognizer faceRecognizer = LBPHFaceRecognizer.create();
          //sum 为 训练样本总数
          int sum = 0;
          for (String faceName : source.keySet()) {
              final List<String> faces = source.get(faceName);
              sum += faces.size();
          }
          log.info("total face:{}", sum);
          Mat labels = new Mat(sum, 1, opencv_core.CV_32SC1);
          //开辟一个给定初始大小的matVector 用来存放mat
          MatVector images = new MatVector(sum);
          //设置标签
          //这里的1 是代表 标签的序号的   比如1代表胡歌  2 代表黄晓明 。。。 更新的话只要接着下面的数就行
          setLabels(source, images, labels, 1);
          //进行训练
          faceRecognizer.train(images, labels);
          //保存
          faceRecognizer.save("D:\\desk\\project\\opencv_lesson412\\model\\lbph_face_model.xml");
      }
      
  • update
    • /**
       * 更新
       *
       * @param source .
       */
      public static void update(Map<String, List<String>> source) {
          final LBPHFaceRecognizer faceRecognizer = LBPHFaceRecognizer.create();
          //如果不先读取对应的xml文件的话  是无法进行更新的  会将原来的 覆盖掉
          faceRecognizer.read("D:\\desk\\project\\opencv_lesson412\\model\\lbph_face_model.xml");
          int sum = 0;
          for (String faceName : source.keySet()) {
              final List<String> faces = source.get(faceName);
              sum += faces.size();
          }
          log.info("total face:{}", sum);
          //开辟一个给定初始大小的matVector 用来存放mat
          MatVector images = new MatVector(sum);
          //final int length = labels.getIntBuffer().array().length;
          Mat labels = new Mat(sum, 1, opencv_core.CV_32SC1);
          // kind 这个值根据 自己的业务设计 这里只是演示
          setLabels(source, images, labels, 4);
          faceRecognizer.update(images, labels);
          faceRecognizer.save("D:\\desk\\project\\opencv_lesson412\\model\\lbph_face_model.xml");
      }
      
  • recognizer
    • /**
       * 人脸识别
       *
       * @param filePath 图片绝对路径
       * @param threshold 人脸识别的阈值
       */
      public static void faceRecognizer(String filePath, double threshold) {
          LBPHFaceRecognizer faceRecognizer = LBPHFaceRecognizer.create();
          //读取训练好的xml文件
          faceRecognizer.read("model/lbph_face_model.xml");
          //设置阈值
          //阈值越低则识别的越严格,阈值越高则识别的越容易 但错误率也会提高
          //在进行是别的时候 宁愿识别的严格一点 让错误率低一些。
          //我一般设置在50-70左右
          faceRecognizer.setThreshold(threshold);
          
          //级联分类器 获取人脸识别模型 可以从该网址中获取 或者自己训练得到
          //https://github.com/opencv/opencv/tree/master/data/haarcascades
          CascadeClassifier cascadeClassifier = new CascadeClassifier();
          cascadeClassifier.load("model/haarcascade_frontalface_alt.xml");
          //进行是别的人脸需要转化为灰度图
          final Mat grayImage = opencv_imgcodecs.imread(filePath, opencv_imgcodecs.IMREAD_GRAYSCALE);
          //直方图
          opencv_imgproc.equalizeHist(grayImage, grayImage);
          RectVector faces = new RectVector();
          cascadeClassifier.detectMultiScale(grayImage, faces);
          //标签
          IntPointer label = new IntPointer(1);
          //置信率
          DoublePointer confidence = new DoublePointer(1);
        
          //识别的时候 有可能出现的问题 就是 明明是一张脸 但是却识别出了 两张 出来, 可能得到两个不同的结果
          for (Rect face : faces.get()) {
              final Mat apply = grayImage.apply(face);
              opencv_imgproc.resize(apply, apply, new Size(180, 180));
              faceRecognizer.predict(apply, label, confidence);
              final int i = label.get(0);
              final double v = confidence.get(0);
              String name;
              System.out.println(i);
              if (i == 1) {
                  name = "黄家驹";
              } else if (i == 2) {
                  name = "胡歌";
              } else if (i == 3) {
                  name = "lujy";
              } else if (i == 4) {
                  name = "tf-boys";
              } else {
                  name = null;
                  System.out.println("识别失败");
              }
          }
      
  • setLables
    • static void setLabels(Map<String, List<String>> source, MatVector images, Mat labels, int kind) {
          int kindIndex = kind;
          int index = 0;
          final IntBuffer buffer = labels.createBuffer();
          for (String faceName : source.keySet()) {
              final List<String> faces = source.get(faceName);
              for (String face : faces) {
                  //存放每一张图片的序号和每种图片序号
                  buffer.put(index, kindIndex);
                  final Mat imread = opencv_imgcodecs.imread(face, 0);
                  //存放每一张图片的 mat
                  images.put(index++, imread);
              }
              kindIndex++;
          }
      }
      

4.总结

之前刚开始摸索的时候遇到过许多的问题,但是没有具体记录,如果有小伙伴遇到了什么无法解决的问题 可以底下评论大家一起讨论解决。总得来说的话,人脸识别虽然成功了 但是 由于环境原因和其他一些原因,还是会有一些误差。之后的话,可能会考虑和web端关联 做一个人脸考勤系统。
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐