云计算百科
云计算领域专业知识百科平台

基于虹软服务器版本SDK实现多路网络摄像头人脸检测

一、背景需求

​ 随着城市安防、智慧园区、智能零售以及工业监控等场景的快速发展,多路网络摄像头实时人脸检测已成为安防等系统中的核心需求。为了实现这些需求,我选择了虹软(ArcSoft)服务器版人脸识别 SDK(以下简称“SDK”),利用其在人脸检测、跟踪、识别方面的高精度与高性能特性,实现多路网络摄像头人脸检测功能。 ​ 网络摄像头离不开ONVIF和RTSP,其中ONVIF(开放式网络视频接口论坛)是一个全球性开放标准,旨在实现不同品牌网络视频设备(如摄像头、录像机)之间的互联互通;RTSP是一种流媒体控制传输层协议,作为ONVIF中的一员,负责播放、暂停、快进、拉取实时视频流、暂停等操作;

​ 本文不详细介绍ONVIF,主要针对RTSP流获取、人脸SDK集成、实时检测人脸和检测结果渲染这几点展开说明。

二、前期准备

在这里插入图片描述

2.1 ONVIF介绍

​ ONVIF通过制定统一的通信协议,解决早期监控系统厂商壁垒问题,让用户能用不同品牌设备构建统一的IP安防系统,支持设备发现、配置、流媒体传输、云台控制、事件管理等功能。ONVIF的技术实现主要基于Web Services架构,通过一系列标准协议实现不同设备间的互操作。技术架构包括服务层(SOAP & XML)、接口描述(WSDL)、传输层(HTTP)和流媒体层(RTSP)。整体技术架构图如下。

​ 海康、大华等主流摄像头基本都支持ONVIF,不同厂商的产品可通过ONVIF实现互联。由于ONVIF体系过于庞大,本文就不做详细介绍了。

2.2 获取RTSP流

2.2.1 本地RTSP流

​ 使用VLC media Player播放器,加载本地视频文件,通过设置流的方式提供本地RTSP播放地址,本方式适合本地临时测试。

2.2.2 远程RTSP流

​ 接入远程硬件摄像头(如海康监控摄像头,基本都支持ONVIF协议),获取摄像头IP地址(带端口)、登录用户名和密码,在地址中输入rtsp://用户名:密码@IP地址:554/Streaming/Channels/通道号(主码流101,子码流102)即可获取视频流。

2.3 获取LINUX PRO SDK

