Motion detection using a webcam, Python, OpenCV and Differential Images

Categories: Computer-Vision, Tutorials

This tutotial will show how simple it is to implement motion detection using Differential Images.  As an example we capture the input of a webcam and visualize the motions in it. I recorded a short video of what we want to achieve:

Windows (bad webcam): Click here.

Linux (better webcam): Click here.

Requirements

You need Python 2.7 and OpenCV 2.3 or higher. And of course you need a webcam. 🙂

You can download precompiled OpenCV-bindings for windows here.

Capturing and showing webcam-input

First we want to capture the webcam stream. This is pretty simple as OpenCV offers this feature already:

import cv2
cam = cv2.VideoCapture(0)
s, img = cam.read()

winName = "Movement Indicator"
cv2.namedWindow(winName, cv2.CV_WINDOW_AUTOSIZE)

while s:
  cv2.imshow( winName,img )

  s, img = cam.read()

  key = cv2.waitKey(10)<br>
  if key == 27:<br>
    cv2.destroyWindow(winName)
    break

print "Goodbye"

This script will create a window showing your actual webcam input. You can close the window using "ESCAPE". What we do in detail is binding the first capturing device to a VideoCapture-Object and read it image by image with cam.read(). With imshow() we show the previously read image in the Window we created using namedWindow(). waitKey() waits 10ms for a user-input. If the pressed key is "ESCAPE" we destroy the window and break the while-loop.

Differential Images

Differential Images are the result of the subtraction of two images:

g_{dif}(x,y) = g_1(x,y) - g_2(x,y)

So as you can see a differential image shows the difference between two images. With those images you can make movement visible.

In our script we use a differential image calculated from three consecutive images I_{t-1}, I_{t} and I_{t+1}. The advantage of this is that  the uninteresting background is removed from the result:

 \begin{array}{l c r} \Delta I_1=I_{t+1} - I_t & \Delta I_2=I_{t} - I_{t-1} & \Delta I = \Delta I_1 \wedge \Delta I_2 \end{array}

OpenCV offers the possibility to subtract two images from each other using absdiff(). Also logical operations on two images is already implemented. We use the method bitwise_and() to achieve the final differential image. In python it looks like this:

def diffImg(t0, t1, t2):
  d1 = cv2.absdiff(t2, t1)
  d2 = cv2.absdiff(t1, t0)
  return cv2.bitwise_and(d1, d2)<br><br>

And now?

The last thing we have to do is bringing the differential image function into our previous script. Before the loop starts we read the first three images t_minus, t and t_plus and convert them into greyscale images as we dont need color information. With those images it is possible to start calculating differential images. After showing the differential image, we just have to get rid of the oldest image and read the next one. The final script looks like this:

import cv2

def diffImg(t0, t1, t2):
  d1 = cv2.absdiff(t2, t1)
  d2 = cv2.absdiff(t1, t0)
  return cv2.bitwise_and(d1, d2)

cam = cv2.VideoCapture(0)

winName = "Movement Indicator"
cv2.namedWindow(winName, cv2.CV_WINDOW_AUTOSIZE)

# Read three images first:
t_minus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)
t = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)
t_plus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)

while True:
  cv2.imshow( winName, diffImg(t_minus, t, t_plus) )

  # Read next image
  t_minus = t
  t = t_plus
  t_plus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)

  key = cv2.waitKey(10)
  if key == 27:
    cv2.destroyWindow(winName)
    break

print "Goodbye"

Thats it. It is a simple tutorial, but with the knowledge about differential images and how to read webcam streams, you can have pretty much fun (e.g. you can write simple tracking-algorithms now 🙂 ).

Download the result here:

