Пример #1
0
class FaceDetection(DirectObject):
    def __init__(self):
        #Muestra un texto en pantalla, utilizando la interfaz 2D de Panda3D.
        #Posteriormente se actualizara con otros datos, por eso se mantiene la referencia
        self.title = OnscreenText(text="prueba 1", style=1, fg=(0,0,0,1), pos=(0.8,-0.95), scale = .07)

        #Lee los datos de configuracion de los clasificadores HAAR. En este caso, para deteccion de rostros en posicion frontal
        self.cascade = cv.Load("haarcascades\\haarcascade_frontalface_alt.xml")
        
        #Utiliza por defecto la camara para obtener las imagenes, y no guarda un archivo
        self.cameraHelper = CameraHelper(True, False, "d:\\face-detection.avi")
	
        #Crea una textura para utilizar como fondo, donde se mostraran las imagenes de video
	#CardMaker en realidad es un poligono rectangular, que es util para asociarlo al renderer2D, ya que
	#podremos hacer que ocupe toda la pantalla y mostrar nuestras imagenes como fondo.
        self.cardMaker = CardMaker("My Fullscreen Card");
        self.cardMaker.setFrameFullscreenQuad()

	#Agrega el rectangulo al renderer render2dp. Asi como existe render2d, existe uno adicional que es utilizado
	#en general para casos donde necesita mostrarse un fondo, sea estatico o de video. El render2d estandar,
	#por el contrario, se usa para mostrar informacion al usuario que siempre debe ser visible
        self.card = NodePath(self.cardMaker.generate())
        self.card.reparentTo(render2dp)
	
        #Le da baja prioridad para que los objetos dibujados posteriormente siempre se vean sobre ella
        base.cam2dp.node().getDisplayRegion(0).setSort(-20)
        
        #Crea un rectangulo que se utilizara para mostrar la imagen superpuesta sobre la cara
        self.faceMaker = CardMaker("Face Texture");
        self.faceImage = NodePath(self.faceMaker.generate())
        self.faceImage.setTexture(loader.loadTexture("margarita-glass3.png"))
        self.faceImage.reparentTo(aspect2d)
        #self.faceImage.reparentTo(render2d)
        self.faceImage.setTransparency(TransparencyAttrib.MAlpha)
        
        self.setup_handlers()
        self.setup_lights()

        self.count = 0
        
        #Establece un fondo negro
        #base.setBackgroundColor(0, 0, 0)

        #Carga el objeto tetera (incluido con Panda3D), y lo ubica en el mundo
        #self.teapot = loader.loadModel('models/teapot')
        #self.teapot.reparentTo(base.render)
        #self.teapot.setPos(-10, -10, -10)

        #Coloca la camara en el origen de coordenadas, y hace que apunte hacia la tetera
        #camera.setPos(0, 0, 0)
        #camera.lookAt(-10, -10, -10)

        taskMgr.add(self.onFrameChanged, "frameChange")


    def exitApplication(self):
        self.cameraHelper = None
        sys.exit()
    
    def setup_handlers(self):
	#Deshabilita el manejo por defecto del mouse
        base.disableMouse()
	
	#Agrega un gestor que al recibir el mensaje de que se presiono ESC, sale del programa
        self.accept("escape", self.exitApplication)

    def setup_lights(self):
        #Crea una luz ambiente, para que los objetos tengan una iluminacion base por defecto
        self.ambientLight = AmbientLight("ambientLight")
        self.ambientLight.setColor(Vec4(.8, .8, .75, 1))
        render.setLight(render.attachNewNode(self.ambientLight))

        #Crea una luz direccional, que va a permitir destacar la profundidad de los objetos
        self.directionalLight = DirectionalLight("directionalLight")
        self.directionalLight.setDirection(Vec3(0, 0, -2.5))
        self.directionalLight.setColor(Vec4(0.9, 0.8, 0.9, 1))
        render.setLight(render.attachNewNode(self.directionalLight))
	
    def onFrameChanged(self, task):
	#Captura un cuadro del origen indicado
        image = self.cameraHelper.captureFrame()
        
	#Si no quedan imagenes para mostrar, sale del programa (por ejemplo, se termina el video grabado)
        if image == None:
            return Task.done

        nuevaImagen = self.detectFaces(image)
        
        #Crea una textura utilizando la imagen capturada por OpenCV
        texture = self.createTexture(image)

        #En caso de que haya ocurrido un error, utilizar la imagen previa
        if texture != None:
            self.oldTexture = texture
		
        #Muestra la captura como fondo de la imagen
        self.card.setTexture(self.oldTexture)
        
        return Task.cont

    def createTexture(self, image):
        (width, height) = cv.GetSize(image)
	
        #Panda3D interpreta las imagenes al reves que OpenCV (verticalmente invertidas), por lo que es necesario tener esto en cuenta
        cv.Flip(image, image, 0)
	
        #OpenCV permite convertir la representacion interna de las imagenes a un formato descomprimido que puede ser guardado en un archivo.
        #Esto puede utilizarse desde Panda3D para tomar la imagen y utilizarla como una textura.
        imageString = image.tostring()
        #PTAUchar es una clase que permite tomar un bloque de datos y utilizarlo desde componentes de Panda3D (en particular es util para texturas)
        imagePointer = PTAUchar.emptyArray(0)
        imagePointer.setData(imageString)
        
        try:
            self.count += 1
            #Crea un nuevo objeto textura
            texture = Texture('image' + str(self.count))
            #Establece propiedades de la textura, como tamanio, tipo de datos y modelo de color. Las imagenes de OpenCV las estamos manejando
            #como RGB, donde cada canal es de 8bits (un numero entero)
            texture.setup2dTexture(width, height, Texture.TUnsignedByte, Texture.FRgb)
            #Indicamos que utilice el bloque de datos obtenido anteriormente como origen de datos para la textura
            texture.setRamImage(CPTAUchar(imagePointer), MovieTexture.CMOff)
        except:
            texture = None
        
        return texture

    def detectFaces(self, image):
        #Tamanio minimo de los rostros a detectar
        minFaceSize = (20, 20)
        #Escala a aplicar a la imagen para reducir tiempo de procesamiento
        imageScale = 2
        haarScale = 1.2
        minNeighbors = 2
        haarFlags = 0

        (screenWidth, screenHeight) = cv.GetSize(image)
        aspectRatio = float(screenWidth) / screenHeight
        
        #Crea imagenes temporales para la conversion a escala de grises y el escalado de la imagen
        grayImage = cv.CreateImage((image.width,image.height), 8, 1)
        smallImage = cv.CreateImage((cv.Round(image.width / imageScale), cv.Round (image.height / imageScale)), 8, 1)

        #Convierte la imagen capturada a escala de grises
        cv.CvtColor(image, grayImage, cv.CV_BGR2GRAY)

        #Crea una version de tamanio reducido para reducir el tiempo de procesamiento
        cv.Resize(grayImage, smallImage, cv.CV_INTER_LINEAR)

        #Ecualiza la imagen, con el fin de mejorar el contraste
        cv.EqualizeHist(smallImage, smallImage)

        #Detecta los rostros existentes en la imagen, y calcula el tiempo utilizado para hacerlo
        t = cv.GetTickCount()
        faces = cv.HaarDetectObjects(smallImage, self.cascade, cv.CreateMemStorage(0), haarScale, minNeighbors, haarFlags, minFaceSize)
        t = cv.GetTickCount() - t

        #Crea una imagen nueva donde se va a dibujar el cuadrado sin mostrar el fondo
        nuevaImagen = cv.CreateImage((image.width,image.height), 8, 3)
        pt1 = (int(0 * imageScale), int(0 * imageScale))
        pt2 = (int((image.width) * imageScale), int((image.height) * imageScale))
        cv.Rectangle(nuevaImagen, pt1, pt2, cv.RGB(118, 152, 141), -1, 8, 0)

        print "detection time = %gms" % (t/(cv.GetTickFrequency()*1000.))
        
        if faces:
            for ((x, y, w, h), n) in faces:
                #Calcula los puntos de inicio y fin del rectangulo detectado
                pt1 = (int(x * imageScale), int(y * imageScale))
                pt2 = (int((x + w) * imageScale), int((y + h) * imageScale))

                #Dibuja un rectangulo en la imagen origen, para destacar el rostro detectado
                #cv.Rectangle(nuevaImagen, pt1, pt2, cv.RGB(255, 0, 0), 3, 8, 0)
                cv.Rectangle(image, pt1, pt2, cv.RGB(255, 0, 0), 3, 8, 0)

                #Calcula la posicion del objeto a mostrar en Panda3D
                #Se utiliza aspect2d, ya que permite tener independencia de dimensiones entre ancho y alto (con render2d la imagen se veria comprimida)
                #En aspect2d, la coordenada x corre desde -aspectRatio hasta aspectRatio. La coordenada y corre desde -1 a 1.
                #
                #En este caso en que utilizamos 2D, es mas notoria la necesidad de definir correctamente el "centro" de nuestros modelos.
                #Esto se realiza en los programas de modelado, pero afectara el posicionamiento dentro de nuestra aplicacion
                #position = (float(x) * imageScale, float(y + h) * imageScale)
                #position = (position[0] - screenWidth / 2, position[1] - screenHeight / 2)
                #position = (2 * aspectRatio * position[0] / screenWidth, 2 * (-position[1] / screenHeight), 0)
                
                position = (float(x) * imageScale, float(y) * imageScale)
                position = (position[0] - screenWidth / 2, position[1] - screenHeight / 2)
                position = (2 * aspectRatio * position[0] / screenWidth, 2 * (-position[1] / screenHeight), 0)

                #Ubica el centro del objeto en la posicion correcta
                self.faceImage.setPos(position[0], 0, position[1])
                
                texture = self.faceImage.getTexture()
                #self.faceImage.setScale(2 * imageScale * float(w) / texture.getXSize(), 1, 2 * imageScale * float(h) / texture.getYSize())
                self.faceImage.setScale(imageScale * float(w) / texture.getXSize(), 1, imageScale * float(h) / texture.getYSize())
                print self.faceImage.getScale()
        return nuevaImagen