mirror of
https://github.com/esimov/pigo-wasm-demos.git
synced 2025-09-26 20:31:20 +08:00
wasm: updated face triangulation demo
This commit is contained in:
43
draw/ellipse.go
Normal file
43
draw/ellipse.go
Normal 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
14
go.mod
@@ -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
18
go.sum
@@ -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=
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user