People: Detect number of faces (experimental) #22
This commit is contained in:
parent
f5a1cc6231
commit
a6bf89d104
11 changed files with 267 additions and 169 deletions
|
@ -241,7 +241,7 @@
|
||||||
</v-checkbox>
|
</v-checkbox>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
|
|
||||||
<v-flex xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
<v-flex v-if="config.experimental" xs12 sm6 lg3 class="px-2 pb-2 pt-2">
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
v-model="settings.features.people"
|
v-model="settings.features.people"
|
||||||
:disabled="busy"
|
:disabled="busy"
|
||||||
|
|
|
@ -47,3 +47,24 @@ func LocationLabel(name string, uncertainty int) Label {
|
||||||
func (l Label) Title() string {
|
func (l Label) Title() string {
|
||||||
return txt.Title(txt.Clip(l.Name, txt.ClipDefault))
|
return txt.Title(txt.Clip(l.Name, txt.ClipDefault))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FaceLabels returns matching labels if there are people in the image.
|
||||||
|
func FaceLabels(count int, src string, uncertainty int) Labels {
|
||||||
|
var r LabelRule
|
||||||
|
|
||||||
|
if count < 1 {
|
||||||
|
return Labels{}
|
||||||
|
} else if count == 1 {
|
||||||
|
r = rules["portrait"]
|
||||||
|
} else {
|
||||||
|
r = rules["people"]
|
||||||
|
}
|
||||||
|
|
||||||
|
return Labels{Label{
|
||||||
|
Name: r.Label,
|
||||||
|
Source: src,
|
||||||
|
Uncertainty: uncertainty,
|
||||||
|
Priority: r.Priority,
|
||||||
|
Categories: r.Categories,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
|
@ -12,13 +12,13 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"academic gown": {
|
"academic gown": {
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"accordion": {
|
"accordion": {
|
||||||
Label: "instrument",
|
Label: "instrument",
|
||||||
|
@ -516,7 +516,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"bathtub": {
|
"bathtub": {
|
||||||
Label: "living",
|
Label: "living",
|
||||||
|
@ -630,7 +630,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"bernese mountain dog": {
|
"bernese mountain dog": {
|
||||||
Label: "dog",
|
Label: "dog",
|
||||||
|
@ -672,7 +672,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"binder": {
|
"binder": {
|
||||||
Label: "office",
|
Label: "office",
|
||||||
|
@ -804,13 +804,13 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"bonnet": {
|
"bonnet": {
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"book jacket": {
|
"book jacket": {
|
||||||
Label: "book",
|
Label: "book",
|
||||||
|
@ -882,7 +882,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"box turtle": {
|
"box turtle": {
|
||||||
Label: "turtle",
|
Label: "turtle",
|
||||||
|
@ -924,7 +924,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"breakwater": {
|
"breakwater": {
|
||||||
Label: "water",
|
Label: "water",
|
||||||
|
@ -936,7 +936,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"briard dog": {
|
"briard dog": {
|
||||||
Label: "dog",
|
Label: "dog",
|
||||||
|
@ -1134,7 +1134,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"cardigan dog": {
|
"cardigan dog": {
|
||||||
Label: "dog",
|
Label: "dog",
|
||||||
|
@ -1242,7 +1242,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"chain saw": {
|
"chain saw": {
|
||||||
Label: "outdoor",
|
Label: "outdoor",
|
||||||
|
@ -1392,7 +1392,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"clock": {
|
"clock": {
|
||||||
Label: "display",
|
Label: "display",
|
||||||
|
@ -1686,7 +1686,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"cup": {
|
"cup": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -2088,7 +2088,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"feather boa": {
|
"feather boa": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -2298,7 +2298,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"gallery": {
|
"gallery": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -2466,7 +2466,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"grand piano": {
|
"grand piano": {
|
||||||
Label: "instrument",
|
Label: "instrument",
|
||||||
|
@ -2616,7 +2616,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"hair spray": {
|
"hair spray": {
|
||||||
Label: "bottle",
|
Label: "bottle",
|
||||||
|
@ -2820,7 +2820,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"horizontal bar": {
|
"horizontal bar": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -3054,7 +3054,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"jeep": {
|
"jeep": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -3072,7 +3072,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"jigsaw puzzle": {
|
"jigsaw puzzle": {
|
||||||
Label: "puzzle",
|
Label: "puzzle",
|
||||||
|
@ -3126,7 +3126,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"king crab": {
|
"king crab": {
|
||||||
Label: "crab",
|
Label: "crab",
|
||||||
|
@ -3198,7 +3198,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"labrador retriever dog": {
|
"labrador retriever dog": {
|
||||||
Label: "dog",
|
Label: "dog",
|
||||||
|
@ -3516,7 +3516,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"malamute dog": {
|
"malamute dog": {
|
||||||
Label: "dog",
|
Label: "dog",
|
||||||
|
@ -3660,7 +3660,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"milk can": {
|
"milk can": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -3696,7 +3696,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"minivan": {
|
"minivan": {
|
||||||
Label: "car",
|
Label: "car",
|
||||||
|
@ -3792,7 +3792,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"mosque": {
|
"mosque": {
|
||||||
Label: "tower",
|
Label: "tower",
|
||||||
|
@ -3870,19 +3870,19 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"neck brace": {
|
"neck brace": {
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"necklace": {
|
"necklace": {
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"nematode": {
|
"nematode": {
|
||||||
Label: "worm",
|
Label: "worm",
|
||||||
|
@ -4020,7 +4020,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"ox": {
|
"ox": {
|
||||||
Label: "cow",
|
Label: "cow",
|
||||||
|
@ -4080,7 +4080,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"palace": {
|
"palace": {
|
||||||
Label: "historic",
|
Label: "historic",
|
||||||
|
@ -4220,6 +4220,12 @@ var rules = LabelRules{
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
|
"people": {
|
||||||
|
Label: "people",
|
||||||
|
Threshold: 0.300000,
|
||||||
|
Priority: 0,
|
||||||
|
Categories: []string{},
|
||||||
|
},
|
||||||
"perfume": {
|
"perfume": {
|
||||||
Label: "bottle",
|
Label: "bottle",
|
||||||
Threshold: 0.700000,
|
Threshold: 0.700000,
|
||||||
|
@ -4428,7 +4434,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"pool table": {
|
"pool table": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -4896,7 +4902,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"sax": {
|
"sax": {
|
||||||
Label: "instrument",
|
Label: "instrument",
|
||||||
|
@ -5256,7 +5262,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"soft-coated wheaten terrier dog": {
|
"soft-coated wheaten terrier dog": {
|
||||||
Label: "dog",
|
Label: "dog",
|
||||||
|
@ -5274,7 +5280,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"sorrel": {
|
"sorrel": {
|
||||||
Label: "",
|
Label: "",
|
||||||
|
@ -5538,7 +5544,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"sulphur butterfly": {
|
"sulphur butterfly": {
|
||||||
Label: "butterfly",
|
Label: "butterfly",
|
||||||
|
@ -5598,7 +5604,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"swimming trunks": {
|
"swimming trunks": {
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
|
@ -6060,7 +6066,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"viaduct": {
|
"viaduct": {
|
||||||
Label: "building",
|
Label: "building",
|
||||||
|
@ -6282,7 +6288,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"white stork": {
|
"white stork": {
|
||||||
Label: "bird",
|
Label: "bird",
|
||||||
|
@ -6330,7 +6336,7 @@ var rules = LabelRules{
|
||||||
Label: "portrait",
|
Label: "portrait",
|
||||||
Threshold: 0.500000,
|
Threshold: 0.500000,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
Categories: []string{"portrait"},
|
Categories: []string{},
|
||||||
},
|
},
|
||||||
"wine bottle": {
|
"wine bottle": {
|
||||||
Label: "bottle",
|
Label: "bottle",
|
||||||
|
|
|
@ -143,8 +143,6 @@ rapeseed:
|
||||||
fashion:
|
fashion:
|
||||||
label: portrait
|
label: portrait
|
||||||
threshold: 0.5
|
threshold: 0.5
|
||||||
categories:
|
|
||||||
- portrait
|
|
||||||
|
|
||||||
vestment:
|
vestment:
|
||||||
see: fashion
|
see: fashion
|
||||||
|
@ -4099,6 +4097,10 @@ portrait:
|
||||||
categories:
|
categories:
|
||||||
- people
|
- people
|
||||||
|
|
||||||
|
people:
|
||||||
|
label: people
|
||||||
|
threshold: 0.3
|
||||||
|
|
||||||
shower cap:
|
shower cap:
|
||||||
label: portrait
|
label: portrait
|
||||||
categories:
|
categories:
|
||||||
|
|
|
@ -17,9 +17,9 @@ type Marker struct {
|
||||||
RefUID string `gorm:"type:VARBINARY(42);index;" json:"UID" yaml:"UID,omitempty"`
|
RefUID string `gorm:"type:VARBINARY(42);index;" json:"UID" yaml:"UID,omitempty"`
|
||||||
MarkerSrc string `gorm:"type:VARBINARY(8);default:'';" json:"Src" yaml:"Src,omitempty"`
|
MarkerSrc string `gorm:"type:VARBINARY(8);default:'';" json:"Src" yaml:"Src,omitempty"`
|
||||||
MarkerType string `gorm:"type:VARBINARY(8);default:'';" json:"Type" yaml:"Type"`
|
MarkerType string `gorm:"type:VARBINARY(8);default:'';" json:"Type" yaml:"Type"`
|
||||||
|
MarkerScore int `gorm:"type:SMALLINT"`
|
||||||
MarkerLabel string `gorm:"type:VARCHAR(255);" json:"Label" yaml:"Label,omitempty"`
|
MarkerLabel string `gorm:"type:VARCHAR(255);" json:"Label" yaml:"Label,omitempty"`
|
||||||
MarkerMeta string `gorm:"type:TEXT;" json:"Meta" yaml:"Meta,omitempty"`
|
MarkerMeta string `gorm:"type:TEXT;" json:"Meta" yaml:"Meta,omitempty"`
|
||||||
Uncertainty int `gorm:"type:SMALLINT"`
|
|
||||||
X float32 `gorm:"type:FLOAT;" json:"X" yaml:"X,omitempty"`
|
X float32 `gorm:"type:FLOAT;" json:"X" yaml:"X,omitempty"`
|
||||||
Y float32 `gorm:"type:FLOAT;" json:"Y" yaml:"Y,omitempty"`
|
Y float32 `gorm:"type:FLOAT;" json:"Y" yaml:"Y,omitempty"`
|
||||||
W float32 `gorm:"type:FLOAT;" json:"W" yaml:"W,omitempty"`
|
W float32 `gorm:"type:FLOAT;" json:"W" yaml:"W,omitempty"`
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
package face
|
package face
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
pigo "github.com/esimov/pigo/core"
|
||||||
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"path/filepath"
|
||||||
|
"runtime/debug"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
|
||||||
|
|
||||||
pigo "github.com/esimov/pigo/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed cascade/lps/*
|
|
||||||
var efs embed.FS
|
|
||||||
|
|
||||||
//go:embed cascade/facefinder
|
//go:embed cascade/facefinder
|
||||||
var cascadeFile []byte
|
var cascadeFile []byte
|
||||||
|
|
||||||
|
@ -62,49 +58,58 @@ var (
|
||||||
|
|
||||||
// Detector struct contains Pigo face detector general settings.
|
// Detector struct contains Pigo face detector general settings.
|
||||||
type Detector struct {
|
type Detector struct {
|
||||||
minSize int
|
minSize int
|
||||||
maxSize int
|
maxSize int
|
||||||
angle float64
|
angle float64
|
||||||
shiftFactor float64
|
shiftFactor float64
|
||||||
scaleFactor float64
|
scaleFactor float64
|
||||||
iouThreshold float64
|
iouThreshold float64
|
||||||
}
|
scoreThreshold float32
|
||||||
|
perturb int
|
||||||
func DefaultDetector() *Detector {
|
|
||||||
return &Detector{
|
|
||||||
minSize: 20,
|
|
||||||
maxSize: 1000,
|
|
||||||
angle: 0.0,
|
|
||||||
shiftFactor: 0.1,
|
|
||||||
scaleFactor: 1.1,
|
|
||||||
iouThreshold: 0.2,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect runs the detection algorithm over the provided source image.
|
// Detect runs the detection algorithm over the provided source image.
|
||||||
func Detect(fileName string, fd *Detector) (det Faces, err error) {
|
func Detect(fileName string) (faces Faces, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("face: %s (panic)\nstack: %s", r, debug.Stack())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fd := &Detector{
|
||||||
|
minSize: 20,
|
||||||
|
maxSize: 1000,
|
||||||
|
angle: 0.0,
|
||||||
|
shiftFactor: 0.1,
|
||||||
|
scaleFactor: 1.1,
|
||||||
|
iouThreshold: 0.2,
|
||||||
|
scoreThreshold: 10.0,
|
||||||
|
perturb: 63,
|
||||||
|
}
|
||||||
|
|
||||||
if !fs.FileExists(fileName) {
|
if !fs.FileExists(fileName) {
|
||||||
return det, fmt.Errorf("face: file '%s' not found", fileName)
|
return faces, fmt.Errorf("face: file '%s' not found", txt.Quote(filepath.Base(fileName)))
|
||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
log.Debugf("face: detecting faces in %s", txt.Quote(filepath.Base(fileName)))
|
||||||
|
|
||||||
log.Debugf("\nface: detecting faces in %s", txt.Quote(fileName))
|
det, params, err := fd.Detect(fileName)
|
||||||
|
|
||||||
faces, params, err := fd.Detect(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return det, fmt.Errorf("face: %v (detect faces)", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
det, err = fd.Results(faces, params)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return det, fmt.Errorf("face: %s (Faces)", err)
|
return faces, fmt.Errorf("face: %v (detect faces)", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("\nface: %s done in \x1b[92m%.2fs\n", txt.Quote(fileName), time.Since(start).Seconds())
|
if det == nil {
|
||||||
|
return faces, fmt.Errorf("face: no result")
|
||||||
|
}
|
||||||
|
|
||||||
return det, nil
|
faces, err = fd.Faces(det, params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return faces, fmt.Errorf("face: %s (faces)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return faces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect runs the detection algorithm over the provided source image.
|
// Detect runs the detection algorithm over the provided source image.
|
||||||
|
@ -117,9 +122,7 @@ func (fd *Detector) Detect(fileName string) (faces []pigo.Detection, params pigo
|
||||||
return faces, params, err
|
return faces, params, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(file *os.File) {
|
defer file.Close()
|
||||||
_ = file.Close()
|
|
||||||
}(file)
|
|
||||||
|
|
||||||
srcFile = file
|
srcFile = file
|
||||||
|
|
||||||
|
@ -157,70 +160,73 @@ func (fd *Detector) Detect(fileName string) (faces []pigo.Detection, params pigo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Faces adds landmark coordinates to detected faces and returns the results.
|
// Faces adds landmark coordinates to detected faces and returns the results.
|
||||||
func (fd *Detector) Results(faces []pigo.Detection, params pigo.CascadeParams) (Faces, error) {
|
func (fd *Detector) Faces(det []pigo.Detection, params pigo.CascadeParams) (Faces, error) {
|
||||||
var (
|
var (
|
||||||
qThresh float32 = 5.0
|
results Faces
|
||||||
perturb = 63
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
detections Faces
|
|
||||||
eyesCoords []Point
|
eyesCoords []Point
|
||||||
landmarkCoords []Point
|
landmarkCoords []Point
|
||||||
puploc *pigo.Puploc
|
puploc *pigo.Puploc
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, face := range faces {
|
for _, face := range det {
|
||||||
if face.Q > qThresh {
|
if face.Q < fd.scoreThreshold {
|
||||||
faceCoord := NewPoint(
|
continue
|
||||||
"face",
|
}
|
||||||
face.Row-face.Scale/2,
|
|
||||||
face.Col-face.Scale/2,
|
|
||||||
face.Scale,
|
|
||||||
)
|
|
||||||
|
|
||||||
if face.Scale > 50 {
|
faceCoord := NewPoint(
|
||||||
// Find left eye.
|
"face",
|
||||||
puploc = &pigo.Puploc{
|
face.Row-face.Scale/2,
|
||||||
Row: face.Row - int(0.075*float32(face.Scale)),
|
face.Col-face.Scale/2,
|
||||||
Col: face.Col - int(0.175*float32(face.Scale)),
|
face.Scale,
|
||||||
Scale: float32(face.Scale) * 0.25,
|
)
|
||||||
Perturbs: perturb,
|
|
||||||
}
|
|
||||||
|
|
||||||
leftEye := plc.RunDetector(*puploc, params.ImageParams, fd.angle, false)
|
if face.Scale > 50 {
|
||||||
|
// Find left eye.
|
||||||
|
puploc = &pigo.Puploc{
|
||||||
|
Row: face.Row - int(0.075*float32(face.Scale)),
|
||||||
|
Col: face.Col - int(0.175*float32(face.Scale)),
|
||||||
|
Scale: float32(face.Scale) * 0.25,
|
||||||
|
Perturbs: fd.perturb,
|
||||||
|
}
|
||||||
|
|
||||||
if leftEye.Row > 0 && leftEye.Col > 0 {
|
leftEye := plc.RunDetector(*puploc, params.ImageParams, fd.angle, false)
|
||||||
eyesCoords = append(eyesCoords, NewPoint(
|
|
||||||
"eye_l",
|
|
||||||
leftEye.Row,
|
|
||||||
leftEye.Col,
|
|
||||||
int(leftEye.Scale),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find right eye.
|
if leftEye.Row > 0 && leftEye.Col > 0 {
|
||||||
puploc = &pigo.Puploc{
|
eyesCoords = append(eyesCoords, NewPoint(
|
||||||
Row: face.Row - int(0.075*float32(face.Scale)),
|
"eye_l",
|
||||||
Col: face.Col + int(0.185*float32(face.Scale)),
|
leftEye.Row,
|
||||||
Scale: float32(face.Scale) * 0.25,
|
leftEye.Col,
|
||||||
Perturbs: perturb,
|
int(leftEye.Scale),
|
||||||
}
|
))
|
||||||
|
}
|
||||||
|
|
||||||
rightEye := plc.RunDetector(*puploc, params.ImageParams, fd.angle, false)
|
// Find right eye.
|
||||||
|
puploc = &pigo.Puploc{
|
||||||
|
Row: face.Row - int(0.075*float32(face.Scale)),
|
||||||
|
Col: face.Col + int(0.185*float32(face.Scale)),
|
||||||
|
Scale: float32(face.Scale) * 0.25,
|
||||||
|
Perturbs: fd.perturb,
|
||||||
|
}
|
||||||
|
|
||||||
if rightEye.Row > 0 && rightEye.Col > 0 {
|
rightEye := plc.RunDetector(*puploc, params.ImageParams, fd.angle, false)
|
||||||
eyesCoords = append(eyesCoords, NewPoint(
|
|
||||||
"eye_r",
|
|
||||||
rightEye.Row,
|
|
||||||
rightEye.Col,
|
|
||||||
int(rightEye.Scale),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if rightEye.Row > 0 && rightEye.Col > 0 {
|
||||||
|
eyesCoords = append(eyesCoords, NewPoint(
|
||||||
|
"eye_r",
|
||||||
|
rightEye.Row,
|
||||||
|
rightEye.Col,
|
||||||
|
int(rightEye.Scale),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftEye != nil && rightEye != nil {
|
||||||
for _, eye := range eyeCascades {
|
for _, eye := range eyeCascades {
|
||||||
for _, flpc := range flpcs[eye] {
|
for _, flpc := range flpcs[eye] {
|
||||||
flp := flpc.GetLandmarkPoint(leftEye, rightEye, params.ImageParams, perturb, false)
|
if flpc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
flp := flpc.GetLandmarkPoint(leftEye, rightEye, params.ImageParams, fd.perturb, false)
|
||||||
if flp.Row > 0 && flp.Col > 0 {
|
if flp.Row > 0 && flp.Col > 0 {
|
||||||
landmarkCoords = append(landmarkCoords, NewPoint(
|
landmarkCoords = append(landmarkCoords, NewPoint(
|
||||||
eye,
|
eye,
|
||||||
|
@ -230,7 +236,7 @@ func (fd *Detector) Results(faces []pigo.Detection, params pigo.CascadeParams) (
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
flp = flpc.GetLandmarkPoint(leftEye, rightEye, params.ImageParams, perturb, true)
|
flp = flpc.GetLandmarkPoint(leftEye, rightEye, params.ImageParams, fd.perturb, true)
|
||||||
if flp.Row > 0 && flp.Col > 0 {
|
if flp.Row > 0 && flp.Col > 0 {
|
||||||
landmarkCoords = append(landmarkCoords, NewPoint(
|
landmarkCoords = append(landmarkCoords, NewPoint(
|
||||||
eye+"_v",
|
eye+"_v",
|
||||||
|
@ -241,22 +247,31 @@ func (fd *Detector) Results(faces []pigo.Detection, params pigo.CascadeParams) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find mouth.
|
// Find mouth.
|
||||||
for _, mouth := range mouthCascades {
|
for _, mouth := range mouthCascades {
|
||||||
for _, flpc := range flpcs[mouth] {
|
for _, flpc := range flpcs[mouth] {
|
||||||
flp := flpc.GetLandmarkPoint(leftEye, rightEye, params.ImageParams, perturb, false)
|
if flpc == nil {
|
||||||
if flp.Row > 0 && flp.Col > 0 {
|
continue
|
||||||
landmarkCoords = append(landmarkCoords, NewPoint(
|
}
|
||||||
"mouth_"+mouth,
|
|
||||||
flp.Row,
|
flp := flpc.GetLandmarkPoint(leftEye, rightEye, params.ImageParams, fd.perturb, false)
|
||||||
flp.Col,
|
if flp.Row > 0 && flp.Col > 0 {
|
||||||
int(flp.Scale),
|
landmarkCoords = append(landmarkCoords, NewPoint(
|
||||||
))
|
"mouth_"+mouth,
|
||||||
}
|
flp.Row,
|
||||||
|
flp.Col,
|
||||||
|
int(flp.Scale),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flp := flpcs["lp84"][0].GetLandmarkPoint(leftEye, rightEye, params.ImageParams, perturb, true)
|
}
|
||||||
|
|
||||||
|
flpc := flpcs["lp84"][0]
|
||||||
|
|
||||||
|
if flpc != nil {
|
||||||
|
flp := flpc.GetLandmarkPoint(leftEye, rightEye, params.ImageParams, fd.perturb, true)
|
||||||
if flp.Row > 0 && flp.Col > 0 {
|
if flp.Row > 0 && flp.Col > 0 {
|
||||||
landmarkCoords = append(landmarkCoords, NewPoint(
|
landmarkCoords = append(landmarkCoords, NewPoint(
|
||||||
"lp84",
|
"lp84",
|
||||||
|
@ -266,16 +281,18 @@ func (fd *Detector) Results(faces []pigo.Detection, params pigo.CascadeParams) (
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
detections = append(detections, Face{
|
|
||||||
Rows: params.ImageParams.Rows,
|
|
||||||
Cols: params.ImageParams.Cols,
|
|
||||||
Face: faceCoord,
|
|
||||||
Eyes: eyesCoords,
|
|
||||||
Landmarks: landmarkCoords,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results = append(results, Face{
|
||||||
|
Rows: params.ImageParams.Rows,
|
||||||
|
Cols: params.ImageParams.Cols,
|
||||||
|
Score: int(face.Q),
|
||||||
|
Face: faceCoord,
|
||||||
|
Eyes: eyesCoords,
|
||||||
|
Landmarks: landmarkCoords,
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return detections, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ type Faces []Face
|
||||||
type Face struct {
|
type Face struct {
|
||||||
Rows int `json:"rows,omitempty"`
|
Rows int `json:"rows,omitempty"`
|
||||||
Cols int `json:"cols,omitempty"`
|
Cols int `json:"cols,omitempty"`
|
||||||
|
Score int `json:"score,omitempty"`
|
||||||
Face Point `json:"face,omitempty"`
|
Face Point `json:"face,omitempty"`
|
||||||
Eyes Points `json:"eyes,omitempty"`
|
Eyes Points `json:"eyes,omitempty"`
|
||||||
Landmarks Points `json:"landmarks,omitempty"`
|
Landmarks Points `json:"landmarks,omitempty"`
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestDetect(t *testing.T) {
|
||||||
"2.jpg": 1,
|
"2.jpg": 1,
|
||||||
"3.jpg": 1,
|
"3.jpg": 1,
|
||||||
"4.jpg": 1,
|
"4.jpg": 1,
|
||||||
"5.jpg": 2,
|
"5.jpg": 1,
|
||||||
"6.jpg": 1,
|
"6.jpg": 1,
|
||||||
"7.jpg": 0,
|
"7.jpg": 0,
|
||||||
"8.jpg": 0,
|
"8.jpg": 0,
|
||||||
|
@ -30,6 +30,7 @@ func TestDetect(t *testing.T) {
|
||||||
"16.jpg": 1,
|
"16.jpg": 1,
|
||||||
"17.jpg": 1,
|
"17.jpg": 1,
|
||||||
"18.jpg": 2,
|
"18.jpg": 2,
|
||||||
|
"19.jpg": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fastwalk.Walk("testdata", func(fileName string, info os.FileMode) error {
|
if err := fastwalk.Walk("testdata", func(fileName string, info os.FileMode) error {
|
||||||
|
@ -40,7 +41,7 @@ func TestDetect(t *testing.T) {
|
||||||
t.Run(fileName, func(t *testing.T) {
|
t.Run(fileName, func(t *testing.T) {
|
||||||
baseName := filepath.Base(fileName)
|
baseName := filepath.Base(fileName)
|
||||||
|
|
||||||
faces, err := Detect(fileName, DefaultDetector())
|
faces, err := Detect(fileName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -58,7 +59,7 @@ func TestDetect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if i, ok := expected[baseName]; ok {
|
if i, ok := expected[baseName]; ok {
|
||||||
assert.Equal(t, len(faces), i)
|
assert.Equal(t, i, len(faces))
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("unknown test result for %s", baseName)
|
t.Errorf("unknown test result for %s", baseName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package face
|
package face
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
pigo "github.com/esimov/pigo/core"
|
pigo "github.com/esimov/pigo/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed cascade/lps
|
||||||
|
var efs embed.FS
|
||||||
|
|
||||||
// FlpCascade holds the binary representation of the facial landmark points cascade files
|
// FlpCascade holds the binary representation of the facial landmark points cascade files
|
||||||
type FlpCascade struct {
|
type FlpCascade struct {
|
||||||
*pigo.PuplocCascade
|
*pigo.PuplocCascade
|
||||||
|
@ -19,7 +23,7 @@ func ReadCascadeDir(plc *pigo.PuplocCascade, path string) (result map[string][]*
|
||||||
cascades, err := efs.ReadDir(path)
|
cascades, err := efs.ReadDir(path)
|
||||||
|
|
||||||
if len(cascades) == 0 {
|
if len(cascades) == 0 {
|
||||||
return nil, errors.New("the cascade directory is empty")
|
return result, errors.New("the cascade directory is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -27,11 +31,20 @@ func ReadCascadeDir(plc *pigo.PuplocCascade, path string) (result map[string][]*
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cascade := range cascades {
|
for _, cascade := range cascades {
|
||||||
cf, err := filepath.Abs(path + "/" + cascade.Name())
|
cf := filepath.Join(path, cascade.Name())
|
||||||
|
|
||||||
|
f, err := efs.ReadFile(cf)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return result, err
|
||||||
}
|
}
|
||||||
flpc, err := plc.UnpackFlp(cf)
|
|
||||||
|
flpc, err := plc.UnpackCascade(f)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
result[cascade.Name()] = append(result[cascade.Name()], &FlpCascade{flpc, err})
|
result[cascade.Name()] = append(result[cascade.Name()], &FlpCascade{flpc, err})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
internal/face/testdata/19.jpg
vendored
Normal file
BIN
internal/face/testdata/19.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
|
@ -9,9 +9,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/classify"
|
"github.com/photoprism/photoprism/internal/classify"
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
"github.com/photoprism/photoprism/internal/face"
|
||||||
"github.com/photoprism/photoprism/internal/meta"
|
"github.com/photoprism/photoprism/internal/meta"
|
||||||
"github.com/photoprism/photoprism/internal/nsfw"
|
"github.com/photoprism/photoprism/internal/nsfw"
|
||||||
"github.com/photoprism/photoprism/internal/query"
|
"github.com/photoprism/photoprism/internal/query"
|
||||||
|
@ -600,6 +602,13 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
||||||
if file.FilePrimary {
|
if file.FilePrimary {
|
||||||
labels := photo.ClassifyLabels()
|
labels := photo.ClassifyLabels()
|
||||||
|
|
||||||
|
if Config().Experimental() && Config().Settings().Features.People {
|
||||||
|
faces := ind.detectFaces(m)
|
||||||
|
|
||||||
|
photo.AddLabels(classify.FaceLabels(len(faces), entity.SrcImage, 10))
|
||||||
|
photo.PhotoPeople = len(faces)
|
||||||
|
}
|
||||||
|
|
||||||
if err := photo.UpdateTitle(labels); err != nil {
|
if err := photo.UpdateTitle(labels); err != nil {
|
||||||
log.Debugf("%s in %s (update title)", err, logName)
|
log.Debugf("%s in %s (update title)", err, logName)
|
||||||
}
|
}
|
||||||
|
@ -759,7 +768,7 @@ func (ind *Index) NSFW(jpeg *MediaFile) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// classifyImage returns all matching labels for a media file.
|
// classifyImage classifies a JPEG image and returns matching labels.
|
||||||
func (ind *Index) classifyImage(jpeg *MediaFile) (results classify.Labels) {
|
func (ind *Index) classifyImage(jpeg *MediaFile) (results classify.Labels) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
@ -812,3 +821,31 @@ func (ind *Index) classifyImage(jpeg *MediaFile) (results classify.Labels) {
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectFaces detects faces in a JPEG image and returns them.
|
||||||
|
func (ind *Index) detectFaces(jpeg *MediaFile) face.Faces {
|
||||||
|
if jpeg == nil {
|
||||||
|
return face.Faces{}
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbName, err := jpeg.Thumbnail(Config().ThumbPath(), "fit_720")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("%s in %s", err, txt.Quote(jpeg.BaseName()))
|
||||||
|
return face.Faces{}
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
faces, err := face.Detect(thumbName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("%s in %s", err, txt.Quote(jpeg.BaseName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
log.Debugf("index: face detection took %s", elapsed)
|
||||||
|
|
||||||
|
return faces
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue