天天看點

【Win10 應用開發】人臉識别

可能你會認為人臉識别用起來會很複雜,老周當初也這麼想,但通過實際操作後,我發現非然。

經過微軟封裝的東西,向來都是複雜問題簡單化,隻要用得舒心,代碼越少越好,用最少的代碼做最多的事情,此為大師境界也。

好,屁話不說,先介紹一下如何完成人臉識别(或者叫人臉檢測,随你怎麼翻譯,反正知道是怎麼一回事就行)。核心的類是FaceDetector,不要問我這個類在哪裡,自己打開對象浏覽器搜尋。

第一步,通路靜态屬性IsSupported,看看目前平台是不是支援人臉識别,如果傳回false,那就沒戲了。

第二步,調用靜态方法CreateAsync得到一個FaceDetector執行個體,是以該類不需要手動執行個體化,初始化過程由運作庫來完成,然後把初始化好的執行個體傳回給咱們,然後就可以用它來幹活了。

第三步,調用執行個體方法DetectFacesAsync進行識别,識别完後會傳回一個DetectedFace清單,每個DetectedFace表示一張臉,因為你用來識别的照片中可能包含N張臉。FaceBox屬性包含了人臉的矩形區域,就是人臉位于整張照片的哪個位置,可通過X,Y坐标描述矩形的左上角位置,并用寬度和高度來表明那張臉的大小。

DetectFacesAsync方法需要一個SoftwareBitmap類型的參數,該參數就是你要用來識别人臉的圖像。

可能大家已經知道,通過BitmapDecoder類的GetSoftwareBitmapAsync方法可以傳回一個SoftwareBitmap執行個體,不過要注意的是,FaceDetector在進行檢測時并不是所有像素格式都支援,可以調用GetSupportedBitmapPixelFormats方法來擷取所有受支援的像素格式清單,經老周測試,該方法傳回Nv12和Gray8,也就是目前隻支援這兩種格式。當然,你也可以通過IsBitmapPixelFormatSupported方法來驗證一下某個像素格式是否被支援。

好了,基本用法已經說完了,确實不是很複雜。下面,老周給大家示範一個例子,該例子允許你選擇一張照片,然後識别出照片上的人臉,并用一個矩形來标記。

先看看UI的設計,主要的XAML如下:

<Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <Button Content="選擇要識别的照片" Click="OnClick"/>
            <Viewbox  Grid.Row="1" Margin="5" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <Canvas Name="cv" Width="{x:Bind img.Width,Mode=OneWay}" Height="{x:Bind img.Height,Mode=OneWay}" >
                    <Image Name="img" Stretch="None"/>
                </Canvas>
            </Viewbox>
            
            <TextBlock Name="tbMessage" Foreground="Red" FontSize="16" Grid.Row="2"/>
        </Grid>      

為啥我要用一個ViewBox呢,因為這個控件有一個好處,就是會自動将它裡面的可視化對象進行縮放,待會兒我要在Image上顯示圖檔,而且還要用到Rectangle元素來标記人臉的位置,為了讓絕對坐标計算起來能與原圖相等,就把這些内容都放在ViewBox中,讓Viewbox來進行縮放,這樣一來,就能夠根據視窗的大小自動調整顯示區域了。

之是以用Canvas,是因它是絕對坐标定位的,這友善我稍後放置Rectangle元素。

處理按鈕事件,通過OpenFilePicker來打開圖像檔案。

FileOpenPicker picker = new FileOpenPicker();
            picker.FileTypeFilter.Add(".jpg");
            picker.FileTypeFilter.Add(".jpeg");
            picker.FileTypeFilter.Add(".png");

            StorageFile file = await picker.PickSingleFileAsync();      

記得以前跟大家講過,picker調用後要挂起目前應用,并通過響應應用激活來處理擷取的檔案,這是在WP 8.1的文章中說過,但是,現在不用了,很簡單,因為Windows Phone App和Windows App已經完全統一了,是以不必再考慮平台隔離的代碼了。

下面代碼完成兩件事:1、在Image控件上顯示圖像;2、進行識别,并用矩形标記人臉位置。

if (file != null)
            {
                using (IRandomAccessStream streamIn= await file.OpenReadAsync())
                {
                    // 對圖像檔案進行解碼
                    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(streamIn);
                    // 擷取圖像内容
                    SoftwareBitmap sbmp = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight);

                    WriteableBitmap bmp = new WriteableBitmap(sbmp.PixelWidth, sbmp.PixelHeight);
                    sbmp.CopyToBuffer(bmp.PixelBuffer);
                    this.img.Source = bmp;
                    img.Width = bmp.PixelWidth;
                    img.Height = bmp.PixelHeight;

                    // 通過循環,嘗試以各種受支援的格式來進行識别
                    // 如果識别順利,就跳出循環
                    // 否則進入下一輪循環
                    int n = 0;
                    while (n < formats.Count)
                    {
                        if (FaceDetector.IsBitmapPixelFormatSupported(formats[n]))
                        {
                            FaceDetector detector = await FaceDetector.CreateAsync();
                            try
                            {
                                // 轉換圖像像素格式
                                SoftwareBitmap sbmp2 = SoftwareBitmap.Convert(sbmp, formats[n]);
                                // 進行檢測
                                IList<DetectedFace> results = await detector.DetectFacesAsync(sbmp2);

                                // 清理Canvas中的矩形
                                while (cv.Children.Count > 1)
                                    cv.Children.RemoveAt(cv.Children.Count - 1);
                                // 在界面上添加矩形
                                for (int i = 0; i < results.Count; i++)
                                {
                                    DetectedFace dface = results[i];
                                    Rectangle rect = new Rectangle();

                                    rect.Stroke = new SolidColorBrush(Colors.Yellow);
                                    rect.StrokeThickness = 5d;
                                    // 定位矩形
                                    Canvas.SetLeft(rect, dface.FaceBox.X);
                                    Canvas.SetTop(rect, dface.FaceBox.Y);
                                    rect.Width = dface.FaceBox.Width;
                                    rect.Height = dface.FaceBox.Height;
                                    cv.Children.Add(rect);
                                }
                                tbMessage.Text = "人臉識别完成。";
                                break;
                            }
                            catch
                            {
                                tbMessage.Text = "人臉識别失敗。";
                                n++;
                            }
                            //cv.InvalidateArrange();
                        }
                    }

                }      

因為我要在Image上顯示圖像,是以從decoder中得到的SoftwareBitmap不能直接用于識别,原因是我剛才說了,目前SDK的人臉識别隻支援少量的像素格式,Bgra8是不受支援的,是以可以用SoftwareBitmap的Convert方法轉換格式,并傳回轉換後的SoftwareBitmap對象。

這裡我用一個while循環來完成識别:

int n = 0;
                    while (n < formats.Count)
                    {
                        if (FaceDetector.IsBitmapPixelFormatSupported(formats[n]))
                        {
                            FaceDetector detector = await FaceDetector.CreateAsync();
                            try
                            {
                                ……
                                break;
                            }
                            catch
                            {
                                ……
                                n++;
                            }
                        }      

意思是,我用FaceDetector所支援的所有像素格式都去嘗試進行識别,隻要其中有一種格式能夠順利完成識别,就終止循環(break);如果第一種格式不能識别,就把n++來使用第二種格式來識别。

=============================================

示例的大緻情況就是這樣,做完後我們當然要來試試效果了。

首先,來檢測一下八戒的豬臉。

【Win10 應用開發】人臉識别

很顯然,豬臉也能檢測出來,不錯。

接着,我又請神仙妹妹來試鏡。發現效果甚好。

【Win10 應用開發】人臉識别
【Win10 應用開發】人臉識别

然後,我又找來一位MM再試,效果也甚佳。

【Win10 應用開發】人臉識别

怎麼樣,這姿勢不錯吧。

示例源碼下載下傳:https://files.cnblogs.com/files/tcjiaan/FaceDetApp.zip

繼續閱讀