從零玩轉人臉識別之RGB人臉活體檢測

從零玩轉RGB人臉活體檢測

前言

本期教程人臉識別第三方平台為虹軟科技,本文章講解的是人臉識別RGB活體追蹤技術,免費的功能很多可以自行搭配,希望在你看完本章課程有所收穫。

人臉追蹤示例

ArcFace 離線SDK,包含人臉檢測、性別檢測、年齡檢測、人臉識別、影像品質檢測、RGB活體檢測、IR活體檢測等能力,初次使用時需聯網激活,激活後即可在本地無網路環境下工作,可根據具體的業務需求結合人臉識別SDK靈活地進行應用層開發。

SDK功能模組圖.png

功能介紹

1. 人臉檢測

對傳入的影像數據進行人臉檢測,返回人臉的邊框以及朝向資訊,可用於後續的人臉識別、特徵提取、活體檢測等操作;

  • 支援IMAGE模式和VIDEO模式人臉檢測。
  • 支援單人臉、多人臉檢測,最多支援檢測人臉數為50。

2.人臉追蹤

對來自於影片流中的影像數據,進行人臉檢測,並對檢測到的人臉進行持續跟蹤。(我們是實時的所以就只能使用第三方操作,先不使用這個)

3.人臉特徵提取

提取人臉特徵資訊,用於人臉的特徵比對。

4.人臉屬性檢測

人臉屬性,支援檢測年齡、性別以及3D角度。

人臉3D角度:俯仰角(pitch), 橫滾角(roll), 偏航角(yaw)。

3D角度.png

5.活體檢測

離線活體檢測,靜默式識別,在人臉識別過程中判斷操作用戶是否為真人,有效防禦照片、影片、紙張等不同類型的作弊攻擊,提高業務安全性,讓人臉識別更安全、更快捷,體驗更佳。支援單目RGB活體檢測、雙目(IR/RGB)活體檢測,可滿足各類人臉識別終端產品活體檢測應用。

開造

訪問地址: //ai.arcsoft.com.cn/technology/faceTracking.html 進入開發者中心進行註冊以及認證個人資訊
1. 點擊我的應用 > 新建應用

image-20210702134809401

2.填寫資訊立即創建 點擊 添加SDK

image-20210702135034579

3.選中免費版人臉識別

image-20210702135111937

4. 填寫授權碼資訊
選擇平台先選擇windows的根據你的電腦配置來 是64位還是32位的, 語言選擇Java

image-20210702135225642

sdk

5. 介紹sdk文件

image-20210702135945966

一、創建Springboot工程:ArcFace

1. maven依賴

    <dependencies>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>

        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-configuration-processor</artifactid>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
            <scope>test</scope>
        </dependency>

        <!--支援html-->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-thymeleaf</artifactid>
        </dependency>
		<!--虹軟sdk-->
        <dependency>
            <groupid>com.arcsoft.face</groupid>
            <artifactid>arcsoft-sdk-face</artifactid>
            <version>3.0.0.0</version>
            <scope>system</scope>
            <systempath>${basedir}/lib/arcsoft-sdk-face-3.0.0.0.jar</systempath>
        </dependency>

    </dependencies>

    

2.創建lib文件夾將sdk複製

進來記得add依賴有小箭頭就行

image-20210702141738386

3.複製到測試類當中

image-20210702141922830

image-20210702142020712

4.填寫好對應的appId和sdkKey

image-20210702142107593

5.複製演算法庫路徑

image-20210702142620385

image-20210702142640567

6.啟動測試

我進行刪除了一些功能就示範特徵、活體檢測, 其他的可自己試一試

image-20210702144631386

體驗到此結束,可以自己多玩玩

二、改造ArcFace工程

效果圖

image-20210702145621981

人臉追蹤

1. 創建FaceRecognitionUtils

package top.yangbuyi.utils;

import com.arcsoft.face.*;
import com.arcsoft.face.enums.*;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.arcsoft.face.toolkit.ImageInfoEx;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @program: ArcFace
 * @ClassName: FaceRecognitionUtils
 * @create: 2021-07-01 11:00
 * @author: Yang shuai
 * @FaceRecognitionUtils: 人臉識別簡易版$實現人臉檢測活體是否為人臉,追蹤人臉
 **/
public class FaceRecognitionUtils {

	/**
	 * APP ID,請先在虹軟開發者中心註冊、認證之後創建應用獲取
	 */
	@Value("crm.appId")
	private static String APP_ID = "";
	/**
	 * SDK KEY,請先在虹軟開發者中心註冊、認證之後創建應用獲取
	 */
	@Value("crm.sdk")
	private static String SDK_KEY = "";
	/**
	 * dll插件庫地址
	 * linx 和 win 是不一樣的
	 */
	@Value("crm.face")
	private static String FACE_ENGINE = "WIN64";


	private final static Logger logger = LogManager.getLogger(FaceRecognitionUtils.class.getName());

	// 人臉引擎
	private static FaceEngine faceEngine = new FaceEngine(FACE_ENGINE);

	// 創建引擎功能對象(用於初始化引擎)
	private static FunctionConfiguration functionConfiguration1 = new FunctionConfiguration();

	// 創建引擎功能對象(用於人臉檢測)
	private static FunctionConfiguration functionConfiguration2 = new FunctionConfiguration();

	static {
		// 初始化引擎功能(用於初始化引擎)
		{
			// 是否支援年齡檢測功能
			functionConfiguration1.setSupportAge(true);
			// 是否支援3D檢測功能
			functionConfiguration1.setSupportFace3dAngle(true);
			// 是否支援人臉檢測功能
			functionConfiguration1.setSupportFaceDetect(true);
			// 是否支援人臉識別功能
			functionConfiguration1.setSupportFaceRecognition(true);
			// 是否支援性別檢測功能
			functionConfiguration1.setSupportGender(true);
			// 是否支援RGB活體檢測功能
			functionConfiguration1.setSupportLiveness(true);
			// 是否支援IR活體檢測功能
			functionConfiguration1.setSupportIRLiveness(true);
		}
		// 初始化引擎功能(用於人臉檢測)
		{
			// 是否支援年齡檢測功能
			functionConfiguration2.setSupportAge(true);
			// 是否支援3D檢測功能
			functionConfiguration2.setSupportFace3dAngle(true);
			// 是否支援性別檢測功能
			functionConfiguration2.setSupportGender(true);
			// 是否支援RGB活體檢測功能
			functionConfiguration2.setSupportLiveness(true);
		}
	}

	/**
	 * 在線激活SDK
	 *
	 * @return
	 */
	public static void sdkActivation() {
		int errorCode = faceEngine.activeOnline(APP_ID, SDK_KEY);
		if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
			// SDK激活失敗
			logger.error("在線激活SDK失敗!錯誤碼:" + errorCode);
		} else {
			// SDK激活成功
			logger.info("在線激活SDK成功!");
		}

	}


	/**
	 * 初始化引擎
	 *
	 * @param detectMode   檢測模式(推薦IMAGE 模式)
	 * @param detectOrient 人臉檢測角度(推薦人臉檢測角度,逆時針0度)
	 * @param faceMaxNum   人臉檢測最大數量(推薦10)
	 * @param faceScaleVal 最小人臉比例(VIDEO模式推薦16;IMAGE模式推薦32)
	 */
	public static void InitializeTheEngine(DetectMode detectMode, DetectOrient detectOrient, int faceMaxNum, int faceScaleVal) {
		// 創建引擎配置類
		EngineConfiguration engineConfiguration = new EngineConfiguration();
		// 設置detectMode參數
		engineConfiguration.setDetectMode(detectMode);
		// 設置detectFaceOrientPriority參數
		engineConfiguration.setDetectFaceOrientPriority(detectOrient);
		// 設置人臉檢測最大數量
		engineConfiguration.setDetectFaceMaxNum(faceMaxNum);
		// 設置detectFaceScaleVal參數為:識別的最小人臉比例 = 圖片長邊 / 人臉框長邊的比值
		engineConfiguration.setDetectFaceScaleVal(faceScaleVal);
		// 配置引擎功能
		engineConfiguration.setFunctionConfiguration(functionConfiguration1);
		// 檢測角度
//		engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);

		// 初始化引擎
		int errorCode = faceEngine.init(engineConfiguration);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 引擎初始化失敗
			logger.error("引擎初始化失敗!錯誤碼:" + errorCode);
		} else {
			// 引擎初始化成功
			logger.info("引擎初始化成功!");
		}
	}


	/**
	 * 人臉檢測(傳入分離的影像資訊數據)
	 *
	 * @param imageInfo    影像資訊
	 * @param faceInfoList 人臉資訊列表
	 * @return 檢測結果,檢測成功或是失敗!
	 */
	public static boolean faceDetection1(ImageInfo imageInfo, List<faceinfo> faceInfoList) {
		int errorCode = faceEngine.detectFaces(imageInfo.getImageData(),
				imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(),
				faceInfoList);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉檢測失敗
			logger.error("人臉檢測失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 人臉檢測成功
			logger.info("人臉檢測成功!");
			return true;
		}
	}


	/**
	 * 人臉檢測(傳入ImageInfoEx影像資訊數據)
	 *
	 * @param imageInfo    影像資訊
	 * @param faceInfoList 人臉資訊列表
	 * @return 檢測結果,檢測成功或是失敗!
	 */
	public static boolean faceDetection2(ImageInfo imageInfo, List<faceinfo> faceInfoList) {
		ImageInfoEx imageInfoEx = new ImageInfoEx();
		imageInfoEx.setHeight(imageInfo.getHeight());
		imageInfoEx.setWidth(imageInfo.getWidth());
		imageInfoEx.setImageFormat(imageInfo.getImageFormat());
		imageInfoEx.setImageDataPlanes(new byte[][]{imageInfo.getImageData()});
		imageInfoEx.setImageStrides(new int[]{imageInfo.getWidth() * 3});
		int errorCode = faceEngine.detectFaces(imageInfoEx,
				DetectModel.ASF_DETECT_MODEL_RGB, faceInfoList);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉檢測失敗
			logger.error("人臉檢測失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 人臉檢測成功
			logger.info("人臉檢測成功!");
			return true;
		}
	}


	/**
	 * 人臉特徵提取,是否檢測到人臉
	 *
	 * @param imageInfo
	 * @return
	 */
	public static byte[] extractFaceFeature(ImageInfo imageInfo) {
		try {
			//人臉檢測得到人臉列表
			List<faceinfo> faceInfoList = new ArrayList<faceinfo>();
			//人臉檢測
			int i = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);

			if (faceInfoList.size() > 0) {
				FaceFeature faceFeature = new FaceFeature();
				//提取人臉特徵
				faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
				return faceFeature.getFeatureData();
			}
		} catch (Exception e) {
			logger.error("", e);
		}

		return null;
	}

	/**
	 * 人臉特徵提取(傳入分離的影像資訊數據)
	 *
	 * @param imageInfo 影像資訊
	 * @param faceInfo  人臉資訊
	 * @return 人臉特徵
	 */
	public static FaceFeature faceFeatureExtraction1(ImageInfo imageInfo, FaceInfo faceInfo) {
		FaceFeature faceFeature = new FaceFeature();
		int errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(),
				imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(),
				faceInfo, faceFeature);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉特徵提取失敗
			logger.error("人臉特徵提取失敗!錯誤碼:" + errorCode);
			return null;
		} else {
			// 人臉特徵提取成功
			logger.info("人臉特徵提取成功!");
			return faceFeature;
		}
	}


	/**
	 * 人臉特徵提取(傳入ImageInfoEx影像資訊數據)
	 *
	 * @param imageInfo 影像資訊
	 * @param faceInfo  人臉資訊
	 * @return 人臉特徵
	 */
	public static FaceFeature faceFeatureExtraction2(ImageInfo imageInfo, FaceInfo faceInfo) {
		ImageInfoEx imageInfoEx = new ImageInfoEx();
		imageInfoEx.setHeight(imageInfo.getHeight());
		imageInfoEx.setWidth(imageInfo.getWidth());
		imageInfoEx.setImageFormat(imageInfo.getImageFormat());
		imageInfoEx.setImageDataPlanes(new byte[][]{imageInfo.getImageData()});
		imageInfoEx.setImageStrides(new int[]{imageInfo.getWidth() * 3});

		// 創建人臉特徵對象
		FaceFeature faceFeature = new FaceFeature();
		int errorCode = faceEngine.extractFaceFeature(imageInfoEx, faceInfo, faceFeature);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉特徵提取失敗
			logger.error("人臉特徵提取失敗!錯誤碼:" + errorCode);
			return null;
		} else {
			// 人臉特徵提取成功
			logger.info("人臉特徵提取成功!");
			return faceFeature;
		}
	}


	/**
	 * 人臉特徵比對
	 *
	 * @param targetFaceFeature 目標人臉特徵
	 * @param sourceFaceFeature 來源人臉特徵
	 * @param compareModel      比對模型
	 * @return 比對相似度
	 */
	public static Float faceFeatureComparison(FaceFeature targetFaceFeature, FaceFeature sourceFaceFeature, CompareModel compareModel) {
		// 創建比對相似度對象
		FaceSimilar faceSimilar = new FaceSimilar();
		int errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, compareModel, faceSimilar);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉特徵比對失敗
			logger.error("人臉特徵比對失敗!錯誤碼:" + errorCode);
			return null;
		} else {
			// 人臉特徵比對成功
			logger.info("人臉特徵比對成功!");
			return faceSimilar.getScore();
		}
	}


	/**
	 * 人臉特徵比對(默認LIFE_PHOTO比對模型)
	 *
	 * @param targetFaceFeature 目標人臉特徵
	 * @param sourceFaceFeature 來源人臉特徵
	 * @return 比對相似度
	 */
	public static Float faceFeatureComparison(FaceFeature targetFaceFeature, FaceFeature sourceFaceFeature) {
		// 創建比對相似度對象
		FaceSimilar faceSimilar = new FaceSimilar();
		int errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature,
				faceSimilar);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉特徵比對失敗
			logger.error("人臉特徵比對失敗!錯誤碼:" + errorCode);
			return null;
		} else {
			// 人臉特徵比對成功
			logger.info("人臉特徵比對成功!");
			return faceSimilar.getScore();
		}
	}


	/**
	 * 人臉屬性檢測(傳入分離的影像資訊數據)
	 *
	 * @param imageInfo    影像資訊
	 * @param faceInfoList 人臉資訊列表
	 * @return 檢測結果,檢測成功或是失敗!
	 */
	public static boolean faceAttributeDetection1(ImageInfo imageInfo, List<faceinfo> faceInfoList) {
		int errorCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(),
				imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, functionConfiguration2);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉屬性檢測失敗
			logger.error("人臉屬性檢測失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 人臉屬性檢測成功
			logger.info("人臉屬性檢測成功!");
			return true;
		}
	}


	/**
	 * 人臉屬性檢測(傳入ImageInfoEx影像資訊數據)
	 *
	 * @param imageInfo    影像資訊
	 * @param faceInfoList 人臉資訊列表
	 * @return 檢測結果,檢測成功或是失敗!
	 */
	public static boolean faceAttributeDetection2(ImageInfo imageInfo, List<faceinfo> faceInfoList) {
		ImageInfoEx imageInfoEx = new ImageInfoEx();
		imageInfoEx.setHeight(imageInfo.getHeight());
		imageInfoEx.setWidth(imageInfo.getWidth());
		imageInfoEx.setImageFormat(imageInfo.getImageFormat());
		imageInfoEx.setImageDataPlanes(new byte[][]{imageInfo.getImageData()});
		imageInfoEx.setImageStrides(new int[]{imageInfo.getWidth() * 3});
		int errorCode = faceEngine.process(imageInfoEx, faceInfoList,
				functionConfiguration2);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 人臉屬性檢測失敗
			logger.error("人臉屬性檢測失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 人臉屬性檢測成功
			logger.info("人臉屬性檢測成功!");
			return true;
		}
	}


	/**
	 * 獲取年齡資訊
	 * 注意:人臉屬性檢測之後方可調用
	 *
	 * @return 獲取結果,獲取失敗或是成功!
	 */
	public static boolean getAgeInfo(List<ageinfo> ageInfoList) {
		int errorCode = faceEngine.getAge(ageInfoList);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 獲取年齡資訊失敗
			logger.error("獲取年齡資訊失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 已成功獲取年齡資訊
			logger.info("已成功獲取年齡資訊!");
			return true;
		}
	}


	/**
	 * 獲取性別(0為男性,1為女性。)
	 * 注意:人臉屬性檢測之後方可調用
	 *
	 * @return 獲取結果,獲取失敗或是成功!
	 */
	public static boolean getGender(List<genderinfo> genderInfoList) {
		// 性別檢測
		int errorCode = faceEngine.getGender(genderInfoList);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 獲取性別失敗
			logger.error("獲取性別失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 已成功獲取年齡資訊
			logger.info("已成功獲取性別!");
			return true;
		}
	}


	/**
	 * 獲取人臉三維角度資訊
	 * 人臉3D角度:俯仰角(pitch), 橫滾角(roll), 偏航角(yaw)。
	 * 注意:人臉屬性檢測之後方可調用
	 *
	 * @return 獲取結果,獲取失敗或是成功!
	 */
	public static boolean getFace3DAngle(List<face3dangle> face3DAngleList) {
		// 人臉三維角度檢測
		int errorCode = faceEngine.getFace3DAngle(face3DAngleList);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 獲取人臉三維角度資訊失敗
			logger.error("獲取人臉三維角度資訊失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 已成功獲取人臉三維角度資訊
			logger.info("已成功獲取人臉三維角度資訊!");
			return true;
		}
	}


	/**
	 * 獲取RGB活體資訊
	 * RGB活體值,未知=-1 、非活體=0 、活體=1、超出人臉=-2
	 * 注意:人臉屬性檢測之後方可調用
	 *
	 * @return 獲取結果,獲取失敗或是成功!
	 */
	public static boolean getLiveness(List<livenessinfo> livenessInfoList) {
		// RGB活體檢測
		int errorCode = faceEngine.getLiveness(livenessInfoList);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 獲取RGB活體資訊失敗
			logger.error("獲取RGB活體資訊失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 已成功獲取RGB活體資訊
			logger.info("已成功獲取RGB活體資訊!");
			return true;
		}
	}

	private static String base64Process(String base64Str) {
		if (!StringUtils.isEmpty(base64Str)) {
			String photoBase64 = base64Str.substring(0, 30).toLowerCase();
			int indexOf = photoBase64.indexOf("base64,");
			if (indexOf > 0) {
				base64Str = base64Str.substring(indexOf + 7);
			}

			return base64Str;
		} else {
			return "";
		}
	}

	/**
	 * IR活體檢測(傳入分離的影像資訊數據)
	 * 注意:
	 * 引擎需要支援IR活體檢測功能
	 *
	 * @return 檢測結果,檢測成功或是失敗!
	 */
	public static boolean detectionLiveness_IR1(String string) throws IOException {
		// 創建影像資訊
//		ImageInfo imageInfoGray = getGrayData(file);
		byte[] decode = Base64.decode(base64Process(string));
		BufferedImage bufImage = ImageIO.read(new ByteArrayInputStream(decode));
		ImageInfo imageInfoGray = ImageFactory.bufferedImage2ImageInfo(bufImage);

		// 創建人臉資訊列表
		List<faceinfo> faceInfoListGray = new ArrayList<faceinfo>();
		// 人臉檢測(傳入分離的影像資訊數據)
		int errorCode1 = faceEngine.detectFaces(imageInfoGray.getImageData(),
				imageInfoGray.getWidth(), imageInfoGray.getHeight(),
				imageInfoGray.getImageFormat(), faceInfoListGray);
		// 創建引擎功能實例對象
		FunctionConfiguration configuration = new FunctionConfiguration();
		// 設置引擎支援IR活體檢測
		configuration.setSupportIRLiveness(true);
		// IR活體檢測
		int errorCode2 = faceEngine.processIr(imageInfoGray.getImageData(),
				imageInfoGray.getWidth(), imageInfoGray.getHeight(),
				imageInfoGray.getImageFormat(), faceInfoListGray, configuration);
		if (errorCode1 != ErrorInfo.MOK.getValue() || errorCode2 != ErrorInfo.MOK.getValue()) {
			String errorCode = errorCode1 == 0 ? errorCode2 + "" : errorCode1 + "";
			// IR活體檢測失敗
			logger.error("IR活體檢測失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// IR活體檢測成功
			logger.info("IR活體檢測成功!");
			return true;
		}
	}


	/**
	 * IR活體檢測(傳入ImageInfoEx影像資訊數據)
	 * 注意:
	 * 引擎需要支援年齡檢測功能
	 *
	 * @param imageInfo 影像資訊
	 * @return 檢測結果,檢測成功或是失敗!
	 */
	public static boolean detectionLiveness_IR2(ImageInfo imageInfo) {
		ImageInfoEx imageInfoEx = new ImageInfoEx();
		imageInfoEx.setHeight(imageInfo.getHeight());
		imageInfoEx.setWidth(imageInfo.getWidth());
		imageInfoEx.setImageFormat(imageInfo.getImageFormat());
		imageInfoEx.setImageDataPlanes(new byte[][]{imageInfo.getImageData()});
		imageInfoEx.setImageStrides(new int[]{imageInfo.getWidth() * 3});
		List<faceinfo> faceInfoList1 = new ArrayList<>();
		int errorCode1 = faceEngine.detectFaces(imageInfoEx,
				DetectModel.ASF_DETECT_MODEL_RGB, faceInfoList1);
		FunctionConfiguration fun = new FunctionConfiguration();
		fun.setSupportAge(true);
		int errorCode2 = faceEngine.processIr(imageInfoEx, faceInfoList1,
				fun);
		if (errorCode1 != ErrorInfo.MOK.getValue() || errorCode2 != ErrorInfo.MOK.getValue()) {
			String errorCode = errorCode1 == 0 ? errorCode2 + "" : errorCode1 + "";
			// IR活體檢測失敗
			logger.error("IR活體檢測失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// IR活體檢測成功
			logger.info("IR活體檢測成功!");
			return true;
		}
	}


	/**
	 * 獲取IR活體資訊
	 * IR活體值,未知=-1 、非活體=0 、活體=1、超出人臉=-2
	 *
	 * @return 獲取結果,獲取失敗或是成功!
	 */
	public static boolean getIrLiveness(List<irlivenessinfo> irLivenessInfo) {
		// IR活體檢測
		int errorCode = faceEngine.getLivenessIr(irLivenessInfo);
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 獲取IR活體資訊失敗
			logger.error("獲取IR活體資訊失敗!錯誤碼:" + errorCode);
			return false;
		} else {
			// 已成功獲取IR活體資訊
			logger.info("已成功獲取IR活體資訊!");
			return true;
		}
	}


	/**
	 * 銷毀SDK引擎
	 */
	public static void destroyTheSDKEngine() {
		int errorCode = faceEngine.unInit();
		if (errorCode != ErrorInfo.MOK.getValue()) {
			// 銷毀SDK引擎失敗
			logger.error("銷毀SDK引擎失敗!錯誤碼:" + errorCode);
		} else {
			// 銷毀SDK引擎成功
			logger.info("銷毀SDK引擎成功!");
		}
	}

}

