CodeSnippits Dynamic Webcam

From PyWiki

Revision as of 21:32, 6 February 2010 by Newacct (Talk | contribs)
(diff) ← Older revision | Current revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

Dynamic Webcam

Here is one solution to have a webcam feeded material.

Here is how it works:

  • Image acquisition is made in one thread with the openCV library
  • The material update is performed in the main thread with code taken from this wiki at Dynamic Textures

It was tested under:

  • WinXP SP3
  • python-ogre 1.6
  • openGL renderer (directX not working)

Please update here if you have it working with another platform.

Prerequisites

openCV dlls

Get the openCV library. Take the one called opencv-win. When issuing this article the current version is opencv-win-1.1pre1.

Once installed, you should have the dlls under C:\Program Files\OpenCV\bin. Here, you have two options:

Either:

  • Update your PATH environment variable so that the dlls are taken into account (openCV installer can do it for you).

Or:

  • Copy the following dlls to your working directory:
    • cv110.dll
    • cxcore110.dll
    • highgui110.dll

openCV python interface

You will need to get Gary Bishop's python interface to openCV. The full story is here, but here is the short todo list for impatients:

  • Get cvtypes.zip from here
  • Take CVtypes.py from the zip file and place it in your working dir.
  • Update CVTypes.py lines 42 to 44:

From:

_cxDLL = cdll.cxcore100
_cvDLL = cdll.cv100
_hgDLL = cdll.highgui100

To:

_cxDLL = cdll.cxcore110
_cvDLL = cdll.cv110
_hgDLL = cdll.highgui110
  • Update CVTypes.py line 4167:

From

