手把手 Golang 实现静态图像与视频流人脸识别
静态图像人脸识别
首先我们来进行静态的人脸识别,Golang 这边相较于 Python 社区来说相对少一些,不过依然有一些优秀的库可以供我们使用。今天我们用到的就是
在我们开始码代码之前,首先需要安装 dlib。Windows 平台相对麻烦一些,具体在官网有安装方案,这里我介绍两个平台。
Ubuntu 18.10+, Debian sid
最新版本的 Ubuntu 和 Debian 都提供合适的 dlib 包,所以只需要运行。
1 2 3 4 | # 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
确保安装了
1 | brew install dlib |
创建项目及准备工作
在 GOPATH 的 src 目录下,创建项目文件,命令如下。
1 2 3 | sudo makedir go-face-test # 创建 main.go sudo touch main.go |
然后进入该目录下,生成 mod 文件。
1 | 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 目录下下载相应的测试数据。
1 | git clone https: //github.com/Kagami/go-face-testdata testdata |
最终的项目结构应该如图。
代码实现
首先,我们利用代码检查环境是否正常。初始化识别器,释放资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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" ) } |
编译然后运行代码。
1 | sudo go run main.go |
应该得到下面输出。
1 2 | Face Recognition... Recognizer Initialized |
到这一步,我们已经成功的设置好了需要的一切。
检测图片中人脸数量
首先准备一张林俊杰的照片,放到任意目录下,为了演示方便,我放在了 main.go 同级目录下。
如你所见,现在什么都没有,只有一张图片,接下来我们要让计算机计算图片中的人脸数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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 封装进行识别的方法,传入相应路径的图片文件,执行代码后结果如下。
1 2 3 | Face Recognition... Recognizer Initialized 图片人脸数量: 1 |
现在笨笨的计算机已经会数人脸数量了。那....如果一张照片里面有多人准不准呢,我们试试看,准备一张多人合照图片。
heyin.jpeg
我们将第 31 行代码换成如下即可。
1 | faces, err := rec.RecognizeFile( "heyin.jpeg" ) |
运行后的结果应该打印 (图片人脸数量: 6),接下来正式看展我们的人脸识别。
人脸识别
首先我们准备一张合照,这里依然沿用上面的 heyin.jpeg。
整个处理过程大致分为以下几步。
1.将合影中人物映射到唯一 ID, 然后将唯一 ID 和对应人物相关联。
1 2 3 4 5 6 7 8 9 10 | 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,人物名字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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.调用三次。
1 2 3 | RecognizePeople(rec, "jay.jpeg" ) RecognizePeople(rec, "linjunjie.jpeg" ) RecognizePeople(rec, "taozhe.jpeg" ) |
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | 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]) } |
运行结果
最后我们运行代码。
1 2 | go build main.go ./main |
结果如下
1 2 3 4 5 6 7 | 图片人脸数量: 6 1 周杰伦 5 林俊杰 4 陶喆 |
恭喜你,你已经成功的识别出这三张图片是谁了,到这一步,静态的图像人脸识别已经完成了。
静态人脸识别总结
到这一步我们已经可以成功的利用 Go 实现了静态人脸识别。将其运用到项目中也不是不可,不过它有诸多局限,使用的场景较为单一,只能用在例如用户上传人脸身份识别,单一人脸识别等场景;图片格式较为单一,暂时不支持 PNG 格式等缺点。
视频流人脸识别
背景
静态的人脸识别应用场景较为局限,不能够放到比较重要的环境中,例如金融,保险,安防等领域,存在伪造等可能。而且单纯的静态人脸识别,意义不大。动态的视频流拥有更加广阔的应用空间,充分应用在智能安防,手势识别,美颜等领域。5G 时代,众多业务将围绕视频这一块展开,如何将视频业务与核心业务实现解耦,声网的 RTE 组件做得不错,作为 RTE-PaaS 的开创者,声网已经有较多的技术积累,通过 RTE 组件的形式有很多好处。
RTE 优点
1.应用无关性
可以在不同的项目间共享,实现复用,避免多次开发的重复性工作
2.平台无关性
广泛应用于操作系统,编程语言及各领域
3.丰富的三方模块
能够提供例如白板教学,视频美颜,鉴黄等众多模块供开发者使用
代码实现
这里我们来实现一下视频流的相关人脸识别,之前的静态识别就是为了动态视频流人脸识别做铺垫。我们来说一下视频流的人脸识别的实现思路,静态的图像人脸识别已经完成,而视频是多帧的连续,我们只需要抽取片段捕获关键帧,识别出人像,人后输出对应关联的人名。
准备工作
这里我们用到的是
1.设置视频捕捉的设备,一般来说默认 0
1 2 3 4 5 6 7 8 9 10 | // 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.打开展示窗口
1 2 3 | // open display window window := gocv.NewWindow( "Face Detect" ) defer window.Close() |
3.准备图像矩阵,检测到人脸时显示矩形框的配置
1 2 3 4 5 6 | // prepare image matrix img := gocv.NewMat() defer img.Close() // color for the rect when faces detected blue := color.RGBA{0, 0, 255, 0} |
4.加载人脸识别分类器,用一个死循环,里面加上我们的相关识别服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 三种格式。转换后的字节流放在内存中,然后将字节流传入我们的人脸识别函数中即可。
1 2 3 4 5 6 7 8 9 10 | // 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 的原因是在视频流级别的识别中可能会出现没有人脸的情况,这个时候程序应当是正常运行的,不能退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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]) } |
最后完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | 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 等组件进行人脸识别的相关开发。为开发节约大量时间和成本,可以将开发重心转移到更加核心的业务。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步