Monday, January 19, 2009

Web Camera, Rovio, Remote Surveillance - in 20 minutes

Have you ever wondered how to grab pictures from a web cam into your .NET application. What if that web cam is remote and password protected. What if it is mobile web cam such as Rovio. What if you like to detect motion events and fire some logic to notify you of such motion.

Well, today I would like to walk you through on how to create such application.

Before we begin you need to download AForge if you like to have motion sensing capabilities - http://code.google.com/p/aforge/ . "AForge.NET is a C# framework designed for developers and researchers in the fields of Computer Vision and Artificial Intelligence - image processing, neural networks, genetic algorithms, machine learning, etc."

You would not need the whole package just several classes. I had to modify some of the original AForge classes to suit my needs, but you are welcome to use original provided you comply with licensing requirements.

Part 1 - Figuring out where web cams store their pictures.

In my case I had two types of web cams. TRENDnet Wireless Internet Camera Server TV-IP201W and Rovio which I mentioned earlier. By logging in to my web cam server I could see the following page. This web page is generated by a camera embedded web server. My guess that it is running some sort of windows embedded OS since it is rendering aspx pages, but leave this to the reader to figure out since it is irrelevant for this post.

WebServer

This front page image is updated every time you request a page. So my guess was that camera stores temporary images internally and provides a link to those images. The next step is to figure out the exact uri of the image. This was easily done by opening page source and examining html code. Please see the screen shot below.

Image_link

Yes, that weird number is a relative reference to server's folder. Convert it to full uri and you would get something like http://MyCameraServer.com/goform/capture?12378465237865336 .
This is our first type of cameras.

Now lets explore Rovio, which has a very simple to use set of APIs. There I found that I can just go to something like http://MyCameraServer:MyPort/Jpeg/CamImg0000.jpg , where MyCameraServer:Myport is an address where you set up your Rovio. That's it. Easy.

Part 2 - Connecting to remote server.

To connect to remote web server we would utilize classes WebRequest and WebResponce from System.Net namespace. It is very simple. First we need to create web request and provide URI, then we need to add credentials, then execute request, which returns response as instance of WebResponce class. Later we can access stream reader of web response and read our data.
Below is an example.

Dim request As WebRequest
Dim response As WebResponse
Dim reader As Stream
Dim data(8191) As Byte
Dim count As Integer
Dim total As Integer

request = WebRequest.Create(url)
request.Credentials = New NetworkCredential("login name", "password")
response = request.GetResponse()
reader = response.GetResponseStream()
total = 0

Dim myBitmap As Bitmap = New Bitmap(reader, True)

While True
count = reader.Read(data, 0, 8192)

If count <= 0 Then
Exit While
End If

total += 8192
End While

reader.Close()



Part 3 - Automatic refresh

You probably would not want to refresh this picture every time. So here is what I did, there are many ways, I prefer the quickest one :). Set the timer and refresh the picture by timer. It works great, except now if you set it to refresh too often your UI will freeze. So the idea is to load pictures on a separate thread. And for that I have created this simple class ThreadingHelper.


Imports System.Threading

Public Class ThreadingHelper
Private Shared _currentForm As Form

''----------------------------------------------------------------------------
'' methods related to threading 
''----------------------------------------------------------------------------
Public Delegate Function DelegateBegin(ByVal endCode As DelegateEnd) As DelegateEnd
Public Delegate Sub DelegateEnd()

Public Sub BeginCode(ByVal currentForm As Form, _
ByVal _beginCode As DelegateBegin, _
ByVal _endCode As DelegateEnd)

_currentForm = currentForm

Dim callbackTransfer As AsyncCallback = New AsyncCallback(AddressOf TransferContext)

_beginCode.BeginInvoke(_endCode, callbackTransfer, Nothing)

End Sub


Public Sub TransferContext(ByVal asyncResult As System.IAsyncResult)

Dim asyncResultBegin As Runtime.Remoting.Messaging.AsyncResult
Dim delegateBegin As DelegateBegin
Dim endCode As DelegateEnd

asyncResultBegin = CType(asyncResult, Runtime.Remoting.Messaging.AsyncResult)

delegateBegin = asyncResultBegin.AsyncDelegate

endCode = delegateBegin.EndInvoke(asyncResult)
Try
If _currentForm.InvokeRequired Then
_currentForm.Invoke(endCode)
Else
endCode()
End If
Catch ex As NullReferenceException
''if error occured on a different thread for the purpose of this streaming application we are just going to swallow it.
'' write youe handler if you like 
Catch ex As ObjectDisposedException

Catch ex As Exception


End Try

End Sub
End Class


Here is how to use it.


myThreading = New ThreadingHelper()

myThreading.BeginCode(Me, AddressOf GetPicture, AddressOf UpdatePicture)



Part 4 - Rest of the code.

Imports System.Net
Imports System.IO
Imports MotionDetection

Public Class Form1
Private Const url1 As String = "http://myserver/goform/capture?1078432126434196"
Private Const url2 As String = "http://myserver/goform/capture?1073114869312048"
Private Const url3 As String = "http://myserver/goform/capture?1225387068534147"
Private Const url4 As String = "http://myserver/goform/capture?1225387347966623"
Private Const url5 As String = "http://myserver/goform/capture?1082672364580373"
Private Const url6 As String = "http://myserver/Jpeg/CamImg0000.jpg"

