Nov 8, 2010

Mostrar la webcam con PyQt+OpenCV

Lo que vamos a ver ahora es una de las figuritas difíciles de encontrar en internet que es mostrar la webcam usando PyQt.
Ojo, lo digo con conocimiento de causa porque los pocos tutos que encontré sobre usar la webcam en python estaban completamente desactualizados, no funcionaban en Linux, no eran para Qt, proponían soluciones descabelladas como escribir código en C++ y hacer un wrapper para Python, o el ejemplo ni siquiera estaba en Python.

http://www.jperla.com/blog/post/capturing-frames-from-a-webcam-on-linux
http://brunoprog64.wordpress.com/2009/03/08/jugando-con-webcams-en-python/
http://stackoverflow.com/questions/3001881/display-an-webcam-stream-in-pyqt4-using-opencv-camera-capture
http://qt-apps.org/content/show.php/Qt+Opencv+webcam+viewer?content=89995

Así que después de varias horas de prueba y error, y uniendo la poca info que había reunido hasta el momento logre hacer andar la webcam de una forma mas que decente.
Este sería básicamente el proceso:


Para capturar los frames desde la webcam utilizamos la librería OpenCV.
OpenCV es una librería creada por Intel con el fin de ser utilizada en sistemas de reconocimiento de rostros, formas, deteccion de movimiento y un largo etcétera. Es Software Libre(BSD) y multiplataforma(Linux, Windows, Mac, etc.).
Como vemos, OpenCV es una librería con características mas que interesantes, pero por ahora nos limitaremos exclusivamente a capturar un seudo-video desde la webcam.






Veamos el código:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Webcam viewer.
# Copyright (C) 2010  Gonzalo Exequiel Pedone
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with This program.  If not, see <http://www.gnu.org/licenses/>.
#
# Email   : hipersayan DOT x AT gmail DOT com
# Web-Site: http://hipersayanx.blogspot.com/

import sys

# Cargamos los modulos de Qt necesarios para el programa.
from PyQt4 import uic, QtGui, QtCore

# Cargamos el modulo de OpenCV.
import cv

class Webcam:
    def __init__(self):
        # Cargamos la GUI desde el archivo UI.
        self.MainWindow = uic.loadUi('webcam.ui')

        # Tomamos el dispositivo de captura a partir de la webcam.
        self.webcam = cv.CreateCameraCapture(0)

        # Creamos un temporizador para que cuando se cumpla el tiempo limite tome una captura desde la webcam.
        self.timer = QtCore.QTimer(self.MainWindow);

        # Conectamos la señal timeout() que emite nuestro temporizador con la funcion show_frame().
        self.MainWindow.connect(self.timer, QtCore.SIGNAL('timeout()'), self.show_frame)

        # Tomamos una captura cada 1 mili-segundo.
        self.timer.start(1);

    """
    show_frame() -> None

    Esta funcion toma una captura desde la webcam y la muestra en una QLabel.
    """
    def show_frame(self):
        # Tomamos una captura desde la webcam.
        ipl_image = cv.QueryFrame(self.webcam)

        # Leemos los pixeles de la imagen(numero_de_bytes_por_pixels * ancho * alto).
        data = ipl_image.tostring()

        # Creamos una imagen a partir de los datos.
        #
        # QImage
        # (
        #   Los pixeles que conforman la imagen,
        #   Ancho de de la imagen,
        #   Alto de de la imagen,
        #   Numero de bytes que conforman una linea (numero_de_bytes_por_pixels * ancho),
        #   Formato de la imagen
        # )
        image = QtGui.QImage(data, ipl_image.width, ipl_image.height, ipl_image.channels * ipl_image.width, QtGui.QImage.Format_RGB888)
 
        # Creamos un pixmap a partir de la imagen.
        # OpenCV entraga los pixeles de la imagen en formato BGR en lugar del tradicional RGB,
        # por lo tanto tenemos que usar el metodo rgbSwapped() para que nos entregue una imagen con
        # los bytes Rojo y Azul intercambiados, y asi poder mostrar la imagen de forma correcta.
        pixmap = QtGui.QPixmap()
        pixmap.convertFromImage(image.rgbSwapped())

        # Mostramos el QPixmap en la QLabel.
        self.MainWindow.lblWebcam.setPixmap(pixmap)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    webcam = Webcam()
    webcam.MainWindow.show()
    app.exec_()

