From 7e1a7d0ecad447614c56e2faeeecc956b6c213f9 Mon Sep 17 00:00:00 2001 From: Kostas Drakontidis Date: Fri, 29 May 2026 23:46:15 +0300 Subject: [PATCH] verify faces from camera --- .../cameraSession/Dockerfile | 55 ++++++ src/officesense_vision/cameraSession/Makefile | 10 ++ .../cameraSession/cameraSession.go | 163 ++++++++++++++++++ src/officesense_vision/cameraSession/go.mod | 10 ++ src/officesense_vision/cameraSession/go.sum | 6 + 5 files changed, 244 insertions(+) create mode 100644 src/officesense_vision/cameraSession/Dockerfile create mode 100644 src/officesense_vision/cameraSession/Makefile create mode 100644 src/officesense_vision/cameraSession/cameraSession.go create mode 100644 src/officesense_vision/cameraSession/go.mod create mode 100644 src/officesense_vision/cameraSession/go.sum diff --git a/src/officesense_vision/cameraSession/Dockerfile b/src/officesense_vision/cameraSession/Dockerfile new file mode 100644 index 0000000..c876026 --- /dev/null +++ b/src/officesense_vision/cameraSession/Dockerfile @@ -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 \ No newline at end of file diff --git a/src/officesense_vision/cameraSession/Makefile b/src/officesense_vision/cameraSession/Makefile new file mode 100644 index 0000000..f87430d --- /dev/null +++ b/src/officesense_vision/cameraSession/Makefile @@ -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/ diff --git a/src/officesense_vision/cameraSession/cameraSession.go b/src/officesense_vision/cameraSession/cameraSession.go new file mode 100644 index 0000000..2f64c24 --- /dev/null +++ b/src/officesense_vision/cameraSession/cameraSession.go @@ -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)) +} diff --git a/src/officesense_vision/cameraSession/go.mod b/src/officesense_vision/cameraSession/go.mod new file mode 100644 index 0000000..0dfa768 --- /dev/null +++ b/src/officesense_vision/cameraSession/go.mod @@ -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 diff --git a/src/officesense_vision/cameraSession/go.sum b/src/officesense_vision/cameraSession/go.sum new file mode 100644 index 0000000..13712c1 --- /dev/null +++ b/src/officesense_vision/cameraSession/go.sum @@ -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=