verify faces from camera

This commit is contained in:
Kostas Drakontidis
2026-05-29 23:46:15 +03:00
parent 1ac5ddbf33
commit 7e1a7d0eca
5 changed files with 244 additions and 0 deletions
@@ -0,0 +1,55 @@
ARG BUILDPLATFORM=linux/amd64
FROM --platform=$BUILDPLATFORM golang:1.26-bookworm AS builder
# Install dlib deps
RUN dpkg --add-architecture arm64 && \
apt-get update && apt-get install -y \
g++-aarch64-linux-gnu \
libdlib-dev:arm64 \
libjpeg-dev:arm64 \
libblas-dev:arm64 \
libopenblas-dev:arm64 \
libatlas-base-dev:arm64 \
liblapack-dev:arm64 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Compiler wrapper for arm64
RUN printf '#!/bin/bash\nexec aarch64-linux-gnu-gcc "${@//-march=native/-march=armv8-a}"\n' \
> /usr/local/bin/arm64-gcc && \
printf '#!/bin/bash\nexec aarch64-linux-gnu-g++ "${@//-march=native/-march=armv8-a}"\n' \
> /usr/local/bin/arm64-g++ && \
chmod +x /usr/local/bin/arm64-gcc /usr/local/bin/arm64-g++
# Compiler variables for cross-compile to arm64
ENV CGO_ENABLED=1 \
GOOS=linux \
GOARCH=arm64 \
CC=/usr/local/bin/arm64-gcc \
CXX=/usr/local/bin/arm64-g++ \
PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
COPY go.mod go.sum ./
RUN go mod download
# Pre-compile all CGO deps into cache
RUN go build -v ./... 2>/dev/null || \
go list -m all && \
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
go build -gcflags="-trimpath" -v \
$(go list -f '{{if .CgoFiles}}{{.ImportPath}}{{end}}' ./...) 2>/dev/null; exit 0
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
go mod download
COPY . .
# Build - reuse CGO cache
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
go build -o cameraSession
FROM scratch AS export
COPY --from=builder /app/cameraSession /cameraSession
@@ -0,0 +1,10 @@
all:
docker buildx build --platform linux/amd64 --cache-from type=local,src=./.docker-cache --cache-to type=local,dest=./.docker-cache --target export --output type=local,dest=./output .
models:
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
clean:
rm -r output/
@@ -0,0 +1,163 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"os"
"sync"
"github.com/Kagami/go-face"
"github.com/blackjack/webcam"
)
type InputPacket struct {
UUID string `json:"uuid"`
Descriptor face.Descriptor `json:"descriptor"`
}
type ResultPacket struct {
UUID string `json:"uuid"`
Verified bool `json:"verified"`
}
var (
rec *face.Recognizer
mtx sync.Mutex
results = make([]ResultPacket, 0)
)
func captureCameraFrame() ([]byte, error) {
cam, err := webcam.Open("/dev/video0")
if err != nil {
return nil, err
}
defer cam.Close()
format := webcam.PixelFormat(uint32(0x47504a4d)) // MJPG
_, _, _, err = cam.SetImageFormat(format, 1280, 720)
if err != nil {
return nil, fmt.Errorf("camera format error: %v", err)
}
err = cam.StartStreaming()
if err != nil {
return nil, err
}
defer cam.StopStreaming()
for {
err = cam.WaitForFrame(5)
if err != nil {
continue
}
frame, err := cam.ReadFrame()
if err != nil {
return nil, err
}
if len(frame) != 0 {
result := make([]byte, len(frame))
copy(result, frame)
return result, nil
}
}
}
// verfication thread
func verifyFace(packet InputPacket, descriptors []face.Descriptor, wg *sync.WaitGroup) {
defer wg.Done()
const threshold = 0.35
verified := false
for _, descriptor := range descriptors {
distance := face.SquaredEuclideanDistance(packet.Descriptor, descriptor)
if distance < threshold {
verified = true
break
}
}
mtx.Lock()
results = append(results, ResultPacket{
UUID: packet.UUID,
Verified: verified,
})
mtx.Unlock()
}
func main() {
var err error
// initialize recognizer
rec, err = face.NewRecognizer("./models")
if err != nil {
log.Fatalf("failed to initialize recognizer: %v", err)
}
defer rec.Close()
// capture image
cameraImage, err := captureCameraFrame()
if err != nil {
log.Fatalf("failed to read camera: %v", err)
}
// recognize faces from camera input
faces, err := rec.RecognizeCNN(cameraImage)
if err != nil {
log.Fatalf("recognition error: %v", err)
}
if len(faces) == 0 {
fmt.Println("no face found.")
return
}
var descriptors []face.Descriptor
// extract descriptors to array
for _, f := range faces {
descriptors = append(descriptors, f.Descriptor)
}
// read stdin
scanner := bufio.NewScanner(os.Stdin)
var wg sync.WaitGroup
for scanner.Scan() {
line := scanner.Bytes()
var packet InputPacket
err := json.Unmarshal(line, &packet)
if err != nil {
log.Printf("invalid json packet: %v", err)
continue
}
wg.Add(1)
go verifyFace(packet, descriptors, &wg)
}
if err := scanner.Err(); err != nil {
log.Printf("stdin error: %v", err)
}
// wait threads to finish
wg.Wait()
// print results to stdout
output, err := json.Marshal(results)
if err != nil {
log.Fatalf("failed to marshal results: %v", err)
}
fmt.Println(string(output))
}
@@ -0,0 +1,10 @@
module OfficeSense/cameraSession
go 1.26.3
require (
github.com/Kagami/go-face v0.0.0-20210630145111-0c14797b4d0e
github.com/blackjack/webcam v0.6.1
)
require golang.org/x/sys v0.14.0 // indirect
@@ -0,0 +1,6 @@
github.com/Kagami/go-face v0.0.0-20210630145111-0c14797b4d0e h1:lqIUFzxaqyYqUn4MhzAvSAh4wIte/iLNcIEWxpT/qbc=
github.com/Kagami/go-face v0.0.0-20210630145111-0c14797b4d0e/go.mod h1:9wdDJkRgo3SGTcFwbQ7elVIQhIr2bbBjecuY7VoqmPU=
github.com/blackjack/webcam v0.6.1 h1:K0T6Q0zto23U99gNAa5q/hFoye6uGcKr2aE6hFoxVoE=
github.com/blackjack/webcam v0.6.1/go.mod h1:zs+RkUZzqpFPHPiwBZ6U5B34ZXXe9i+SiHLKnnukJuI=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=