​ 登录虹软开放平台(地址https://ai.arcsoft.com.cn),按要求注册认证,然后选择SDK(注意要选择人脸识别LINUX PRO)。前期开发测试可以申请试用码,申请通过后直接下载SDK。

2.4 开发环境

​ Ubuntu虚拟机或WSL,Visual Studio Code,maven + spring boot,jdk 1.8。前期先使用本地机器WSL环境开发,等正式上线后再迁移到云服务器上。

三、项目架构

在这里插入图片描述

  • 获取本地/远程RTSP流。
  • UI界面/接口:使用VUE3实现UI界面,支持输入多路RTSP地址。
  • 拉流解码/转码:使用 ffmpeg编解码,opencv转换格式,每路 RTSP流有独立的处理周期。
  • 人脸检测/识别:使用线程池,每个线程持有独立的SDK引擎实例,保证线程安全。
  • 结果显示/上报:将人脸检测结果实时绘制到原视频帧上,将绘制结果编码推流,实时渲染在UI上。
  • 四、实现流程

    4.1 项目创建

    使用java spring boot + maven框架创建项目,导入SDK,填写SDK试用码。编写激活和初始化功能,首次运行项目,验证SDK可用。项目框架如下。

    在这里插入图片描述

    使用当前比较主流的技术,后端使用Java语言实现相关逻辑,前端使用VUE3。

    4.2 实现流程

    4.2.1 RTSP流输入

    ​ 使用VUE3框架,简单编写了一个RTSP流实时输入和效果展示页面,目前支持4路RTSP流输入,页面如下图所示。如果只是实现后台检测服务,无需UI显示,可以省去此步骤。 在这里插入图片描述

    ​ 通过前文介绍的方式获取到稳定的RTSP流地址,输入地址点击播放后,通过StreamController类接收相关参数,代码如下。

    @Controller
    @Slf4j
    public class StreamController {
    @Autowired
    private VideoPlayerService videoPlayerService;
    //解析RTSP流地址
    @RequestMapping(value = "/stream")
    public void test(HttpServletRequest request, HttpServletResponse response, @RequestParam(required = false) String address) throws Exception {
    response.addHeader("Content-Disposition", "attachment;filename=\\"" + "127.0.0.1" + "\\"");
    response.setContentType("video/x-flv");
    response.setHeader("Connection", "keep-alive");
    response.setHeader("accept_ranges", "bytes");
    response.setHeader("pragma", "no-cache");
    response.setHeader("cache_control", "no-cache");
    response.setHeader("transfer_encoding", "CHUNKED");
    response.setStatus(200);
    if(StringUtils.isEmpty(address)){
    throw new IllegalAccessException("地址不能为空");
    }
    try(FrameGrabber grabber=new FFmpegFrameGrabber(address)) {
    videoPlayerService.servletStreamPlayer(grabber, response.getOutputStream());
    }
    }
    }

    4.2.2 拉流解码和转码

    ​ 因为SDK对图像数据格式有一定要求,需要稳定、高效地将视频流逐帧解码/转码成符合要求的图像数据,所以我选择了“org.bytedeco.javacv”这个第三方库。这个库集成了ffmpeg,支持对rtsp流进行逐帧解码;集成了opencv,支持不同颜色格式之间转换。javacv库需要在pom.xml文件中集成,javacv可在github上搜索,根据需求填写,示例如下图所示。 在这里插入图片描述

    ​ FFmpegFrameGrabber和OpenCVFrameConverter这两个类是核心,FFmpegFrameGrabber是对FFmpeg解码流程的完整封装,包含打开输入源、解析流信息、初始化解码器、分配AVMFrame/AVMPacket等主要功能,可以对视频进行抓帧、解码;OpenCVFrameConverter负责将解码出来的帧数据进行转码,FFmpeg解码出来的通常是YUV420格式,可以转换成人脸SDK要求的BGR格式。注意,要实现多路RTSP流同时解析,每路都需要创建converter实例。

    public void servletStreamPlayer(FrameGrabber grabber, ServletOutputStream servletOutputStream) throws Exception {
    //要实现多路RTSP流同时解析,每路都需要创建一个converter实例
    OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
    // 开启ffmpeg相关log,建议前期开发测试阶段开启
    //avutil.av_log_set_level(avutil.AV_LOG_DEBUG);
    // 生产环境关闭warning信息
    //avutil.av_log_set_level(avutil.AV_LOG_ERROR);
    // 超时时间(10秒)
    grabber.setOption("stimeout", "10 * 1000 * 1000");
    // 强制使用TCP,要不然无法解码成功
    grabber.setOption("rtsp_transport", "tcp");
    grabber.setOption("threads", "1");
    // 设置缓存大小,提高画质、减少卡顿花屏
    grabber.setOption("buffer_size", "1024000");
    // 读写超时,适用于所有协议的通用读写超时
    grabber.setOption("rw_timeout", "10 * 1000 * 1000");
    // 探测视频流信息,为空默认5000000微秒
    grabber.setOption("probesize", "10 * 1000 * 1000");
    // 解析视频流信息,为空默认5000000微秒
    grabber.setOption("analyzeduration", "10 * 1000 * 1000");
    grabber.start();
    //启动人脸处理引擎
    FacePreview faceProcessEngine = new FacePreview();
    for (; ; ) {
    Frame frame = grabber.grab();
    if (frame == null) {
    break;
    }
    //抓取一帧视频并将其转换为图像IplImage
    IplImage iplImage = converter.convert(frame);
    }
    }

    4.2.3 人脸检测识别
    4.2.3.1 人脸检测参数配置

    ​ 根据官方解释,SDK单个引擎的同一功能模块中的算法功能函数不支持多线程调用,所以为了支持多路视频多线程并发检测,需要创建SDK人脸检测引擎池(以下简称“引擎池”)。借助springboot框架和“org.apache.commons.pool2.GenericObjectPool”对象池类,我们可以很方便的实现引擎池。在application.properties文件中添加以下引擎池配置

    config.arcface-sdk.detect-pool-size=5//大小可根据实际需求配置
    config.arcface-sdk.app-id=从虹软官网获取appId
    config.arcface-sdk.sdk-key=从虹软官网获取sdkKey
    config.arcface-sdk.active-key=从虹软官网获取activeKey

    ​ 根据config文件实现通用人脸检测引擎池配置类,片段代码如下

    @Configuration
    @Slf4j
    public class FaceEnginePoolConfig {
    @Value("${config.arcface-sdk.app-id}")
    public String appId;
    @Value("${config.arcface-sdk.sdk-key}")
    public String sdkKey;
    @Value("${config.arcface-sdk.active-key}")
    public String activeKey;
    @Value("${config.arcface-sdk.active-file}")
    public String activeFile;
    @Value("${config.arcface-sdk.detect-pool-size}")
    public Integer detectPoolSize;
    @Value("${config.arcface-sdk.compare-pool-size}")
    public Integer comparePoolSize;
    @Value("${config.arcface-sdk.faceModel}")
    public String faceModel;

    @Bean
    public FaceEnginePool faceEnginePool() {
    FaceModel faceModels = null;
    if ("large".equals(faceModel)) {
    faceModels = FaceModel.ASF_REC_LARGE;
    }else if ("middle".equals(faceModel)) {
    faceModels = FaceModel.ASF_REC_MIDDLE;
    }else {
    throw new BusinessException(ErrorCodeEnum.FAIL,"无效的人脸模型 config.arcface-sdk.faceModel");
    }
    return new FaceEnginePool(appId, sdkKey, activeKey, activeFile, detectPoolSize, comparePoolSize, faceModels);
    }
    }

    4.2.3.1 创建人脸检测引擎

    创建通用人脸检测引擎池类FaceEnginePool,片段代码如下。

    @Slf4j
    public class FaceEnginePool {
    private GenericObjectPool<FaceEngine> ftPool;

    public FaceEnginePool(String appId, String sdkKey, String activeKey, String activeFile, Integer detectPoolSize, Integer comparePoolSize,FaceModel faceModel) {
    init();
    }

    private void init() {
    GenericObjectPoolConfig<FaceEngine> detectFtPoolConfig = new GenericObjectPoolConfig<>();
    detectFtPoolConfig.setMaxIdle(detectPoolSize);
    detectFtPoolConfig.setMaxTotal(detectPoolSize);
    detectFtPoolConfig.setMinIdle(detectPoolSize);
    detectFtPoolConfig.setLifo(false);
    EngineConfiguration detectFtCfg = new EngineConfiguration();
    FunctionConfiguration detectFtFunctionCfg = new FunctionConfiguration();
    detectFtFunctionCfg.setSupportFaceDetect(true);//开启人脸检测功能
    detectFtFunctionCfg.setSupportAge(true);//开启年龄检测功能
    detectFtFunctionCfg.setSupportGender(true);//开启性别检测功能
    detectFtFunctionCfg.setSupportLiveness(true);//开启活体检测功能
    detectFtCfg.setFunctionConfiguration(detectFtFunctionCfg);
    detectFtCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_VIDEO);//图片检测模式,如果是连续帧的视频流图片,那么改成VIDEO模式
    detectFtCfg.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);//人脸旋转角度
    detectFtCfg.setFaceModel(faceModel);
    //底层库算法对象池
    ftPool = new GenericObjectPool<>(new FaceEngineFactory(appId, sdkKey, activeKey, activeFile, detectFtCfg), detectFtPoolConfig);

    //初始化特征比较线程池
    GenericObjectPoolConfig<FaceEngine> comparePoolConfig = new GenericObjectPoolConfig<>();
    comparePoolConfig.setMaxIdle(comparePoolSize);
    comparePoolConfig.setMaxTotal(comparePoolSize);
    comparePoolConfig.setMinIdle(comparePoolSize);
    comparePoolConfig.setLifo(false);
    EngineConfiguration compareCfg = new EngineConfiguration();
    FunctionConfiguration compareFunctionCfg = new FunctionConfiguration();
    compareFunctionCfg.setSupportFaceRecognition(true);//开启人脸识别功能
    compareFunctionCfg.setSupportMaskDetect(true);//开启口罩检测功能
    compareFunctionCfg.setSupportImageQuality(true);//开启图像质量检测功能
    compareCfg.setFunctionConfiguration(compareFunctionCfg);
    compareCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);//图片检测模式,如果是连续帧的视频流图片,那么改成VIDEO模式
    compareCfg.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);//人脸旋转角度
    compareCfg.setFaceModel(faceModel);
    comparePool = new GenericObjectPool<>(new FaceEngineFactory(appId, sdkKey, activeKey, activeFile, compareCfg), comparePoolConfig);//底层库算法对象池
    try {
    comparePool.preparePool();
    } catch (Exception e) {
    log.error("", e);
    }
    }

    //通用人脸检测引擎池,适用于视频预览人脸检测
    public GenericObjectPool<FaceEngine> ftPool() {
    return ftPool;
    }
    }

    4.2.3.3 人脸检测识别

    创建人脸检测、识别功能逻辑封装类FaceRecognize,支持人脸框检测、人脸图像质量检测和人脸比对识别等功能。片段代码如下。

    @Slf4j
    public class FaceRecognize {
    //用于视频预览人脸检测的引擎池
    private final GenericObjectPool<FaceEngine> ftEnginePool = SpringUtil.getBean(FaceEnginePool.class).ftPool();
    //VIDEO模式人脸检测引擎,用于视频预览人脸检测
    private FaceEngine ftEngine = null;

    //视频预览图人脸框检测
    public List<FacePreviewInfo> detectFaces(ImageInfo imageInfo) {
    if (ftEngine != null) {
    List<FaceInfo> faceInfoList = new ArrayList<>();
    int code = ftEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(),
    imageInfo.getImageFormat(), faceInfoList);
    List<FacePreviewInfo> previewInfoList = new LinkedList<>();
    for (FaceInfo faceInfo : faceInfoList) {
    FacePreviewInfo facePreviewInfo = new FacePreviewInfo();
    facePreviewInfo.setFaceInfo(faceInfo);
    previewInfoList.add(facePreviewInfo);
    }
    clearFaceResultRegistry(faceInfoList);
    return previewInfoList;
    }
    return null;
    }

    //检测识别图像质量
    public boolean imageQuality(ImageInfo imageInfo, FaceInfo faceInfo) {}

    //人脸比对
    public FaceResult faceCompare(FaceInfo faceInfo, ImageInfo imageInfo) {
    faceResultRegistry.remove(faceInfo.getFaceId());
    FaceResult faceResult = new FaceResult();
    faceResultRegistry.put(faceInfo.getFaceId(), faceResult);
    frService.submit(new FaceInfoRunnable(faceInfo, imageInfo, faceResult));
    return faceResult;
    }

    //通过faceId获取人脸比对结果
    public FaceResult getFaceCompareResult(int faceId) {
    return faceResultRegistry.get(faceId);
    }

    4.2.4 检测结果整合显示
    4.2.4.1 检测结果处理

    ​ 如果需要在UI上实时显示检测结果,比如绘制人脸框、显示人脸比对结果,需要缓存SDK检测到的人脸框数据,提取坐标Rect信息,然后使用opencv_imgproc.cvRectangle函数将Rect绘制到单帧图像上,使用opencv_imgproc.cvPutText函数将人脸比对结果绘制到单帧图形上,通过recoder编码成H264视频流,最后通过ServletOutputStream输出到浏览器上显示。人脸框检测和人脸比对逻辑片段代码如下。

    public class FacePreview {
    private static final CvScalar color = cvScalar(0, 0, 255, 0); // blue [green] [red]
    private static final CvFont cvFont = cvFont(opencv_imgproc.FONT_HERSHEY_DUPLEX);
    private final FaceRecognize faceRecognize;
    //模拟触发人脸对比事件,当needRecognizeCount计数满足条件时触发一次人脸比对事件,实际可按照项目需求修改
    private int needRecognizeCount;

    //使用SDK检测人脸框,并通过opencv绘制人脸识别结果至IplImage上
    public void preview(IplImage iplImage) {
    ImageInfo imageInfo = new ImageInfo();
    imageInfo.setWidth(iplImage.width());
    imageInfo.setHeight(iplImage.height());
    imageInfo.setImageFormat(ImageFormat.CP_PAF_RGB24);
    byte[] imageData = new byte[iplImage.imageSize()];
    iplImage.imageData().get(imageData);
    imageInfo.setImageData(imageData);
    List<FaceRecognize.FacePreviewInfo> previewInfoList = faceRecognize.detectFaces(imageInfo);
    for (FaceRecognize.FacePreviewInfo facePreviewInfo : previewInfoList) {
    //提取人脸框坐标Rect
    int x = facePreviewInfo.getFaceInfo().getRect().getLeft();
    int y = facePreviewInfo.getFaceInfo().getRect().getTop();
    int xMax = facePreviewInfo.getFaceInfo().getRect().getRight();
    int yMax = facePreviewInfo.getFaceInfo().getRect().getBottom();
    //将人脸框绘制到iplImage上,也就是视频预览帧数据上
    CvPoint pt1 = cvPoint(x, y);
    CvPoint pt2 = cvPoint(xMax, yMax);
    opencv_imgproc.cvRectangle(iplImage, pt1, pt2, color, 3, 4, 0);
    }
    FaceRecognize.FacePreviewInfo facePreviewInfo = previewInfoList.get(0);
    if (needRecognizeCount % 200 == 0) {
    FaceRecognize.FaceResult faceResult = faceRecognize.faceCompare(facePreviewInfo.getFaceInfo(), imageInfo);
    if (faceResult != null && faceResult.isFlag()) {
    int x = facePreviewInfo.getFaceInfo().getRect().getLeft();
    int y = facePreviewInfo.getFaceInfo().getRect().getTop();
    CvPoint pt3 = cvPoint(x, y 2);
    opencv_imgproc.cvPutText(iplImage, faceResult.getName(), pt3, cvFont, color);
    }
    } else {
    faceResult = faceRecognize.getFaceCompareResult(facePreviewInfo.getFaceInfo().getFaceId());
    if (faceResult != null && faceResult.isFlag()) {
    int x = facePreviewInfo.getFaceInfo().getRect().getLeft();
    int y = facePreviewInfo.getFaceInfo().getRect().getTop();
    CvPoint pt3 = cvPoint(x, y 2);
    opencv_imgproc.cvPutText(iplImage, faceResult.getName(), pt3, cvFont, color);
    }
    }
    }
    }

    4.2.4.1 检测结果实时渲染

    ​ 得到绘制结果帧数据(IplImage)后,将帧数据编码成h264文件,并推送到浏览器UI上,这里需要注意设置格式为“flv”,其他配置可根据需求修改。代码片段如下。

    private boolean needShowFacePreview = true;
    public void servletStreamPlayer(FrameGrabber grabber, ServletOutputStream servletOutputStream) throws Exception {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    FFmpegFrameRecorder recorder = null;
    if (needShowFacePreview) {
    recorder = new FFmpegFrameRecorder(outputStream, grabber.getImageWidth(), grabber.getImageHeight(), 0);
    //UI渲染流格式/转码,当前支持flv格式
    recorder.setFormat("flv");
    recorder.setInterleaved(false);
    recorder.setVideoOption("tune", "zerolatency");
    recorder.setVideoOption("preset", "ultrafast");
    recorder.setVideoOption("crf", "26");
    recorder.setVideoOption("threads", "1");
    double frameRate = grabber.getFrameRate();
    recorder.setFrameRate(frameRate);// 设置帧率
    recorder.setGopSize(25);// 设置gop,关键帧
    int videoBitrate = grabber.getVideoBitrate();
    recorder.setVideoBitrate(videoBitrate);// 设置码率500kb/s,画质
    recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
    recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
    recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
    recorder.setTrellis(1);
    recorder.setMaxDelay(0);// 设置延迟
    recorder.setAudioChannels(grabber.getAudioChannels());
    recorder.start();
    }
    FacePreview faceProcessEngine = new FacePreview();
    long startTime = 0;
    long videoTS;
    for (; ; ) {
    Frame frame = grabber.grab();
    if (frame == null) {
    break;
    }
    IplImage iplImage = converter.convert(frame);//抓取一帧视频并将其转换为图像
    if (needShowFacePreview) {
    //需要将人脸检测框实时显示在UI上,首先将IplImage传入人脸处理引擎进行处理,
    //处理后IplImage会被修改,然后通过recoder编码成H264视频流,最后通过ServletOutputStream输出到浏览器
    if (iplImage != null) {
    faceProcessEngine.preview(iplImage);
    frame = converter.convert(iplImage);
    }
    if (startTime == 0) {
    startTime = System.currentTimeMillis();
    }
    videoTS = 1000 * (System.currentTimeMillis() startTime);
    if (recorder != null) {
    if (videoTS > recorder.getTimestamp()) {
    recorder.setTimestamp((videoTS));
    }
    recorder.record(frame);
    }
    if (outputStream.size() > 0) {
    //将检测结果实时渲染到UI上
    byte[] bytes = outputStream.toByteArray();
    servletOutputStream.write(bytes);
    outputStream.reset();
    }
    } else {
    //不需要将人脸检测框实时显示在UI上,自定义实现保存逻辑,例如将结果聚合成JSON数据,在适当时机推送给其他端。
    }
    }
    }

    ​ 如下图(保护隐私,做了脱敏处理),输入多个RTSP地址,通过人脸识别检测后,会实时渲染到UI上。

    在这里插入图片描述

    ​ 如果不需要实时显示人脸框,只保留后台服务,可以删除VUE3和HTML相关功能。也可以自定义实现人脸检测数据保存功能,对此本文不做详细展开。

    六、总结

    ​ 本文主要介绍了虹软LINUX PRO人脸识别SDK的集成流程,对RTSP流进行解码,并进行人脸检测和显示结果,使用了当前主流的前后端开发技术。文中使用的ffmpeg + opencv是开源框架,支持很多协议和格式,如果大家想拓展其他视频流检测功能,可以在本文基础上二次开发,本文就不做详细介绍了。

    ​ 附上本文涉及到的工程源码路径。

    ​ 主工程(java + springboot):https://github.com/WeiLiqiang/ArcFaceDemoForLinuxPRO

    ​ UI工程(vue3):https://github.com/WeiLiqiang/ArcFaceDemoForLinuxPROUI

    s0-1768271821449)]

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 基于虹软服务器版本SDK实现多路网络摄像头人脸检测
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!