cvConvertPointsHomogenious = cfunc('cvConvertPointsHomogenious', _cvDLL, None,

To:

cvConvertPointsHomogenious = cfunc('cvConvertPointsHomogeneous', _cvDLL, None,

Check webcam acquisition

Edit a test python file with the following code:

from CVtypes import cv
win = 'Show Cam'
cv.NamedWindow(win)
cap = cv.CreateCameraCapture(0)
while cv.WaitKey(1) != 27:
    img = cv.QueryFrame(cap)
    cv.ShowImage(win, img)

Plug your USB webcam, and run this test app... you should see your face. If not, well, report on the forum, something went wrong...

Webcam Demo app

Here is the full Demo_webcam.py. Place it under demos\webcam so that there is no mess when looking for SampleFramework.

import sys
sys.path.insert(0,'..')
 
import os
 
import ogre.renderer.OGRE as ogre
import ctypes
import SampleFramework as sf
 
# For WEBCAM
from CVtypes import cv
import threading
import traceback
import time
 
# WEBCAM RESOLUTION
WEBCAM_WIDTH = 160
WEBCAM_HEIGHT = 120
# CROPPED AREA FROM WEBCAM TOPLEFT CORNER (SAME AS WEBCAM_WIDTH AND WEBCAM_HEIGHT FOR NO CROP)
WIDTH = 160
HEIGHT = 120
 
# THREADS COMMUNICATION
pixBuf = []     # Shared data between webcam thread and main thread
newPix = False  # Inform main thread that new data is available
waitPix = True  # Inform webcam thread that new data is needed
lock = threading.Lock() # Resources lock
 
class MyFeed ( threading.Thread ):
   " Take care of images acquisition "
 
   def __init__(self,nada):
      print "Starting webcam"
      try:
        self.cap = cv.CreateCameraCapture(0)
        # Set width/height
        cv.SetCaptureProperty( self.cap, cv.CAP_PROP_FRAME_WIDTH, WEBCAM_WIDTH)
        cv.SetCaptureProperty( self.cap, cv.CAP_PROP_FRAME_HEIGHT, WEBCAM_HEIGHT )
        # Acquire one image, will throw one exeption in case of no webcam present
        tmp = cv.QueryFrame(self.cap)
        tmp2 = cv.ImageAsBuffer(tmp)
      except:
        print "No webcam here"
        self.cap = None
      self.running = True
      self.readyToStop = False
      threading.Thread.__init__ ( self )
 
   def stop(self):
      if self.running:
          # Normal stop
          self.running = False
          if self.cap == None:
              print "No webcam to stop"
          else:
              print "Waiting for webcam to stop...",
              while not self.readyToStop:
                pass
              print "ok"
              cv.ReleaseCapture(self.cap)
      else:
          # In case stop in called when already stopped
          return None
 
   def run ( self ):
      # Check if we may run
      if self.cap == None:
          print "No webcam present, aborting thread"
          return True
      # We may run!
      global pixBuf,newPix,waitPix,lock
      while self.running:
         if waitPix:
             # Acquire image
             img = cv.QueryFrame(self.cap)
             # Do not process if abort in progress
             if not self.running:
                break
             print "New image"
             lock.acquire()
             pixBuf = cv.ImageAsBuffer(img)
             newPix = True
             waitPix = False
             lock.release()
         else:
             time.sleep(0.05)
 
      # Once loop is finished, thread may be terminated
      self.readyToStop = True
 
class textureListener(ogre.FrameListener):
    def __init__(self, app):
        ogre.FrameListener.__init__(self)
        self.app = app
        self.grnVal = 0
        self.fading = True
 
    def frameStarted(self, frameEvent):
        # Update material from webcam image
        global pixBuf,newPix,waitPix,lock
        if newPix:
            print "Updating"
            try:
              lock.acquire()
              ret = self.app.setTextureManual(self.app.mTex, WIDTH,HEIGHT, pixBuf)
              if not ret:
                 # Aborting
                 lock.release()  
                 return False
              # Tell next frameStarted that this image was already taken into account
              newPix = False
              # Tell thread to acquire a new image
              waitPix = True
              lock.release()
            except:
              print "Exception:"
              traceback.print_exc(file=sys.stdout)
        return True
 
class textureTest(sf.Application):
    def __init__(self):    
        sf.Application.__init__(self)
 
    def _createScene(self):
        self.mTex = ogre.TextureManager.getSingleton().createManual(
                                 "DynamicTexture",
                                 ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME,
                                 ogre.TEX_TYPE_2D,
                                 WIDTH, HEIGHT,
                                 0,
                                 ogre.PF_BYTE_BGRA,
                                 ogre.TU_DEFAULT)
        colArray = [0,255,0,128] * WIDTH * HEIGHT
        self.setTextureManual(self.mTex, WIDTH, HEIGHT, colArray)
        self.material = ogre.MaterialManager.getSingleton().create(
                             "DynamicTextureMaterial",
                             ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME)
 
        self.material.getTechnique(0).getPass(0).createTextureUnitState("DynamicTexture")
        self.material.getTechnique(0).getPass(0).setSceneBlending(ogre.SBT_TRANSPARENT_ALPHA)
        self.ent = self.sceneManager.createEntity("mrben", "cube.mesh")
        self.ent.setMaterialName("DynamicTextureMaterial")
 
        self.node = self.sceneManager.getRootSceneNode().createChildSceneNode('Knot3Node', (-100, -10, -200))
        self.node.attachObject(self.ent)
        self.node.setPosition(ogre.Vector3(125, 10, 0)) 
        self.node.setScale(2,2,2)
        self.node.yaw(0.4)
        self.node.roll(0.2)
 
        self.lstnr = textureListener(self)
        ogre.Root.getSingleton().addFrameListener(self.lstnr)
 
    #Texture is a manually created Texture 
    #dataArray is an array of pixel colour data 
    def setTextureManual(self, texture, width, height, dataArray): 
        # Get the pixel buffer 
        pixelBuffer = texture.getBuffer() 
        # Lock the pixel buffer and get a pixel box 
        pointer = pixelBuffer.lock(0,width*height*4,ogre.HardwareBuffer.HBL_NORMAL) 
        #number 4 here is use to accommodate the bytes for r, g, b, a may vary with different pixel formats 
        storageclass = ctypes.c_uint8 * (width*height*4) 
        cbuffer=storageclass.from_address(ogre.CastInt(pointer)) 
        posRGBA = 0 
        posRGB  = 0
        try:
          for j in range(height):
              for i in range( width ) :
                  cbuffer[posRGBA]= ord(dataArray[posRGB]) # B 
                  posRGBA+=1 
                  posRGB+=1
 
                  cbuffer[posRGBA]= ord(dataArray[posRGB])  # G 
                  posRGBA+=1 
                  posRGB+=1
 
                  cbuffer[posRGBA]= ord(dataArray[posRGB])  # R 
                  posRGBA+=1 
                  posRGB+=1
 
                  cbuffer[posRGBA]= 128   # A 
                  posRGBA+=1
              posRGB += (WEBCAM_WIDTH - width)*3
 
          # Unlock the pixel buffer 
          pixelBuffer.unlock()
 
        except:
          print "Error during buffer fill at position ",posRGB
          return False
        return True
 
if __name__=='__main__':
    ta = textureTest()
 
    feed = MyFeed("test")
    feed.start()
 
    try:
        ta.go()
    except:
        print "Application stopping"
        traceback.print_exc(file=sys.stdout)
 
    feed.stop()

TODO

In order to speed up things:

  • Implement advice from Dynamic Textures:
    • set up arrays in advance instead of creating them every frame,
    • use Numeric Python to handle them.
  • Make one buffer in the webcam thread, so that the next frame is ready when waitPix becomes True.