C#實現基於ffmpeg加虹軟的人臉識別

来源:http://www.cnblogs.com/sweetwxh/archive/2017/10/09/face_recognization.html
-Advertisement-
Play Games

基於ffmpeg和虹軟人臉識別庫的C#開源實現,對虹軟人臉識別庫進行了包裝,便於在C#中快速、安全的調用識別函數。同時,開源代碼中,包含完整的實現示例。 ...


關於人臉識別

目前的人臉識別已經相對成熟,有各種收費免費的商業方案和開源方案,其中OpenCV很早就支持了人臉識別,在我選擇人臉識別開發庫時,也橫向對比了三種庫,包括線上識別的百度、開源的OpenCV和商業庫虹軟(中小型規模免費)。

百度的人臉識別,才上線不久,文檔不太完善,之前聯繫百度,官方也給了我基於Android的Example,但是不太符合我的需求,一是照片需要上傳至百度伺服器(這個是最大的問題),其次,人臉的定位需要自行去實現(捕獲到人臉後上傳進行識別)。

OpenCV很早以前就用過,當時做人臉+車牌識別時,最先考慮的就是OpenCV,但是識別率在當時不算很高,後來是採用了一個電子科大的老師自行開發的識別庫(相對易用,識別率也還不錯),所以這次準備做時,沒有選擇OpenCV。

虹軟其實在無意間發現的,當時正在尋找開發庫,正在測試Python的一個方案,就發現有新聞說虹軟的識別庫全面開放並且可以免費使用,而且是離線識別,所以就下載嘗試了一下,發現識別率還不錯,所以就暫定了採用虹軟的識別方案。這裡主要就給大家分享一下開發過程當中的一些坑和使用心得,順便開源識別庫的C# Wrapper。

SDK的C# Wrapper

由於虹軟的庫是採用C++開發的,而我的應用程式採用的是C#,所以,需要對庫進行包裝,便於C#的調用,包裝的主要需求是可以在C#中快速方便的調用,無需考慮記憶體、指針等問題,並且具備一定的容錯性。Wrapper庫目前已經開源,大家可以到Github上進行下載,地址點擊這裡。Wrapper庫基本上沒有什麼可以說的,無非是對PInvoke的包裝,只是裡面做了比較多的細節處理,屏蔽了調用細節,提供了相對高層的函數。有興趣的可以看看源代碼。

Wrapper庫的使用例子

基本使用

人臉檢測(靜態圖片):

using (var detection = LocatorFactory.GetDetectionLocator("appId", "sdkKey"))
{
    var image = Image.FromFile("test.jpg");
    var bitmap = new Bitmap(image);

    var result = detection.Detect(bitmap, out var locateResult);
    //檢測到位置信息在使用完畢後,需要釋放資源,避免記憶體泄露
    using (locateResult)
    {
        if (result == ErrorCode.Ok && locateResult.FaceCount > 0)
        {
            using (var g = Graphics.FromImage(bitmap))
            {
                var face = locateResult.Faces[0].ToRectangle();
                g.DrawRectangle(new Pen(Color.Chartreuse), face.X, face.Y, face.Width, face.Height);
            }

            bitmap.Save("output.jpg", ImageFormat.Jpeg);
        }
    }
}

人臉跟蹤(人臉跟蹤一般用於視頻的連續幀識別,相較於檢測,又更高的執行效率,這裡用靜態圖片做例子,實際使用和檢測沒啥區別):

using (var detection = LocatorFactory.GetTrackingLocator("appId", "sdkKey"))
{
    var image = Image.FromFile("test.jpg");
    var bitmap = new Bitmap(image);

    var result = detection.Detect(bitmap, out var locateResult);
    using (locateResult)
    {
        if (result == ErrorCode.Ok && locateResult.FaceCount > 0)
        {
            using (var g = Graphics.FromImage(bitmap))
            {
                var face = locateResult.Faces[0].ToRectangle();
                g.DrawRectangle(new Pen(Color.Chartreuse), face.X, face.Y, face.Width, face.Height);
            }

            bitmap.Save("output.jpg", ImageFormat.Jpeg);
        }
    }
}

人臉對比:

