diff --git a/wasm/canvas/canvas.go b/wasm/canvas/canvas.go index 4755af5..bc4d36e 100644 --- a/wasm/canvas/canvas.go +++ b/wasm/canvas/canvas.go @@ -2,6 +2,7 @@ package canvas import ( "fmt" + "math" "syscall/js" "github.com/esimov/pigo/wasm/detector" @@ -17,7 +18,7 @@ type Canvas struct { window js.Value doc js.Value body js.Value - windowSize struct{ width, height float64 } + windowSize struct{ width, height int } // Canvas properties canvas js.Value @@ -37,8 +38,8 @@ func NewCanvas() *Canvas { c.doc = c.window.Get("document") c.body = c.doc.Get("body") - c.windowSize.width = c.window.Get("innerWidth").Float() - c.windowSize.height = c.window.Get("innerHeight").Float() + c.windowSize.width = c.window.Get("innerWidth").Int() + c.windowSize.height = c.window.Get("innerHeight").Int() c.canvas = c.doc.Call("createElement", "canvas") c.canvas.Set("width", c.windowSize.width) @@ -52,27 +53,31 @@ func NewCanvas() *Canvas { // Render calls the `requestAnimationFrame` Javascript function in asynchronous mode. func (c *Canvas) Render() { - var data = make([]byte, int(c.windowSize.width*c.windowSize.height*4)) + var data = make([]byte, c.windowSize.width*c.windowSize.height*4) c.done = make(chan struct{}) det := detector.NewDetector() - c.renderer = js.FuncOf(func(this js.Value, args []js.Value) interface{} { - go func() { - c.reqID = c.window.Call("requestAnimationFrame", c.renderer) - // Draw the webcam frame to the canvas element - c.ctx.Call("drawImage", c.video, 0, 0) - rgba := c.ctx.Call("getImageData", 0, 0, c.windowSize.width, c.windowSize.height).Get("data") - c.Log(rgba.Get("length").Int()) + if err := det.UnpackCascades(); err == nil { + c.renderer = js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go func() { + c.reqID = c.window.Call("requestAnimationFrame", c.renderer) + // Draw the webcam frame to the canvas element + c.ctx.Call("drawImage", c.video, 0, 0) + width, height := c.windowSize.width, c.windowSize.height + rgba := c.ctx.Call("getImageData", 0, 0, width, height).Get("data") + //c.Log(rgba.Get("length").Int()) - uint8Arr := js.Global().Get("Uint8Array").New(rgba) - js.CopyBytesToGo(data, uint8Arr) - res := det.FindFaces(data) - fmt.Println(res) - }() - return nil - }) - c.window.Call("requestAnimationFrame", c.renderer) - <-c.done + uint8Arr := js.Global().Get("Uint8Array").New(rgba) + js.CopyBytesToGo(data, uint8Arr) + pixels := c.rgbaToGrayscale(data) + res := det.DetectFaces(pixels, width, height) + fmt.Println(res) + }() + return nil + }) + c.window.Call("requestAnimationFrame", c.renderer) + <-c.done + } } // Stop stops the rendering. @@ -146,6 +151,31 @@ func (c *Canvas) StartWebcam() (*Canvas, error) { } } +func (c *Canvas) rgbaToGrayscale(data []uint8) []uint8 { + rows, cols := c.windowSize.width, c.windowSize.height + + // for i := 0; i < rows*cols; i++ { + // r := data[i*4+0] + // g := data[i*4+1] + // b := data[i*4+2] + // var gray = uint8(math.Round(0.2126*float64(r) + 0.7152*float64(g) + 0.0722*float64(b))) + // data[i*4+0] = gray + // data[i*4+1] = gray + // data[i*4+2] = gray + // } + + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + // gray = 0.2*red + 0.7*green + 0.1*blue + data[r*cols+c] = uint8(math.Round( + 0.2126*float64(data[r*4*cols+4*c+0]) + + 0.7152*float64(data[r*4*cols+4*c+1]) + + 0.0722*float64(data[r*4*cols+4*c+2]))) + } + } + return data +} + // Log calls the `console.log` Javascript function func (c *Canvas) Log(args ...interface{}) { c.window.Get("console").Call("log", args...) diff --git a/wasm/detector/detector.go b/wasm/detector/detector.go index 8c9ee0d..d80220e 100644 --- a/wasm/detector/detector.go +++ b/wasm/detector/detector.go @@ -1,7 +1,8 @@ package detector import ( - "log" + "errors" + "fmt" pigo "github.com/esimov/pigo/core" ) @@ -27,8 +28,42 @@ var ( mouthCascade = []string{"lp93", "lp84", "lp82", "lp81"} ) -func (d *Detector) FindFaces(pixels []uint8) [][]int { - results := d.clusterDetection(pixels, 480, 640) +func (d *Detector) UnpackCascades() error { + p := pigo.NewPigo() + + cascade, err = d.FetchCascade("https://raw.githubusercontent.com/esimov/pigo/master/cascade/facefinder") + if err != nil { + return errors.New("error reading the facefinder cascade file") + } + // Unpack the binary file. This will return the number of cascade trees, + // the tree depth, the threshold and the prediction from tree's leaf nodes. + faceClassifier, err = p.Unpack(cascade) + if err != nil { + return errors.New("error unpacking the facefinder cascade file") + } + + plc := pigo.NewPuplocCascade() + + puplocCascade, err = d.FetchCascade("https://raw.githubusercontent.com/esimov/pigo/master/cascade/puploc") + if err != nil { + return errors.New("error reading the puploc cascade file") + } + + puplocClassifier, err = plc.UnpackCascade(puplocCascade) + if err != nil { + return errors.New("error unpacking the puploc cascade file") + } + + flpcs, err = d.parseCascadeFiles("https://raw.githubusercontent.com/esimov/pigo/master/cascade/lps/") + if err != nil { + return errors.New("error unpacking the facial landmark points detection cascades") + } + return nil +} + +func (d *Detector) DetectFaces(pixels []uint8, width, height int) [][]int { + results := d.clusterDetection(pixels, width, height) + fmt.Println(results) dets := make([][]int, len(results)) for i := 0; i < len(results); i++ { @@ -92,56 +127,21 @@ func (d *Detector) FindFaces(pixels []uint8) [][]int { // clusterDetection runs Pigo face detector core methods // and returns a cluster with the detected faces coordinates. -func (d *Detector) clusterDetection(pixels []uint8, rows, cols int) []pigo.Detection { - det := NewDetector() +func (d *Detector) clusterDetection(pixels []uint8, width, height int) []pigo.Detection { imgParams = &pigo.ImageParams{ Pixels: pixels, - Rows: rows, - Cols: cols, - Dim: cols, + Rows: width, + Cols: height, + Dim: height, } cParams := pigo.CascadeParams{ - MinSize: 60, - MaxSize: 600, + MinSize: 100, + MaxSize: 1200, ShiftFactor: 0.1, ScaleFactor: 1.1, ImageParams: *imgParams, } - // Ensure that the face detection classifier is loaded only once. - if len(cascade) == 0 { - cascade, err = det.FetchCascade("https://raw.githubusercontent.com/esimov/pigo/master/cascade/facefinder") - if err != nil { - det.Log("Error reading the cascade file: %v", err) - } - p := pigo.NewPigo() - - // Unpack the binary file. This will return the number of cascade trees, - // the tree depth, the threshold and the prediction from tree's leaf nodes. - faceClassifier, err = p.Unpack(cascade) - if err != nil { - log.Fatalf("Error unpacking the cascade file: %s", err) - } - } - - // Ensure that we load the pupil localization cascade only once - if len(puplocCascade) == 0 { - puplocCascade, err = det.FetchCascade("https://raw.githubusercontent.com/esimov/pigo/master/cascade/puploc") - if err != nil { - det.Log("Error reading the puploc cascade file: %v", err) - } - - puplocClassifier, err = puplocClassifier.UnpackCascade(puplocCascade) - if err != nil { - log.Fatalf("Error unpacking the puploc cascade file: %s", err) - } - - flpcs, err = d.parseCascadeFiles("https://raw.githubusercontent.com/esimov/pigo/master/cascade/lps/") - if err != nil { - log.Fatalf("Error unpacking the facial landmark detection cascades: %s", err) - } - } - // Run the classifier over the obtained leaf nodes and return the detection results. // The result contains quadruplets representing the row, column, scale and detection score. dets := faceClassifier.RunCascade(cParams, 0.0) diff --git a/wasm/detector/fetch.go b/wasm/detector/fetch.go index 0a3a7ab..f3788fc 100644 --- a/wasm/detector/fetch.go +++ b/wasm/detector/fetch.go @@ -34,6 +34,7 @@ func (d *Detector) FetchCascade(url string) ([]byte, error) { response.Call("arrayBuffer").Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} { go func() { buffer := args[0] + fmt.Println(buffer.Get("byteLength").Int()) uint8Array := js.Global().Get("Uint8Array").New(buffer) jsbuf := make([]byte, uint8Array.Get("length").Int())