21 comments on “Motion detection using a webcam, Python, OpenCV and Differential Images”

  1. sopotos says:

    hello, nice post very usefull for my research thanks a lot

  2. C'est un véritable bonheur de lire ce poste

  3. mark says:

    import cv2

    def diffImg(t0, t1, t2):
    d1 = cv2.absdiff(t2, t1)
    d2 = cv2.absdiff(t1, t0)
    return cv2.bitwise_and(d1, d2)

    cam = cv2.VideoCapture(0)
    t_dic = {}
    winName = "Movement Indicator"
    cv2.namedWindow(winName)
    NUM = 0
    while True:
    if NUM!=4:
    flag, img = cam.read()
    if flag:
    t = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    t_dic[NUM] = t
    NUM += 1
    print NUM
    cv2.waitKey(1000)
    else:
    break

    print "out "
    print t_dic
    while True:
    cv2.imshow( winName, diffImg(t_dic[0], t_dic[1], t_dic[2]) )

    # Read next image
    t_dic[0] = t_dic[1]
    t_dic[1] = t_dic[2]
    t_dic[2] = t_dic[3]

    key = cv2.waitKey(0)
    if key == 27:
    cv2.destroyWindow(winName)
    break

    print "Goodbye"

    hey ,your works, doesn't work in my mac ,so i have to fix it roughly, hope somebody could get helpful info

    1. beguinner says:

      @Mark Thanks for the code posted at 19. July 2013 at 16:42. It is not formated (indent). So it doesn't work.
      Maybe you have formatted code or a new function?

  4. David says:

    Very interesting post. I was looking for something like this to play with my RPi. For sure I will ask your some questions :).

    Congrats!

  5. srasquasz says:

    Hi. Great code 🙂 Could You tell me how to save image(frame) if motion is detected? I tried with imwrite command but i don't know how to refer to bitwise to save it. Thanks.

    1. stein says:

      cv2.imwrite("image.png", diffImg(t_minus, t, t_plus)) should do the trick 🙂

      1. srasquasz says:

        Thanks for quick reply 🙂 i will try it tomorrow with little modification:
        cv2.imwrite(datetime.now().strftime('%Y%m%d_%Hh%Mm%Ss%f') + '.jpg', diffImg(t_minus, t, t_plus))

        but i have one more question. I don't need "if" to detect if motion then or this code just simply will save photo when it detect movement?

        1. stein says:

          Oh, sry, sure you need an IF 🙂 look at the comment below on how to detect motion:

          Maybe you could use this function to detect motion in the current diffImg:
          http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#int%20countNonZero(InputArray%20src)

          x = threshold (some pixel will be white even there is no real motion)
          if cv2.countNonZero(img) > x:
          imwrite...

          This is a very simple way, and untested. For a more stable detection and stuff, you can use optical flow like http://en.wikipedia.org/wiki/Lucas%E2%80%93Kanade_method or http://docs.opencv.org/modules/video/doc/motion_analysis_and_object_tracking.html

          1. srasquasz says:

            i've tried do this this way:

            while True:
            cv2.imshow( winName, diffImg(t_minus, t, t_plus) )

            x = cv2.threshold(diffImg(t_minus, t, t_plus), 0, 255, cv2.THRESH_OTSU)

            t_minus = t
            t = t_plus
            t_plus = cv2.cvtColor(cam.read()[1], cv2.COLOR_RGB2GRAY)

            if cv2.countNonZero(t_plus) > x:
            cv2.imwrite(datetime.now().strftime('%Y%m%d_%Hh%Mm%Ss%f') + '.jpg', diffImg(t_minus, t, t_plus))

            key = cv2.waitKey(10)
            if key == 27:
            cv2.destroyWindow(winName)
            break

            but it doesn't took any photos.. Could you tell me what can go wrong?

          2. stein says:

            Sry, I wasnt very clear 🙂

            x should be a number not an image. You can try 0 first, then it should take a picture everytime a white pixel is shown.

            I would set x to 10 percent of your images pixel count. But you have to test what works best for your situation.

            Furthermore it should be:

            dimg=diffImg(t_minus, t, t_plus)
            if cv2.countNonZero(dimg) > x:
            cv2.imwrite(datetime.now().strftime('%Y%m%d_%Hh%Mm%Ss%f') + '.jpg', dimg)

          3. srasquasz says:

            i've done it by:

            cam.set(3,640)
            cam.set(4,480)

            and in while loop

            print cv2.countNonZero(diffImg(t_minus, t, t_plus))

            if cv2.countNonZero(diffImg(t_minus, t, t_plus)) >170000:
            cv2.imwrite(datetime.now().strftime('%Y%m%d_%Hh%Mm%Ss%f') + '.jpg', dimg)

            170000 depends on size window if i change it to e.g. 320/240 then it's about 25000 🙂

            So it's work great but there is one more think - it's take several pics per second and i want to take one pic per 1-2 seconds so i need some timer but i don't know how to use it. There should be timer which disable pic option and start count from 0 if pic was taken to 2 and then enable this. I've tried but it stopped video and tooked several pics too..

  6. sherif says:

    I Like it so much, but how can i use the difference image or mat to decide if an object is moving or not

    1. stein says:

      You could check if most pixels are black / zero (e.g. by calculating mean or look at the image histogram). If yes, there was no movement, else something is moving 🙂

  7. steve g says:

    Hi, sorry for the python noob question but when I try to run the movement.py script I get the error:

    Traceback (most recent call last):
    File "movement.py", line 1, in
    import cv2
    ImportError: No module named cv2

    I'm trying to do this on a BeagleBone running the Angstrom distro. The python version is 2.7.2 and opencv is 2.4.2 I have every opencv package installed including opencv-dbg, opencv-dev etc.

    1. stein says:

      Did you install the python opencv-bindings? See "requirements". On Linux just install python-opencv via your package manager.

  8. Roger says:

    Thanks it was exactly what I wanted!!
    I have a question... How can i classify humans and non-humans in the same video? by means of some further processing algorithm?

    1. stein says:

      Hey,

      you could try face-recognition, which is very easy to achieve with Python and OpenCV:

      cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
      
      def facedetect(img):
          gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
          gray = cv2.equalizeHist(gray)
          faces = cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=2,
                                           minSize=(80, 80),
                                           flags = cv2.cv.CV_HAAR_SCALE_IMAGE)
          if len(faces) == 0:
              return []
          else:
              for f in faces:
                  print f
              return faces
      

      This method returns the bounding-boxes of all faces detected in your video (img is a normal image, not a differential image). Maybe you can combine both algorithms to achieve your goal! 🙂

      You can download the XML-File here!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>