using (var proccesor = new FaceProcessor("appid",
                "locatorKey", "recognizeKey", true))
{
    var image1 = Image.FromFile("test2.jpg");
    var image2 = Image.FromFile("test.jpg");

    var result1 = proccesor.LocateExtract(new Bitmap(image1));
    var result2 = proccesor.LocateExtract(new Bitmap(image2));
    
    //FaceProcessor是個整合包裝類,集成了檢測和識別,如果要單獨使用識別,可以使用FaceRecognize類
    //這裡做演示,假設圖片都只有一張臉
    //可以將FeatureData持久化保存,這個即是人臉特征數據,用於後續的人臉匹配
    //File.WriteAllBytes("XXX.data", feature.FeatureData);FeatureData會自動轉型為byte數組

    if ((result1 != null) & (result2 != null))
        Console.WriteLine(proccesor.Match(result1[0].FeatureData, result2[0].FeatureData, true));
}

使用註意事項

LocateResult(檢測結果)和Feature(人臉特征)都包含需要釋放的記憶體資源,在使用完畢後,記得需要釋放,否則會引起記憶體泄露。FaceProcessorFaceRecognizeMatch函數,在完成比較後,可以自動釋放,只需要最後兩個參數指定為true即可,如果是用於人臉匹配(1:N),則可以採用預設參數,這種情況下,第一個參數指定的特征數據不會自動釋放,用於迴圈和特征庫的特征進行比對。

整合的完整例子

在Github上,有完整的FaceDemo例子,裡面主要實現了通過ffmpeg採集RTSP協議的圖像(使用海康的攝像機),然後進行人臉匹配。在開發過程中遇到不少的坑。

人臉識別的首要工作就是捕獲攝像機視頻幀,這一塊上是坑的最久的,因為最開始採用的是OpenCV的包裝庫,Emgu.CV,在開發過程中,捕獲USB攝像頭時,倒是問題不大,沒有出現過異常。在捕獲RTSP視頻流時,會不定時的出現AccessviolationException異常,短則幾十分鐘,長則幾個小時,總之就是不穩定。在官方Github地址上,也提了Issue,他們給出的答覆是屏蔽的我業務邏輯,僅捕獲視頻流試試,結果問題依然,所以,我基本坑定了試Emgu.CV上面的問題。後來經過反覆的實驗,最終確定了選擇ffmpeg。

ffmepg主要採用ProcessStartInfo進行調用,我採用的是NReco.VideoConverter(一個ffmpeg調用的包裝,可以通過nuget搜索安裝),雖然ffmpeg解決了穩定性問題,但是實際開發時,也遇到了不少坑,其中,最主要的是NReco.VideoConverter沒有任何文檔和例子(實際有,需要75刀購買),所以,自己研究了半天,如何捕獲視頻流並轉換為Bitmap對象。只要實現這一步,後續就是調用Wrapper就行了。

FaceDemo詳解

上面說到了,通過ffmpeg捕獲視頻流並轉換Bitmap是重點,所以,這裡也主要介紹這一塊。

首先是ffmpeg的調用參數:

var setting =
new ConvertSettings
{
    CustomOutputArgs = "-an -r 15 -pix_fmt bgr24 -updatefirst 1"
}; //-s 1920x1080 -q:v 2 -b:v 64k

task = ffmpeg.ConvertLiveMedia("rtsp://admin:[email protected]:554/h264/ch1/main/av_stream", null,
outputStream, Format.raw_video, setting);
task.OutputDataReceived += DataReceived;
task.Start();

-an表示不捕獲音頻流,-r表示幀率,根據需求和實際設備調整此參數,-pix_fmt比較重要,一般情況下,指定為bgr24不會有太大問題(還是看具體設備),之前就是用成了rgb24,結果捕獲出來的圖像,人都變成阿凡達了,顏色是反的。最後一個參數,坑的我差點放棄這個方案。本身,ffmpeg在調用時,需要指定一個文件名模板,捕獲到的輸出會按照模板生成文件,如果要將數據輸出到控制台,則最後傳入一個-即可,最開始沒有指定updatefirst,ffmpeg在捕獲了第一幀後就拋出了異常,最後查了半天ffmpeg說明(完整參數說明非常多,輸出到文本有1319KB),發現了這個參數,表示持續更新第一個文件。最後,在調用視頻捕獲是,需要指定輸出格式,必須指定為Format.raw_video,實際上這個格式名稱有些誤導人,按道理將應該叫做raw_image,因為最終輸出的是每幀原始的點陣圖數據。

