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 :
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-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
Tutorials
+7.3k Android Studio : AlertDialog to get user attention example
+8.4k Golang : Set or add headers for many or different handlers
+11.4k SSL : The certificate is not trusted because no issuer chain was provided
+8.6k Golang : Find network service name from given port and protocol
+15.4k Golang : Convert date format and separator yyyy-mm-dd to dd-mm-yyyy
+9.7k Golang : Get escape characters \u form from unicode characters
+8k Golang : Configure Apache and NGINX to access your Go service example
+13.2k Golang : Read XML elements data with xml.CharData example
+21.8k Golang : Securing password with salt
+20.9k Golang : How to get time zone and load different time zone?
+8.8k Golang : Intercept and compare HTTP response code example
+8.7k Golang : Go as a script or running go with shebang/hashbang style