1. 程式人生 > >C#_Demo_攝像頭實時_4線程人臉識別註冊開發全過程

C#_Demo_攝像頭實時_4線程人臉識別註冊開發全過程

字節 sender 部分 刪除 微信 結果集 col 後來 lane

v效率有點低,大家看看哪裏開可以節省時間?
源代碼:https://github.com/catzhou2002/ArcFaceDemo
說實話,為了提高識別效率,我也是竭盡所能,幹了不少自認為的優化,如有興趣聽我說說。
第一部分 單線程時候的各種折騰
一、折騰LPASVLOFFSCREEN
話說這個LPASVLOFFSCREEN的結果文檔裏面沒有說明,或者是我沒找到。
我也不知道從哪裏復制來的,主要折騰的是ppu8Plane[0]地址,一般操作是

鎖定圖片內存
ppu8Plane[0]分配制定長度的內存
把圖片內存中的字節復制到一個臨時數組
然後用Marshal.Copy復制到指定的地址
解鎖圖片內存

我改成:

鎖定圖片內存

ppu8Plane[0]指向圖片地址
等不需要LPASVLOFFSCREEN時(人臉檢測、獲取特征值、性別判斷、年齡估算等結束後)解鎖圖片內存

就晚一點解鎖,省了好多事情,耗時由4毫秒沒成2微妙。當時就發了個帖:C# Bitmap轉ASVLOFFSCREEN的最佳方式?
後來覺得這名字實在記不住,也不C#,改成了ImageData,整個轉換過程如下:

var bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
var imageData = new ImageData
{
PixelArrayFormat = 513,//Rgb24,
Width = bitmap.Width,
Height = bitmap.Height,
Pitch = new int[4] { bmpData.Stride, 0, 0, 0 },
ppu8Plane = new IntPtr[4] { bmpData.Scan0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero }
};
....
bitmap.UnlockBits(bmpData);

  

其實如果是視頻圖片的話,圖片的寬度和高度都是固定的,想了想,沒折騰。
二、單線程時將獲取到的FaceModel直接做人臉比對的參數

ExtractFeature(_FaceMatchEngine, ref imageData, ref faceFeatureInput, out var <font color="#ff8c00">faceModel</font>);
FacePairMatch(_FaceMatchEngine, ref fm, ref <font color="#ff8c00">faceModel</font>, out float score);

  

一般操作是faceModel裏面的字節復制到臨時字節數組,然後創建新的FaceModel,分配內存,在將臨時字節數組復制到FaceModel。
三、人臉庫直接用FaceModel

/// <summary>
/// 人臉庫
/// </summary>
public class FaceLib
{
public List<Item> Items { get; set; } = new List<Item>();
public class Item
{
/// <summary>
/// 用於排序
/// </summary>
public long OrderId { get; set; }
/// <summary>
/// 文件名作為ID
/// </summary>
public string ID { get; set; }
/// <summary>
/// 人臉模型
/// </summary>
<font color="#ff8c00"> public FaceModel FaceModel { get; set; }</font> 
}
}

  

四、比對結果>0.5就算成功
五、人臉庫增加OrderId
識別成功後再次比對就很快,應該是首發命中。
六、將人臉比對和結果顯示分開
一開始沒想太多,將人臉比對和結果顯示放在新視頻幀事件裏面,流程是:

新視頻幀(30幀/秒)
獲取檢測和識別的結果(人臉框和ID)
顯示檢測和識別的結果

結果視頻卡頓,獲取人臉特征的200毫秒成為瓶頸,改成:

人臉比對

Task.Factory.StartNew(() =>
{
Task.Delay(1000).Wait();
while (!_CancellationTokenSource.IsCancellationRequested)
{
#region 200毫秒左右
MatchFrame(); 
#endregion
}
}, _CancellationTokenSource.Token);  

結果顯示

private void VideoPlayer_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(Pens.White, _FaceResult.Rectangle);
e.Graphics.DrawString(_FaceResult.ID , this.Font, Brushes.White, _FaceResult.Rectangle.Left, _FaceResult.Rectangle.Top - 20);
}  

測試了一下,效果還可以,就在博客園發表了
C# 虹軟SDK視頻人臉識別和註冊
,還順手弄了個打賞二維碼。
發表完覺得這麽辛苦寫出來的文章,必須到首頁去亮個相,9天後終於學會發表到博客園首頁了,於是刪除了打賞二維碼,去首頁亮了個相。
話說首頁和非首頁效果著實不一樣,截圖為證:

技術分享圖片

第二部分 多線程的折騰
一、確定4線程為最佳
各種測試後得出的結論,也不知道對不對,也不知道為什麽,哎。
因網友的要求,同步到了github
二、刪除了單線程
有了更快的,就不要慢的了。
三、n張臉如何分配給4個線程獲取特征值?
動了不少腦筋,Interlocked.Increment是關鍵。
最終有改了下面的內容

如果只有一張臉(竊以為一張臉的概率比較高),也用Task,影響效率,增加了 if (detectResult.FaceCount ==

Intptr之間復制字節用CopyMemory比較快
兩三張臉的時候開4個線程不好,改成 new Task[TaskNum < detectResult.FaceCount ?
TaskNum : detectResult.FaceCount]

四、識別結果(集)的折騰

弄了個結果集,按最大人臉數設了個List( Items = new List<FaceResult>();)
增加了FaceFeatureInput FFI,省的每次都去創建
並將人臉方向設成1(Orient = 1)(因為是視頻圖片,其他方向的人臉,呵呵),人臉檢測後都不要去獲取人臉方向的值
增加並初始化了FaceModel(FaceModel FaceModel = new FaceModel() { Size =
22020, PFeature = Marshal.AllocCoTaskMem(22020) };),獲取到的特征字節直接復制過來便可

五、保存特征值到人臉庫的時候同時保存頭像

因為虹軟說了,sdk升級的時候,特征值也有可能變化。那咱先把頭像保存起來,到時候重新生成一下。
主要的操作是把矩形放大一點(Inflate((int)(r.Width * 0.5), (int)(r.Height * 0.5))),咱保存的頭像怎麽著得是個人頭吧。
(想來條分割線,居然只有華麗的分割線,算了。順便吐槽一下,這個論壇的編輯器實在是讓人無語_)
各種折騰後,黔驢技窮了,10,000人臉的庫得出10張不認識的臉的結論,需要10秒鐘。當然,換好一點的電腦可以提高效率,如我的臺式機(i5-7500),輸入圖片只有1張臉的時候,遍歷

1萬張人臉僅需390毫秒
5萬張人臉也就1525毫秒
10萬張人臉說我內存不夠,可能是我的程序是32位的緣故,換成64位的sdk估計3秒鐘也能搞定(太麻煩,不折騰了)

結論是:虹軟中型sdk用於考勤、小區門禁、寫字樓門禁等場所完全沒問題。
下一步我打算(其實已經差不多完成,我公司的項目——酒店自助機)改成單臉多線程識別,增加以下功能:

40次檢測人臉數為0,則確認為沒人,識別頻率降低
是否換人了?
同一個人3、4次識別不出ID後,確認為陌生人,不在遍歷
刷身份證獲取照片人臉比對後存入人臉庫

另外想跟企業微信結合開發開發門禁、CRM什麽的,有興趣的朋友一起交流交流?

C#_Demo_攝像頭實時_4線程人臉識別註冊開發全過程