41b252d820
Adds two unofficial env variables so advanced users can experiment: 1. PHOTOPRISM_FACE_KIDS_DIST=0.6950 (range: 0.1-1.5, -1 to disable) 2. PHOTOPRISM_FACE_IGNORE_DIST=0.86 (range: 0.1-1.5, -1 to disable)
115 lines
2.3 KiB
Go
115 lines
2.3 KiB
Go
package photoprism
|
|
|
|
import (
|
|
"github.com/montanaflynn/stats"
|
|
"github.com/photoprism/photoprism/internal/query"
|
|
)
|
|
|
|
// Stats shows statistics on face embeddings.
|
|
func (w *Faces) Stats() (err error) {
|
|
if embeddings, err := query.Embeddings(true, false, 0, 0); err != nil {
|
|
return err
|
|
} else if samples := len(embeddings); samples == 0 {
|
|
log.Infof("faces: found no samples")
|
|
} else {
|
|
log.Infof("faces: computing distance of %d samples", samples)
|
|
|
|
distMin := make([]float64, samples)
|
|
distMax := make([]float64, samples)
|
|
|
|
for i := 0; i < samples; i++ {
|
|
min := -1.0
|
|
max := -1.0
|
|
|
|
for j := 0; j < samples; j++ {
|
|
if i == j {
|
|
continue
|
|
}
|
|
|
|
d := embeddings[i].Dist(embeddings[j])
|
|
|
|
if min < 0 || d < min {
|
|
min = d
|
|
}
|
|
|
|
if max < 0 || d > max {
|
|
max = d
|
|
}
|
|
}
|
|
|
|
distMin[i] = min
|
|
distMax[i] = max
|
|
}
|
|
|
|
minMedian, _ := stats.Median(distMin)
|
|
minMin, _ := stats.Min(distMin)
|
|
minMax, _ := stats.Max(distMin)
|
|
|
|
log.Infof("faces: min Ø %f < median %f < %f", minMin, minMedian, minMax)
|
|
|
|
maxMedian, _ := stats.Median(distMax)
|
|
maxMin, _ := stats.Min(distMax)
|
|
maxMax, _ := stats.Max(distMax)
|
|
|
|
log.Infof("faces: max Ø %f < median %f < %f", maxMin, maxMedian, maxMax)
|
|
}
|
|
|
|
if faces, err := query.Faces(true, false, false); err != nil {
|
|
log.Errorf("faces: %s", err)
|
|
} else if samples := len(faces); samples > 0 {
|
|
log.Infof("faces: computing distance of faces matching to the same person")
|
|
|
|
dist := make(map[string][]float64)
|
|
|
|
for i := 0; i < samples; i++ {
|
|
f1 := faces[i]
|
|
|
|
e1 := f1.Embedding()
|
|
min := -1.0
|
|
max := -1.0
|
|
|
|
if k, ok := dist[f1.SubjUID]; ok {
|
|
min = k[0]
|
|
max = k[1]
|
|
}
|
|
|
|
for j := 0; j < samples; j++ {
|
|
if i == j {
|
|
continue
|
|
}
|
|
|
|
f2 := faces[j]
|
|
|
|
if f1.SubjUID != f2.SubjUID {
|
|
continue
|
|
}
|
|
|
|
d := e1.Dist(f2.Embedding())
|
|
|
|
if min < 0 || d < min {
|
|
min = d
|
|
}
|
|
|
|
if max < 0 || d > max {
|
|
max = d
|
|
}
|
|
}
|
|
|
|
if max > 0 {
|
|
dist[f1.SubjUID] = []float64{min, max}
|
|
}
|
|
}
|
|
|
|
if l := len(dist); l == 0 {
|
|
log.Infof("faces: analyzed %d clusters, found no matches", samples)
|
|
} else {
|
|
log.Infof("faces: %d faces match to the same person", l)
|
|
}
|
|
|
|
for subj, d := range dist {
|
|
log.Infof("faces: %s Ø min %f, max %f", subj, d[0], d[1])
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|