2.創建ErrorCodeEnum

package top.yangbuyi.constant;

/**
 * 錯誤程式碼枚舉
 *
 * @author yang buyi
 * @date 2021/07/02
 */
public enum ErrorCodeEnum {

	MOK(0, "成功"),
	UNKNOWN(1, "未知錯誤"),
	INVALID_PARAM(2, "無效參數"),
	UNSUPPORTED(3, "引擎不支援"),
	NO_MEMORY(4, "記憶體不足"),
	BAD_STATE(5, "狀態錯誤"),
	USER_CANCEL(6, "用戶取消相關操作"),
	EXPIRED(7, "操作時間過期"),
	USER_PAUSE(8, "用戶暫停操作"),
	BUFFER_OVERFLOW(9, "緩衝上溢"),
	BUFFER_UNDERFLOW(10, "緩衝下溢"),
	NO_DISKSPACE(11, "存貯空間不足"),
	COMPONENT_NOT_EXIST(12, "組件不存在"),
	GLOBAL_DATA_NOT_EXIST(13, "全局數據不存在"),
	NO_FACE_DETECTED(14, "未檢出到人臉"),
	FACE_DOES_NOT_MATCH(15, "人臉不匹配"),
	INVALID_APP_ID(28673, "無效的AppId"),
	INVALID_SDK_ID(28674, "無效的SdkKey"),
	INVALID_ID_PAIR(28675, "AppId和SdkKey不匹配"),
	MISMATCH_ID_AND_SDK(28676, "SdkKey 和使用的SDK 不匹配"),
	SYSTEM_VERSION_UNSUPPORTED(28677, "系統版本不被當前SDK所支援"),
	LICENCE_EXPIRED(28678, "SDK有效期過期,需要重新下載更新"),
	APS_ENGINE_HANDLE(69633, "引擎句柄非法"),
	APS_MEMMGR_HANDLE(69634, "記憶體句柄非法"),
	APS_DEVICEID_INVALID(69635, " Device ID 非法"),
	APS_DEVICEID_UNSUPPORTED(69636, "Device ID 不支援"),
	APS_MODEL_HANDLE(69637, "模板數據指針非法"),
	APS_MODEL_SIZE(69638, "模板數據長度非法"),
	APS_IMAGE_HANDLE(69639, "影像結構體指針非法"),
	APS_IMAGE_FORMAT_UNSUPPORTED(69640, "影像格式不支援"),
	APS_IMAGE_PARAM(69641, "影像參數非法"),
	APS_IMAGE_SIZE(69642, "影像尺寸大小超過支援範圍"),
	APS_DEVICE_AVX2_UNSUPPORTED(69643, "處理器不支援AVX2指令"),
	FR_INVALID_MEMORY_INFO(73729, "無效的輸入記憶體"),
	FR_INVALID_IMAGE_INFO(73730, "無效的輸入影像參數"),
	FR_INVALID_FACE_INFO(73731, "無效的臉部資訊"),
	FR_NO_GPU_AVAILABLE(73732, "當前設備無GPU可用"),
	FR_MISMATCHED_FEATURE_LEVEL(73733, "待比較的兩個人臉特徵的版本不一致"),
	FACEFEATURE_UNKNOWN(81921, "人臉特徵檢測錯誤未知"),
	FACEFEATURE_MEMORY(81922, "人臉特徵檢測記憶體錯誤"),
	FACEFEATURE_INVALID_FORMAT(81923, "人臉特徵檢測格式錯誤"),
	FACEFEATURE_INVALID_PARAM(81924, "人臉特徵檢測參數錯誤"),
	FACEFEATURE_LOW_CONFIDENCE_LEVEL(81925, "人臉特徵檢測結果置信度低"),
	ASF_EX_BASE_FEATURE_UNSUPPORTED_ON_INIT(86017, "Engine不支援的檢測屬性"),
	ASF_EX_BASE_FEATURE_UNINITED(86018, "需要檢測的屬性未初始化"),
	ASF_EX_BASE_FEATURE_UNPROCESSED(86019, "待獲取的屬性未在process中處理過"),
	ASF_EX_BASE_FEATURE_UNSUPPORTED_ON_PROCESS(86020, "PROCESS不支援的檢測屬性,例如FR,有自己獨立的處理函數"),
	ASF_EX_BASE_INVALID_IMAGE_INFO(86021, "無效的輸入影像"),
	ASF_EX_BASE_INVALID_FACE_INFO(86022, "無效的臉部資訊"),
	ASF_BASE_ACTIVATION_FAIL(90113, "人臉比對SDK激活失敗,請打開讀寫許可權"),
	ASF_BASE_ALREADY_ACTIVATED(90114, "人臉比對SDK已激活"),
	ASF_BASE_NOT_ACTIVATED(90115, "人臉比對SDK未激活"),
	ASF_BASE_SCALE_NOT_SUPPORT(90116, "detectFaceScaleVal 不支援"),
	ASF_BASE_VERION_MISMATCH(90117, "SDK版本不匹配"),
	ASF_BASE_DEVICE_MISMATCH(90118, "設備不匹配"),
	ASF_BASE_UNIQUE_IDENTIFIER_MISMATCH(90119, "唯一標識不匹配"),
	ASF_BASE_PARAM_NULL(90120, "參數為空"),
	ASF_BASE_SDK_EXPIRED(90121, "SDK已過期"),
	ASF_BASE_VERSION_NOT_SUPPORT(90122, "版本不支援"),
	ASF_BASE_SIGN_ERROR(90123, "簽名錯誤"),
	ASF_BASE_DATABASE_ERROR(90124, "資料庫插入錯誤"),
	ASF_BASE_UNIQUE_CHECKOUT_FAIL(90125, "唯一標識符校驗失敗"),
	ASF_BASE_COLOR_SPACE_NOT_SUPPORT(90126, "輸入的顏色空間不支援"),
	ASF_BASE_IMAGE_WIDTH_NOT_SUPPORT(90127, "輸入影像的byte數據長度不正確"),
	ASF_NETWORK_BASE_COULDNT_RESOLVE_HOST(94209, "無法解析主機地址"),
	ASF_NETWORK_BASE_COULDNT_CONNECT_SERVER(94210, "無法連接伺服器"),
	ASF_NETWORK_BASE_CONNECT_TIMEOUT(94211, "網路連接超時"),
	ASF_NETWORK_BASE_UNKNOWN_ERROR(94212, "未知錯誤");


