recognizer

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: MIT Imports: 18 Imported by: 3

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

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 updatesAddImageToDataset keeps the identifier in sync as each face is added; no need to rebuild the whole sample set.
  • Match distance/confidenceIdentify/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 filesrec.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 errorsAddImageToDataset/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.

  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 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

shape_predictor_5_face_landmarks.dat, mmod_human_face_detector.dat and dlib_face_recognition_resnet_model_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/shape_predictor_5_face_landmarks.dat.bz2
bunzip2 shape_predictor_5_face_landmarks.dat.bz2
wget https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2
bunzip2 dlib_face_recognition_resnet_model_v1.dat.bz2
wget https://github.com/davisking/dlib-models/raw/master/mmod_human_face_detector.dat.bz2
bunzip2 mmod_human_face_detector.dat.bz2

Optional: shape_predictor_68_face_landmarks.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/shape_predictor_68_face_landmarks.dat.bz2
bunzip2 shape_predictor_68_face_landmarks.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 (
	"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)

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

}

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 (
	"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"))

	if 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 shape_predictor_68_face_landmarks.dat (see Models) and set rec.Model.Landmark before Init:

rec := recognizer.Recognizer{}
rec.Model.Landmark = "shape_predictor_68_face_landmarks.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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoFace is returned when an image has no detected face, where an
	// operation requires at least one.
	ErrNoFace = errors.New("not a face on the image")
	// ErrNotSingleFace is returned when an image doesn't have exactly
	// one detected face, where an operation requires exactly one.
	ErrNotSingleFace = errors.New("not a single face on the image")
	// ErrNoMatch is returned by Identify when the descriptor doesn't
	// match any Dataset entry within Tolerance.
	ErrNoMatch = errors.New("can't identify")
	// ErrDatasetFileNotFound is returned by LoadDataset when Path
	// doesn't exist.
	ErrDatasetFileNotFound = errors.New("file not found")
)

Sentinel errors for the "expected" failure conditions -- check for these with errors.Is instead of matching on the error message text, which isn't part of the API contract and may change.

Functions

This section is empty.

Types

type Data

type Data struct {
	Id         string
	Descriptor goFace.Descriptor
}

Data descriptor of the human face.

type Face

type Face struct {
	Data
	Rectangle image.Rectangle
	// Shapes holds the facial landmark points found by the shape predictor
	// model (5 points with the default shape_predictor_5_face_landmarks.dat).
	Shapes []image.Point
	// Distance is the squared Euclidean distance between this face's
	// descriptor and the matched Dataset entry's descriptor. Only set by
	// Identify/IdentifyMultiples; zero otherwise.
	Distance float64
	// Confidence is a convenience score in [0,1], normalized as
	// 1-Distance/Tolerance -- not a calibrated probability. Only set by
	// Identify/IdentifyMultiples; zero otherwise.
	Confidence float64
}

Face holds coordinates and descriptor of the human face.

type ModelFiles added in v1.0.6

type ModelFiles struct {
	// Landmark is the shape predictor file name. Empty (the default)
	// uses go-face's shape_predictor_5_face_landmarks.dat, which returns
	// 5 landmark points (eye corners and nose base) in Face.Shapes. Set
	// to "shape_predictor_68_face_landmarks.dat" for full facial contour
	// landmarks (jawline, eyebrows, nose bridge, eyes, lips) instead --
	// it's a much larger download and slightly slower per face, so only
	// opt in if you need the extra landmark detail.
	Landmark string
	// Descriptor is the face-descriptor (ResNet) model file name. Empty
	// uses go-face's default dlib_face_recognition_resnet_model_v1.dat.
	Descriptor string
	// CNN is the CNN face detector model file name, used when UseCNN is
	// true. Empty uses go-face's default mmod_human_face_detector.dat.
	CNN string
}

ModelFiles selects non-default model file names, all resolved relative to the modelDir passed to Init. Set fields before calling Init; they have no effect afterward, since model loading happens inside Init.

type Recognizer

type Recognizer struct {
	Tolerance float32

	UseCNN  bool
	UseGray bool
	// Model selects non-default model file names. Set before Init.
	Model ModelFiles
	// Dataset holds the known face samples. Mutate it only through
	// AddImageToDataset (keeps the classifier in sync automatically) or
	// LoadDataset (call SetSamples afterward -- see SetSamples).
	Dataset []Data
	// contains filtered or unexported fields
}

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

func (*Recognizer) AddImageToDataset

func (_this *Recognizer) AddImageToDataset(Path string, Id string) error

