手把手 Golang 實現靜態圖像與視頻流人臉識別

提及人臉識別,你們首先想到的實現方式應該是 Python 去作相關的處理,由於相關的機器學習框架,庫都已經封裝得比較好了。可是咱們今天討論的實現方式換成 Golang,利用 Golang 去作靜態圖像和視頻流人臉識別的相應處理。git

靜態圖像人臉識別

首先咱們來進行靜態的人臉識別,Golang 這邊相較於 Python 社區來講相對少一些,不過依然有一些優秀的庫能夠供咱們使用。今天咱們用到的就是 go-face 這個庫。該庫利用 dlib 去實現人臉識別,一個很受歡迎的機器學習工具集,它能夠說是人臉識別中使用最多的軟件包之一。在產學界有普遍應用,涵蓋了機器人學,嵌入式設備,移動設備等等。在它官網的文檔中提到在 Wild 基準測試中識別標記面部的準確度達到驚人的 99.4%,這也說明爲何它能獲得普遍的應用。github

在咱們開始碼代碼以前,首先須要安裝 dlib。Windows 平臺相對麻煩一些,具體在官網有安裝方案,這裏我介紹兩個平臺。web

Ubuntu 18.10+, Debian sid

最新版本的 Ubuntu 和 Debian 都提供合適的 dlib 包,因此只須要運行。macos

# Ubuntu
sudo apt-get install libdlib-dev libblas-dev liblapack-dev libjpeg-turbo8-dev
# Debian
sudo apt-get install libdlib-dev libblas-dev liblapack-dev libjpeg62-turbo-dev

macOS

確保安裝了 Homebrew編程

brew install dlib

建立項目及準備工做

在 GOPATH 的 src 目錄下,建立項目文件,命令以下。segmentfault

sudo makedir go-face-test
# 建立 main.go
sudo touch main.go

而後進入該目錄下,生成 mod 文件。app

sudo go mod init

調用該命令後,在 go-face-test 目錄下應該已經生成了 go.mod 文件。框架

該庫須要三個模型 shape_predictor_5_face_landmarks.dat, mmod_human_face_detector.datdlib_face_recognition_resnet_model_v1.dat,在 go-face-test 目錄下下載相應的測試數據。機器學習

git clone https://github.com/Kagami/go-face-testdata testdata

最終的項目結構應該如圖。編程語言

img

代碼實現

首先,咱們利用代碼檢查環境是否正常。初始化識別器,釋放資源。

package main

import (
    "fmt"

    "github.com/Kagami/go-face"
)

const dataDir = "testdata"

// testdata 目錄下兩個對應的文件夾目錄
const (
    modelDir  = dataDir + "/models"
    imagesDir = dataDir + "/images"
)

func main() {
    fmt.Println("Face Recognition...")

    // 初始化識別器
    rec, err := face.NewRecognizer(modelDir)
    if err != nil {
        fmt.Println("Cannot INItialize recognizer")
    }
    defer rec.Close()

    fmt.Println("Recognizer Initialized")
}

編譯而後運行代碼。

sudo go run main.go

應該獲得下面輸出。

Face Recognition...
Recognizer Initialized

到這一步,咱們已經成功的設置好了須要的一切。

檢測圖片中人臉數量

首先準備一張林俊杰的照片,放到任意目錄下,爲了演示方便,我放在了 main.go 同級目錄下。

img

如你所見,如今什麼都沒有,只有一張圖片,接下來咱們要讓計算機計算圖片中的人臉數量。

package main

import (
    "fmt"
    "log"

    "github.com/Kagami/go-face"
)

const dataDir = "testdata"

// testdata 目錄下兩個對應的文件夾目錄
const (
    modelDir  = dataDir + "/models"
    imagesDir = dataDir + "/images"
)

func main() {
    fmt.Println("Face Recognition...")

    // 初始化識別器
    rec, err := face.NewRecognizer(modelDir)
    if err != nil {
        fmt.Println("Cannot INItialize recognizer")
    }
    defer rec.Close()

    fmt.Println("Recognizer Initialized")

    // 調用該方法,傳入路徑。返回面部數量和任何錯誤
    faces, err := rec.RecognizeFile("linjunjie.jpeg")
    if err != nil {
        log.Fatalf("沒法識別: %v", err)
    }
    // 打印人臉數量
    fmt.Println("圖片人臉數量: ", len(faces))
}

核心代碼其實就是一行,go-face 封裝進行識別的方法,傳入相應路徑的圖片文件,執行代碼後結果以下。

Face Recognition...
Recognizer Initialized
圖片人臉數量:  1

如今笨笨的計算機已經會數人臉數量了。那....若是一張照片裏面有多人準不許呢,咱們試試看,準備一張多人合照圖片。

img
heyin.jpeg