	private Integer code;
	private String description;

	ErrorCodeEnum(Integer code, String description) {
		this.code = code;
		this.description = description;
	}

	public Integer getCode() {
		return code;
	}

	public void setCode(Integer code) {
		this.code = code;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public static ErrorCodeEnum getDescriptionByCode(Integer code) {
		for (ErrorCodeEnum errorCodeEnum : ErrorCodeEnum.values()) {
			if (code.equals(errorCodeEnum.getCode())) {
				return errorCodeEnum;
			}
		}
		return ErrorCodeEnum.UNKNOWN;
	}

}

3.創建ArcFaceController

package top.yangbuyi.controller;

import com.arcsoft.face.*;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import top.yangbuyi.constant.ErrorCodeEnum;
import top.yangbuyi.utils.FaceRecognitionUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: ArcFace
 * @ClassName: ArcFaceController
 * @create: 2021-07-01 14:30
 * @author: Yang Shuai
 * @since: JDK1.8
 * @ArcFaceController: 人臉活體檢測$
 **/


@RestController
@Slf4j
@RequestMapping("arcFace")
public class ArcFaceController {

	/**
	 * 人臉識別檢測
	 *
	 * @param url base64 地址
	 * @param oId 組織架構 ID
	 * @param uid 當前登錄檢測的用戶ID
	 * @return
	 */
	@RequestMapping(value = "arcFaceSearch", method = RequestMethod.POST)
	public Map arcFaceSearch(@RequestParam String url, @RequestParam Integer oId, @RequestParam Integer uid) {
		// 前端展示原圖
		String urlTemp = url;

		// ...業務
		final HashMap<string, object=""> stringObjectHashMap = new HashMap<>(14);
		stringObjectHashMap.put("success", false);

		// 初始化引擎
		FaceRecognitionUtils.InitializeTheEngine(DetectMode.ASF_DETECT_MODE_IMAGE, DetectOrient.ASF_OP_0_ONLY, 10, 32);

		if (!StringUtils.isEmpty(url)) {
			String photoBase64 = url.substring(0, 30).toLowerCase();
			int indexOf = photoBase64.indexOf("base64,");
			if (indexOf > 0) {
				url = url.substring(indexOf + 7);
			}
			// 開始轉碼
			byte[] decode = Base64.decode(url);
			BufferedImage bufImage = null;
			try {
				bufImage = ImageIO.read(new ByteArrayInputStream(decode));
			} catch (IOException e) {
				e.printStackTrace();
				return stringObjectHashMap;
			}

			// 獲取圖片資訊
			ImageInfo imageInfo = ImageFactory.bufferedImage2ImageInfo(bufImage);

			//人臉特徵獲取
			byte[] bytes = FaceRecognitionUtils.extractFaceFeature(imageInfo);
			// 校驗是否顯示出人臉
			if (bytes == null) {
				System.out.println(ErrorCodeEnum.NO_FACE_DETECTED.getDescription());
				stringObjectHashMap.put("msg", ErrorCodeEnum.NO_FACE_DETECTED.getDescription());
				return stringObjectHashMap;
			}

			// 創建影像中的人臉資訊對象列表
			List<faceinfo> faceInfoList1 = new ArrayList<>();

			// 檢測影像中人臉
			FaceRecognitionUtils.faceDetection1(imageInfo, faceInfoList1);

			// 檢測影像中人臉屬性
			FaceRecognitionUtils.faceAttributeDetection1(imageInfo, faceInfoList1);

			// 檢測人臉特徵

			/* 影像中的人臉年齡 */
			{
				// 創建影像中的人臉年齡列表
				List<ageinfo> ageInfoList1 = new ArrayList<>();
				// 檢測影像中的人臉年齡列表
				FaceRecognitionUtils.getAgeInfo(ageInfoList1);
				// 將影像中的年齡列表列印到控制台
				if (ageInfoList1.size() > 0) {
					stringObjectHashMap.put("age", ageInfoList1.get(0).getAge());
				}
			}

			/* 影像中的人臉性別 */
			// 創建影像中的人臉性別列表
			List<genderinfo> genderInfoList1 = new ArrayList<>();
			// 檢測影像中的人臉性別列表
			FaceRecognitionUtils.getGender(genderInfoList1);
			// 將影像中的性別列表列印到控制台
			if (genderInfoList1.size() > 0) {
				stringObjectHashMap.put("gender", genderInfoList1.get(0).getGender() == 0 ? "男" : "女");
			}

			/* 影像1中的人臉三維角度 */
			// 創建影像中的人臉三維角度資訊列表
			List<face3dangle> face3DAngleList1 = new ArrayList<>();
			// 獲取影像1中的人臉三維角度資訊列表
			FaceRecognitionUtils.getFace3DAngle(face3DAngleList1);
			// 將影像中的人臉三維角度資訊列表列印到控制台
			if (face3DAngleList1.size() > 0) {
				List<map<string, object="">> td = new ArrayList<>();
				Map<string, object=""> map = new HashMap<>();
				map.put("俯仰角", face3DAngleList1.get(0).getPitch());
				map.put("橫滾角", face3DAngleList1.get(0).getRoll());
				map.put("偏航角", face3DAngleList1.get(0).getYaw());
				td.add(map);
				stringObjectHashMap.put("ThreeDimensional", td);
			}

			/* 影像1中的人臉RGB活體值 */
			// 創建影像中的RGB活體資訊列表
			List<livenessinfo> livenessInfoList1 = new ArrayList<>();
			// 獲取影像1中的RGB活體資訊列表
			FaceRecognitionUtils.getLiveness(livenessInfoList1);
			// 將影像中的RGB活體資訊列表列印到控制台
			if (livenessInfoList1.size() > 0) {
				stringObjectHashMap.put("RgbLiveness", livenessInfoList1.get(0).getLiveness());
			}

			/**
			 * 注意: 活體只能支援一個人臉否則返回未知
			 * 所以我們可以進行使用他來判斷是否有多個人檢測 直接判定失敗
			 */
			if (livenessInfoList1.size() > 0 && livenessInfoList1.get(0).getLiveness() == 1) {
				stringObjectHashMap.put("success", true);
				stringObjectHashMap.put("baseUrl", urlTemp);
			}
		} else {
			stringObjectHashMap.put("data", "url,不允許為空");
		}
		return stringObjectHashMap;
	}

}

4. 創建路由跳轉前端頁面 RouteController

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @program: ArcFace
 * @ClassName: RouteController
 * @create: 2021-07-02 09:14
 * @author: Yang Shuai
 * @since: JDK1.8
 * @RouteController: $
 **/

@Controller
public class RouteController {

