Golang : Surveillance with web camera and OpenCV




Times are bad and the economy in Malaysia is not doing well. People in my neighborhood are worried about the increasing crime rate. They are looking for a cheap solution to monitor their home while they are away.

What they are looking for ... is kinda like home Do-It-Yourself Internet-Of-Things(IoT) project.

The code example that follows is a Golang program that uses off-the-shelf web camera and sends out the images to a web browser via base64 string encoded images. It is an enhancement of previous tutorial ( https://www.socketloop.com/tutorials/golang-activate-web-camera-and-broadcast-out-base64-encoded-images ).

The JavaScript in the main page will auto refresh the images every 2 seconds. It has the ability to identify faces and allow the user to play dog barking sound from the web browser if the user spots "undesirable" visitors.

First, download the MP3 sound file and face detection XML file at:

http://www.orangefreesounds.com/wp-content/uploads/2016/10/Dog-barking-noise.zip

and

https://raw.githubusercontent.com/lazywei/go-opencv/master/samples/haarcascadefrontalfacealt.xml

unzip the file and extract out the mp3 file.

Second, the program will invoke a command-line MP3 player.

If you are running this program on a MacOSX, there's nothing to change. The program uses afplay.

If you are using Windows, see https://lawlessguy.wordpress.com/2015/06/27/update-to-a-command-line-mp3-player-for-windows/

If you are using Linux, replace afplay in the code with mplayer or mpg123

Next, you need get these if you haven't done so.

For MacOSX:

>brew install homebrew/science/opencv

and finally:

go get github.com/lazywei/go-opencv/opencv

Here you go!


 package main

 import (
 "bytes"
 "encoding/base64"
 "fmt"
 "github.com/lazywei/go-opencv/opencv"
 "image/png"
 "net/http"
 "os"
 "os/exec"
 "os/signal"
 "strconv"
 "syscall"
 )

 // global variables
 var (
 webCamera = new(opencv.Capture)
 cascade = new(opencv.HaarCascade)
 )

 func bark(w http.ResponseWriter, r *http.Request) {

 fmt.Println("Barking!")

 // afplay is specific to MacOSX
 // it is a command line audio file player
 // replace with the appropriate player for Linux or Windows

 cmd := exec.Command("afplay", "Dog-barking-noise.mp3")
 err := cmd.Run()

 if err != nil {
 error := `Cannot play mp3 file!`
 w.Write([]byte(error))
 } else {
 html := `<h2>Barked!!</h2>`
 w.Write([]byte(html))
 }

 }

 func homepage(w http.ResponseWriter, r *http.Request) {

 fmt.Println("Camera recording...")

 html := `<!DOCTYPE html>

 <body>

 <head>
 <title>Golang survillance via OpenCV and base64 encoded string images</title>
 <style type="text/css">
 #buttons-area {
 font-family: Arial, Helvetica, sans serif;
 font-size: 30px;
 margin: 50px auto;
 letter-spacing: -0.07em;
 display: block;
 padding: 5px;
 border: solid #cdcdcd 1px;
 width: 700px;
 text-color: white;
 background: #f4f4f4;
 }

 #webcamfeed-area {
 margin: 50px auto;
 padding: 5px;
 border: solid #cdcdcd 1px;
 width: 700px;
 background: blue;
 }

 #bark-area {
 margin: 50px auto;
 padding: 5px;
 border: solid #cdcdcd 1px;
 width: 700px;
 background: linear-gradient(to bottom,#e5e7eb 0,#d8dade 100%);
 }

 button {
 text-align: center;
 width: 48%;
 height: 40px;
 font-size: 18px;
 border-radius: 6px;
 }
 </style>
 <div id="webcamfeed-area"></div>
 <div id="bark-area"></div>

 <div id="buttons-area">
 <h1 style="line-height: 1.25em;">Feed from web camera</h1>
 <button type="button" onclick="bark()">Play barking sound</button>
 </div>
 </body>

 </html>
 <script type="text/javascript">

 function bark() {
 fetch('/bark').then(function(response) {
 return response.text();
 }).then(function(text) {
 // <!DOCTYPE ....
 var barkfeedback = text;
 document.getElementById('bark-area').innerHTML = barkfeedback;
 }).catch(function(err) {
 console.log(err)
 });
 }

 function refresh() {

 // fetch API is not available for Safari...

 fetch('/refresh').then(function(response) {
 return response.text();
 }).then(function(text) {
 // <!DOCTYPE ....
 var img2html = text;
 document.getElementById('webcamfeed-area').innerHTML = img2html;
 }).catch(function(err) {
 console.log(err)
 });
 }
 refresh(); // run on page load
 setInterval(function() {
 refresh() // run after every 2 seconds
 }, 2000);
 </script>`

 w.Write([]byte(html))
 }

 func refresh(w http.ResponseWriter, r *http.Request) {
 //for {
 if webCamera.GrabFrame() {
 imgFrame := webCamera.RetrieveFrame(1)
 if imgFrame != nil {
 // my webcamera is HD. Have to make the image frame
 // smaller, so that it does not blow up the browser.
 imgFrame = opencv.Resize(imgFrame, 700, 0, opencv.CV_INTER_LINEAR)

 // from https://github.com/lazywei/go-opencv/blob/master/samples/webcam_facedetect.go
 // detect faces in image
 faces := cascade.DetectObjects(imgFrame)
 for _, value := range faces {
 // add one green rectangle

 opencv.Rectangle(imgFrame,
 opencv.Point{value.X() + value.Width(), value.Y()},
 opencv.Point{value.X(), value.Y() + value.Height()},
 opencv.NewScalar(0, 255, 0, 0.0), 6, 2, 0)

 // add one red circle just for fun
 // NewScalar(blue, green, red, alpha)...

 opencv.Circle(imgFrame,
 opencv.Point{
 value.X() + (value.Width() / 2),
 value.Y() + (value.Height() / 2),
 },
 value.Width()/3,
 opencv.NewScalar(0, 0, 255, 0.0), 8, 1, 0)

 }

 // convert IplImage(Intel Image Processing Library)
 // to image.Image
 goImgFrame := imgFrame.ToImage()

 // and then convert to []byte
 // with the help of png.Encode() function

 frameBuffer := new(bytes.Buffer)
 //frameBuffer := make([]byte, imgFrame.ImageSize())
 err := png.Encode(frameBuffer, goImgFrame)

 if err != nil {
 panic(err)
 }

 // convert the buffer bytes to base64 string - use buf.Bytes() for new image
 imgBase64Str := base64.StdEncoding.EncodeToString(frameBuffer.Bytes())

 // Embed into an html without PNG file
 img2html := "<html><body><img src=\"data:image/png;base64," + imgBase64Str + "\" /></body></html>"

 w.Header().Set("Content-Type", "text/html")

 w.Write([]byte(fmt.Sprintf(img2html)))
 flusher := w.(http.Flusher) // flush everything
 flusher.Flush() // out to browser

 fmt.Println("Streaming " + strconv.Itoa(len(img2html)) + " bytes.")

 }
 }
 //}

 }

 func main() {

 webCamera = opencv.NewCameraCapture(0)

 if webCamera == nil {
 panic("Unable to open camera")
 }

 // grab the xml file from
 // wget https://raw.githubusercontent.com/lazywei/go-opencv/master/samples/haarcascade_frontalface_alt.xml
 cascade = opencv.LoadHaarClassifierCascade("./haarcascade_frontalface_alt.xml")

 fmt.Println("Server started. Press Ctrl-C to stop server")

 // Catch the Ctrl-C and SIGTERM from kill command

 ch := make(chan os.Signal, 1)

 signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)

 go func() {
 signalType := <-ch
 signal.Stop(ch)
 fmt.Println("Signal type intercepted : ", signalType)
 fmt.Println("Exit command received. Exiting...")
 fmt.Println("Cleaning up ...")
 webCamera.Release()
 os.Exit(0)
 }()

 fmt.Println("Broadcasting...")
 mux := http.NewServeMux()
 mux.HandleFunc("/", homepage)
 mux.HandleFunc("/refresh", refresh)
 mux.HandleFunc("/bark", bark)

 http.ListenAndServe(":8080", mux)
 }

Build the code and execute the binary. Point your browser to localhost:8080 and if everything goes smoothly, you should see something like this :

detect faces and broadcast out base64 string encoded images

NOTES:

Remember to at least protect the page with .htaccess basic authentication if you want to expose the server and allow access from public. Use hard to guess username and password.

Happy coding and barking!

References:

https://github.com/lazywei/go-opencv/blob/master/samples/webcam_facedetect.go

http://osxdaily.com/2010/12/07/command-line-mp3-player-in-mac-os-x/

https://www.socketloop.com/tutorials/golang-activate-web-camera-and-broadcast-out-base64-encoded-images

https://www.socketloop.com/tutorials/golang-encode-image-to-base64-example

https://developers.google.com/web/updates/2015/03/introduction-to-fetch

https://socketloop.com/tutorials/golang-take-screen-shot-of-browser-with-jquery-example

  See also : Golang : Save webcamera frames to video file





By Adam Ng

IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.


Advertisement