leandroveronezi
go-recognizer
Go

Face detection and recognition using golang

Last updated Jul 21, 2026
78
Stars
12
Forks
0
Issues
0
Stars/day
Attention Score
33
Language breakdown
Go 100.0%
β–Έ Files click to expand
README

go-recognizer

Face detection and recognition for Go, built on top of dlib via go-face. It wraps the lower-level go-face API into a small, batteries-included Recognizer type: load a photo, find faces, identify them against a labeled dataset, and draw the results back onto the image β€” in a handful of method calls.

CI Go Reference Latest tag MIT Licensed

[!NOTE]
dlib's face pipeline (shape-predictor landmarks + a custom ResNet-29
metric-learning descriptor) predates most of the last decade's face
recognition research -- even the newest dlib-compatible alternatives
(2021/2024) are incremental tweaks to that same older approach, not a
leap to what's state of the art today. For actively-developed, modern
models (YuNet, RetinaFace, ArcFace-family, SFace, GhostFaceNet...), see
go-onnxface.

Features

  • Detection β€” find one or many faces in an image, sorted left to right.
  • Recognition β€” identify detected faces against a dataset of known people.
  • Incremental dataset updates β€” AddImageToDataset keeps the identifier in
sync as each face is added; no need to rebuild the whole sample set.
  • Match distance/confidence β€” Identify/IdentifyMultiples return the
matched face's Distance and a normalized Confidence score, not just an ID.
  • Landmarks β€” detected faces carry their Shapes (facial landmark
points). Defaults to 5 points (eye corners, nose base); set rec.Model.Landmark before Init to opt into the 68-point model for full facial contour (jawline, eyebrows, nose bridge, eyes, lips).
  • Swappable model files β€” rec.Model.Landmark/Descriptor/CNN let
you point Init at differently-named or fine-tuned model files instead of go-face's defaults.
  • Configurable matching β€” tune the distance Tolerance used to accept a match.
  • CNN or HOG detector β€” trade speed for accuracy with UseCNN.
  • Grayscale preprocessing β€” optional, via UseGray.
  • Beyond JPEG input β€” go-face's own file loader only understands JPEG,
but with the default UseGray = true, go-recognizer decodes the source image with Go's standard image package first (JPEG and PNG are supported out of the box) and re-encodes it before handing it to go-face, so PNG sources work without extra steps. This doesn't apply when UseGray = false: the original file is passed straight through, so it must already be a JPEG.
  • Dataset persistence β€” save/load known faces to/from a JSON file.
  • Drawing helpers β€” annotate the source image with boxes, labels, and
landmark points for the faces found.
  • Typed errors β€” AddImageToDataset/RecognizeSingle/Identify/
LoadDataset return sentinel errors (ErrNoFace, ErrNotSingleFace, ErrNoMatch, ErrDatasetFileNotFound) checkable with errors.Is, instead of matching on error text. See Errors below.

Requirements

go-recognizer depends on go-face, which in turn requires dlib (>= 19.10) and the libjpeg development headers to compile.

go-face uses cgo, so CGO_ENABLED=1 is required at build time (this is the default on most setups, but some environments/CI images turn it off). If you see errors like undefined: face.NewRecognizer or undefined: face.Descriptor instead of a compiler error, that's almost always CGO being disabled β€” run go env -w CGO_ENABLED=1 or set the env var for the build.

Ubuntu 18.10+, Debian sid

Latest versions of Ubuntu and Debian provide a 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.

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

Other systems

Try installing dlib/libjpeg with your distribution's package manager, or compile dlib from source. go-face won't work with old dlib packages such as libdlib18. If your system isn't covered here, open an issue with the distribution/version and we'll try to help.

Docker

examples/Dockerfile builds dlib from source (Alpine has no dlib package) and compiles the detection example against it, ending with a ~50MB runtime image. Useful as a reference for containerized builds, and for the compiler/CMake compatibility patches it applies -- dlib's released source doesn't build out of the box with GCC 15+ or CMake 4.x.

Installation

go get github.com/leandroveronezi/go-recognizer
import "github.com/leandroveronezi/go-recognizer"

Models

shapepredictor5facelandmarks.dat, mmodhumanface_detector.dat and dlibfacerecognitionresnetmodel_v1.dat are required at runtime. Download them from the dlib-models repo:

mkdir models && cd models
wget https://github.com/davisking/dlib-models/raw/master/shapepredictor5facelandmarks.dat.bz2
bunzip2 shapepredictor5facelandmarks.dat.bz2
wget https://github.com/davisking/dlib-models/raw/master/dlibfacerecognitionresnetmodel_v1.dat.bz2
bunzip2 dlibfacerecognitionresnetmodel_v1.dat.bz2
wget https://github.com/davisking/dlib-models/raw/master/mmodhumanface_detector.dat.bz2
bunzip2 mmodhumanface_detector.dat.bz2

Optional: shapepredictor68facelandmarks.dat for full facial contour landmarks (see rec.Model.Landmark below). It's a much larger download (~95MB uncompressed, vs ~9MB for the 5-point model), so it's opt-in rather than required.

wget https://github.com/davisking/dlib-models/raw/master/shapepredictor68facelandmarks.dat.bz2
bunzip2 shapepredictor68facelandmarks.dat.bz2

Examples

Runnable versions of the examples below live in examples/, one per subfolder. Run them from inside examples/ so the relative fotos/models paths resolve, e.g. cd examples && go run ./detection.

Face detection

package main

