JavaCV臉部辨識三部曲之二:訓練

2023-06-29 09:00:39

歡迎存取我的GitHub

這裡分類和彙總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本文是《JavaCV臉部辨識三部曲》的第二篇,前文《視訊中的人臉儲存為圖片》咱們藉助攝像頭為兩位群眾演員生成大量人臉照片,如下圖,群眾演員A的照片儲存在E:\temp\202112\18\001\man,B的照片儲存在E:\temp\202112\18\001\woman
  • 照片準備好,並且每張照片的身份都已確定,本篇要做的就是用上述照片生成模型檔案,今後新的人臉就可以中這個模型來檢查了
  • 關於訓練,可以用下圖來表示,一共六張照片兩個類別,訓練完成後得到模型檔案faceRecognizer.xml

編碼

  • 訓練的程式碼很簡單,在一個java檔案中搞定吧,simple-grab-push是整個《JavaCV的攝像頭實戰》系列一直再用的工程,現在該工程中新增檔案TrainFromDirectory.java,完整程式碼如下,有幾處要注意的地方稍後提到:
package com.bolingcavalry.grabpush.extend;

import com.bolingcavalry.grabpush.Constants;
import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.MatVector;
import org.bytedeco.opencv.opencv_core.Size;
import org.bytedeco.opencv.opencv_face.FaceRecognizer;
import org.bytedeco.opencv.opencv_face.FisherFaceRecognizer;

import java.io.File;
import java.io.IOException;
import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.List;

import static org.bytedeco.opencv.global.opencv_core.CV_32SC1;
import static org.bytedeco.opencv.global.opencv_imgcodecs.IMREAD_GRAYSCALE;
import static org.bytedeco.opencv.global.opencv_imgproc.resize;

/**
 * @author willzhao
 * @version 1.0
 * @description 訓練
 * @date 2021/12/12 18:26
 */
public class TrainFromDirectory {

    /**
     * 從指定目錄下
     * @param dirs
     * @param outputPath
     * @throws IOException
     */
    private void train(String[] dirs, String outputPath) throws IOException {
        int totalImageNums = 0;

        // 統計每個路徑下的照片數,加在一起就是照片總數
        for(String dir : dirs) {
            List<String> files = getAllFilePath(dir);
            totalImageNums += files.size();
        }

        System.out.println("total : " + totalImageNums);

        // 這裡用來儲存每一張照片的序號,和照片的Mat物件
        MatVector imageIndexMatMap = new MatVector(totalImageNums);

        Mat lables = new Mat(totalImageNums, 1, CV_32SC1);

        // 這裡用來儲存每一張照片的序號,和照片的類別
        IntBuffer lablesBuf = lables.createBuffer();

        // 類別序號,從1開始,dirs中的每個目錄就是一個類別
        int kindIndex = 1;

        // 照片序號,從0開始
        int imageIndex = 0;

        // 每個目錄下的照片都遍歷
        for(String dir : dirs) {
            // 得到當前目錄下所有照片的絕對路徑
            List<String> files = getAllFilePath(dir);

            // 處理一個目錄下的每張照片,它們的序號不同,類別相同
            for(String file : files) {
                // imageIndexMatMap放的是照片的序號和Mat物件
                imageIndexMatMap.put(imageIndex, read(file));
                // bablesBuf放的是照片序號和類別
                lablesBuf.put(imageIndex, kindIndex);
                // 照片序號加一
                imageIndex++;
            }

            // 每當遍歷完一個目錄,才會將類別加一
            kindIndex++;
        }

        // 範例化臉部辨識類
        FaceRecognizer faceRecognizer = FisherFaceRecognizer.create();
        // 訓練,入參就是圖片集合和分類集合
        faceRecognizer.train(imageIndexMatMap, lables);
        // 訓練完成後,模型儲存在指定位置
        faceRecognizer.save(outputPath);
        //釋放資源
        faceRecognizer.close();
    }

    /**
     * 讀取指定圖片的灰度圖,調整為指定大小
     * @param path
     * @return
     */
    private static Mat read(String path) {
        Mat faceMat = opencv_imgcodecs.imread(path,IMREAD_GRAYSCALE);
        resize(faceMat, faceMat, new Size(Constants.RESIZE_WIDTH, Constants.RESIZE_HEIGHT));
        return faceMat;
    }

    /**
     * 把指定路徑下所有檔案的絕對路徑放入list集合中返回
     * @param path
     * @return
     */
    public static List<String> getAllFilePath(String path) {
        List<String> paths = new LinkedList<>();

        File file = new File(path);

        if (file.exists()) {
            // 列出該目錄下的所有檔案
            File[] files = file.listFiles();

            for (File f : files) {
                if (!f.isDirectory()) {
                    // 把每個檔案的絕對路徑都放在list中
                    paths.add(f.getAbsolutePath());
                }
            }
        }

        return paths;
    }

    public static void main(String[] args) throws IOException {

        String base = "E:\\temp\\202112\\18\\001\\";

        // 儲存圖片的兩個目錄
        // man目錄下儲存了群眾演員A的所有人臉照片,
        // woman目錄下儲存了群眾演員B的所有人臉照片
        String[] dirs = {base + "man", base + "woman"};

        // 開始訓練,並指定模型輸出位置
        new TrainFromDirectory().train(dirs, base + "faceRecognizer.xml");
    }
}
  • 上述程式碼有以下幾處要注意:
  1. 靜態方法read用於將圖片轉為Mat
  2. 靜態方法getAllFilePath可以遍歷指定目錄下的所有檔案,把它們的絕對路徑返回
  3. train一共獲取了man和woman兩個目錄下的照片,man目錄下的照片的類別是1,women目錄下的照片類別是2
  4. 識別類是FisherFaceRecognizer,現在的訓練和下一篇的識別都用這個類

執行

  • 執行main方法,待執行完成後,如下圖,可見目錄E:\temp\202112\18\001下已經生成模型檔案faceRecognizer.xml
  • 至此,本篇任務已完成,下一篇進入終極實戰,用本篇訓練的模型識別攝像頭中的人臉,並把識別結果展示在預覽頁面上;

原始碼下載

名稱 連結 備註
專案主頁 https://github.com/zq2599/blog_demos 該專案在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該專案原始碼的倉庫地址,https協定
git倉庫地址(ssh) [email protected]:zq2599/blog_demos.git 該專案原始碼的倉庫地址,ssh協定
  • 這個git專案中有多個資料夾,本篇的原始碼在javacv-tutorials資料夾下,如下圖紅框所示:
  • javacv-tutorials裡面有多個子工程,《JavaCV的攝像頭實戰》系列的程式碼在simple-grab-push工程下:

歡迎關注部落格園:程式設計師欣宸

學習路上,你不孤單,欣宸原創一路相伴...