People: Add face clustering and matching to meta worker #22

This commit is contained in:
Michael Mayer 2021-08-14 14:24:48 +02:00
parent 05daa9f7be
commit 42027962aa
6 changed files with 49 additions and 28 deletions

4
go.mod
View file

@ -63,9 +63,9 @@ require (
github.com/ugorji/go v1.2.6 // indirect
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6
github.com/urfave/cli v1.22.5
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
gonum.org/v1/gonum v0.9.3 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/photoprism/go-tz.v2 v2.1.1

4
go.sum
View file

@ -314,6 +314,8 @@ golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -378,6 +380,8 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

View file

@ -21,7 +21,7 @@ var FacesCommand = cli.Command{
},
{
Name: "index",
Usage: "Performs face clustering and recognition",
Usage: "Performs face clustering and matching",
Action: facesIndexAction,
},
},
@ -58,7 +58,7 @@ func facesStatsAction(ctx *cli.Context) error {
return nil
}
// facesIndexAction performs face clustering and recognition.
// facesIndexAction performs face clustering and matching.
func facesIndexAction(ctx *cli.Context) error {
start := time.Now()

View file

@ -16,7 +16,7 @@ import (
"github.com/mpraski/clusters"
)
// Faces represents a worker for face clustering and recognition.
// Faces represents a worker for face clustering and matching.
type Faces struct {
conf *config.Config
}
@ -143,19 +143,22 @@ func (w *Faces) Analyze() (err error) {
return nil
}
// Start face clustering and recognition.
// Disabled tests if facial recognition is disabled.
func (w *Faces) Disabled() bool {
return !(w.conf.Experimental() && w.conf.Settings().Features.People)
}
// Start face clustering and matching.
func (w *Faces) Start() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("faces: %s (panic)\nstack: %s", r, debug.Stack())
log.Error(err)
err = fmt.Errorf("%s (panic)\nstack: %s", r, debug.Stack())
log.Errorf("faces: %s", err)
}
}()
if !w.conf.Experimental() {
return fmt.Errorf("faces: experimental features disabled")
} else if !w.conf.Settings().Features.People {
return fmt.Errorf("faces: disabled in settings")
if w.Disabled() {
return fmt.Errorf("facial recognition is disabled")
}
if err := mutex.MainWorker.Start(); err != nil {
@ -266,7 +269,7 @@ func (w *Faces) Start() (err error) {
for _, marker := range markers {
if mutex.MainWorker.Canceled() {
return fmt.Errorf("faces: worker canceled")
return fmt.Errorf("worker canceled")
}
var faceId string

View file

@ -32,8 +32,8 @@ func NewMoments(conf *config.Config) *Moments {
func (w *Moments) Start() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("moments: %s (panic)\nstack: %s", r, debug.Stack())
log.Error(err)
err = fmt.Errorf("%s (panic)\nstack: %s", r, debug.Stack())
log.Errorf("moments: %s", err)
}
}()

View file

@ -14,23 +14,23 @@ import (
"github.com/photoprism/photoprism/internal/query"
)
// Meta represents a background metadata maintenance worker.
// Meta represents a background metadata optimization worker.
type Meta struct {
conf *config.Config
}
// NewMeta returns a new background metadata maintenance worker.
// NewMeta returns a new Meta worker.
func NewMeta(conf *config.Config) *Meta {
return &Meta{conf: conf}
}
// originalsPath returns the original media files path as string.
func (worker *Meta) originalsPath() string {
return worker.conf.OriginalsPath()
func (m *Meta) originalsPath() string {
return m.conf.OriginalsPath()
}
// Start starts the metadata worker.
func (worker *Meta) Start(delay time.Duration) (err error) {
// Start metadata optimization routine.
func (m *Meta) Start(delay time.Duration) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("metadata: %s (panic)\nstack: %s", r, debug.Stack())
@ -46,13 +46,14 @@ func (worker *Meta) Start(delay time.Duration) (err error) {
log.Debugf("metadata: starting routine check")
settings := worker.conf.Settings()
settings := m.conf.Settings()
done := make(map[string]bool)
limit := 50
offset := 0
optimized := 0
// Run index optimization.
for {
photos, err := query.PhotosCheck(limit, offset, delay)
@ -86,9 +87,9 @@ func (worker *Meta) Start(delay time.Duration) (err error) {
log.Debugf("metadata: optimized photo %s", photo.String())
}
for _, m := range merged {
log.Infof("metadata: merged %s", m.PhotoUID)
done[m.PhotoUID] = true
for _, p := range merged {
log.Infof("metadata: merged %s", p.PhotoUID)
done[p.PhotoUID] = true
}
}
@ -105,20 +106,33 @@ func (worker *Meta) Start(delay time.Duration) (err error) {
log.Infof("metadata: optimized %d photos", optimized)
}
// Explicitly set quality of photos without primary file to -1.
if err := query.ResetPhotoQuality(); err != nil {
log.Warnf("metadata: %s (reset photo quality)", err.Error())
}
// Update photo counts for labels and places.
if err := entity.UpdatePhotoCounts(); err != nil {
log.Warnf("metadata: %s (update photo counts)", err.Error())
}
moments := photoprism.NewMoments(worker.conf)
if err := moments.Start(); err != nil {
// Run moments worker.
if w := photoprism.NewMoments(m.conf); w == nil {
log.Errorf("moments: failed creating worker")
} else if err := w.Start(); err != nil {
log.Warnf("moments: %s", err)
}
// Run faces worker.
if w := photoprism.NewFaces(m.conf); w == nil {
log.Errorf("faces: failed creating worker")
} else if w.Disabled() {
// Do nothing.
} else if err := w.Start(); err != nil {
log.Warnf("faces: %s", err)
}
// Run garbage collection.
runtime.GC()
return nil