到此為止,還並沒有解決視頻流數據的捕獲,因為又來一個坑,ProcessStartInfo的控制台緩衝區大小隻有32768 bytes,即,每一次的輸出,實際上並不是一個完整的點陣圖數據。

//完整代碼參加Github源代碼
//代碼片段1
private Bitmap _image;
private IntPtr _pImage;

{
    _pImage = Marshal.AllocHGlobal(1920 * 1080 * 3);
    _image = new Bitmap(1920, 1080, 1920 * 3, PixelFormat.Format24bppRgb, _pImage);
}

//代碼片段2
private MemoryStream outputStream;

private void DataReceived(object sender, EventArgs e)
{
    if (outputStream.Position == 6220800)
        lock (_imageLock)
        {
            var data = outputStream.ToArray();

            Marshal.Copy(data, 0, _pImage, data.Length);

            outputStream.Seek(0, SeekOrigin.Begin);
        }
}

花了不少時間摸索(不要看只有幾行,人都整崩潰了),得出了上述代碼。首先,我捕獲的圖像數據是24位的,並且圖像大小是1080p的,所以,實際上,一個原始點陣圖數據的大小為stride * height,即width * 3 * height,大小為6220800 bytes。所以,在判斷了捕獲數據到達這個大小後,就進行Bitmap轉換處理,然後將MemoryStream的位置移動到最開始。需要註意的時,由於捕獲到的是原始數據(不包含bmp的HeaderInfo),所以註意看Bitmap的構造方式,是通過一個指向原始數據位置的指針就行構造的,更新該圖像時,也僅需要更新指針指向的位置數據即可,無需在建立新的Bitmap實例。

點陣圖數據獲取到了,就可以進行識別處理了,高高興興的加上了識別邏輯,但是現實總是充滿了意外和驚喜,沒錯,坑又來了。沒有加入識別邏輯的時候,捕獲到的圖像在PictureBox上顯示非常正常,清晰、流暢,加上識別邏輯後,開始出現花屏(捕獲到的圖像花屏)、拖影、顯示延遲(至少會延遲10-20秒以上)、程式卡頓,總之就是各種問題。最開始,我的識別邏輯寫到DataReceived方法裡面的,這個方法是運行於主線程外的另一個線程中的,其實按道理將,捕獲、識別、顯示位於一個線程中,應該是不會出現問題,我估計(不確定,沒有去深入研究,如果誰知道實際原因,可以留言告訴我),是因為ffmpeg的原因,因為ffmpeg是單獨的一個進程在跑,他的數據捕獲是持續在進行的,而識別模塊的處理時間大於每一幀的採集時間,所以,緩衝區中的數據沒有得到及時處理,ffmpeg接收到的部分圖像數據(大於32768的數據)被丟棄了,然後就出現了各種問題。最後,又是一次耗時不短的探索之旅。

private void Render()
{
    while (_renderRunning)
    {
        if (_image == null)
            continue;

        Bitmap image;

        lock (_imageLock)
        {
            image = (Bitmap) _image.Clone();
        }

        if (_shouldShot){
            WriteFeature(image);
            _shouldShot = false;
        }

        Verify(image);

        if (videoImage.InvokeRequired)
            videoImage.Invoke(new Action(() => { videoImage.Image = image; }));
        else
            videoImage.Image = image;
    }
}

如上代碼所述,我單獨開了一個線程,用於圖像的識別處理和顯示,每次都從已捕獲到的圖像中克隆出新的Bitmap實例進行處理。這種方式的缺點在於,有可能會導致丟幀的現象,因為上面說到了,識別時間(如果檢測到新的人臉,那麼加上匹配,大約需要130ms左右)大於每幀時間,但是並不影響識別效果和需求的實現,基本丟棄的幀可以忽律。最後,運行,穩定了、完美了,實際也感覺不到丟幀。

Demo程式,我運行了大約4天左右,中間沒有出現過任何異常和識別錯誤。

寫在最後