Private cameras(,) As String = { _
{"camera 1", url1}, _
{"camera 2", url2}, _
{"camera 3", url3}, _
{"camera 4", url4}, _
{"camera 5", url5}, _
{"rovio 1", url6} _
}

Private Shared _cameraIndex As Integer = 0
Private Shared _motionLevel As Double = 10.0
Private Shared _detectMotion As Boolean = False
Private Shared globalBitmap As Bitmap
Private Shared _motionDetector As MotionDetector3
Private myThreading As ThreadingHelper

Private Shared urlCurr As String = url1

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

For i As Integer = 0 To (cameras.Length / 2) - 1
ComboBoxCameras.Items.Add(cameras(i, 0))
Next

ComboBoxCameras.SelectedIndex = 0
urlCurr = cameras(ComboBoxCameras.SelectedIndex, 1)

myThreading = New ThreadingHelper()

_motionDetector = New MotionDetector3()
_motionDetector.MotionLevelCalculation = True

Timer1.Start()
End Sub

Private Sub LoadFile(ByVal url As String)
Try
Dim request As WebRequest
Dim response As WebResponse
Dim reader As Stream
Dim data(8191) As Byte
Dim count As Integer
Dim total As Integer

request = WebRequest.Create(url)
request.Credentials = New NetworkCredential("login name", "password")
response = request.GetResponse()
reader = response.GetResponseStream()
total = 0

Dim myBitmap As Bitmap = New Bitmap(reader, True)

While True
count = reader.Read(data, 0, 8192)

If count <= 0 Then
Exit While
End If

total += 8192
End While

reader.Close()

If Not globalBitmap Is Nothing Then
globalBitmap.Dispose()
End If

''to prevent cross threading access to a shared memeber you would need to clone image since it would be stored on the local stack and you would need to update it
globalBitmap = myBitmap.Clone(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.PixelFormat.Format32bppRgb)

If Not myBitmap Is Nothing Then
myBitmap.Dispose()
End If
response.Close()
Catch ex As Exception
'MessageBox.Show(ex.Message)
End Try
End Sub

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Try
Timer1.Stop()
myThreading.BeginCode(Me, AddressOf GetPicture, AddressOf UpdatePicture)
Catch ex As Exception
MessageBox.Show(ex.Message)
Timer1.Start()
End Try

End Sub

Private Function GetPicture(ByVal endCode As ThreadingHelper.DelegateEnd) As ThreadingHelper.DelegateEnd
Try
LoadFile(urlCurr)

Catch ex As Exception

End Try

Return endCode
End Function

Private Sub UpdatePicture()
Try
If Not PictureBox1.Image Is Nothing Then
PictureBox1.Image.Dispose()
End If

Dim myBitmap As Bitmap

myBitmap = globalBitmap.Clone(New Rectangle(0, 0, globalBitmap.Width, globalBitmap.Height), Imaging.PixelFormat.Format32bppRgb)

If _detectMotion Then
_motionDetector.ProcessFrame(myBitmap)
If (_motionDetector.MotionLevel * 100 > _motionLevel) Then
_motionLevel += 10
TextBoxMotionLevel.Text = _motionLevel.ToString()
MessageBox.Show("Motion detected, here we can automatically send e-mail and deliver files over network somehow.")
_motionDetector.Reset()
End If
End If

PictureBox1.Image = myBitmap


Timer1.Start()
Catch ex As Exception
MessageBox.Show(ex.Message)
Timer1.Start()
End Try

End Sub

Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
Timer1.Stop()
If Not PictureBox1.Image Is Nothing Then
PictureBox1.Image.Dispose()
End If
End Sub

Private Sub ComboBoxCameras_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBoxCameras.SelectedIndexChanged
urlCurr = cameras(ComboBoxCameras.SelectedIndex, 1)
_cameraIndex = ComboBoxCameras.SelectedIndex
End Sub


Private Sub CheckBoxDetectMotion_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CheckBoxDetectMotion.CheckedChanged
_detectMotion = CheckBoxDetectMotion.Checked
End Sub

Private Sub ButtonSetMotionLevel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonSetMotionLevel.Click
Dim newLevel As Double
If (Double.TryParse(TextBoxMotionLevel.Text, newLevel)) Then
_motionLevel = newLevel
End If
End Sub

Private Sub ButtonResetDetector_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonResetDetector.Click
_motionDetector.Reset()
End Sub
End Class


And here is the result:
We could have almost streaming video :). The picture appears dark, because I have no control over the light switch in a remote facility. but hopefully you get the idea.


WebCam

And finally with motion detector.

MotionDetector

You could set level of the motion in % and then write your custom code and what to do about detected motion. For example you could send an e-mail with picture attached or using Rovio you could send a specific sound command and alert an "intruder" :).

In my next post I will provide a little more explanation on what modifications I made to AForge library.

This was a very nice ISolvable problem :)

1 comment:

  1. i need contact you about software
    please rmrbranco at_ gmail dot_ com

    ReplyDelete