face

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2026 License: CC0-1.0 Imports: 7 Imported by: 0

README

go-face

CI

Fork of Kagami/go-face, maintained as a dependency of go-recognizer. The upstream project has had no recent activity; this fork exists to fix build-portability issues (e.g. the hardcoded -march=native CXXFLAGS that breaks CI/container builds) as they come up. It is not a general-purpose replacement or actively triaged for unrelated issues/PRs.

go-face implements face recognition for Go using dlib, a popular machine learning toolkit. Read Face recognition with Go article for some background details if you're new to FaceNet concept.

Requirements

To compile go-face you need to have dlib (>= 19.10) and libjpeg development packages installed.

Ubuntu 18.10+, Debian sid

Latest versions of Ubuntu and Debian provide suitable dlib package so just run:

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

Make sure you have Homebrew installed.

brew install dlib
Windows

Make sure you have MSYS2 installed.

  1. Run MSYS2 MSYS shell from Start menu
  2. Run pacman -Syu and if it asks you to close the shell do that
  3. Run pacman -Syu again
  4. Run pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-dlib
    1. If you already have Go and Git installed and available in PATH uncomment set MSYS2_PATH_TYPE=inherit line in msys2_shell.cmd located in MSYS2 installation folder
    2. Otherwise run pacman -S mingw-w64-x86_64-go git
  5. Run MSYS2 MinGW 64-bit shell from Start menu to compile and use go-face
Other systems

Try to install dlib/libjpeg with package manager of your distribution or compile from sources. Note that go-face won't work with old packages of dlib such as libdlib18. Alternatively create issue with the name of your system and someone might help you with the installation process.

Known issue: linking against ATLAS-based BLAS

face.go's #cgo LDFLAGS links -lblas -lcblas -llapack, which matches a reference BLAS/LAPACK or OpenBLAS install (what the Ubuntu/Debian packages above provide). Some systems instead ship a BLAS built on ATLAS, which splits things up differently and needs a different link line -- typically something like:

-lf77blas -lcblas -latlas -lgfortran -lm -lquadmath

(plus installing gfortran). If you hit undefined reference to 'cblas_sgemm' or similar at link time, this mismatch is the likely cause. There's no single flag set that works for both without detecting which BLAS backend is actually installed (e.g. via pkg-config), which this fork doesn't do yet -- for now, fork and adjust the LDFLAGS line for your system.

Models

Currently shape_predictor_5_face_landmarks.dat, mmod_human_face_detector.dat and dlib_face_recognition_resnet_model_v1.dat are required. You may download them from go-face-testdata repo:

wget https://github.com/Kagami/go-face-testdata/raw/master/models/shape_predictor_5_face_landmarks.dat
wget https://github.com/Kagami/go-face-testdata/raw/master/models/dlib_face_recognition_resnet_model_v1.dat
wget https://github.com/Kagami/go-face-testdata/raw/master/models/mmod_human_face_detector.dat

Usage

To use go-face in your Go code:

import "github.com/leandroveronezi/go-face"

To install go-face:

go get github.com/leandroveronezi/go-face

For further details see the pkg.go.dev documentation.

Additions in this fork

On top of what's in upstream Kagami/go-face:

  • AppendSample(sample Descriptor, cat int32) -- adds one descriptor to the existing sample set without discarding the ones already loaded via SetSamples, so a single new sample is identifiable immediately.
  • RecognizeRaw/RecognizeRawCNN/RecognizeSingleRaw/RecognizeSingleRawCNN -- take an already-decoded, tightly packed RGB pixel buffer (3 bytes/pixel, row-major) instead of JPEG-encoded bytes, skipping JPEG decode entirely.
  • NewRecognizerWithModels(modelDir, shapePredictorFile, resnetFile, cnnFile string) -- lets you point at differently-named or fine-tuned model files instead of the hardcoded defaults. Pass "" for any file to keep its default. Use shape_predictor_68_face_landmarks.dat as shapePredictorFile for full facial contour landmarks (jawline, eyebrows, nose bridge, eyes, lips) instead of the default 5 points.
  • Accepts grayscale (1-component) JPEGs by requesting RGB output from libjpeg during decode, instead of rejecting anything that isn't already 3-component.
  • Architecture-aware build flags (no more hardcoded -march=native, which fails outright on ARM/Apple Silicon and is unreliable across x86 CPU vendors in CI/containers), and a build fix for GCC 15+/newer dlib header layouts (see commit history for specifics).

