提及人脸识别,你们首先想到的实现方式应该是 Python 去作相关的处理,由于相关的机器学习框架,库都已经封装得比较好了。可是咱们今天讨论的实现方式换成 Golang,利用 Golang 去作静态图像和视频流人脸识别的相应处理。git
首先咱们来进行静态的人脸识别,Golang 这边相较于 Python 社区来讲相对少一些,不过依然有一些优秀的库能够供咱们使用。今天咱们用到的就是 go-face 这个库。该库利用 dlib 去实现人脸识别,一个很受欢迎的机器学习工具集,它能够说是人脸识别中使用最多的软件包之一。在产学界有普遍应用,涵盖了机器人学,嵌入式设备,移动设备等等。在它官网的文档中提到在 Wild 基准测试中识别标记面部的准确度达到惊人的 99.4%,这也说明为何它能获得普遍的应用。github
在咱们开始码代码以前,首先须要安装 dlib。Windows 平台相对麻烦一些,具体在官网有安装方案,这里我介绍两个平台。web
最新版本的 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
确保安装了 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.dat 和 dlib_face_recognition_resnet_model_v1.dat,在 go-face-test 目录下下载相应的测试数据。机器学习
git clone https://github.com/Kagami/go-face-testdata testdata
最终的项目结构应该如图。编程语言
首先,咱们利用代码检查环境是否正常。初始化识别器,释放资源。
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 同级目录下。
如你所见,如今什么都没有,只有一张图片,接下来咱们要让计算机计算图片中的人脸数量。
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
如今笨笨的计算机已经会数人脸数量了。那....若是一张照片里面有多人准不许呢,咱们试试看,准备一张多人合照图片。
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 张图片,感兴趣的小伙伴能够传入其余图片尝试。
jay.jpeg
linjunjie.jpeg
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 只支持将捕获到的图片转成 PNG,JPG,GIF 三种格式。转换后的字节流放在内存中,而后将字节流传入咱们的人脸识别函数中便可。
// 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]) }
接下来咱们运行代码,应该可以拉起摄像头,这个时候我手持林俊杰的照片进行识别,咱们能够看到左下角已经输出对应的人名了。
到这一步,恭喜你,你已经可以完成视频流人脸识别了。可是,这里要说明一下,为了快速的实现,咱们的样本集是比较少的,识别成功率相对来讲比较低。不过一个简单的动态人脸识别已经搭好了。
虽然咱们实现了动态的人脸识别,可是在更为复杂的应用场景下难以实现相应的需求,并且存在图片格式等限制,缺少人脸处理的其余模块,美颜,鉴黄等功能。不过经过第三方的 SDK,例如声网等平台去实现对应的需求,园区的人脸识别,视频会议,云课堂等场景,可以实现快速搭建,可以几行代码就可以完成相应的接入,并围绕 RTE 等组件进行人脸识别的相关开发。为开发节约大量时间和成本,能够将开发重心转移到更加核心的业务。