Y este sería el resultado final:







Esta es una imagen de mi Ferrari 250 LM made in Italy B). Aunque es una lastima que la calidad de la imagen no es muy buena porque la he tomado con una webcam VGA que era lo único que tenia a mano :(.
Y aquí les dejo el conjunto completo de fuentes del proyecto.

21 comments:

  1. Hola amigo, desde hace mucho que intento lo que tu ya has logrado, baje tu codigo pero me da el siguiente error, espero me ayudes

    Traceback (most recent call last):
    File "./webcam.py", line 77, in show_frame
    pixmap.convertFromImage(image.rgbSwapped())
    AttributeError: 'QPixmap' object has no attribute 'convertFromImage'

    ReplyDelete
  2. Es que ese método solo esta disponible a partir de PyQt 4.8.x, para versiones anteriores, tenés que usar fromImage().

    http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qpixmap.html

    ReplyDelete
  3. [root@Detra webcam]# python webcam.py
    Traceback (most recent call last):
    File "webcam.py", line 29, in
    import cv
    File "/usr/lib/python2.6/cv.py", line 1, in
    from cv2.cv import *
    ImportError: No module named cv2.cv


    Me alludas con esto xfavor?

    ReplyDelete
  4. @Daniel

    ¿Que sistema operativo y versión estás usando?
    ¿Que versión de OpenCV?

    ReplyDelete
  5. ups sierto, pues uso fedora 13 , python 2.6.4

    y opencv ..... aqui dejo lo q me arrojo rpm..

    rpm -q opencv --->
    opencv-2.0.0-9.fc13.i686

    rpm -q opencv-python --->
    opencv-python-2.0.0-9.fc13.i686

    ReplyDelete
  6. según parece te falta el archivo "cv2.so", buscalo de así:

    find / -iname 'cv2.so' 2>/dev/null

    Decime que ruta aparece. Sería cuestión de arreglarlo con un enlace simbólico si está en una ruta diferente de la que espera OpenCV.

    ReplyDelete
  7. pues... no, lo ejecuto y luego de un par de minutos directamente me devuelve el promp , sin ninguna salida :( ...mm instale opencv con yum, luego opencv-python de la misma manera, alguna otra idea... gracias

    ReplyDelete
  8. O sea, ¿esta vez se ejecuta pero sin mostrar un error?
    Hace una cosa, proba haber si te funciona este código, en python:

    import cv

    cv.NamedWindow("prueba")
    cv.WaitKey(0)

    Eso te tendría que mostrar una ventana con el titulo "prueba" sin usar Qt, veamos si OpenCV esta bien instalado.

    ReplyDelete
  9. haa no, me refiero al comando "find / -iname 'cv2.so' 2>/dev/null" <--- me devuelve el promp sin ninguna salida;

    al ejecutar el codigo:

    ----------------------------------------------------------------------------------------

    import cv

    cv.NamedWindow("prueba")
    cv.WaitKey(0)


    -----------------------------------------


    me devuelve:


    Traceback (most recent call last):
    File "p3.py", line 1, in
    import cv
    File "/usr/lib/python2.6/cv.py", line 1, in
    from cv2.cv import *
    ImportError: No module named cv2.cv

    ReplyDelete
  10. :s es algún fallo con la instalación, falta alguna dependencia o algo, sino vas tener que instalar OpenCV desde el código fuente, o vas a tener que actualizar a la última versión de Fedora.

    ReplyDelete
  11. ok intentare instalar las dependencias con yum e inslatar opencv desde las fuentes, gracias por tu ayuda

    ReplyDelete
  12. Hola Hipersayan.

    Me das una mano? me acabo de pasar a Arch (2 dias), y segun tengo entendido tu tambien usas Arch :D , no logro instalar los wrappers de opencv-python , usando $ pacman -Ss opencv tengo:


    extra/opencv 2.3.1_a-4 [instalado]
    Open Source Computer Vision Library
    extra/opencv-docs 2.3.1_a-4 [instalado]
    Open Source Computer Vision Library (documentation)
    extra/opencv-samples 2.3.1_a-4 [instalado]
    Open Source Computer Vision Library (samples)


    ... y como ves lo tengo todo instalado , pero no hay opencv-python , usando $ yaourt opencv tengo:


    1 extra/opencv 2.3.1_a-4 [installed]
    Open Source Computer Vision Library
    2 extra/opencv-docs 2.3.1_a-4 [installed]
    Open Source Computer Vision Library (documentation)
    3 extra/opencv-samples 2.3.1_a-4 [installed]
    Open Source Computer Vision Library (samples)
    4 aur/aruco 1.1.0-1 (3)
    A minimal library for Augmented Reality applications based on OpenCV
    5 aur/haskell-hopencv 0.1.2.2-3 (0)
    A binding for the OpenCV computer vision library
    6 aur/mingw32-opencv 2.1.0-1 (0)
    Intel(R) Open Source Computer Vision Library (MinGW build)
    7 aur/opencv-old 2.1.0-3 (16)
    Older version of the Intel(R) Open Source Computer Vision Library, currently required by sikuli-ide
    8 aur/opencv-qt 2.3.1-1 (3)
    Open Source Computer Vision Library compiled with QT support
    9 aur/opencv-qt-all 2.3.1_a-1 (2)
    Open Source Computer Vision Library. Build with Qt backend, python bindings and full examples.
    10 aur/opencv-svn 4703-1 (Out of Date) (21)
    Collection of algorithms, documentation and sample code for real time computer vision.
    11 aur/opencv-tbb 2.3.1-2 (Out of Date) (4)
    Intel(R) Open Source Computer Vision Library with Threading Building Blocks and SSE3
    12 aur/opencvs 20110824193500-1 (0)
    OpenCVS
    13 aur/perl-image-objectdetect 0.110.0-1 (Out of Date) (1)
    CPAN package - Simple module to detect objects from picture using OpenCV
    14 aur/python2-pyopencv 2.1.0.wr1.2.0-2 (6)
    Python bindings for OpenCV 2.1
    ==> Introduzca el número de los paquetes que se instalarán (ejemplos: 1 2 3 ó 1-3)
    ==> ------------------------------------------------------------------------------
    ==>




    ..como puedes ver la opcion 14 parece la inidicada, pero al instalarla , durante la compilacion me arroja un error, y yaourt termina diciendo los siguientes paquetes NO se han instalado : python2-pyopencv ....

    y pues hasta ai llego :( me puedes decir porfavor como isiste tu para instalarlo en tu arch? :D gracias.



    Posdata:

    Al momento de escrivir esto intente nuevamente el yaourt opencv

    y a haora solo me lanza esto:

    1 extra/opencv 2.3.1_a-4 [installed]
    Open Source Computer Vision Library
    2 extra/opencv-docs 2.3.1_a-4 [installed]
    Open Source Computer Vision Library (documentation)
    3 extra/opencv-samples 2.3.1_a-4 [installed]
    Open Source Computer Vision Library (samples)
    The URL http://aur.archlinux.org/rpc.php?type=search&arg=opencv returned error : 404
    ==> Introduzca el número de los paquetes que se instalarán (ejemplos: 1 2 3 ó 1-3)
    ==> ------------------------------------------------------------------------------



    los mismos paquetes que me lanzo pacman, ya no estan los que me lanzo antes :(

    ReplyDelete
  13. Otra posdata (sorry, es mi idiotes):

    reinstale opencv con pacman y descubri que se instalaban solo los archivos cv2.so y cv.py en

    /usr/lib/python2.7/site-packages/

    al lanzar import cv en python:

    $python
    Python 3.2.2 (default, Nov 21 2011, 16:51:01)
    [GCC 4.6.2] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import cv
    Traceback (most recent call last):
    File "", line 1, in
    ImportError: No module named cv
    >>>



    luego con un
    $PYTHONPATH=/usr/lib/python2.7/site-packages/

    el mismo resultado , asi que me situo en /usr/lib/python2.7/site-packages/
    y lanzo python desde aqui:

    [site-packages]$python
    Python 3.2.2 (default, Nov 21 2011, 16:51:01)
    [GCC 4.6.2] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import cv
    Traceback (most recent call last):
    File "", line 1, in
    File "cv.py", line 1, in
    from cv2.cv import *
    ImportError: dynamic module does not define init function (PyInit_cv2)
    >>>


    que ocurre :'( ??

    gracias.

    ReplyDelete
  14. En Arch el interprete por defecto de Python es Python 3, OpenCV todavía no está portado a Python 3, para hacer funcionar el ejemplo de arriba, primero instalá Python 2. PyQt y OpenCV (si ya los tenés instalados, no hagas este paso):

    pacman -S python2 python2-pyqt opencv

    Y después ejecutás el ejemplo como:

    python2 ejemplo.py

    También convendría cambiar el shebang por:

    #!/usr/bin/env python2

    Le das permisos de ejecución:

    chmod 744 ejemplo.py

    y así lo podes ejecutar como:

    ./ejemplo.py

    ó

    . ejemplo.py

    Atención con el punto y el espacio delante del archivo.

    ReplyDelete
  15. :O woow muchisimas gracias , lo descubri!, y justo depues, pase a ver si me avias respondido :D parece que ya estoy progresando :D jaja te agradesco un mundo!!

    ReplyDelete
  16. Muchas gracias capo¡¡¡ esto vale oro¡¡¡¡
    Saludos

    ReplyDelete
  17. Buen día buen aporte!! tengo una duda como almaceno la toma de la imagen en mi disco como "jpg"?
    requiero hacer varias tomas (cada determinado tiempo) y estarlas almacenando

    ReplyDelete
    Replies
    1. Lo ideal sería establecer la variable pixmap (linea 76) como global (self.pixmap), y luego con un segundo timer (o el mismo timer, dependiendo de tus necesidades) vas guardando el pixmap en un jpg usando el método QPixmap::save("imagen.jpg")

      Delete
  18. Hola

    primero muchas gracias por tu aporte.

    una duda, como se podria detener la captura y que al detenerse quede la imagen en la QLabel, y si esta captura me gusta poder guardarla, si no es buena la imagen poder seguir capturando. (podria ser con push Button)

    gracias de nuevo.

    ReplyDelete
    Replies
    1. En lugar de:

      image = QtGui.QImage(data, ipl_image.width, ipl_image.height, ipl_image.channels * ipl_image.width, QtGui.QImage.Format_RGB888)

      declaras image globalmente:

      self.image = QtGui.QImage(data, ipl_image.width, ipl_image.height, ipl_image.channels * ipl_image.width, QtGui.QImage.Format_RGB888)

      Luego podes hacer lo que te plazca con ese frame.

      Si querés hacer captura manual, simplemente detené el timer:

      self.timer.stop()

      Y llamas a show_frame() desde el slot de algún botón.

      @QtCore.pyqtSlot
      def on_botonCapturar_clicked():
      self.show_frame()


      O conectando señales y slots directamente:

      self.MainWindow.botonCapturar.clicked.connect(self.show_frame)

      Delete