AddImageToDataset add a sample image to the dataset.

Returns ErrNoFace if the image has no detected face, or ErrNotSingleFace if it has more than one -- check with errors.Is.

The new entry is appended to the underlying classifier immediately (via goFace.AppendSample), so it's classifiable right away -- no need to call SetSamples afterward. SetSamples is still required after LoadDataset or after mutating Dataset directly, since those don't go through this incremental path.

func (*Recognizer) Close

func (_this *Recognizer) Close()

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

func (*Recognizer) DownloadModels added in v1.1.0

func (_this *Recognizer) DownloadModels(dir string) error

DownloadModels downloads the model files Init requires by default (shape_predictor_5_face_landmarks.dat, dlib_face_recognition_resnet_model_v1.dat, mmod_human_face_detector.dat) from the dlib-models repository into dir, creating dir if it doesn't exist yet.

Any file that already exists in dir is left untouched and not re-downloaded, so it's safe to call this on every startup before Init.

func (*Recognizer) DrawFaces

func (_this *Recognizer) DrawFaces(Path string, F []Face) (image.Image, error)

DrawFaces draws the faces identified in the original image

func (*Recognizer) DrawFaces2

func (_this *Recognizer) DrawFaces2(Path string, F []goFace.Face) (image.Image, error)

DrawFaces2 draws the faces in the original image

func (*Recognizer) DrawLandmarks added in v1.0.5

func (_this *Recognizer) DrawLandmarks(Path string, F []goFace.Face) (image.Image, error)

DrawLandmarks draws the facial landmark points found in the original image.

func (*Recognizer) GrayScale

func (_this *Recognizer) GrayScale(imgSrc image.Image) image.Image

GrayScale Convert an image to grayscale

func (*Recognizer) Identify added in v1.1.0

func (_this *Recognizer) Identify(Path string) ([]Face, error)

Identify returns the single face identified in the image.

Returns ErrNotSingleFace if the image doesn't have exactly one face, or ErrNoMatch if the face doesn't match any Dataset entry within Tolerance -- check with errors.Is.

Matches against the sample set from the most recent SetSamples call, not necessarily the current Dataset -- see SetSamples.

func (*Recognizer) IdentifyMultiples added in v1.1.0

func (_this *Recognizer) IdentifyMultiples(Path string) ([]Face, error)

IdentifyMultiples returns every face identified in the image. Faces with no Dataset entry within Tolerance are skipped -- an empty slice (not an error) is returned if none matched.

Matches against the sample set from the most recent SetSamples call, not necessarily the current Dataset -- see SetSamples.

func (*Recognizer) Init

func (_this *Recognizer) Init(Path string) error

Init initialise a recognizer interface. Set Model before calling Init to choose non-default model files (e.g. Model.Landmark for full facial contour landmarks instead of the default 5 points).

func (*Recognizer) LoadDataset

func (_this *Recognizer) LoadDataset(Path string) error

LoadDataset loads the data from the json file into the Dataset.

Returns ErrDatasetFileNotFound if Path doesn't exist -- check with errors.Is.

Call SetSamples afterward: as with AddImageToDataset, Identify and IdentifyMultiples won't see the loaded entries until SetSamples runs again.

func (*Recognizer) LoadImage

func (_this *Recognizer) LoadImage(Path string) (image.Image, error)

LoadImage Load an image from file

func (*Recognizer) RecognizeMultiples

func (_this *Recognizer) RecognizeMultiples(Path string) ([]goFace.Face, error)

RecognizeMultiples 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.

func (*Recognizer) RecognizeSingle

func (_this *Recognizer) RecognizeSingle(Path string) (goFace.Face, error)

RecognizeSingle returns the face on the image, or ErrNotSingleFace if it doesn't have exactly one -- check with errors.Is.

func (*Recognizer) SaveDataset

func (_this *Recognizer) SaveDataset(Path string) error

SaveDataset saves Dataset data to a json file

func (*Recognizer) SaveImage

func (_this *Recognizer) SaveImage(Path string, Img image.Image) error

SaveImage Save an image to jpeg file

func (*Recognizer) SetSamples

func (_this *Recognizer) SetSamples()

SetSamples rebuilds the classifier's sample set from the entire current Dataset.

AddImageToDataset already keeps the classifier in sync incrementally, so this is only needed after LoadDataset (bulk load) or after mutating Dataset directly -- those don't go through AddImageToDataset's incremental path, so the classifier would otherwise keep matching against a stale or empty sample set.

Jump to

Keyboard shortcuts

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