mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
CI: Apply Go more linter recommendations to "ai/face" package #5330
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
59c8754ca3
commit
b954de52e9
7 changed files with 36 additions and 19 deletions
|
|
@ -3,7 +3,7 @@ package face
|
|||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
_ "image/jpeg"
|
||||
_ "image/jpeg" // register JPEG decoder for pigo image decoding
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
|
|
@ -146,7 +146,7 @@ func (d *pigoDetector) Detect(fileName string) (faces []pigo.Detection, params p
|
|||
d.angles = append([]float64(nil), DetectionAngles...)
|
||||
}
|
||||
|
||||
file, err := os.Open(fileName)
|
||||
file, err := os.Open(fileName) //nolint:gosec // fileName comes from caller; reading local images is expected
|
||||
|
||||
if err != nil {
|
||||
return faces, params, err
|
||||
|
|
@ -169,11 +169,12 @@ func (d *pigoDetector) Detect(fileName string) (faces []pigo.Detection, params p
|
|||
|
||||
var maxSize int
|
||||
|
||||
if cols < 20 || rows < 20 || cols < d.minSize || rows < d.minSize {
|
||||
switch {
|
||||
case cols < 20 || rows < 20 || cols < d.minSize || rows < d.minSize:
|
||||
return faces, params, fmt.Errorf("image size %dx%d is too small", cols, rows)
|
||||
} else if cols < rows {
|
||||
case cols < rows:
|
||||
maxSize = cols - 4
|
||||
} else {
|
||||
default:
|
||||
maxSize = rows - 4
|
||||
}
|
||||
|
||||
|
|
@ -370,8 +371,8 @@ func (d *pigoDetector) Faces(det []pigo.Detection, params pigo.CascadeParams, fi
|
|||
|
||||
// Create face.
|
||||
f := Face{
|
||||
Rows: params.ImageParams.Rows,
|
||||
Cols: params.ImageParams.Cols,
|
||||
Rows: params.Rows,
|
||||
Cols: params.Cols,
|
||||
Score: int(face.Q),
|
||||
Area: faceCoord,
|
||||
Eyes: eyesCoords,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
// Embedding represents a face embedding.
|
||||
type Embedding []float64
|
||||
|
||||
// NullEmbedding is a zero-value placeholder embedding used when no data is available.
|
||||
var NullEmbedding = make(Embedding, 512)
|
||||
|
||||
// NewEmbedding creates a new embedding from an inference result.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@ import (
|
|||
"math/rand/v2"
|
||||
)
|
||||
|
||||
// Kind identifies the type of embedding.
|
||||
type Kind int
|
||||
|
||||
const (
|
||||
// RegularFace represents a standard face embedding.
|
||||
RegularFace Kind = iota + 1
|
||||
// ChildrenFace represents a child face embedding.
|
||||
ChildrenFace
|
||||
// BackgroundFace represents non-face/background embeddings.
|
||||
BackgroundFace
|
||||
// AmbiguousFace represents embeddings that should be treated as uncertain.
|
||||
AmbiguousFace
|
||||
)
|
||||
|
||||
|
|
@ -20,7 +25,7 @@ func RandomDist() float64 {
|
|||
|
||||
// RandomFloat64 adds a random distance offset to a float64.
|
||||
func RandomFloat64(f, d float64) float64 {
|
||||
return f + (rand.Float64()-0.5)*d
|
||||
return f + (rand.Float64()-0.5)*d //nolint:gosec // pseudo-random is sufficient for test fixtures
|
||||
}
|
||||
|
||||
// RandomEmbeddings returns random embeddings for testing.
|
||||
|
|
@ -76,7 +81,7 @@ func RandomChildrenEmbedding() (result Embedding) {
|
|||
}
|
||||
|
||||
d := 0.1 / 512.0
|
||||
n := rand.IntN(len(Children))
|
||||
n := rand.IntN(len(Children)) //nolint:gosec // deterministic seeding not required for synthetic embeddings
|
||||
e := Children[n].Embedding
|
||||
|
||||
for i := range result {
|
||||
|
|
@ -97,7 +102,7 @@ func RandomBackgroundEmbedding() (result Embedding) {
|
|||
}
|
||||
|
||||
d := 0.1 / 512.0
|
||||
n := rand.IntN(len(Background))
|
||||
n := rand.IntN(len(Background)) //nolint:gosec // deterministic seeding not required for synthetic embeddings
|
||||
e := Background[n].Embedding
|
||||
|
||||
for i := range result {
|
||||
|
|
|
|||
|
|
@ -7,12 +7,17 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// EngineName identifies a face detection engine implementation.
|
||||
type EngineName = string
|
||||
|
||||
const (
|
||||
// EngineAuto selects the default engine based on availability.
|
||||
EngineAuto EngineName = "auto"
|
||||
// EnginePigo enables the built-in Pigo cascade detector.
|
||||
EnginePigo EngineName = "pigo"
|
||||
// EngineONNX enables the ONNX runtime-powered SCRFD detector.
|
||||
EngineONNX EngineName = "onnx"
|
||||
// EngineNone disables face detection.
|
||||
EngineNone EngineName = "none"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/jpeg"
|
||||
_ "image/jpeg" // register JPEG decoder for ONNX engine input
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -17,7 +17,7 @@ import (
|
|||
onnxruntime "github.com/yalue/onnxruntime_go"
|
||||
)
|
||||
|
||||
// ONNXOptions configures how the ONNX runtime-backed detector is initialised.
|
||||
// ONNXOptions configures how the ONNX runtime-backed detector is initialized.
|
||||
type ONNXOptions struct {
|
||||
ModelPath string
|
||||
LibraryPath string
|
||||
|
|
@ -27,6 +27,7 @@ type ONNXOptions struct {
|
|||
}
|
||||
|
||||
const (
|
||||
// DefaultONNXModelFilename is the bundled ONNX model name used when none is provided.
|
||||
DefaultONNXModelFilename = "scrfd.onnx"
|
||||
onnxDefaultScoreThreshold = 0.50
|
||||
onnxDefaultNMSThreshold = 0.40
|
||||
|
|
@ -307,7 +308,7 @@ func (o *onnxEngine) Close() error {
|
|||
|
||||
// Detect identifies faces in the provided image using the ONNX runtime session.
|
||||
func (o *onnxEngine) Detect(fileName string, findLandmarks bool, minSize int) (Faces, error) {
|
||||
file, err := os.Open(fileName)
|
||||
file, err := os.Open(fileName) //nolint:gosec // fileName provided by caller; reading local images is required for detection
|
||||
if err != nil {
|
||||
return Faces{}, err
|
||||
}
|
||||
|
|
@ -437,9 +438,9 @@ func (o *onnxEngine) buildBlob(img image.Image) ([]float32, float32, error) {
|
|||
var r, g, b float32
|
||||
if x < newWidth && y < newHeight {
|
||||
cr, cg, cb, _ := resized.At(x, y).RGBA()
|
||||
r = float32(uint8(cr >> 8))
|
||||
g = float32(uint8(cg >> 8))
|
||||
b = float32(uint8(cb >> 8))
|
||||
r = float32((cr >> 8) & 0xff)
|
||||
g = float32((cg >> 8) & 0xff)
|
||||
b = float32((cb >> 8) & 0xff)
|
||||
}
|
||||
|
||||
blob[idx] = (r - onnxInputMean) / onnxInputStd
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ func (m *Model) Detect(fileName string, minSize int, cacheCrop bool, expected in
|
|||
return faces, nil
|
||||
}
|
||||
|
||||
// Init initialises tensorflow models if not disabled
|
||||
// Init initializes tensorflow models if not disabled.
|
||||
func (m *Model) Init() (err error) {
|
||||
if m.disabled {
|
||||
return nil
|
||||
|
|
@ -139,6 +139,10 @@ func (m *Model) Run(img image.Image) Embeddings {
|
|||
// TODO: pre-whiten image as in facenet
|
||||
|
||||
trainPhaseBoolTensor, err := tf.NewTensor(false)
|
||||
if err != nil {
|
||||
log.Errorf("faces: failed to create phase_train tensor: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
output, err := m.model.Session.Run(
|
||||
map[tf.Output]*tf.Tensor{
|
||||
|
|
|
|||
|
|
@ -123,11 +123,11 @@ func TestNet(t *testing.T) {
|
|||
t.Logf("Dist for %d %d (faces are %d %d) is %f", i, j, faceIndexToPersonID[i], faceIndexToPersonID[j], dist)
|
||||
if faceIndexToPersonID[i] == faceIndexToPersonID[j] {
|
||||
if dist < 1.21 {
|
||||
correct += 1
|
||||
correct++
|
||||
}
|
||||
} else {
|
||||
if dist >= 1.21 {
|
||||
correct += 1
|
||||
correct++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue