wasm: updated face triangulation demo

This commit is contained in:
Endre Simo
2022-04-30 08:51:23 +03:00
parent fa686a7849
commit 768c1efdb6
4 changed files with 146 additions and 45 deletions

43
draw/ellipse.go Normal file
View File

@@ -0,0 +1,43 @@
package draw
import (
"image"
"image/color"
)
type Ellipse struct {
Cx int // center x
Cy int // center y
Rx int // semi-major axis x
Ry int // semi-minor axis y
Width int
Height int
}
func (e *Ellipse) ColorModel() color.Model {
return color.AlphaModel
}
func (e *Ellipse) Bounds() image.Rectangle {
min := image.Point{
X: e.Cx - e.Rx,
Y: e.Cy - e.Ry,
}
max := image.Point{
X: e.Cx + e.Rx,
Y: e.Cy + e.Ry,
}
return image.Rectangle{Min: min, Max: max} // size of just mask
}
func (e *Ellipse) At(x, y int) color.Color {
// Equation of ellipse
p1 := float64((x-e.Cx)*(x-e.Cx)) / float64(e.Rx*e.Rx)
p2 := float64((y-e.Cy)*(y-e.Cy)) / float64(e.Ry*e.Ry)
eqn := p1 + p2
if eqn <= 1 {
// inside
return color.Alpha{255}
}
return color.Alpha{0}
}

14
go.mod
View File

@@ -1,9 +1,17 @@
module github.com/esimov/pigo-wasm-demos
go 1.13
go 1.18
require (
github.com/esimov/pigo v1.4.4
github.com/esimov/triangle v1.2.3
github.com/esimov/pigo v1.4.5
github.com/esimov/triangle v1.3.0
github.com/esimov/triangle/v2 v2.0.0
github.com/fogleman/gg v1.3.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
)
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 // indirect
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
)

18
go.sum
View File

