虹软人脸SDK 调用常见问题和最佳实践指南
1. 文档说明
本文档针对在实际项目落地过程中,调用虹软(ArcSoft)人脸识别SDK出现的常见问题进行总结,涉及内容包括:
- 性能优化(人脸识别速度提升、JPEG转BGR24性能优化等)
- 人脸抠图
- 多人脸图片中最优人脸选择
- 服务端高并发图片请求优化
- 内存泄漏排查
2. JPEG转BGR24耗时长优化
在实际项目开发过程中,会遇到JPEG转BGR24耗时长问题,导致人脸操作整体耗时增加。java中自带原生图像处理类ImageIO,但是该接口转码速度较慢,内部实际流程如下:
JPEG
↓
JPEG Decoder
↓
BufferedImage
↓
Raster
↓
byte[]
需要经历:
- JPEG解码
- BufferedImage构造
- ColorModel转换
- Raster数据拷贝
推荐使用javaCV接口实现图片转码,将jpeg转换成BRG24。
Mat mat = imdecode(...)
内部流程:
JPEG
↓
OpenCV
↓
libjpeg-turbo
↓
Mat(BGR)
直接输出:
BGR24
减少了中间对象和颜色格式转换。
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.opencv.opencv_core.Mat;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imdecode;
import static org.bytedeco.opencv.global.opencv_imgcodecs.IMREAD_COLOR;
public class JpegDecoder {
public static BGRImage jpegToBGR(byte[] jpegData) {
BytePointer jpegPtr = new BytePointer(jpegData);
Mat jpegMat = new Mat(
1,
jpegData.length,
org.bytedeco.opencv.global.opencv_core.CV_8UC1,
jpegPtr
);
Mat mat = imdecode(jpegMat, IMREAD_COLOR);
if (mat.empty()) {
throw new RuntimeException("decode failed");
}
int width = mat.cols();
int height = mat.rows();
byte[] bgr = new byte[width * height * 3];
mat.data().get(bgr);
mat.release();
jpegMat.release();
jpegPtr.close();
return new BGRImage(width, height, bgr);
}
}
3. 服务器场景下高并发人脸图片请求耗时优化
问题描述
在Linux Server场景中,经常会遇到大量人脸图片并发请求的情况,这可能会导致人脸相关操作被阻塞,例如:人脸特征提取,人脸特征提取在SDK提供的接口中,耗时相对较高,高并发的人脸识别请求,会导致人脸特征提取耗时更长。
推荐解决方案
使用引擎池和线程池,能够有效解决上述问题,这里推荐人脸SDK引擎的数量和线程池中线程数量相同,线程数量不超过设备的核心数。具体可以参考链接:虹软人脸服务器版SDK(Linux/ARM Pro)多线程调用及性能优化_虹软 服务器版本-CSDN博客
4. 如何实现高效精准的人脸抠图
问题描述
在服务器端人脸识别场景中,客户通常会上传一张图片,服务器调用人脸识别SDK完成人脸检测、人脸特征提取和身份识别。同时,很多项目还需要保存当前识别人员的人脸缩略图,用于:
- 人员档案管理
- 访客记录存档
- 前端列表展示
- 历史记录查询
- 抓拍图片展示
由于SDK输出的人脸框主要服务于人脸识别和特征提取,其框选区域通常较为紧凑,直接按照检测框裁剪,可能出现额头、下巴或脸部边缘缺失的问题,影响展示效果。
推荐解决方案
建议基于SDK返回的人脸框进行适当扩展后再进行裁剪。下面给出C++版本的裁剪代码示例,可以通过调用calcFaceROI得到可靠的裁剪ROI,函数中的缩放因子可以根据实际调整。
struct FaceInput
{
int x1, y1, x2, y2;
float yaw;
float pitch;
int img_w;
int img_h;
};
struct FaceROI
{
int x1, y1, x2, y2;
};
#include
#include
static float clampf(float x, float a, float b)
{
return std::min(std::max(x, a), b);
}
FaceROI calcFaceROI(const FaceInput& in)
{
FaceROI out;
float x1 = (float)in.x1;
float y1 = (float)in.y1;
float x2 = (float)in.x2;
float y2 = (float)in.y2;
float w = x2 - x1;
float h = y2 - y1;
//----------------------------
// 根据人脸大小调整扩张比例
//----------------------------
float scale = 200.f / h;
scale = clampf(scale, 0.8f, 1.3f);
//----------------------------
// 左右固定扩张
//----------------------------
float left_expand = 0.35f * scale;
float right_expand = 0.35f * scale;
//----------------------------
// 上下根据pitch轻微调整
//----------------------------
float pitch_n = clampf(in.pitch / 45.f, -1.f, 1.f);
float top_expand = 0.55f * (1.f - pitch_n * 0.3f);
float bottom_expand = 0.45f * (1.f + pitch_n * 0.3f);
top_expand *= scale;
bottom_expand *= scale;
//----------------------------
// 计算ROI
//----------------------------
float nx1 = x1 - left_expand * w;
float nx2 = x2 + right_expand * w;
float ny1 = y1 - top_expand * h;
float ny2 = y2 + bottom_expand * h;
//----------------------------
// 保持ROI尺寸,整体平移
//----------------------------
// X方向
if (nx1 < 0)
{
nx2 -= nx1;
nx1 = 0;
}
if (nx2 > in.img_w)
{
float diff = nx2 - in.img_w;
nx1 -= diff;
nx2 = (float)in.img_w;
if (nx1 < 0)
nx1 = 0;
}
// Y方向
if (ny1 < 0)
{
ny2 -= ny1;
ny1 = 0;
}
if (ny2 > in.img_h)
{
float diff = ny2 - in.img_h;
ny1 -= diff;
ny2 = (float)in.img_h;
if (ny1 < 0)
ny1 = 0;
}
out.x1 = (int)std::round(nx1);
out.y1 = (int)std::round(ny1);
out.x2 = (int)std::round(nx2);
out.y2 = (int)std::round(ny2);
return out;
}
5. 人脸注册耗时较长优化
问题描述
在服务器场景下,人脸相关应用初次工作起来时,会从数据库读取人脸特征信息注册到SDK内存,过程中可能会遇到注册耗时比较长的情况。
推荐解决方案
人脸SDK接口中有注册单一人脸和批量注册人脸接口。注册多个人脸时,可以使用批量注册人脸接口,减少SDK内部内存的搬运。另外,当程序刚起来时,从数据库读人脸特征信息注册到人脸SDK中时,可以一次性多注册一些人脸特征,例如一次性注册1000张人脸特征信息。这里的1000可以具体根据实际的设备内存情况调整。
public int registerFaceFeature(List faceFeatureInfoList)
另外,也可以调用人脸质量过滤接口,拦截一些低质量照片,降低服务器注册压力。
6. 人脸底库过大,比对太慢怎么办
问题描述
服务器场景下,当人脸底库较大时,高并发的人脸特征请求会导致服务器压力过大,增加比对的耗时,如何提高识别的效率?
推荐解决方案
除了根据硬件情况,增加SDK引擎数量之外,还可以对人脸底库进行针对性的分组,将大人脸底库分成多个小底库,通过compareFaceFeature接口来实现并发比对。例如,在学校的业务场景中,全校可能有几千到上万人,可以根据班级以及行政机构将所有人按组划分,当产生课堂签到点名需求时,课堂上的终端设备上传抓拍照,并带上当前班级信息,后端服务器只需要比对当前班级所在组的人员信息就行。
7. 照片中多人脸如何选择合适的人脸进行比对
问题描述
目前的人脸SDK中支持设置最大检测人脸的数量,例如设置最大人脸检测数量为5个。当图片中出现多张人脸时,应该如何选择合适的人脸进行比对?
推荐解决方案
在门禁场景中,可以结合人脸质量分、最大人脸策略,选择最优的人脸进行人脸识别。
* 检测到 N 张人脸
* │
* ▼
* ┌──────────────────────────┐
* │ Step 1: 质量分过滤 │
* │ 对每张人脸做 imageQualityDetect │
* │ 保留 qualityScore >= 60 的 │
* └──────────┬───────────────┘
* │
* 有合格的?
* ┌─────┴─────┐
* │ │
* 有 没有
* │ │
* ▼ ▼
* ┌─────────┐ ┌──────────────┐
* │ Step 2: │ │ Step 2: │
* │ 从合格 │ │ 退化为最大人脸 │
* │ 的中选 │ │ (直接选面积 │
* │ 最大人脸│ │ 最大的) │
* └────┬────┘ └──────┬───────┘
* └───────┬──────┘
* ▼
* 返回选中的人脸
private FaceInfo findLargestFace(List faceInfoList) {
if (faceInfoList == null || faceInfoList.isEmpty()) {
return null;
}
FaceInfo largestFace = faceInfoList.get(0);
int largestArea = 0;
for (FaceInfo faceInfo : faceInfoList) {
Rect rect = faceInfo.getRect();
if (rect != null) {
int area = rect.getWidth() * rect.getHeight();
if (area > largestArea) {
largestArea = area;
largestFace = faceInfo;
}
}
}
Rect rect = largestFace.getRect();
logger.info("最终选中: 面积 = {}", largestArea);
return largestFace;
}
public FaceInfo selectBestFace(ImageInfo imageInfo, List faceInfoList) {
// Step 1: 质量分过滤
List qualifiedFaces = new ArrayList<>();
for (int i = 0; i < faceCount; i++) {
ImageQuality imageQuality = new ImageQuality();
faceEngineInstance.imageQualityDetect(imageInfo, faceInfo,
FaceQualityMask.ASF_FACE_QUALITY_WITH_LIVENESS.getValue(), imageQuality);
float score = imageQuality.getQualityScore();
if (score >= QUALITY_THRESHOLD) { // 60.0f
qualifiedFaces.add(faceInfo); // 合格
}
}
// Step 2: 从合格的里面选面积最大的
if (!qualifiedFaces.isEmpty()) {
return findLargestFace(qualifiedFaces); // 正常路径
}
// 全部不合格 → 降级为最大人脸
logger.warn("全部不合格,退化为最大人脸策略");
return findLargestFace(faceInfoList);
}
8. 调用人脸识别SDK 出现内存泄漏排查
问题描述
在调用人脸SDK Java版本接口开发相关应用时,会遇到内存泄露的情况,应该如何排查?
推荐解决方案
对于人脸SDK运行时内存可以分为java堆内存和native内存,java堆内存由JVM自己管理,会自动触发GC。而native内存是在人脸SDK中的so库中分配的,JVM无法管理。调用虹软人脸SDK时,最大的风险点就是创建了引擎,后续不使用时却没有及时给释放掉,导致native层内存泄漏。
例如,有的开发者可能会对于一个前端请求,就创建一个人脸SDK引擎,但是创建了之后却没有及时的释放,导致内存泄漏。建议创建引擎池来管理引擎,最后统一释放所有引擎,避免内存泄漏。
public void initPool() {
// 初始化
logger.info("开始初始化人脸识别引擎实例池和线程池...");
// 初始化FaceEngine实例池
enginePool = new LinkedBlockingQueue<>(poolSize);
for (int i = 0; i < poolSize; i++) {
ArcFaceEngine arcFaceEngine = new ArcFaceEngine();
// 激活并初始化SDK引擎
if (!arcFaceEngine.activateAndInitSdk(appId, sdkKey, activeKey, libPath)) {
logger.error("人脸识别SDK初始化和激活失败,应用程序将无法正常运行。");
return;
}
// 将初始化好的引擎放入队列
try{
enginePool.put(arcFaceEngine);
logger.info("人脸识别引擎实例 {} 初始化成功并加入池中.", i);
}catch(InterruptedException e){
logger.error("人脸识别引擎实例 {} 加入池失败.", i);
return;
}
}
// 初始化线程池,大小与引擎池一致,以便每个线程可以独占一个引擎
executorService = Executors.newFixedThreadPool(poolSize);
logger.info("人脸识别线程池初始化成功,大小为: {}", poolSize);
}
原文地址: https://www.cveoy.top/t/topic/qHhz 著作权归作者所有。请勿转载和采集!