雖然虹軟官方表示,免費識別庫適用於1000人臉庫以下的識別,實際上,做一定的工作(工作量其實也不小),也是可以實現較大規模的人臉搜索滴。例如,採用多線程進行匹配,如果人臉庫人臉數量大於1000,則可以考慮每個線程分別進行處理,人臉特征數據做緩存(一個人臉的特征數據是22KB,對記憶體要求較高),以提升程式的識別搜索效率。或者人臉庫特別大的情況下,可以採用分散式處理,人臉特征載入到Redis資料庫當中,多個進程多個線程讀取處理,每個線程上傳自己的識別結果,然後主進程做結果合併判斷工作,主要的挑戰就在於多線程的工作分配一致性和對單點故障的容錯性。


更新:

DEMO中的例子採用了IP Camera,一般情況下,大家可能用USB Camera居多,所以,更新了源代碼,增加了USB Camera的例子,只需要屏蔽掉IP Camara代碼即可。

task = ffmpeg.ConvertLiveMedia("video=USB2.0 PC CAMERA", "dshow",
           outputStream, Format.raw_video, setting);

需要註意的有以下幾點:

  • 設備名稱可以通過控制面板或者ffmpeg的命令獲取:ffmpeg -list_devices true -f dshow -i dummy
  • 註意修改捕獲的圖像大小,一般USB攝像頭是640*480,更新的代碼增加了全局變數,可以直接修改。
  • 如果要查詢USB攝像頭支持的解析度,也可以通過ffmpeg命令:ffmpeg -list_options true -f dshow -i video="USB2.0 PC CAMERA"

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 主要包括Alias指令、AliasMatch、Redirect、RedirectMatch、ScriptAlias和ScriptAliasMatch。它們由mod_alias模塊提供,該模塊用於提供簡單的路徑映射和重定向需求,更複雜的內容見URL重寫。 其中ScriptAlias用法和Alias完全 ...
  • 本文目錄:1.1 htpasswd命令1.2 身份認證類基本指令1.3 Require指令1.4 web身份認證示例 httpd對web身份認證的支持很豐富,提供的控制也非常細緻。無疑,功能豐富意味著模塊多。關於完整的模塊,見http://httpd.apache.org/docs/2.4/mod/ ...
  • [1]顯示 [2]文件 [3]關機 [4]登錄 [5]shell [6]VIM ...
  • Tomcat 概述: Tomcat是Apache 軟體基金會(Apache Software Foundation)的Jakarta 項目中的一個核心項目,由Apache、Sun 和其他一些公司及個人共同開發而成。由於有了Sun 的參與和支持,最新的Servlet 和JSP 規範總是能在Tomcat ...
  • 在講vim編輯器之前,我們要先明白為什麼要學vim編輯器。系統管理員的重要工作就是要修改與設定某些重要軟體的配置文件,因此至少要學會一種以上的文字介面的文書編輯器。 現在開始正式學習vim編輯器。基本上vim共分為三種模式,分別是一般模式,編輯模式,與指令列模式,這三種模式的作用分別是: 1)一般模 ...
  • 首先解釋下:本文只是對Asp.net MVC4高級編程這本書學習記錄的學習筆記,書本內容感覺挺簡單的,但學習容易忘記,因此在邊看的同時邊作下了筆記,可能其它朋友看的話沒有情境和邏輯順序還請諒解! 一、MVC控制器渲染視圖的三種方式。 如下以HomeController控制器中的代碼為例: 1、預設方 ...
  • 局部函數是C 7中的一個新功能,允許在一個函數中定義另一個函數。 何時使用局部函數? 局部函數的主要功能與匿名方法非常相似:在某些情況下,創建一個命名函數在讀者的認知負擔方面代價太大。有時,函數本身就是另一個函數的部分邏輯,因此用一個單獨的命名實體來污染“外部”範圍是毫無意義的。 您可能認為此功能是 ...
  • 在數值字元串的格式化中有很多格式化的格式,比如:用"C"表示貨幣格式,用"P"表示百分比格式,FCL中支持多種格式化字元串的方式。有時候我們會把一個數值轉換成string類型,然後再從string類型轉換成數值類型,這時候就要考慮轉換回來後的數值會不會和原來的相等呢? 首先的一種情況: 使用G常規格 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...