@@ -1,20 +1,22 @@
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/esimov/pigo v1.4.4 h1:Ab9uYXw0F0Y7OyZQQGwJjktl5LlHdL3ovdXe/T0juK8=
github.com/esimov/pigo v1.4.4/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w=
github.com/esimov/triangle v1.2.3 h1:vFFrsgd92inC78WstyHbvnX5rvu4cs6E/qdNR/RqQhw=
github.com/esimov/triangle v1.2.3/go.mod h1:vcz3ofMgK5W5T2E7bFIoU4i60UX1RdqT7/0xGEbnOKw=
github.com/fogleman/gg v1.0.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/esimov/pigo v1.4.5 h1:ySG0QqMh02VNALvHnx04L1ScRu66N6XA5vLLga8GiLg=
github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w=
github.com/esimov/triangle v1.3.0 h1:8637fdGbeNNoax1BuXFPc5+lTCvi/XWKW8FNuehCSC4=
github.com/esimov/triangle v1.3.0/go.mod h1:82lxRK5Bg56WTzWlp23OzXCIf1V5Q9O8Y2TcVpQ00eo=
github.com/esimov/triangle/v2 v2.0.0 h1:bUAfG42rcQeQYLvSnvwcerlts8z8z7r3FCvdBppfRcA=
github.com/esimov/triangle/v2 v2.0.0/go.mod h1:xv6cfzSPsHctnxhBG67GKPXZutUdmTusHV2vwbXHZIk=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
golang.org/x/image v0.0.0-20171214225156-12117c17ca67/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 h1:K3x+yU+fbot38x5bQbU2QqUAVyYLEktdNH2GxZLnM3U=
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -4,12 +4,15 @@ import (
"fmt"
"image"
"image/color"
"log"
"image/draw"
"math"
"sync"
"syscall/js"
"github.com/esimov/pigo-wasm-demos/detector"
"github.com/esimov/triangle"
ellipse "github.com/esimov/pigo-wasm-demos/draw"
triangle "github.com/esimov/triangle/v2"
"golang.org/x/sync/errgroup"
)
// Canvas struct holds the Javascript objects needed for the Canvas creation
@@ -17,6 +20,7 @@ type Canvas struct {
done chan struct{}
succCh chan struct{}
errCh chan error
lock sync.Mutex
// DOM elements
window js.Value
@@ -116,8 +120,9 @@ func (c *Canvas) Render() error {
if err != nil {
return err
}
c.renderer = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
go func() error {
c.window.Get("stats").Call("begin")
c.reqID = c.window.Call("requestAnimationFrame", c.renderer)
@@ -137,9 +142,12 @@ func (c *Canvas) Render() error {
data = make([]byte, len(data))
res := det.DetectFaces(gray, height, width)
c.drawDetection(res)
if err := c.drawDetection(res); err != nil {
return err
}
c.window.Get("stats").Call("end")
return nil
}()
return nil
})
@@ -272,18 +280,28 @@ func (c *Canvas) imgToPix(img image.Image) []uint8 {
}
// triangulate triangulates the detected face region
func (c *Canvas) triangulate(data []uint8, dets []int) []uint8 {
func (c *Canvas) triangulate(unionMask *image.NRGBA, data []uint8, dets []int) ([]uint8, error) {
// Converts the array buffer to an image
img := c.pixToImage(data, int(float64(dets[2])*0.85))
res, _, _, err := c.triangle.Draw(img, nil, func() {})
// Call the face triangulation algorithm
triangled, _, _, err := c.triangle.Draw(img, *c.processor, func() {})
if err != nil {
log.Fatal(err.Error())
return nil, err
}
return c.imgToPix(res)
// Paste triangled image into the face template
c.lock.Lock()
draw.Draw(c.frame, img.Bounds(), triangled, image.Point{}, draw.Over)
c.lock.Unlock()
draw.DrawMask(img.(draw.Image), img.Bounds(), c.frame, image.Point{}, unionMask, image.Point{}, draw.Over)
return c.imgToPix(img), nil
}
// drawDetection draws the detected faces and eyes.
func (c *Canvas) drawDetection(dets [][]int) {
func (c *Canvas) drawDetection(dets [][]int) error {
c.processor.MaxPoints = c.trianglePoints
c.processor.Grayscale = c.isGrayScaled
c.processor.StrokeWidth = c.strokeWidth
@@ -292,39 +310,69 @@ func (c *Canvas) drawDetection(dets [][]int) {
c.triangle = &triangle.Image{*c.processor}
// Create a Union mask
c.lock = sync.Mutex{}
unionMask := image.NewNRGBA(image.Rect(0, 0, c.windowSize.width, c.windowSize.height))
g := new(errgroup.Group)
for i := 0; i < len(dets); i++ {
if dets[i][3] > 50 {
c.ctx.Call("beginPath")
c.ctx.Set("lineWidth", 2)
c.ctx.Set("strokeStyle", "rgba(255, 0, 0, 0.5)")
g.Go(func() error {
if dets[i][3] > 50 {
c.ctx.Call("beginPath")
c.ctx.Set("lineWidth", 2)
c.ctx.Set("strokeStyle", "rgba(255, 0, 0, 0.5)")
row, col, scale := dets[i][1], dets[i][0], dets[i][2]
col = col + int(float64(col)*0.1)
scale = int(float64(scale) * 0.85)
row, col, scale := dets[i][1], dets[i][0], dets[i][2]
col = col + int(float64(col)*0.1)
scale = int(float64(scale) * 0.85)
// Substract the image under the detected face region.
imgData := make([]byte, scale*scale*4)
subimg := c.ctx.Call("getImageData", row-scale/2, col-scale/2, scale, scale).Get("data")
uint8Arr := js.Global().Get("Uint8Array").New(subimg)
js.CopyBytesToGo(imgData, uint8Arr)
// Substract the image under the detected face region.
imgData := make([]byte, scale*scale*4)
subimg := c.ctx.Call("getImageData", row-scale/2, col-scale/2, scale, scale).Get("data")
uint8Arr := js.Global().Get("Uint8Array").New(subimg)
js.CopyBytesToGo(imgData, uint8Arr)
// Triangulate the detected face region.
buffer := c.triangulate(imgData, dets[i])
uint8Arr = js.Global().Get("Uint8Array").New(scale * scale * 4)
js.CopyBytesToJS(uint8Arr, buffer)
// Add to union mask
ellipse := &ellipse.Ellipse{
Cx: row,
Cy: col,
Rx: int(float64(scale) * 0.8 / 2),
Ry: int(float64(scale) * 0.8 / 1.6),
Width: c.windowSize.width,
Height: c.windowSize.height,
}
uint8Clamped := js.Global().Get("Uint8ClampedArray").New(uint8Arr)
rawData := js.Global().Get("ImageData").New(uint8Clamped, scale)
c.lock.Lock()
draw.Draw(unionMask, unionMask.Bounds(), ellipse, image.Point{}, draw.Over)
c.lock.Unlock()
// Replace the underlying face region with the triangulated image.
c.ctx.Call("putImageData", rawData, row-scale/2, col-scale/2)
// Triangulate the detected face region.
buffer, err := c.triangulate(unionMask, imgData, dets[i])
if err != nil {
return err
}
uint8Arr = js.Global().Get("Uint8Array").New(scale * scale * 4)
js.CopyBytesToJS(uint8Arr, buffer)
if c.showFrame {
c.ctx.Call("rect", row-scale/2, col-scale/2, scale, scale)
c.ctx.Call("stroke")
uint8Clamped := js.Global().Get("Uint8ClampedArray").New(uint8Arr)
rawData := js.Global().Get("ImageData").New(uint8Clamped, scale)
// Replace the underlying face region with the triangulated image.
c.ctx.Call("putImageData", rawData, row-scale/2, col-scale/2)
if c.showFrame {
c.ctx.Call("rect", row-scale/2, col-scale/2, scale, scale)
c.ctx.Call("stroke")
}
}
}
return nil
})
}
if err := g.Wait(); err != nil {
return err
}
return nil
}
// detectKeyPress listen for the keypress event and retrieves the key code.