咱們將第 31 行代碼換成以下便可。

faces, err := rec.RecognizeFile("heyin.jpeg")

運行後的結果應該打印 (圖片人臉數量: 6),接下來正式看展咱們的人臉識別。

人臉識別

首先咱們準備一張合照,這裏依然沿用上面的 heyin.jpeg

整個處理過程大體分爲如下幾步。

1.將合影中人物映射到惟一 ID, 而後將惟一 ID 和對應人物相關聯。

var samples []face.Descriptor
    var peoples []int32
    for i, f := range faces {
        samples = append(samples, f.Descriptor)
        // 每張臉惟一 id
        peoples = append(peoples, int32(i))
    }

    // Pass samples to the recognizer.
    rec.SetSamples(samples, peoples)

2.接下來咱們封裝一我的臉識別的方法,傳入識別器和照片路徑,打印對應人物 ID,人物名字。

func RecognizePeople(rec *face.Recognizer, file string) {
    people, err := rec.RecognizeSingleFile(file)
    if err != nil {
        log.Fatalf("沒法識別: %v", err)
    }
    if people == nil {
        log.Fatalf("圖片上不是一張臉")
    }
    peopleID := rec.Classify(people.Descriptor)
    if peopleID < 0 {
        log.Fatalf("沒法區分")
    }
    fmt.Println(peopleID)
    fmt.Println(labels[peopleID])
}

3.最後咱們傳入想要識別的圖片,目前傳入了 3 張圖片,感興趣的小夥伴能夠傳入其餘圖片嘗試。

img
jay.jpeg

img
linjunjie.jpeg

img
taozhe.jpeg

4.調用三次。

RecognizePeople(rec, "jay.jpeg")
    RecognizePeople(rec, "linjunjie.jpeg")
    RecognizePeople(rec, "taozhe.jpeg")

代碼以下

package main

import (
    "fmt"
    "log"

    "github.com/Kagami/go-face"
)

const dataDir = "testdata"

// testdata 目錄下兩個對應的文件夾目錄
const (
    modelDir  = dataDir + "/models"
    imagesDir = dataDir + "/images"
)

// 圖片中的人名
var labels = []string{
    "蕭敬騰",
    "周杰倫",
    "unknow",
    "王力宏",
    "陶喆",
    "林俊杰",
}

func main() {
    fmt.Println("Face Recognition...")

    // 初始化識別器
    rec, err := face.NewRecognizer(modelDir)
    if err != nil {
        fmt.Println("Cannot INItialize recognizer")
    }
    defer rec.Close()

    fmt.Println("Recognizer Initialized")

    // 調用該方法,傳入路徑。返回面部數量和任何錯誤
    faces, err := rec.RecognizeFile("heyin.jpeg")
    if err != nil {
        log.Fatalf("沒法識別: %v", err)
    }
    // 打印人臉數量
    fmt.Println("圖片人臉數量: ", len(faces))

    var samples []face.Descriptor
    var peoples []int32
    for i, f := range faces {
        samples = append(samples, f.Descriptor)
        // 每張臉惟一 id
        peoples = append(peoples, int32(i))
    }

    // 傳入樣例到識別器
    rec.SetSamples(samples, peoples)

    RecognizePeople(rec, "jay.jpeg")
    RecognizePeople(rec, "linjunjie.jpeg")
    RecognizePeople(rec, "taozhe.jpeg")
}

func RecognizePeople(rec *face.Recognizer, file string) {
    people, err := rec.RecognizeSingleFile(file)
    if err != nil {
        log.Fatalf("沒法識別: %v", err)
    }
    if people == nil {
        log.Fatalf("圖片上不是一張臉")
    }
    peopleID := rec.Classify(people.Descriptor)
    if peopleID < 0 {
        log.Fatalf("沒法區分")
    }
    fmt.Println(peopleID)
    fmt.Println(labels[peopleID])
}

運行結果

最後咱們運行代碼。

go build main.go
./main

結果以下

圖片人臉數量:  6
1
周杰倫
5
林俊杰
4
陶喆

恭喜你,你已經成功的識別出這三張圖片是誰了,到這一步,靜態的圖像人臉識別已經完成了。

靜態人臉識別總結

到這一步咱們已經能夠成功的利用 Go 實現了靜態人臉識別。將其運用到項目中也不是不可,不過它有諸多侷限,使用的場景較爲單一,只能用在例如用戶上傳人臉身份識別,單一人臉識別等場景;圖片格式較爲單一,暫時不支持 PNG 格式等缺點。

視頻流人臉識別

背景

