基於虹軟人臉識別,實現RTMP直播推流追蹤視訊中所有人臉資訊(C#)

土倫發表於2021-05-20

前言

大家應該都知道幾個很常見的例子,比如在張學友的演唱會,在安檢通道檢票時,通過人像識別系統成功識別捉了好多在逃人員,被稱為逃犯剋星;人行橫道不遵守交通規則闖紅燈的路人被人臉識別系統抓拍放在大屏上以示警告;參加某次活動通過人臉進行簽到來統計實時人流量等等, 我現在也來做一個通過電視直播,追蹤畫面中所有人臉資訊,並捕獲我需要的目標人物。

具體思路及流程

基於虹軟人臉識別,對直播畫面中的每一幀圖片進行檢測,得到圖片中所有人臉資訊。可以新增目標人物的照片,用目標人物的人臉特徵值與直播畫面幀圖片中人臉資訊列表中的每一個特徵值進行比對。如果有匹配到目標人物,把直播畫面抓拍。具體流程如下:

專案結構

播放地址我們可以在網上搜尋一下電視直播RTMP地址,在程式中可進行播放

private void PlayVideo()
{
    videoCapture = new VideoCapture(rtmp);
    if (videoCapture.IsOpened())
    {
        videoInfo.Filename = rtmp;
        videoInfo.Width = (int)videoCapture.FrameWidth;
        videoInfo.Height = (int)videoCapture.FrameHeight;
        videoInfo.Fps = (int)videoCapture.Fps;

        myTimer.Interval = videoInfo.Fps == 0 ? 300 : 1000 / videoInfo.Fps;
        IsStartPlay = true;
        myTimer.Start();
    }
    else
    {
        MessageBox.Show("視訊源異常");
    }
}

private void MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    try
    {
        if (IsStartPlay)
        {
            lock (LockHelper)
            {
                var frame = videoCapture.RetrieveMat();
                if (frame != null)
                {
                    if (frame.Width == videoInfo.Width && frame.Height == videoInfo.Height)
                        this.SetVideoCapture(frame);
                    else
                        LogHelper.Log($"bad frame");
                }
            }
        }
    }catch(Exception ex)
    {
        LogHelper.Log(ex.Message);
    }
}
Bitmap btm = null;
private void SetVideoCapture(Mat frame)//視訊捕獲
{
    try
    {
        btm = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(frame);
        pic_Video.Image = btm;
    }catch(Exception ex)
    {
        LogHelper.Log(ex.Message);
    }
}

以上這些就是在OpenCv中通過VideoCaptrue類對視訊進行讀取操作,然後把影像渲染到PictureBox控制元件上。在PictureBox的Paint事件中進行人臉識別與比對操作。

/// <summary>
/// 比對函式,將每一幀抓拍的照片和目標人物照片進行比對
/// </summary>
/// <param name="bitmap"></param>
/// <param name="e"></param>
/// <returns></returns>
private void CompareImgWithIDImg(Bitmap bitmap, PaintEventArgs e)
{
    if (bitmap != null)
    {
        //保證只檢測一幀,防止頁面卡頓以及出現其他記憶體被佔用情況
        if (isLock == false)
        {
            isLock = true;
            Graphics g = e.Graphics;
            float offsetX = (pic_Video.Width * 1f / bitmap.Width);
            float offsetY = (pic_Video.Height * 1f / bitmap.Height);
            //根據Bitmap 獲取人臉資訊列表
            List<FaceInfoModel> list = FaceUtil.GetFaceInfos(pImageEngine, bitmap);
            foreach (FaceInfoModel sface in list)
            {
                //非同步處理提取特徵值和比對,不然頁面會比較卡
                ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
                {
                    try
                    {
                        //提取人臉特徵
                        float similarity = CompareTwoFeatures(sface.feature, imageTemp);
                        if (similarity > threshold)
                        {
                            this.pic_cutImg.Image = bitmap;
                            this.Invoke((Action)(() =>
                            {
                                this.lbl_simiValue.Text = similarity.ToString();
                            }));
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }));

                MRECT rect = sface.faceRect;
                float x = rect.left * offsetX;
                float width = rect.right * offsetX - x;
                float y = rect.top * offsetY;
                float height = rect.bottom * offsetY - y;
                //根據Rect進行畫框
                g.DrawRectangle(pen, x, y, width, height);
                trackUnit.message = "年齡:" + sface.age.ToString() + "\r\n" + "性別:" + (sface.gender == 0 ? "" : "");
                g.DrawString(trackUnit.message, font, brush, x, y + 5);
            }
            isLock = false;
        }
    }
}

 單張圖片可能包含多張人臉,我們用FaceInfoModel 人臉資訊實體類,把人臉資訊放在此類中。

public class FaceInfoModel
{
    /// <summary>
    /// 年齡
    /// </summary>
    public int age { get; set; }
    /// <summary>
    /// 性別
    /// </summary>
    public int gender { get; set; }
    public ASF_Face3DAngle face3dAngle { get; set; }
    /// <summary>
    /// 人臉框
    /// </summary>
    public MRECT faceRect { get; set; }
    /// <summary>
    /// 人臉角度
    /// </summary>
    public int faceOrient { get; set; }
    /// <summary>
    /// 單人臉特徵
    /// </summary>
    public IntPtr feature { get; set; }
}

多人臉實體類存放單人臉資訊列表

public class MultiFaceModel : IDisposable
{
    /// <summary>
    /// 多人臉資訊
    /// </summary>
    public ASF_MultiFaceInfo MultiFaceInfo { get; private set; }