See the go-recognizer README for a higher-level API built on top of these.

Example

package main

import (
	"fmt"
	"log"
	"path/filepath"

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

// Path to directory with models and test images. Here it's assumed it
// points to the <https://github.com/Kagami/go-face-testdata> clone.
const dataDir = "testdata"

var (
	modelsDir = filepath.Join(dataDir, "models")
	imagesDir = filepath.Join(dataDir, "images")
)

// This example shows the basic usage of the package: create an
// recognizer, recognize faces, identify them using few known ones.
func main() {
	// Init the recognizer.
	rec, err := face.NewRecognizer(modelsDir)
	if err != nil {
		log.Fatalf("Can't init face recognizer: %v", err)
	}
	// Free the resources when you're finished.
	defer rec.Close()

	// Test image with 10 faces.
	testImagePristin := filepath.Join(imagesDir, "pristin.jpg")
	// Recognize faces on that image.
	faces, err := rec.RecognizeFile(testImagePristin)
	if err != nil {
		log.Fatalf("Can't recognize: %v", err)
	}
	if len(faces) != 10 {
		log.Fatalf("Wrong number of faces")
	}

	// Fill known samples. In the real world you would use a lot of images
	// for each person to get better identification results but in our
	// example we just get them from one big image.
	var samples []face.Descriptor
	var cats []int32
	for i, f := range faces {
		samples = append(samples, f.Descriptor)
		// Each face is unique on that image so goes to its own category.
		cats = append(cats, int32(i))
	}
	// Name the categories, i.e. people on the image.
	labels := []string{
		"Sungyeon", "Yehana", "Roa", "Eunwoo", "Xiyeon",
		"Kyulkyung", "Nayoung", "Rena", "Kyla", "Yuha",
	}
	// Pass samples to the recognizer.
	rec.SetSamples(samples, cats)

	// Now let's try to identify some not yet known image.
	testImageNayoung := filepath.Join(imagesDir, "nayoung.jpg")
	nayoungFace, err := rec.RecognizeSingleFile(testImageNayoung)
	if err != nil {
		log.Fatalf("Can't recognize: %v", err)
	}
	if nayoungFace == nil {
		log.Fatalf("Not a single face on the image")
	}
	catID := rec.Identify(nayoungFace.Descriptor)
	if catID < 0 {
		log.Fatalf("Can't identify")
	}
	// Finally print the identified label. It should be "Nayoung".
	fmt.Println(labels[catID])
}

Run with:

mkdir -p ~/go && cd ~/go  # Or cd to your $GOPATH
mkdir -p src/go-face-example && cd src/go-face-example
git clone https://github.com/Kagami/go-face-testdata testdata
edit main.go  # Paste example code
go get && go run main.go

Test

To fetch test data and run tests:

make test

FAQ

How to improve recognition accuracy

There are few suggestions:

  • Try CNN recognizing
  • Try different tolerance values of IdentifyThreshold
  • Try different size/padding/jittering values of NewRecognizerWithConfig
  • Provide more samples of each category to SetSamples if possible
  • Implement better classify heuristics (see classify.cc)
  • Train network (dlib_face_recognition_resnet_model_v1.dat) on your own test data

License

go-face is licensed under CC0.

Documentation

Overview

Package face implements face recognition for Go using dlib, a popular machine learning toolkit.

Example (Basic)

This example shows the basic usage of the package: create an recognizer, recognize faces, identify them using few known ones.

package main

import (
	"fmt"
	"log"
	"path/filepath"

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

// Path to directory with models and test images. Here it's assumed it
// points to the <https://github.com/Kagami/go-face-testdata> clone.
const dataDir = "testdata"

// This example shows the basic usage of the package: create an
// recognizer, recognize faces, identify them using few known ones.
func main() {
	// Init the recognizer.
	rec, err := face.NewRecognizer(filepath.Join(dataDir, "models"))
	if err != nil {
		log.Fatalf("Can't init face recognizer: %v", err)
	}
	// Free the resources when you're finished.
	defer rec.Close()

	// Test image with 10 faces.
	testImagePristin := filepath.Join(dataDir, "images", "pristin.jpg")
	// Recognize faces on that image.
	faces, err := rec.RecognizeFile(testImagePristin)
	if err != nil {
		log.Fatalf("Can't recognize: %v", err)
	}
	if len(faces) != 10 {
		log.Fatalf("Wrong number of faces")
	}

	// Fill known samples. In the real world you would use a lot of images
	// for each person to get better identification results but in our
	// example we just get them from one big image.
	var samples []face.Descriptor
	var cats []int32
	for i, f := range faces {
		samples = append(samples, f.Descriptor)
		// Each face is unique on that image so goes to its own category.
		cats = append(cats, int32(i))
	}
	// Name the categories, i.e. people on the image.
	labels := []string{
		"Sungyeon", "Yehana", "Roa", "Eunwoo", "Xiyeon",
		"Kyulkyung", "Nayoung", "Rena", "Kyla", "Yuha",
	}
	// Pass samples to the recognizer.
	rec.SetSamples(samples, cats)

	// Now let's try to identify some not yet known image.
	testImageNayoung := filepath.Join(dataDir, "images", "nayoung.jpg")
	nayoungFace, err := rec.RecognizeSingleFile(testImageNayoung)
	if err != nil {
		log.Fatalf("Can't recognize: %v", err)
	}
	if nayoungFace == nil {
		log.Fatalf("Not a single face on the image")
	}
	catID := rec.Identify(nayoungFace.Descriptor)
	if catID < 0 {
		log.Fatalf("Can't identify")
	}
	// Finally print the identified label. It should be "Nayoung".
	fmt.Println(labels[catID])
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func SquaredEuclideanDistance

func SquaredEuclideanDistance(d1 Descriptor, d2 Descriptor) (sum float64)

Types

type Descriptor

type Descriptor [128]float32

Descriptor holds 128-dimensional feature vector.

type Face

type Face struct {
	Rectangle  image.Rectangle
	Descriptor Descriptor
	Shapes     []image.Point
}

Face holds coordinates and descriptor of the human face.

func New

func New(r image.Rectangle, d Descriptor) Face

New creates new face with the provided parameters.

func NewWithShape

func NewWithShape(r image.Rectangle, s []image.Point, d Descriptor) Face

type ImageLoadError

type ImageLoadError string

An ImageLoadError is returned when provided image file is corrupted.

func (ImageLoadError) Error

func (e ImageLoadError) Error() string

type Recognizer

type Recognizer struct {
	// contains filtered or unexported fields
}

A Recognizer creates face descriptors for provided images and classifies them into categories.

func NewRecognizer

func NewRecognizer(modelDir string) (rec *Recognizer, err error)

NewRecognizer returns a new recognizer interface. modelDir points to directory with shape_predictor_5_face_landmarks.dat and dlib_face_recognition_resnet_model_v1.dat files.

func NewRecognizerWithConfig

func NewRecognizerWithConfig(modelDir string, size int, padding float32, jittering int) (rec *Recognizer, err error)

func NewRecognizerWithModels added in v1.0.4

func NewRecognizerWithModels(modelDir string, shapePredictorFile, resnetFile, cnnFile string) (rec *Recognizer, err error)

NewRecognizerWithModels is like NewRecognizer but lets you choose the shape predictor, descriptor (resnet), and CNN detector model file names (each relative to modelDir) instead of the defaults (shape_predictor_5_face_landmarks.dat, dlib_face_recognition_resnet_model_v1.dat, mmod_human_face_detector.dat). Pass "" for any file to keep its default.

Use "shape_predictor_68_face_landmarks.dat" as shapePredictorFile for full facial contour landmarks (jawline, eyebrows, nose bridge, eyes, lips) instead of the default 5 eye-corner/nose-base points.

func (*Recognizer) AppendSample added in v1.0.2

func (rec *Recognizer) AppendSample(sample Descriptor, cat int32)

AppendSample adds a single known descriptor to the sample set without discarding the ones already present (from a prior SetSamples or AppendSample call). Thread-safe.

func (*Recognizer) Close

func (rec *Recognizer) Close()

Close frees resources taken by the Recognizer. Safe to call multiple times. Don't use Recognizer after close call.

func (*Recognizer) Identify added in v1.1.0

func (rec *Recognizer) Identify(testSample Descriptor) int

Identify returns class ID for the given descriptor. Negative index is returned if no match. Thread-safe.

func (*Recognizer) IdentifyThreshold added in v1.1.0

func (rec *Recognizer) IdentifyThreshold(testSample Descriptor, tolerance float32) int

Same as Identify but allows to specify max distance between faces to consider it a match. Start with 0.6 if not sure.

func (*Recognizer) Recognize

func (rec *Recognizer) Recognize(imgData []byte) (faces []Face, err error)

Recognize returns all faces found on the provided image, sorted from left to right. Empty list is returned if there are no faces, error is returned if there was some error while decoding/processing image. Only JPEG format is currently supported. Thread-safe.

func (*Recognizer) RecognizeCNN

func (rec *Recognizer) RecognizeCNN(imgData []byte) (faces []Face, err error)

func (*Recognizer) RecognizeFile

func (rec *Recognizer) RecognizeFile(imgPath string) (faces []Face, err error)

Same as Recognize but accepts image path instead.

func (*Recognizer) RecognizeFileCNN

func (rec *Recognizer) RecognizeFileCNN(imgPath string) (faces []Face, err error)

func (*Recognizer) RecognizeRaw added in v1.0.7

func (rec *Recognizer) RecognizeRaw(pixels []byte, width, height int) (faces []Face, err error)

RecognizeRaw is like Recognize but takes an already-decoded, tightly packed RGB pixel buffer (3 bytes per pixel, row-major -- i.e. len(pixels) must equal width*height*3) instead of JPEG-encoded bytes, skipping JPEG decode entirely. Thread-safe.

func (*Recognizer) RecognizeRawCNN added in v1.0.7

func (rec *Recognizer) RecognizeRawCNN(pixels []byte, width, height int) (faces []Face, err error)

func (*Recognizer) RecognizeSingle

func (rec *Recognizer) RecognizeSingle(imgData []byte) (face *Face, err error)

RecognizeSingle returns face if it's the only face on the image or nil otherwise. Only JPEG format is currently supported. Thread-safe.

func (*Recognizer) RecognizeSingleCNN

func (rec *Recognizer) RecognizeSingleCNN(imgData []byte) (face *Face, err error)

func (*Recognizer) RecognizeSingleFile

func (rec *Recognizer) RecognizeSingleFile(imgPath string) (face *Face, err error)

Same as RecognizeSingle but accepts image path instead.

func (*Recognizer) RecognizeSingleFileCNN

func (rec *Recognizer) RecognizeSingleFileCNN(imgPath string) (face *Face, err error)

func (*Recognizer) RecognizeSingleRaw added in v1.0.7

func (rec *Recognizer) RecognizeSingleRaw(pixels []byte, width, height int) (face *Face, err error)

RecognizeSingleRaw is like RecognizeSingle but takes a raw pixel buffer -- see RecognizeRaw.

func (*Recognizer) RecognizeSingleRawCNN added in v1.0.7

func (rec *Recognizer) RecognizeSingleRawCNN(pixels []byte, width, height int) (face *Face, err error)

func (*Recognizer) SetSamples

func (rec *Recognizer) SetSamples(samples []Descriptor, cats []int32)

SetSamples sets known descriptors so you can classify the new ones. Thread-safe.

type SerializationError

type SerializationError string

An SerializationError is returned when provided model is corrupted.

func (SerializationError) Error

func (e SerializationError) Error() string

type UnknownError

type UnknownError string

An UnknownError represents some nonclassified error.

func (UnknownError) Error

func (e UnknownError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL