diff --git a/README.md b/README.md index d9023e2..cecdcbd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # face + +[![GoDoc](https://pkg.go.dev/badge/github.com/dev6699/face)](https://pkg.go.dev/github.com/dev6699/face) +[![Go Report Card](https://goreportcard.com/badge/github.com/dev6699/face)](https://goreportcard.com/report/github.com/dev6699/face) +[![License](https://img.shields.io/github/license/dev6699/face)](LICENSE) + A comprehensive collection of face AI models with integrated pre and post-processing steps, utilizing NVIDIA Triton Inference Server for seamless inference. This repository aims to provide easy-to-use face detection, recognition, and analysis tools. ## Table of Contents @@ -21,28 +26,34 @@ This repository contains a suite of face AI models designed for various applicat - Easy-to-Use Interface: Simple API for quick integration into various applications. ## Installation -1. Clone the Repository: +- ### Use `go get` to install this package: + + ```bash + go get github.com/dev6699/face + ``` + +- ### Clone the Repository: ```bash git clone https://github.com/dev6699/face.git cd face ``` -2. Open the repository in vscode devcontainer. +### Download and Prepare Models: +- Navigate to the [Available Models](#available-models) section to find the download links for each model. +- Download each model and rename the file to `model.onnx`. +- Place each `model.onnx` file into its respective directory within the model_repository folder. +- Example: Setting up the YOLOFace model: -3. Download and Prepare Models: - - Navigate to the [Available Models](#available-models) section to find the download links for each model. - - Download each model and rename the file to `model.onnx`. - - Place each `model.onnx` file into its respective directory within the model_repository folder. - - Example: Setting up the YOLOFace model: - - ```bash - mkdir -p model_repository/yoloface/1 - wget -O model_repository/yoloface/1/model.onnx - ``` - Ensure to replace with the actual URL provided in the [Available Models](#available-models) section. + ```bash + mkdir -p model_repository/yoloface/1 + wget -O model_repository/yoloface/1/model.onnx + ``` + Ensure to replace with the actual URL provided in the [Available Models](#available-models) section. ## Usage +Please refer to the [examples](examples) folder for more information on how to use the models and run various tasks. + 1. Start Triton Inference Server: ```bash diff --git a/docker-compose.yml b/docker-compose.yml index 735d0b0..5201a44 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,3 +15,7 @@ services: reservations: devices: - capabilities: [gpu] + +networks: + default: + name: face_devcontainer_default \ No newline at end of file diff --git a/examples/2dfan4/main.go b/examples/2dfan4/main.go new file mode 100644 index 0000000..2b73b74 --- /dev/null +++ b/examples/2dfan4/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "log" + + "github.com/dev6699/face/client" + "github.com/dev6699/face/examples" + "github.com/dev6699/face/model" + _2dfan4 "github.com/dev6699/face/model/2dfan4" + "github.com/dev6699/face/model/yoloface" + "gocv.io/x/gocv" +) + +func main() { + faceDetectorScore := float32(0.5) + iouThreshold := 0.4 + yolofaceFactory := yoloface.NewFactory(faceDetectorScore, iouThreshold) + _2dfan4Factory := _2dfan4.NewFactory() + err := client.Init( + "tritonserver:8001", + []model.ModelMeta{ + yolofaceFactory(), + _2dfan4Factory(), + }, + ) + if err != nil { + log.Fatal(err) + } + + img := gocv.IMRead("../image.jpg", gocv.IMReadColor) + yoloFaceOutput, err := client.Infer(yolofaceFactory, &yoloface.Input{Img: img}) + if err != nil { + log.Fatal(err) + } + + for _, d := range yoloFaceOutput.Detections { + fanOutput, err := client.Infer(_2dfan4Factory, &_2dfan4.Input{Img: img, BoundingBox: d.BoundingBox}) + if err != nil { + log.Fatal(err) + } + + examples.DrawPoints(&img, fanOutput.FaceLandmark68.Data, examples.Red, 2) + } + + gocv.IMWrite("output.jpg", img) +} diff --git a/examples/arcface/main.go b/examples/arcface/main.go new file mode 100644 index 0000000..e9bff26 --- /dev/null +++ b/examples/arcface/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "log" + + "github.com/dev6699/face/client" + "github.com/dev6699/face/model" + "github.com/dev6699/face/model/arcface" + "github.com/dev6699/face/model/yoloface" + "gocv.io/x/gocv" +) + +func main() { + faceDetectorScore := float32(0.5) + iouThreshold := 0.4 + yolofaceFactory := yoloface.NewFactory(faceDetectorScore, iouThreshold) + arcfaceFactory := arcface.NewFactory() + err := client.Init( + "tritonserver:8001", + []model.ModelMeta{ + yolofaceFactory(), + arcfaceFactory(), + }, + ) + if err != nil { + log.Fatal(err) + } + img := gocv.IMRead("../image.jpg", gocv.IMReadColor) + yoloFaceOutput, err := client.Infer(yolofaceFactory, &yoloface.Input{Img: img}) + if err != nil { + log.Fatal(err) + } + + faceEmbeddings := [][]float32{} + for _, d := range yoloFaceOutput.Detections { + arcfaceOutput, err := client.Infer(arcfaceFactory, &arcface.Input{Img: img, FaceLandmark5: d.FaceLandmark5}) + if err != nil { + log.Fatal(err) + } + faceEmbeddings = append(faceEmbeddings, arcfaceOutput.NormedEmbedding) + } + + similarDistance := 0.6 + for i, f1 := range faceEmbeddings { + for j, f2 := range faceEmbeddings { + faceDistance := model.CalcFaceDistance(f1, f2) + isSimilar := faceDistance < similarDistance + fmt.Printf("Face %d & %d: %.2f %v\n", i, j, faceDistance, isSimilar) + } + } +} diff --git a/examples/genderage/main.go b/examples/genderage/main.go new file mode 100644 index 0000000..1cdf053 --- /dev/null +++ b/examples/genderage/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "log" + + "github.com/dev6699/face/client" + "github.com/dev6699/face/examples" + "github.com/dev6699/face/model" + "github.com/dev6699/face/model/genderage" + "github.com/dev6699/face/model/yoloface" + "gocv.io/x/gocv" +) + +func main() { + faceDetectorScore := float32(0.5) + iouThreshold := 0.4 + yolofaceFactory := yoloface.NewFactory(faceDetectorScore, iouThreshold) + genderAgeFactory := genderage.NewFactory() + err := client.Init( + "tritonserver:8001", + []model.ModelMeta{ + yolofaceFactory(), + genderAgeFactory(), + }, + ) + if err != nil { + log.Fatal(err) + } + + img := gocv.IMRead("../image.jpg", gocv.IMReadColor) + yoloFaceOutput, err := client.Infer(yolofaceFactory, &yoloface.Input{Img: img}) + if err != nil { + log.Fatal(err) + } + + for _, d := range yoloFaceOutput.Detections { + genderAgeOutput, err := client.Infer(genderAgeFactory, &genderage.Input{Img: img, BoundingBox: d.BoundingBox}) + if err != nil { + log.Fatal(err) + } + + genderString := "M" + if genderAgeOutput.Gender == 0 { + genderString = "F" + } + examples.DrawBoundingBoxes(&img, d.BoundingBox, fmt.Sprintf("%s %d", genderString, genderAgeOutput.Age), examples.Green, examples.Red) + } + + gocv.IMWrite("output.jpg", img) +} diff --git a/examples/gfpgan/main.go b/examples/gfpgan/main.go new file mode 100644 index 0000000..ea880db --- /dev/null +++ b/examples/gfpgan/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "log" + + "github.com/dev6699/face/client" + "github.com/dev6699/face/model" + "github.com/dev6699/face/model/gfpgan" + "github.com/dev6699/face/model/yoloface" + "gocv.io/x/gocv" +) + +func main() { + faceDetectorScore := float32(0.5) + iouThreshold := 0.4 + yolofaceFactory := yoloface.NewFactory(faceDetectorScore, iouThreshold) + gfpganFactory := gfpgan.NewFactory(80.0) + err := client.Init( + "tritonserver:8001", + []model.ModelMeta{ + yolofaceFactory(), + gfpganFactory(), + }, + ) + if err != nil { + log.Fatal(err) + } + + img := gocv.IMRead("../image.jpg", gocv.IMReadColor) + yoloFaceOutput, err := client.Infer(yolofaceFactory, &yoloface.Input{Img: img}) + if err != nil { + log.Fatal(err) + } + + for i, d := range yoloFaceOutput.Detections { + gfpganOutput, err := client.Infer(gfpganFactory, &gfpgan.Input{Img: img, FaceLandmark5: d.FaceLandmark5}) + if err != nil { + log.Fatal(err) + } + gocv.IMWrite(fmt.Sprintf("output%d.jpg", i+1), gfpganOutput.OutFrame) + } +} diff --git a/examples/image.jpg b/examples/image.jpg new file mode 100644 index 0000000..024a125 Binary files /dev/null and b/examples/image.jpg differ diff --git a/examples/util.go b/examples/util.go new file mode 100644 index 0000000..975f2cb --- /dev/null +++ b/examples/util.go @@ -0,0 +1,26 @@ +package examples + +import ( + "image" + "image/color" + + "github.com/dev6699/face/model" + "gocv.io/x/gocv" +) + +var ( + Red = color.RGBA{R: 255, G: 0, B: 0, A: 255} + Green = color.RGBA{R: 0, G: 255, B: 0, A: 255} + Blue = color.RGBA{R: 0, G: 0, B: 255, A: 255} +) + +func DrawBoundingBoxes(img *gocv.Mat, box model.BoundingBox, text string, boxColor, textColor color.RGBA) { + gocv.Rectangle(img, image.Rectangle{Min: image.Point{X: int(box.X1), Y: int(box.Y1)}, Max: image.Point{X: int(box.X2), Y: int(box.Y2)}}, boxColor, 2) + gocv.PutText(img, text, image.Point{X: int(box.X1), Y: int(box.Y1) - 5}, gocv.FontHersheySimplex, 0.5, textColor, 2) +} + +func DrawPoints(img *gocv.Mat, points []gocv.Point2f, col color.RGBA, radius int) { + for _, pt := range points { + gocv.Circle(img, image.Pt(int(pt.X), int(pt.Y)), radius, col, -1) + } +} diff --git a/examples/yoloface/main.go b/examples/yoloface/main.go new file mode 100644 index 0000000..9126dca --- /dev/null +++ b/examples/yoloface/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "log" + + "github.com/dev6699/face/client" + "github.com/dev6699/face/examples" + "github.com/dev6699/face/model" + "github.com/dev6699/face/model/yoloface" + "gocv.io/x/gocv" +) + +func main() { + faceDetectorScore := float32(0.5) + iouThreshold := 0.4 + yolofaceFactory := yoloface.NewFactory(faceDetectorScore, iouThreshold) + err := client.Init( + "tritonserver:8001", + []model.ModelMeta{ + yolofaceFactory(), + }, + ) + if err != nil { + log.Fatal(err) + } + + img := gocv.IMRead("../image.jpg", gocv.IMReadColor) + yoloFaceOutput, err := client.Infer(yolofaceFactory, &yoloface.Input{Img: img}) + if err != nil { + log.Fatal(err) + } + + for _, d := range yoloFaceOutput.Detections { + examples.DrawBoundingBoxes(&img, d.BoundingBox, fmt.Sprintf("Score: %.2f", d.Confidence), examples.Green, examples.Green) + examples.DrawPoints(&img, d.FaceLandmark5, examples.Red, 3) + } + + gocv.IMWrite("output.jpg", img) +} diff --git a/model/util.go b/model/util.go index 0501db3..f000b51 100644 --- a/model/util.go +++ b/model/util.go @@ -287,3 +287,25 @@ func getInverseVisionFrame(cropVisionFrame gocv.Mat, inverseMatrix gocv.Mat, tem inverseVisionFrame.ConvertTo(&inverseVisionFrame, gocv.MatTypeCV64F) return inverseVisionFrame } + +// CalcFaceDistance to calculate the distance between two face embeddings +func CalcFaceDistance(faceEmbedding, referenceFaceEmbedding []float32) float64 { + if len(faceEmbedding) == 0 || len(referenceFaceEmbedding) == 0 || len(faceEmbedding) != len(referenceFaceEmbedding) { + return 0 + } + + dotProduct := dotProduct(faceEmbedding, referenceFaceEmbedding) + return 1.0 - float64(dotProduct) +} + +func dotProduct(v1, v2 []float32) float32 { + if len(v1) != len(v2) { + return 0 + } + + var result float32 + for i := 0; i < len(v1); i++ { + result += v1[i] * v2[i] + } + return result +}