    /// <summary>
    /// 單人臉資訊List
    /// </summary>
    public List<ASF_SingleFaceInfo> FaceInfoList { get; private set; }

    /// <summary>
    /// 人臉資訊列表
    /// </summary>
    /// <param name="multiFaceInfo"></param>
    public MultiFaceModel(ASF_MultiFaceInfo multiFaceInfo) 
    {
        this.MultiFaceInfo = multiFaceInfo;
        this.FaceInfoList = new List<ASF_SingleFaceInfo>();
        FaceInfoList = PtrToMultiFaceArray(multiFaceInfo.faceRects, multiFaceInfo.faceOrients, multiFaceInfo.faceNum);
    }
    /// <summary>
    /// 指標轉多人臉列表
    /// </summary>
    /// <param name="faceRect"></param>
    /// <param name="faceOrient"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    private List<ASF_SingleFaceInfo> PtrToMultiFaceArray(IntPtr faceRect, IntPtr faceOrient, int length)
    {
        List<ASF_SingleFaceInfo> FaceInfoList = new List<ASF_SingleFaceInfo>();
        var size = Marshal.SizeOf(typeof(int));
        var sizer = Marshal.SizeOf(typeof(MRECT));

        for (var i = 0; i < length; i++)
        {
            ASF_SingleFaceInfo faceInfo = new ASF_SingleFaceInfo();

            MRECT rect = new MRECT();
            var iPtr = new IntPtr(faceRect.ToInt32() + i * sizer);
            rect = (MRECT)Marshal.PtrToStructure(iPtr, typeof(MRECT));
            faceInfo.faceRect = rect;

            int orient = 0;
            iPtr = new IntPtr(faceOrient.ToInt32() + i * size);
            orient = (int)Marshal.PtrToStructure(iPtr, typeof(int));
            faceInfo.faceOrient = orient;
            FaceInfoList.Add(faceInfo);
        }
        return FaceInfoList;
    }

    public void Dispose()
    {
        Marshal.FreeCoTaskMem(MultiFaceInfo.faceRects);
        Marshal.FreeCoTaskMem(MultiFaceInfo.faceOrients);
    }
}

然後獲取所有人臉資訊,放在列表中備用

/// <summary>
/// 獲取人臉資訊列表
/// </summary>
/// <param name="pEngine"></param>
/// <param name="bitmap"></param>
/// <returns></returns>
public static List<FaceInfoModel>GetFaceInfos(IntPtr pEngine,Image bitmap)
{
    List<FaceInfoModel> listRet = new List<FaceInfoModel>();
    try
    {
        List<int> AgeList = new List<int>();
        List<int> GenderList = new List<int>();
        //檢測人臉,得到Rect框
        ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pEngine, bitmap);
        MultiFaceModel multiFaceModel = new MultiFaceModel(multiFaceInfo);
        //人臉資訊處理
        ImageInfo imageInfo = ImageUtil.ReadBMP(bitmap);
        int retCode = ASFFunctions.ASFProcess(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, ref multiFaceInfo, FaceEngineMask.ASF_AGE| FaceEngineMask.ASF_GENDER);
        //獲取年齡資訊
        ASF_AgeInfo ageInfo = new ASF_AgeInfo();
        retCode = ASFFunctions.ASFGetAge(pEngine, ref ageInfo);
        AgeList = ageInfo.PtrToAgeArray(ageInfo.ageArray, ageInfo.num);
        //獲取性別資訊
        ASF_GenderInfo genderInfo = new ASF_GenderInfo();
        retCode = ASFFunctions.ASFGetGender(pEngine, ref genderInfo);
        GenderList = genderInfo.PtrToGenderArray(genderInfo.genderArray, genderInfo.num);

        for (int i = 0; i < multiFaceInfo.faceNum; i++)
        {
            FaceInfoModel faceInfo = new FaceInfoModel();
            faceInfo.age = AgeList[i];
            faceInfo.gender = GenderList[i];
            faceInfo.faceRect = multiFaceModel.FaceInfoList[i].faceRect;
            faceInfo.feature = ExtractFeature(pEngine, bitmap, multiFaceModel.FaceInfoList[i]);//提取單人臉特徵
            faceInfo.faceOrient = multiFaceModel.FaceInfoList[i].faceOrient;
            listRet.Add(faceInfo);
        }
        return listRet;//返回多人臉資訊
    }
    catch {
        return listRet;
    }
}

從列表中獲取到的多張人臉,在人臉上畫框作出標識,也可以把提取的人臉資訊,年齡、性別作出展示。接下來就是選擇一張目標人物的照片,通過SDK提取目標人物的人臉特徵值作為比較物件,逐一與視訊中的人臉特徵進行比較。如果有判斷到相似度匹配的人臉,則把視訊幀影像呈現出來。

/// <summary>
/// 比較兩個特徵值的相似度,返回相似度
/// </summary>
/// <param name="feature1"></param>
/// <param name="feature2"></param>
/// <returns></returns>
private float CompareTwoFeatures(IntPtr feature1, IntPtr feature2)
{
    float similarity = 0.0f;
    //呼叫人臉匹配方法,進行匹配
    ASFFunctions.ASFFaceFeatureCompare(pImageEngine, feature1, feature2, ref similarity);
    return similarity;
}

之前只實現了從多張人臉中獲取一張最大尺寸的人臉作為比較物件,這樣視訊中也就只能對一張人臉進行畫框標記了,現在是把所有提取到的人臉均進行標記,並把各自特徵值存在列表中,以便與目標人臉特徵值進行匹配。

這樣也就粗略的實現了人臉識別追蹤,並對目標人物進行抓拍的功能了。

GitHub原始碼已上傳

相關文章