People: Add config options for face detection and clustering #22
This commit is contained in:
parent
4988bec841
commit
ee2b49ef4b
12 changed files with 232 additions and 120 deletions
|
@ -141,5 +141,12 @@ func configAction(ctx *cli.Context) error {
|
|||
fmt.Printf("%-25s %d\n", "jpeg-size", conf.JpegSize())
|
||||
fmt.Printf("%-25s %d\n", "jpeg-quality", conf.JpegQuality())
|
||||
|
||||
// Facial recognition.
|
||||
fmt.Printf("%-25s %f\n", "face-score", conf.FaceScore())
|
||||
fmt.Printf("%-25s %d\n", "face-overlap", conf.FaceOverlap())
|
||||
fmt.Printf("%-25s %d\n", "face-cluster-core", conf.FaceClusterCore())
|
||||
fmt.Printf("%-25s %f\n", "face-cluster-dist", conf.FaceClusterDist())
|
||||
fmt.Printf("%-25s %f\n", "face-match-dist", conf.FaceMatchDist())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -13,27 +13,26 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/pbnjay/memory"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
"github.com/pbnjay/memory"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/face"
|
||||
"github.com/photoprism/photoprism/internal/hub"
|
||||
"github.com/photoprism/photoprism/internal/hub/places"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
var log = event.Log
|
||||
|
@ -131,13 +130,23 @@ func (c *Config) Options() *Options {
|
|||
func (c *Config) Propagate() {
|
||||
log.SetLevel(c.LogLevel())
|
||||
|
||||
// Set thumbnail generation parameters.
|
||||
thumb.SizePrecached = c.ThumbSizePrecached()
|
||||
thumb.SizeUncached = c.ThumbSizeUncached()
|
||||
thumb.Filter = c.ThumbFilter()
|
||||
thumb.JpegQuality = c.JpegQuality()
|
||||
|
||||
// Set geocoding parameters.
|
||||
places.UserAgent = c.UserAgent()
|
||||
entity.GeoApi = c.GeoApi()
|
||||
|
||||
// Set facial recognition parameters.
|
||||
face.ScoreThreshold = c.FaceScore()
|
||||
face.OverlapThreshold = c.FaceOverlap()
|
||||
face.ClusterCore = c.FaceClusterCore()
|
||||
face.ClusterDist = c.FaceClusterDist()
|
||||
face.MatchDist = c.FaceMatchDist()
|
||||
|
||||
c.Settings().Propagate()
|
||||
c.Hub().Propagate()
|
||||
}
|
||||
|
|
48
internal/config/face.go
Normal file
48
internal/config/face.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package config
|
||||
|
||||
import "github.com/photoprism/photoprism/internal/face"
|
||||
|
||||
// FaceScore returns the face quality score threshold.
|
||||
func (c *Config) FaceScore() float64 {
|
||||
if c.options.FaceScore < 1 || c.options.FaceScore > 100 {
|
||||
return face.ScoreThreshold
|
||||
}
|
||||
|
||||
return c.options.FaceScore
|
||||
}
|
||||
|
||||
// FaceOverlap returns the image area overlap threshold for faces in percent.
|
||||
func (c *Config) FaceOverlap() int {
|
||||
if c.options.FaceOverlap < 1 || c.options.FaceOverlap > 100 {
|
||||
return face.OverlapThreshold
|
||||
}
|
||||
|
||||
return c.options.FaceOverlap
|
||||
}
|
||||
|
||||
// FaceClusterCore returns the number of faces forming a cluster core.
|
||||
func (c *Config) FaceClusterCore() int {
|
||||
if c.options.FaceClusterCore < 1 || c.options.FaceClusterCore > 100 {
|
||||
return face.ClusterCore
|
||||
}
|
||||
|
||||
return c.options.FaceClusterCore
|
||||
}
|
||||
|
||||
// FaceClusterDist returns the radius of faces forming a cluster core.
|
||||
func (c *Config) FaceClusterDist() float64 {
|
||||
if c.options.FaceClusterDist < 0.1 || c.options.FaceClusterDist > 1.5 {
|
||||
return face.ClusterDist
|
||||
}
|
||||
|
||||
return c.options.FaceClusterDist
|
||||
}
|
||||
|
||||
// FaceMatchDist returns the offset distance when matching faces with clusters.
|
||||
func (c *Config) FaceMatchDist() float64 {
|
||||
if c.options.FaceMatchDist < 0.1 || c.options.FaceMatchDist > 1.5 {
|
||||
return face.MatchDist
|
||||
}
|
||||
|
||||
return c.options.FaceMatchDist
|
||||
}
|
|
@ -3,6 +3,8 @@ package config
|
|||
import (
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/face"
|
||||
)
|
||||
|
||||
// GlobalFlags describes global command-line parameters and flags.
|
||||
|
@ -426,4 +428,34 @@ var GlobalFlags = []cli.Flag{
|
|||
Value: 92,
|
||||
EnvVar: "PHOTOPRISM_JPEG_QUALITY",
|
||||
},
|
||||
cli.Float64Flag{
|
||||
Name: "face-score",
|
||||
Usage: "face `QUALITY` threshold",
|
||||
Value: face.ScoreThreshold,
|
||||
EnvVar: "PHOTOPRISM_FACE_SCORE",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "face-overlap",
|
||||
Usage: "image area overlap threshold in `PERCENT`",
|
||||
Value: face.OverlapThreshold,
|
||||
EnvVar: "PHOTOPRISM_FACE_OVERLAP",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "face-cluster-core",
|
||||
Usage: "`NUMBER` of faces forming a cluster core",
|
||||
Value: face.ClusterCore,
|
||||
EnvVar: "PHOTOPRISM_FACE_CLUSTER_CORE",
|
||||
},
|
||||
cli.Float64Flag{
|
||||
Name: "face-cluster-dist",
|
||||
Usage: "`RADIUS` of faces forming a cluster core",
|
||||
Value: face.ClusterDist,
|
||||
EnvVar: "PHOTOPRISM_FACE_CLUSTER_DIST",
|
||||
},
|
||||
cli.Float64Flag{
|
||||
Name: "face-match-dist",
|
||||
Usage: "`OFFSET` distance when matching faces with clusters",
|
||||
Value: face.MatchDist,
|
||||
EnvVar: "PHOTOPRISM_FACE_MATCH_DIST",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -30,88 +30,93 @@ const (
|
|||
//
|
||||
// See https://github.com/photoprism/photoprism/issues/50#issuecomment-433856358
|
||||
type Options struct {
|
||||
Name string `json:"-"`
|
||||
Version string `json:"-"`
|
||||
Copyright string `json:"-"`
|
||||
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`
|
||||
Test bool `yaml:"-" json:"Test,omitempty" flag:"test"`
|
||||
Demo bool `yaml:"Demo" json:"-" flag:"demo"`
|
||||
Sponsor bool `yaml:"-" json:"-" flag:"sponsor"`
|
||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
ReadOnly bool `yaml:"ReadOnly" json:"ReadOnly" flag:"read-only"`
|
||||
Experimental bool `yaml:"Experimental" json:"Experimental" flag:"experimental"`
|
||||
ConfigPath string `yaml:"ConfigPath" json:"-" flag:"config-path"`
|
||||
ConfigFile string `json:"-"`
|
||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||
OriginalsPath string `yaml:"OriginalsPath" json:"-" flag:"originals-path"`
|
||||
OriginalsLimit int64 `yaml:"OriginalsLimit" json:"OriginalsLimit" flag:"originals-limit"`
|
||||
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
||||
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
|
||||
SidecarPath string `yaml:"SidecarPath" json:"-" flag:"sidecar-path"`
|
||||
TempPath string `yaml:"TempPath" json:"-" flag:"temp-path"`
|
||||
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
|
||||
AssetsPath string `yaml:"AssetsPath" json:"-" flag:"assets-path"`
|
||||
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
||||
Workers int `yaml:"Workers" json:"Workers" flag:"workers"`
|
||||
WakeupInterval int `yaml:"WakeupInterval" json:"WakeupInterval" flag:"wakeup-interval"`
|
||||
AutoIndex int `yaml:"AutoIndex" json:"AutoIndex" flag:"auto-index"`
|
||||
AutoImport int `yaml:"AutoImport" json:"AutoImport" flag:"auto-import"`
|
||||
DisableBackups bool `yaml:"DisableBackups" json:"DisableBackups" flag:"disable-backups"`
|
||||
DisableWebDAV bool `yaml:"DisableWebDAV" json:"DisableWebDAV" flag:"disable-webdav"`
|
||||
DisableSettings bool `yaml:"DisableSettings" json:"-" flag:"disable-settings"`
|
||||
DisablePlaces bool `yaml:"DisablePlaces" json:"DisablePlaces" flag:"disable-places"`
|
||||
DisableExifTool bool `yaml:"DisableExifTool" json:"DisableExifTool" flag:"disable-exiftool"`
|
||||
DisableTensorFlow bool `yaml:"DisableTensorFlow" json:"DisableTensorFlow" flag:"disable-tensorflow"`
|
||||
DisableFFmpeg bool `yaml:"DisableFFmpeg" json:"DisableFFmpeg" flag:"disable-ffmpeg"`
|
||||
DisableDarktable bool `yaml:"DisableDarktable" json:"DisableDarktable" flag:"disable-darktable"`
|
||||
DisableRawtherapee bool `yaml:"DisableRawtherapee" json:"DisableRawtherapee" flag:"disable-rawtherapee"`
|
||||
DisableSips bool `yaml:"DisableSips" json:"DisableSips" flag:"disable-sips"`
|
||||
DisableHeifConvert bool `yaml:"DisableHeifConvert" json:"DisableHeifConvert" flag:"disable-heifconvert"`
|
||||
DetectNSFW bool `yaml:"DetectNSFW" json:"DetectNSFW" flag:"detect-nsfw"`
|
||||
UploadNSFW bool `yaml:"UploadNSFW" json:"-" flag:"upload-nsfw"`
|
||||
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
|
||||
LogFilename string `yaml:"LogFilename" json:"-" flag:"log-filename"`
|
||||
PIDFilename string `yaml:"PIDFilename" json:"-" flag:"pid-filename"`
|
||||
CdnUrl string `yaml:"CdnUrl" json:"CdnUrl" flag:"cdn-url"`
|
||||
SiteUrl string `yaml:"SiteUrl" json:"SiteUrl" flag:"site-url"`
|
||||
SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"`
|
||||
SiteTitle string `yaml:"SiteTitle" json:"SiteTitle" flag:"site-title"`
|
||||
SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"`
|
||||
SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"`
|
||||
SiteAuthor string `yaml:"SiteAuthor" json:"SiteAuthor" flag:"site-author"`
|
||||
DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"`
|
||||
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
|
||||
DatabaseServer string `yaml:"DatabaseServer" json:"-" flag:"database-server"`
|
||||
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`
|
||||
DatabaseUser string `yaml:"DatabaseUser" json:"-" flag:"database-user"`
|
||||
DatabasePassword string `yaml:"DatabasePassword" json:"-" flag:"database-password"`
|
||||
DatabaseConns int `yaml:"DatabaseConns" json:"-" flag:"database-conns"`
|
||||
DatabaseConnsIdle int `yaml:"DatabaseConnsIdle" json:"-" flag:"database-conns-idle"`
|
||||
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
|
||||
HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"`
|
||||
HttpMode string `yaml:"HttpMode" json:"-" flag:"http-mode"`
|
||||
HttpCompression string `yaml:"HttpCompression" json:"-" flag:"http-compression"`
|
||||
RawPresets bool `yaml:"RawPresets" json:"RawPresets" flag:"raw-presets"`
|
||||
DarktableBin string `yaml:"DarktableBin" json:"-" flag:"darktable-bin"`
|
||||
DarktableBlacklist string `yaml:"DarktableBlacklist" json:"-" flag:"darktable-blacklist"`
|
||||
RawtherapeeBin string `yaml:"RawtherapeeBin" json:"-" flag:"rawtherapee-bin"`
|
||||
RawtherapeeBlacklist string `yaml:"RawtherapeeBlacklist" json:"-" flag:"rawtherapee-blacklist"`
|
||||
SipsBin string `yaml:"SipsBin" json:"-" flag:"sips-bin"`
|
||||
HeifConvertBin string `yaml:"HeifConvertBin" json:"-" flag:"heifconvert-bin"`
|
||||
FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"`
|
||||
FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"`
|
||||
FFmpegBitrate int `yaml:"FFmpegBitrate" json:"FFmpegBitrate" flag:"ffmpeg-bitrate"`
|
||||
FFmpegBuffers int `yaml:"FFmpegBuffers" json:"FFmpegBuffers" flag:"ffmpeg-buffers"`
|
||||
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`
|
||||
DetachServer bool `yaml:"DetachServer" json:"-" flag:"detach-server"`
|
||||
DownloadToken string `yaml:"DownloadToken" json:"-" flag:"download-token"`
|
||||
PreviewToken string `yaml:"PreviewToken" json:"-" flag:"preview-token"`
|
||||
ThumbFilter string `yaml:"ThumbFilter" json:"ThumbFilter" flag:"thumb-filter"`
|
||||
ThumbUncached bool `yaml:"ThumbUncached" json:"ThumbUncached" flag:"thumb-uncached"`
|
||||
ThumbSize int `yaml:"ThumbSize" json:"ThumbSize" flag:"thumb-size"`
|
||||
ThumbSizeUncached int `yaml:"ThumbSizeUncached" json:"ThumbSizeUncached" flag:"thumb-size-uncached"`
|
||||
JpegSize int `yaml:"JpegSize" json:"JpegSize" flag:"jpeg-size"`
|
||||
JpegQuality int `yaml:"JpegQuality" json:"JpegQuality" flag:"jpeg-quality"`
|
||||
Name string `json:"-"`
|
||||
Version string `json:"-"`
|
||||
Copyright string `json:"-"`
|
||||
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`
|
||||
Test bool `yaml:"-" json:"Test,omitempty" flag:"test"`
|
||||
Demo bool `yaml:"Demo" json:"-" flag:"demo"`
|
||||
Sponsor bool `yaml:"-" json:"-" flag:"sponsor"`
|
||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
ReadOnly bool `yaml:"ReadOnly" json:"ReadOnly" flag:"read-only"`
|
||||
Experimental bool `yaml:"Experimental" json:"Experimental" flag:"experimental"`
|
||||
ConfigPath string `yaml:"ConfigPath" json:"-" flag:"config-path"`
|
||||
ConfigFile string `json:"-"`
|
||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||
OriginalsPath string `yaml:"OriginalsPath" json:"-" flag:"originals-path"`
|
||||
OriginalsLimit int64 `yaml:"OriginalsLimit" json:"OriginalsLimit" flag:"originals-limit"`
|
||||
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
||||
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
|
||||
SidecarPath string `yaml:"SidecarPath" json:"-" flag:"sidecar-path"`
|
||||
TempPath string `yaml:"TempPath" json:"-" flag:"temp-path"`
|
||||
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
|
||||
AssetsPath string `yaml:"AssetsPath" json:"-" flag:"assets-path"`
|
||||
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
||||
Workers int `yaml:"Workers" json:"Workers" flag:"workers"`
|
||||
WakeupInterval int `yaml:"WakeupInterval" json:"WakeupInterval" flag:"wakeup-interval"`
|
||||
AutoIndex int `yaml:"AutoIndex" json:"AutoIndex" flag:"auto-index"`
|
||||
AutoImport int `yaml:"AutoImport" json:"AutoImport" flag:"auto-import"`
|
||||
DisableBackups bool `yaml:"DisableBackups" json:"DisableBackups" flag:"disable-backups"`
|
||||
DisableWebDAV bool `yaml:"DisableWebDAV" json:"DisableWebDAV" flag:"disable-webdav"`
|
||||
DisableSettings bool `yaml:"DisableSettings" json:"-" flag:"disable-settings"`
|
||||
DisablePlaces bool `yaml:"DisablePlaces" json:"DisablePlaces" flag:"disable-places"`
|
||||
DisableExifTool bool `yaml:"DisableExifTool" json:"DisableExifTool" flag:"disable-exiftool"`
|
||||
DisableTensorFlow bool `yaml:"DisableTensorFlow" json:"DisableTensorFlow" flag:"disable-tensorflow"`
|
||||
DisableFFmpeg bool `yaml:"DisableFFmpeg" json:"DisableFFmpeg" flag:"disable-ffmpeg"`
|
||||
DisableDarktable bool `yaml:"DisableDarktable" json:"DisableDarktable" flag:"disable-darktable"`
|
||||
DisableRawtherapee bool `yaml:"DisableRawtherapee" json:"DisableRawtherapee" flag:"disable-rawtherapee"`
|
||||
DisableSips bool `yaml:"DisableSips" json:"DisableSips" flag:"disable-sips"`
|
||||
DisableHeifConvert bool `yaml:"DisableHeifConvert" json:"DisableHeifConvert" flag:"disable-heifconvert"`
|
||||
DetectNSFW bool `yaml:"DetectNSFW" json:"DetectNSFW" flag:"detect-nsfw"`
|
||||
UploadNSFW bool `yaml:"UploadNSFW" json:"-" flag:"upload-nsfw"`
|
||||
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
|
||||
LogFilename string `yaml:"LogFilename" json:"-" flag:"log-filename"`
|
||||
PIDFilename string `yaml:"PIDFilename" json:"-" flag:"pid-filename"`
|
||||
CdnUrl string `yaml:"CdnUrl" json:"CdnUrl" flag:"cdn-url"`
|
||||
SiteUrl string `yaml:"SiteUrl" json:"SiteUrl" flag:"site-url"`
|
||||
SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"`
|
||||
SiteTitle string `yaml:"SiteTitle" json:"SiteTitle" flag:"site-title"`
|
||||
SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"`
|
||||
SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"`
|
||||
SiteAuthor string `yaml:"SiteAuthor" json:"SiteAuthor" flag:"site-author"`
|
||||
DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"`
|
||||
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
|
||||
DatabaseServer string `yaml:"DatabaseServer" json:"-" flag:"database-server"`
|
||||
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`
|
||||
DatabaseUser string `yaml:"DatabaseUser" json:"-" flag:"database-user"`
|
||||
DatabasePassword string `yaml:"DatabasePassword" json:"-" flag:"database-password"`
|
||||
DatabaseConns int `yaml:"DatabaseConns" json:"-" flag:"database-conns"`
|
||||
DatabaseConnsIdle int `yaml:"DatabaseConnsIdle" json:"-" flag:"database-conns-idle"`
|
||||
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
|
||||
HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"`
|
||||
HttpMode string `yaml:"HttpMode" json:"-" flag:"http-mode"`
|
||||
HttpCompression string `yaml:"HttpCompression" json:"-" flag:"http-compression"`
|
||||
RawPresets bool `yaml:"RawPresets" json:"RawPresets" flag:"raw-presets"`
|
||||
DarktableBin string `yaml:"DarktableBin" json:"-" flag:"darktable-bin"`
|
||||
DarktableBlacklist string `yaml:"DarktableBlacklist" json:"-" flag:"darktable-blacklist"`
|
||||
RawtherapeeBin string `yaml:"RawtherapeeBin" json:"-" flag:"rawtherapee-bin"`
|
||||
RawtherapeeBlacklist string `yaml:"RawtherapeeBlacklist" json:"-" flag:"rawtherapee-blacklist"`
|
||||
SipsBin string `yaml:"SipsBin" json:"-" flag:"sips-bin"`
|
||||
HeifConvertBin string `yaml:"HeifConvertBin" json:"-" flag:"heifconvert-bin"`
|
||||
FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"`
|
||||
FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"`
|
||||
FFmpegBitrate int `yaml:"FFmpegBitrate" json:"FFmpegBitrate" flag:"ffmpeg-bitrate"`
|
||||
FFmpegBuffers int `yaml:"FFmpegBuffers" json:"FFmpegBuffers" flag:"ffmpeg-buffers"`
|
||||
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`
|
||||
DetachServer bool `yaml:"DetachServer" json:"-" flag:"detach-server"`
|
||||
DownloadToken string `yaml:"DownloadToken" json:"-" flag:"download-token"`
|
||||
PreviewToken string `yaml:"PreviewToken" json:"-" flag:"preview-token"`
|
||||
ThumbFilter string `yaml:"ThumbFilter" json:"ThumbFilter" flag:"thumb-filter"`
|
||||
ThumbUncached bool `yaml:"ThumbUncached" json:"ThumbUncached" flag:"thumb-uncached"`
|
||||
ThumbSize int `yaml:"ThumbSize" json:"ThumbSize" flag:"thumb-size"`
|
||||
ThumbSizeUncached int `yaml:"ThumbSizeUncached" json:"ThumbSizeUncached" flag:"thumb-size-uncached"`
|
||||
JpegSize int `yaml:"JpegSize" json:"JpegSize" flag:"jpeg-size"`
|
||||
JpegQuality int `yaml:"JpegQuality" json:"JpegQuality" flag:"jpeg-quality"`
|
||||
FaceScore float64 `yaml:"-" json:"-" flag:"face-score"`
|
||||
FaceOverlap int `yaml:"-" json:"-" flag:"face-overlap"`
|
||||
FaceClusterCore int `yaml:"-" json:"-" flag:"face-cluster-core"`
|
||||
FaceClusterDist float64 `yaml:"-" json:"-" flag:"face-cluster-dist"`
|
||||
FaceMatchDist float64 `yaml:"-" json:"-" flag:"face-match-dist"`
|
||||
}
|
||||
|
||||
// NewOptions creates a new configuration entity by using two methods:
|
||||
|
@ -187,9 +192,18 @@ func (c *Options) SetContext(ctx *cli.Context) error {
|
|||
|
||||
tagValue := v.Type().Field(i).Tag.Get("flag")
|
||||
|
||||
// Automatically assign options to fields with "flag" tag.
|
||||
// Assign value to field with "flag" tag.
|
||||
if tagValue != "" {
|
||||
switch t := fieldValue.Interface().(type) {
|
||||
case float64:
|
||||
// Only if explicitly set or current value is empty (use default).
|
||||
if ctx.IsSet(tagValue) {
|
||||
f := ctx.Float64(tagValue)
|
||||
fieldValue.SetFloat(f)
|
||||
} else if ctx.GlobalIsSet(tagValue) || fieldValue.Float() == 0 {
|
||||
f := ctx.GlobalFloat64(tagValue)
|
||||
fieldValue.SetFloat(f)
|
||||
}
|
||||
case int, int64:
|
||||
// Only if explicitly set or current value is empty (use default).
|
||||
if ctx.IsSet(tagValue) {
|
||||
|
|
|
@ -133,7 +133,7 @@ func (m *Face) Match(embeddings Embeddings) (match bool, dist float64) {
|
|||
case dist < 0:
|
||||
// Should never happen.
|
||||
return false, dist
|
||||
case dist > (m.SampleRadius + face.MatchRadius):
|
||||
case dist > (m.SampleRadius + face.MatchDist):
|
||||
// Too far.
|
||||
return false, dist
|
||||
case m.CollisionRadius > 0.1 && dist > m.CollisionRadius:
|
||||
|
|
|
@ -87,7 +87,7 @@ func Detect(fileName string, findLandmarks bool, minSize int) (faces Faces, err
|
|||
shiftFactor: 0.1,
|
||||
scaleFactor: 1.1,
|
||||
iouThreshold: 0.2,
|
||||
scoreThreshold: ScoreThreshold,
|
||||
scoreThreshold: float32(ScoreThreshold),
|
||||
perturb: 63,
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ func (d *Detector) Faces(det []pigo.Detection, params pigo.CascadeParams, findLa
|
|||
|
||||
for _, face := range det {
|
||||
// Skip result if quality is too low.
|
||||
if face.Q < ScaleScoreThreshold(face.Scale) {
|
||||
if face.Q < QualityThreshold(face.Scale) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -5,33 +5,35 @@ import (
|
|||
)
|
||||
|
||||
var CropSize = crop.Sizes[crop.Tile160]
|
||||
var MatchDist = 0.46
|
||||
var ClusterDist = 0.64
|
||||
var ClusterCore = 4
|
||||
var ClusterMinScore = 15
|
||||
var ClusterMinSize = 95
|
||||
var ClusterRadius = 0.64
|
||||
var MatchRadius = 0.46
|
||||
var SampleThreshold = 2 * ClusterCore
|
||||
var OverlapThreshold = 42
|
||||
var OverlapThresholdFloor = OverlapThreshold - 1
|
||||
var ScoreThreshold = float32(9.0)
|
||||
var ScoreThreshold = 9.0
|
||||
|
||||
// QualityThreshold returns the scale adjusted quality score threshold.
|
||||
func QualityThreshold(scale int) (score float32) {
|
||||
score = float32(ScoreThreshold)
|
||||
|
||||
// ScaleScoreThreshold returns the scale adjusted face score threshold.
|
||||
func ScaleScoreThreshold(scale int) float32 {
|
||||
// Smaller faces require higher quality.
|
||||
switch {
|
||||
case scale < 26:
|
||||
return ScoreThreshold + 26.0
|
||||
score += 26.0
|
||||
case scale < 32:
|
||||
return ScoreThreshold + 16.0
|
||||
score += 16.0
|
||||
case scale < 40:
|
||||
return ScoreThreshold + 11.0
|
||||
score += 11.0
|
||||
case scale < 50:
|
||||
return ScoreThreshold + 9.0
|
||||
score += 9.0
|
||||
case scale < 80:
|
||||
return ScoreThreshold + 6.0
|
||||
score += 6.0
|
||||
case scale < 110:
|
||||
return ScoreThreshold + 2.0
|
||||
score += 2.0
|
||||
}
|
||||
|
||||
return ScoreThreshold
|
||||
return score
|
||||
}
|
||||
|
|
|
@ -6,26 +6,26 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScaleScoreThreshold(t *testing.T) {
|
||||
func TestQualityThreshold(t *testing.T) {
|
||||
t.Run("XXS", func(t *testing.T) {
|
||||
assert.Equal(t, float32(35), ScaleScoreThreshold(21))
|
||||
assert.Equal(t, float32(35), QualityThreshold(21))
|
||||
})
|
||||
t.Run("XS", func(t *testing.T) {
|
||||
assert.Equal(t, float32(25), ScaleScoreThreshold(27))
|
||||
assert.Equal(t, float32(25), QualityThreshold(27))
|
||||
})
|
||||
t.Run("S", func(t *testing.T) {
|
||||
assert.Equal(t, float32(20), ScaleScoreThreshold(33))
|
||||
assert.Equal(t, float32(20), QualityThreshold(33))
|
||||
})
|
||||
t.Run("M", func(t *testing.T) {
|
||||
assert.Equal(t, float32(18), ScaleScoreThreshold(45))
|
||||
assert.Equal(t, float32(18), QualityThreshold(45))
|
||||
})
|
||||
t.Run("L", func(t *testing.T) {
|
||||
assert.Equal(t, float32(15), ScaleScoreThreshold(75))
|
||||
assert.Equal(t, float32(15), QualityThreshold(75))
|
||||
})
|
||||
t.Run("XL", func(t *testing.T) {
|
||||
assert.Equal(t, float32(11), ScaleScoreThreshold(100))
|
||||
assert.Equal(t, float32(11), QualityThreshold(100))
|
||||
})
|
||||
t.Run("XXL", func(t *testing.T) {
|
||||
assert.Equal(t, float32(9), ScaleScoreThreshold(250))
|
||||
assert.Equal(t, float32(9), QualityThreshold(250))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func (w *Faces) Audit(fix bool) (err error) {
|
|||
|
||||
conflicts++
|
||||
|
||||
r := f1.SampleRadius + face.MatchRadius
|
||||
r := f1.SampleRadius + face.MatchDist
|
||||
|
||||
log.Infof("face %s: conflict at dist %f, Ø %f from %d samples, collision Ø %f", f1.ID, dist, r, f1.Samples, f1.CollisionRadius)
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ func (w *Faces) Cluster(opt FacesOptions) (added entity.Faces, err error) {
|
|||
var c clusters.HardClusterer
|
||||
|
||||
// See https://dl.photoprism.org/research/ for research on face clustering algorithms.
|
||||
if c, err = clusters.DBSCAN(face.ClusterCore, face.ClusterRadius, w.conf.Workers(), clusters.EuclideanDistance); err != nil {
|
||||
if c, err = clusters.DBSCAN(face.ClusterCore, face.ClusterDist, w.conf.Workers(), clusters.EuclideanDistance); err != nil {
|
||||
return added, err
|
||||
} else if err = c.Learn(embeddings); err != nil {
|
||||
return added, err
|
||||
|
|
|
@ -178,7 +178,7 @@ func ResolveFaceCollisions() (conflicts, resolved int, err error) {
|
|||
|
||||
conflicts++
|
||||
|
||||
r := f1.SampleRadius + face.MatchRadius
|
||||
r := f1.SampleRadius + face.MatchDist
|
||||
|
||||
log.Infof("face %s: conflict at dist %f, Ø %f from %d samples, collision Ø %f", f1.ID, dist, r, f1.Samples, f1.CollisionRadius)
|
||||
|
||||
|
|
Loading…
Reference in a new issue