靜態的人臉識別應用場景較爲侷限,不可以放到比較重要的環境中,例如金融,保險,安防等領域,存在僞造等可能。並且單純的靜態人臉識別,意義不大。動態的視頻流擁有更加廣闊的應用空間,充分應用在智能安防,手勢識別,美顏等領域。5G 時代,衆多業務將圍繞視頻這一塊展開,如何將視頻業務與核心業務實現解耦,聲網的 RTE 組件作得不錯,做爲 RTE-PaaS 的開創者,聲網已經有較多的技術積累,經過 RTE 組件的形式有不少好處。

RTE 優勢

1.應用無關性

能夠在不一樣的項目間共享,實現複用,避免屢次開發的重複性工做

2.平臺無關性

普遍應用於操做系統,編程語言及各領域

3.豐富的三方模塊

可以提供例如白板教學,視頻美顏,鑑黃等衆多模塊供開發者使用

代碼實現

這裏咱們來實現一下視頻流的相關人臉識別,以前的靜態識別就是爲了動態視頻流人臉識別作鋪墊。咱們來講一下視頻流的人臉識別的實現思路,靜態的圖像人臉識別已經完成,而視頻是多幀的連續,咱們只須要抽取片斷捕獲關鍵幀,識別出人像,人後輸出對應關聯的人名。

準備工做

這裏咱們用到的是 gocv(底層使用 OpenCV),這裏咱們暫時略過具體的安裝流程,按照官方文檔安裝便可。

1.設置視頻捕捉的設備,通常來講默認 0

// set to use a video capture device 0
    deviceID := 0

    // open webcam
    webcam, err := gocv.OpenVideoCapture(deviceID)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer webcam.Close()

2.打開展現窗口

// open display window
    window := gocv.NewWindow("Face Detect")
    defer window.Close()

3.準備圖像矩陣,檢測到人臉時顯示矩形框的配置

// prepare image matrix
    img := gocv.NewMat()
    defer img.Close()

    // color for the rect when faces detected
    blue := color.RGBA{0, 0, 255, 0}

4.加載人臉識別分類器,用一個死循環,裏面加上咱們的相關識別服務

for {
        if ok := webcam.Read(&img); !ok {
            fmt.Printf("cannot read device %v\n", deviceID)
            return
        }
        if img.Empty() {
            continue
        }

        // detect faces
        rects := classifier.DetectMultiScale(img)
        fmt.Printf("found %d faces\n", len(rects))

        // draw a rectangle around each face on the original image
        for _, r := range rects {
            gocv.Rectangle(&img, r, blue, 3)
      imgFace := img.Region(r)
            buff, err:=gocv.IMEncode(".jpg",imgFace)
            if err != nil {
                fmt.Println("encoding to jpg err:%v", err)
                break
            }

            RecognizePeopleFromMemory(rec, buff)
        }

        // show the image in the window, and wait 1 millisecond
        window.IMShow(img)
        window.WaitKey(1)
    }

其中有幾個步驟須要將一下,目前來講 gocv.IMEncode 只支持將捕獲到的圖片轉成 PNGJPGGIF 三種格式。轉換後的字節流放在內存中,而後將字節流傳入咱們的人臉識別函數中便可。

// RecognizeSingle returns face if it's the only face on the image or
// nil otherwise. Only JPEG format is currently supported. Thread-safe.
func (rec *Recognizer) RecognizeSingle(imgData []byte) (face *Face, err error) {
    faces, err := rec.recognize(0, imgData, 1)
    if err != nil || len(faces) != 1 {
        return
    }
    face = &faces[0]
    return
}

注意事項

因爲 go-face 只支持 JPEG 的格式,因此咱們捕捉的幀只能轉換成 JPG 格式

而後簡單的封裝一個字符流的識別函數。這裏須要說明一下,之因此將 log.Fatal 換成了 log.Println 的緣由是在視頻流級別的識別中可能會出現沒有人臉的狀況,這個時候程序應當是正常運行的,不能退出。

func RecognizePeopleFromMemory(rec *face.Recognizer, img []byte) {
    people, err := rec.RecognizeSingle(img)
    if err != nil {
        log.Println("沒法識別: %v", err)
        return
    }
    if people == nil {
        log.Println("圖片上不是一張臉")
        return
    }
    peopleID := rec.Classify(people.Descriptor)
    if peopleID < 0 {
        log.Println("沒法區分")
        return
    }
    fmt.Println(peopleID)
    fmt.Println(labels[peopleID])
}

最後完整代碼以下

package main

import (
    "fmt"
    "image/color"
    "log"

    "github.com/Kagami/go-face"
    "gocv.io/x/gocv"
)

const dataDir = "testdata"

// testdata 目錄下兩個對應的文件夾目錄
const (
    modelDir  = dataDir + "/models"
    imagesDir = dataDir + "/images"
)

// 圖片中的人名
var labels = []string{
    "蕭敬騰",
    "周杰倫",
    "unknow",
    "王力宏",
    "陶喆",
    "林俊杰",
}