import ( "fmt" "path/filepath"

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

const fotosDir = "fotos" const dataDir = "models"

func main() {

rec := recognizer.Recognizer{} err := rec.Init(dataDir)

if err != nil { fmt.Println(err) return }

rec.Tolerance = 0.4 rec.UseGray = true rec.UseCNN = false defer rec.Close()

faces, err := rec.RecognizeMultiples(filepath.Join(fotosDir, "elenco3.jpg"))

if err != nil { fmt.Println(err) return }

img, err := rec.DrawFaces2(filepath.Join(fotosDir, "elenco3.jpg"), faces)

if err != nil { fmt.Println(err) return }

rec.SaveImage("faces2.jpg", img)

}

Face detection result

Face recognition

package main

import ( "errors" "fmt" "path/filepath"

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

const fotosDir = "fotos" const dataDir = "models"

func addFile(rec *recognizer.Recognizer, Path, Id string) {

err := rec.AddImageToDataset(Path, Id)

switch { case errors.Is(err, recognizer.ErrNoFace), errors.Is(err, recognizer.ErrNotSingleFace): fmt.Printf("%s: not exactly one face, skipping\n", Path) case err != nil: fmt.Println(err) }

}

func main() {

rec := recognizer.Recognizer{} err := rec.Init(dataDir)

if err != nil { fmt.Println(err) return }

rec.Tolerance = 0.4 rec.UseGray = true rec.UseCNN = false defer rec.Close()

addFile(&rec, filepath.Join(fotosDir, "amy.jpg"), "Amy") addFile(&rec, filepath.Join(fotosDir, "bernadette.jpg"), "Bernadette") addFile(&rec, filepath.Join(fotosDir, "howard.jpg"), "Howard") addFile(&rec, filepath.Join(fotosDir, "penny.jpg"), "Penny") addFile(&rec, filepath.Join(fotosDir, "raj.jpg"), "Raj") addFile(&rec, filepath.Join(fotosDir, "sheldon.jpg"), "Sheldon") addFile(&rec, filepath.Join(fotosDir, "leonard.jpg"), "Leonard")

// No rec.SetSamples() call needed here: AddImageToDataset already // keeps the identifier in sync incrementally as each face is added.

faces, err := rec.IdentifyMultiples(filepath.Join(fotosDir, "elenco3.jpg"))

if err != nil { fmt.Println(err) return }

for _, f := range faces { fmt.Printf("%s: distance=%.4f confidence=%.2f%%\n", f.Id, f.Distance, f.Confidence*100) }

img, err := rec.DrawFaces(filepath.Join(fotosDir, "elenco3.jpg"), faces)

if err != nil { fmt.Println(err) return }

rec.SaveImage("faces.jpg", img)

}

Face recognition result

Face landmarks

package main

import ( "errors" "fmt" "path/filepath"

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

const fotosDir = "fotos" const dataDir = "models"

func main() {

rec := recognizer.Recognizer{} err := rec.Init(dataDir)

if err != nil { fmt.Println(err) return }

rec.Tolerance = 0.4 rec.UseGray = true rec.UseCNN = false defer rec.Close()

f, err := rec.RecognizeSingle(filepath.Join(fotosDir, "amy.jpg"))

switch { case errors.Is(err, recognizer.ErrNotSingleFace): fmt.Println("amy.jpg doesn't have exactly one face") return case err != nil: fmt.Println(err) return }

fmt.Printf("found %d landmark points\n", len(f.Shapes))

img, err := rec.DrawLandmarks(filepath.Join(fotosDir, "amy.jpg"), []face.Face{f})

if err != nil { fmt.Println(err) return }

rec.SaveImage("landmarks.jpg", img)

}

Face landmarks result

This uses the default 5-point model. For the full facial contour, download shapepredictor68facelandmarks.dat (see Models) and set rec.Model.Landmark before Init:

rec := recognizer.Recognizer{}
rec.Model.Landmark = "shapepredictor68facelandmarks.dat"
err := rec.Init(dataDir)

rec.Model.Descriptor and rec.Model.CNN work the same way, for the face-descriptor (ResNet) and CNN detector model files respectively β€” useful if you're using differently-named or fine-tuned dlib models. All three must be set before calling Init; they're read once, at load time.

Errors

The "expected" failure conditions -- no face detected, more than one face detected, no dataset match, a missing dataset file -- are exposed as sentinel errors, so callers can branch on them with errors.Is instead of matching on the error message text (which isn't part of the API contract and may change):

faces, err := rec.Identify(path)
switch {
case errors.Is(err, recognizer.ErrNotSingleFace):
	// the image doesn't have exactly one face
case errors.Is(err, recognizer.ErrNoMatch):
	// no Dataset entry within Tolerance
case err != nil:
	// something else went wrong (I/O, decode, ...)
}

| Error | Returned by | |-------|-------------| | ErrNoFace | AddImageToDataset, when the image has no detected face | | ErrNotSingleFace | AddImageToDataset, RecognizeSingle, Identify, when the image has more than one detected face (or, for RecognizeSingle/Identify, doesn't have exactly one) | | ErrNoMatch | Identify, when the face doesn't match any Dataset entry within Tolerance | | ErrDatasetFileNotFound | LoadDataset, when Path doesn't exist |

Any other error (I/O, image decoding, etc.) is wrapped with %w, so errors.Unwrap/errors.As still reach the underlying cause.

Contributing

Issues and pull requests are welcome. If you're reporting a build problem, please include your OS/distribution, Go version, and the full compiler output.

License

MIT

πŸ”— More in this category

Β© 2026 GitRepoTrend Β· leandroveronezi/go-recognizer Β· Updated daily from GitHub