| package detector |
|
|
| import ( |
| "bytes" |
| "embed" |
| "encoding/base64" |
| "fmt" |
| "image" |
| "image/color" |
| "image/jpeg" |
| _ "image/png" |
| "math" |
| "os" |
| "sort" |
| "testing" |
|
|
| pigo "github.com/esimov/pigo/core" |
| "github.com/stretchr/testify/assert" |
| "github.com/stretchr/testify/require" |
| "golang.org/x/image/draw" |
| "golang.org/x/image/font" |
| "golang.org/x/image/font/basicfont" |
| "golang.org/x/image/math/fixed" |
|
|
| "github.com/metatube-community/metatube-sdk-go/common/cluster" |
| "github.com/metatube-community/metatube-sdk-go/detector/internal/position" |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| var fs embed.FS |
|
|
| const ( |
| xRatio = 0.0 |
| yRatio = 1.8 |
| ) |
|
|
| func TestDetectMainFacePosition(t *testing.T) { |
| for _, unit := range []struct { |
| filename string |
| position float64 |
| imgRatio float64 |
| advanced bool |
| }{ |
| |
| {filename: "809ee47a17a7938ebd6d908244b962c8", position: 0.40, imgRatio: xRatio, advanced: true}, |
| {filename: "c7806a2f581012eb71dce597f682c7a2", position: 0.22, imgRatio: xRatio, advanced: true}, |
| {filename: "aa237b3a2bfd35dbe10c386c7ac777ae", position: 0.30, imgRatio: xRatio, advanced: true}, |
| {filename: "c2a6bf02b748bb460a0dbb550f39c635", position: 0.70, imgRatio: xRatio, advanced: true}, |
| {filename: "1d8aaf63245426c4a32720bdbf33a651", position: 0.80, imgRatio: xRatio, advanced: true}, |
| {filename: "e953fce3bf5ec5746ead8954bec758e0", position: 0.30, imgRatio: xRatio, advanced: true}, |
| {filename: "d054f170d52c83a773571675d954e3bb", position: 0.75, imgRatio: xRatio, advanced: true}, |
| {filename: "3685c2648be7eeeaa2cef0118873a55f", position: 0.60, imgRatio: xRatio, advanced: true}, |
| {filename: "7848e5995a58df9d063df8543c50c943", position: 0.20, imgRatio: xRatio, advanced: true}, |
| {filename: "c0c99e28da91693a27de2beb6dfd7161", position: 0.50, imgRatio: xRatio, advanced: true}, |
| {filename: "6dbe5b2d7d7056f3b60c6d05f5176529", position: 0.25, imgRatio: xRatio, advanced: true}, |
| {filename: "369993051097480935eadf1f468eaadb", position: 0.70, imgRatio: xRatio, advanced: true}, |
| {filename: "d8df07a8312543f638373eb5921f896d", position: 0.15, imgRatio: xRatio, advanced: true}, |
| {filename: "e8e85575a04d75d2bc29abb4bb7fb447", position: 0.25, imgRatio: xRatio, advanced: true}, |
| {filename: "c977809e691fc2037f3a9279068720c2", position: 0.70, imgRatio: xRatio, advanced: true}, |
| {filename: "e1c5fce943a4ba36576607eaa585b9d8", position: 0.90, imgRatio: xRatio, advanced: true}, |
| {filename: "345a376e579ff02a518b831b1b2b4602", position: 0.20, imgRatio: xRatio, advanced: true}, |
| {filename: "f100611a90fa024c73132457fa77da36", position: 0.65, imgRatio: xRatio, advanced: true}, |
| {filename: "e5ff5d6966391409a0fed7d3446b12aa", position: 0.60, imgRatio: xRatio, advanced: true}, |
| {filename: "068b7fb0c8e3953ff5ed25fe00fc22fd", position: 0.25, imgRatio: xRatio, advanced: true}, |
| {filename: "6335e1276cd7edc191de3768dd62aa03", position: 0.15, imgRatio: xRatio, advanced: true}, |
| {filename: "4307f4c6826a88936e6e4351d70195cb", position: 0.45, imgRatio: xRatio, advanced: true}, |
| {filename: "eec27d560038e3367afe42f4ffa7a8e6", position: 0.65, imgRatio: xRatio, advanced: true}, |
| {filename: "263a3cc91c74673957ea9ca7dbac11f4", position: 0.15, imgRatio: xRatio, advanced: true}, |
| {filename: "bfc6d0dcf7d9750d13d3c52cac84ed9a", position: 0.20, imgRatio: xRatio, advanced: true}, |
| {filename: "db4aec6ce163c3113473af00848f717a", position: 0.85, imgRatio: xRatio, advanced: true}, |
| {filename: "a6d7e2f816aae0c22150688489491d21", position: 0.60, imgRatio: xRatio, advanced: true}, |
| {filename: "91271e4f1c1369ab3f06da9b1175d450", position: 0.70, imgRatio: xRatio, advanced: true}, |
| {filename: "2c1cc55118d22b39dbbae5c8fca2aa1f", position: 0.40, imgRatio: xRatio, advanced: true}, |
| {filename: "f88aa397bf3ea9df387af2de6d12a6c7", position: 0.20, imgRatio: xRatio, advanced: true}, |
| {filename: "21939b16a2dc22be9a4035f04da350db", position: 0.25, imgRatio: xRatio, advanced: true}, |
| {filename: "7e655977ad687e683815567b8081d9f9", position: 0.55, imgRatio: xRatio, advanced: true}, |
| {filename: "3ed1e1a46f25375ba3478d72cd2b9958", position: 0.28, imgRatio: xRatio, advanced: true}, |
| |
| {filename: "3ed1e1a46f25375ba3478d72cd2b9958", position: 0.50, imgRatio: yRatio, advanced: true}, |
| {filename: "eec27d560038e3367afe42f4ffa7a8e6", position: 0.20, imgRatio: yRatio, advanced: true}, |
| {filename: "263a3cc91c74673957ea9ca7dbac11f4", position: 0.25, imgRatio: yRatio, advanced: true}, |
| {filename: "bfc6d0dcf7d9750d13d3c52cac84ed9a", position: 0.24, imgRatio: yRatio, advanced: true}, |
| {filename: "db4aec6ce163c3113473af00848f717a", position: 0.25, imgRatio: yRatio, advanced: true}, |
| {filename: "c977809e691fc2037f3a9279068720c2", position: 0.30, imgRatio: yRatio, advanced: true}, |
| {filename: "1784b7cff949300740437e4a777b9c14", position: 0.25, imgRatio: yRatio, advanced: false}, |
| {filename: "5771fe21c5304bb2e164a278f3dbfc39", position: 0.29, imgRatio: yRatio, advanced: false}, |
| {filename: "99037fa52996b8da6f8e4a630be1c0ff", position: 0.26, imgRatio: yRatio, advanced: false}, |
| |
| |
| |
| |
| |
| |
| } { |
| t.Run(unit.filename, func(t *testing.T) { |
| data, err := fs.ReadFile("testdata/" + unit.filename) |
| require.NoError(t, err) |
|
|
| decoded, err := base64.StdEncoding.DecodeString(string(data)) |
| require.NoError(t, err) |
|
|
| img, _, err := image.Decode(bytes.NewReader(decoded)) |
| require.NoError(t, err) |
|
|
| var ( |
| innerImg image.Image |
| innerFaces []pigo.Detection |
| innerGroups []cluster.Group[position.WeightedVector, float64] |
| ) |
|
|
| pos, found := FindPrimaryFaceAxisRatio( |
| img, unit.imgRatio, unit.advanced, |
| |
| func(img image.Image, faces []pigo.Detection, groups []cluster.Group[position.WeightedVector, float64]) { |
| innerImg = img |
| innerFaces = faces |
| innerGroups = groups |
| }, |
| ) |
|
|
| |
| for _, g := range innerGroups { |
| weight := 0.0 |
| for _, v := range g.Items { |
| weight += v.Weight() |
| } |
| t.Logf( |
| "total:%d, weight:%.2f, avePos=%s, allPos=%s", |
| len(g.Items), weight, |
| position.WeightedAverageVector(g.Items), g.Items, |
| ) |
| } |
|
|
| |
| sort.SliceStable(innerFaces, func(i, j int) bool { |
| return computeFaceWeight(innerFaces[i]) > |
| computeFaceWeight(innerFaces[j]) |
| }) |
| |
| for _, face := range innerFaces { |
| t.Logf( |
| "%v, weight=%.2f, pos=%s", |
| face, |
| computeFaceWeight(face), |
| extractFaceVector(innerImg, face), |
| ) |
| } |
|
|
| if assert.True(t, found) { |
| t.Logf("detected position: %f", pos) |
| } |
|
|
| if !found || !assert.LessOrEqualf(t, |
| math.Abs(unit.position-pos), tolerance, |
| "expect pos=%.2f, but got pos=%.2f", unit.position, pos) { |
| |
| debugImg := drawBoxes(innerImg, innerFaces) |
| saveImage(unit.filename, debugImg) |
| } |
| }) |
| } |
| } |
|
|
| func drawBoxes(img image.Image, dets []pigo.Detection) image.Image { |
| rgba := image.NewRGBA(img.Bounds()) |
| draw.Draw(rgba, rgba.Bounds(), img, image.Point{}, draw.Src) |
|
|
| red := color.RGBA{R: 255, G: 0, B: 0, A: 255} |
| blue := color.RGBA{R: 0, G: 0, B: 255, A: 255} |
| yellow := color.RGBA{R: 255, G: 255, B: 0, A: 255} |
|
|
| _ = blue |
| _ = yellow |
|
|
| for _, m := range dets { |
| x0 := m.Col - m.Scale/2 |
| y0 := m.Row - m.Scale/2 |
| x1 := m.Col + m.Scale/2 |
| y1 := m.Row + m.Scale/2 |
|
|
| if x0 < 0 { |
| x0 = 0 |
| } |
| if y0 < 0 { |
| y0 = 0 |
| } |
| if x1 >= rgba.Bounds().Dx() { |
| x1 = rgba.Bounds().Dx() - 1 |
| } |
| if y1 >= rgba.Bounds().Dy() { |
| y1 = rgba.Bounds().Dy() - 1 |
| } |
|
|
| |
| for x := x0; x <= x1; x++ { |
| rgba.Set(x, y0, red) |
| rgba.Set(x, y1, red) |
| } |
| for y := y0; y <= y1; y++ { |
| rgba.Set(x0, y, red) |
| rgba.Set(x1, y, red) |
| } |
|
|
| |
| label := fmt.Sprintf("(%d,%d,%d,%.2f)", m.Col, m.Row, m.Scale, m.Q) |
| point := fixed.Point26_6{ |
| X: fixed.I(x0 + 2), |
| Y: fixed.I(y0 + 12), |
| } |
| d := &font.Drawer{ |
| Dst: rgba, |
| Src: image.NewUniform(yellow), |
| Face: basicfont.Face7x13, |
| Dot: point, |
| } |
| d.DrawString(label) |
| } |
|
|
| return rgba |
| } |
|
|
| func saveImage(name string, img image.Image) { |
| outputFile := fmt.Sprintf("%s.jpg", name) |
| f, err := os.Create(outputFile) |
| if err != nil { |
| panic(err) |
| } |
| defer f.Close() |
| _ = jpeg.Encode(f, img, nil) |
| } |
|
|