func main() {
    // 初始化識別器
    rec, err := face.NewRecognizer(modelDir)
    if err != nil {
        fmt.Println("Cannot INItialize recognizer")
    }
    defer rec.Close()

    fmt.Println("Recognizer Initialized")

    // 調用該方法,傳入路徑。返回面部數量和任何錯誤
    faces, err := rec.RecognizeFile("heyin.jpeg")
    if err != nil {
        log.Fatalf("沒法識別: %v", err)
    }
    // 打印人臉數量
    fmt.Println("圖片人臉數量: ", len(faces))

    var samples []face.Descriptor
    var peoples []int32
    for i, f := range faces {
        samples = append(samples, f.Descriptor)
        // 每張臉惟一 id
        peoples = append(peoples, int32(i))
    }

    // Pass samples to the recognizer.
    rec.SetSamples(samples, peoples)

    RecognizePeople(rec, "jay.jpeg")
    RecognizePeople(rec, "linjunjie.jpeg")
    RecognizePeople(rec, "taozhe.jpeg")

    // set to use a video capture device 0
    deviceID := 0

    // open webcam
    webcam, err := gocv.OpenVideoCapture(deviceID)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer webcam.Close()

    // open display window
    window := gocv.NewWindow("Face Detect")
    defer window.Close()

    // prepare image matrix
    img := gocv.NewMat()
    defer img.Close()

    // color for the rect when faces detected
    blue := color.RGBA{0, 0, 255, 0}

    // load classifier to recognize faces
    classifier := gocv.NewCascadeClassifier()
    defer classifier.Close()

    if !classifier.Load("./haarcascade_frontalface_default.xml") {
        fmt.Println("Error reading cascade file: data/haarcascade_frontalface_default.xml")
        return
    }

    fmt.Printf("start reading camera device: %v\n", deviceID)
    for {
        if ok := webcam.Read(&img); !ok {
            fmt.Printf("cannot read device %v\n", deviceID)
            return
        }
        if img.Empty() {
            continue
        }

        // detect faces
        rects := classifier.DetectMultiScale(img)
        if len(rects) == 0 {
            continue
        }

        fmt.Printf("found %d faces\n", len(rects))

        // draw a rectangle around each face on the original image
        for _, r := range rects {
            gocv.Rectangle(&img, r, blue, 3)

            imgFace := img.Region(r)
            buff, err:=gocv.IMEncode(".jpg",imgFace)
            if err != nil {
                fmt.Println("encoding to jpg err:%v", err)
                break
            }

            RecognizePeopleFromMemory(rec, buff)
        }

        // show the image in the window, and wait 1 millisecond
        window.IMShow(img)
        window.WaitKey(1)
    }
}

func RecognizePeople(rec *face.Recognizer, file string) {
    people, err := rec.RecognizeSingleFile(file)
    if err != nil {
        log.Fatalf("沒法識別: %v", err)
    }
    if people == nil {
        log.Fatalf("圖片上不是一張臉")
    }
    peopleID := rec.Classify(people.Descriptor)
    if peopleID < 0 {
        log.Fatalf("沒法區分")
    }
    fmt.Println(peopleID)
    fmt.Println(labels[peopleID])
}

func RecognizePeopleFromMemory(rec *face.Recognizer, img []byte) {
    people, err := rec.RecognizeSingle(img)
    if err != nil {
        log.Println("沒法識別: %v", err)
        return
    }
    if people == nil {
        log.Println("圖片上不是一張臉")
        return
    }
    peopleID := rec.Classify(people.Descriptor)
    if peopleID < 0 {
        log.Println("沒法區分")
        return
    }
    fmt.Println(peopleID)
    fmt.Println(labels[peopleID])
}

接下來咱們運行代碼,應該可以拉起攝像頭,這個時候我手持林俊杰的照片進行識別,咱們能夠看到左下角已經輸出對應的人名了。

img

視頻流人臉識別總結

到這一步,恭喜你,你已經可以完成視頻流人臉識別了。可是,這裏要說明一下,爲了快速的實現,咱們的樣本集是比較少的,識別成功率相對來講比較低。不過一個簡單的動態人臉識別已經搭好了。

總結

雖然咱們實現了動態的人臉識別,可是在更爲複雜的應用場景下難以實現相應的需求,並且存在圖片格式等限制,缺少人臉處理的其餘模塊,美顏,鑑黃等功能。不過經過第三方的 SDK,例如聲網等平臺去實現對應的需求,園區的人臉識別,視頻會議,雲課堂等場景,可以實現快速搭建,可以幾行代碼就可以完成相應的接入,並圍繞 RTE 等組件進行人臉識別的相關開發。爲開發節約大量時間和成本,能夠將開發重心轉移到更加核心的業務。

相關文章
相關標籤/搜索