C#_Demo_攝像頭實時_4執行緒人臉識別註冊開發全過程
效率有點低,大家看看哪裡開可以節省時間?
原始碼: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 ==
1) - Intptr之間複製位元組用CopyMemory比較快
- 兩三張臉的時候開4個執行緒不好,改成 new Task[TaskNum < detectResult.FaceCount ?
TaskNum : detectResult.FaceCount]
四、識別結果(集)的折騰
- 弄了個結果集,按最大人臉數設了個List( Items = new List();)
- 增加了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什麼的,有興趣的朋友一起交流交流?
洗洗睡了,晚安。