	@GetMapping("/")
	public String yby() {
	    // ...業務
	    return "index";
	}

}

三. 前端人臉追蹤插件

訪問地址: //trackingjs.com/

裡面有demo可觀看我就不帶大家查看了

image-20210702171350487

1. 創建前端index.html

js請下載demo獲取,連接在最下面



	<meta charset="UTF-8">
	<title>人臉檢測</title>
	<script src="jquery-3.3.1.min.js"></script>
	<script src="tracking.js"></script>
	<script src="face-min.js"></script>
	<script src="training/Landmarks.js"></script>
	<script src="training/Regressor.js"></script>
	<script src="stats.min.js"></script>
	<style>
        #regcoDiv {
            width: 100%;
            height: 530px;
            position: relative;
            background: #eee;
            overflow: hidden;
            border-bottom-right-radius: 10px;
            border-bottom-left-radius: 10px;

            /*-webkit-animation: twinkling 1s infinite ease-in-out;*/
            /*-webkit-animation-duration: 1s;*/
            /*animation-duration: 1s;*/
            /*-webkit-animation-fill-mode: both;*/
            /*animation-fill-mode: both*/
        }

        video, canvas {
            margin-left: 230px;
            /*margin-top: 120px;*/
            position: absolute;
        }

        .className {
            -webkit-animation: twinkling 1s infinite ease-in-out
        }

        .animated {
            -webkit-animation-duration: 1s;
            animation-duration: 1s;
            -webkit-animation-fill-mode: both;
            animation-fill-mode: both
        }

        @-webkit-keyframes twinkling {
            0% {
                background: #eee;
            }

            35% {
                background: #08e800;
            }

            56% {
                background: #1f25d4;
            }

            100% {
                background: #eee;
            }
        }

        @keyframes twinkling {
            0% {
                background: #eee;
            }

            35% {
                background: #08e800;
            }

            56% {
                background: #1f25d4;
            }

            100% {
                background: #eee;
            }
        }
	</style>



