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