<div id="regcoDiv">

</div>
<div>
	<table frame="void">
		<tbody><tr>
			<td>
				<button title="人臉識別" value="人臉識別" onclick="getMedia2()" style="color:#FFFFFF;height: 30px;display:block;margin:0 auto;margin-top:10px;width:120px;background-color: #3F51B5;border-radius:5px;text-align: center;line-height: 30px;font-size: 20px">
					攝影機識別
				</button>
			</td>
		</tr>
		<tr>
			<td colspan="2">
				<button id="snap" onclick="chooseFileChangeComp()" style="color:#FFFFFF;height: 30px;display:block;margin:0 auto;margin-top:10px;width:100px;background-color: #3F51B5;border-radius:5px;text-align: center;line-height: 30px;font-size: 20px">
					提交
				</button>
			</td>
		</tr>
	</tbody></table>

</div>
<div>
	<img id="imageDivComp" src="">
</div>



<script>
    getMedia2()
	
	
    $("#imageDivComp").click(function () {
        $("#chooseFileComp").click();
    });
    var t1;

    /**
     * 開始畫攝影機
     */
    function getMedia2() {
        $("#regcoDiv").empty();
        let vedioComp = "<video id='video2' width='500px' height='500px'  autoplay='autoplay' playsinline webkit-playsinline='true' ></video><canvas id='canvas2' width='500px' height='500px'></canvas>";
        $("#regcoDiv").append(vedioComp);
        let constraints = {
            video: {width: 500, height: 500},
            audio: true
        };
        //獲得video攝影機區域
        let video = document.getElementById("video2");
        // 這裡介紹新的方法,返回一個 Promise對象
        // 這個Promise對象返回成功後的回調函數帶一個 MediaStream 對象作為其參數
        // then()是Promise對象里的方法
        // then()方法是非同步執行,當then()前的方法執行完後再執行then()內部的程式
        // 避免數據沒有獲取到
        let promise = navigator.mediaDevices.getUserMedia(constraints);
        promise.then(function (MediaStream) {
            video.srcObject = MediaStream;
            video.play();
        });

        /**
         * 模擬手機端 三秒主動提交檢測
         * @type {number}
         */
        t1 = window.setInterval(function () {
            chooseFileChangeComp()
        }, 3000)

    }

    /**
	 * 提交檢測 請求介面
	 */
    function chooseFileChangeComp() {
        let regcoDivComp = $("#regcoDiv");
        if (regcoDivComp.has('video').length) {
            let video = document.getElementById("video2");
            let canvas = document.getElementById("canvas2");
            let ctx = canvas.getContext('2d');
            ctx.drawImage(video, 0, 0, 500, 500);
            var base64File = canvas.toDataURL();
            var formData = new FormData();
            formData.append("url", base64File);
            formData.append("oId", 1);
            formData.append("uid", 1);
            $.ajax({
                type: "post",
                url: "/arcFace/arcFaceSearch",
                data: formData,
                contentType: false,
                processData: false,
                async: false,
                success: function (text) {
                    var res = JSON.stringify(text)
                    if (text.success == true && text.RgbLiveness == 1) {
                        console.log(text);
                        clearInterval(t1);
                        console.log(text.baseUrl);
                    } else {
                        console.log(text);
                    }

                },
                error: function (error) {
     
                    alert(JSON.stringify(error))
                }
            });
        }
    }
	
    /**
	 * 人臉追蹤畫框
	 **/
    window.onload = function () {
        let video = document.getElementById("video2");
        let canvas = document.getElementById("canvas2");
        let context = canvas.getContext('2d');

        var tracker = new tracking.LandmarksTracker();
        tracker.setInitialScale(4);
        tracker.setStepSize(2);
        tracker.setEdgesDensity(0.1);

        tracking.track(video, tracker);

        tracker.on('track', function (event) {

            context.clearRect(0, 0, canvas.width, canvas.height);

            if (!event.data) return;
            // 畫框樣式
            event.data.faces.forEach(function (rect) {
                context.strokeStyle = '#eb4c4c';
                context.strokeRect(rect.x, rect.y, rect.width, rect.height);
                context.font = '16px Helvetica';
                context.fillStyle = "#000";
                context.lineWidth = '5';
                context.fillText('人臉橫向: ' + rect.x + 'px', rect.x + rect.width + 5, rect.y + 11);
                context.fillText('人臉縱向: ' + rect.y + 'px', rect.x + rect.width + 5, rect.y + 50);
            });

            /**
             * 人臉追蹤 顆粒
             */
            event.data.landmarks.forEach(function (landmarks) {
                for (var l in landmarks) {
                    context.beginPath();
                    context.fillStyle = "#fff";
                    context.arc(landmarks[l][0], landmarks[l][1], 1, 0, 2 * Math.PI);
                    context.fill();
                }
            });

        });
		// 這裡如果報錯  不用管 
        var gui = new dat.GUI();
        gui.add(tracker, 'edgesDensity', 0.1, 0.5).step(0.01).listen();
        gui.add(tracker, 'initialScale', 1.0, 10.0).step(0.1).listen();
        gui.add(tracker, 'stepSize', 1, 5).step(0.1).listen();
    };

</script>

6. 啟動工程 訪問 //localhost:7000/

四. 人臉識別追蹤就到這裡啦,具體的程式碼已經提交到gitee請前往獲取Java項目 ArcFace

點擊前往獲取demo</string,></map<string,></string,>