OpenCV es una biblioteca de código abierto muy utilizada para el desarrollo de aplicaciones de visión artificial. En OpenCV encontraremos muchos algoritmos de tratamiento de imagen y de clasificación de patrones que nos servirán para crear aplicaciones de visión artificial. OpenCV está desarrollada originalmente en C++. Sin embargo, dado la dificultad de aprendizaje de C++ se ha desarrollado un recubrimiento de OpenCV para que pueda ser utilizado desde Python. En este tutorial nosotros usaremos ese recubrimiento.
Junto con OpenCV utilizaremos otras bibliotecas que forman parte del ecosistema ScyPy. ScyPy es un conjunto de bibliotecas de código abierto para construir aplicaciones matemáticas, científicas e ingenieriles.
Instalación
La instalación de Python y todas las bibliotecas necesarias la realizaremos instalando el ecosistema Anaconda. Este ecosistema facilita la instalación, la actualización y la compatibilidad entre los diferentes componentes instalados. Hay versiones para Windows, para Mac y para Linux. Si la instalación se realiza sobre un ordenador del 2012 o posterior seguro que la versión de 64 bits es la adecuada.Al instalar Anaconda debemos tener cuidado para elegir una versión compatible con la última versión de OpenCV. La versión de OpenCV más actual, cuando se escribió esta entrada, era OpenCV 3.2. Dicha versión de OpenCV es compatible con Python 3.X, pero no con Python 2.X. Por ello, desde aquí se recomienda instalar una versión de Anaconda basada en Python 3.X. En particular , este tutorial utiliza:
- Python 3.6.
- OpenCV 3.2
Tras la instalación por defecto de Anaconda encontraremos ya algunas de las principales bibliotecas que usaremos para el desarrollo de aplicaciones de Visión Artificial. Por ejemplo, dispondremos de Numpy y MatPlotLib. Numpy es una biblioteca de cálculo numérico que se integra con OpenCV en numerosos aspectos. Por ejemplo, las imágenes de OpenCV se tratan como matrices de Numpy. Por otro lado, MatPlotLib es una biblioteca de presentación gráfica de resultados para Numpy que resulta útil por motivos obvios. En este tutorial se usa Numpy 1.11 y MatPlotLib 1.15, aunque Anaconda siempre instala las versiones compatibles más modernas.
Las bibliotecas que incluye Anaconda tienen interrelaciones complejas. Por ejemplo, Matplotlib tiene dependencia de las bibliotecas six, dateutil y py, pero Anaconda suele encargarse de instalar todo lo necesario sin que tengamos que ocuparnos de estos detalles.
Una vez instalado Anaconda hay que instalar OpenCV. Para instalar una biblioteca desde Anaconda hay que abrir un terminal (en el caso de Windows una consola de Anaconda) y teclear:
conda search opencv
anaconda search opencv
anaconda install -c conda-forge opencv3
los siguientes comandos. Si no se observan mensajes de error es que todo ha ido bien, pero si aparecen mensajes de error deberán leerse y entenderse para buscar una solución.
>>> import cv2
>>> import numpy as np
>>> import matplotlib
Instalación de Pycharm
Anaconda incluye por defecto Spyder y Jupyter Notebook. Ambos pueden utilizarse como entornos para desarrollar aplicaciones en Python. Sin embargo, dada su potencia, desde aquí se anima al lector a instalar y a utilizar PyCharm.PyCharm es un IDE para programar en Python que nos permitirá programar con ciertas ayudas: autocompletar, sugerencias de nombre de variable, refactorización... Además nos permitirá depurar nuestro código de una manera muy sencilla.
La versión de PyCharm que vamos a utilizar es gratuita y se denomina Comunity Edition.
-
En GNU/Linux bastará con:
-
Bajar el fichero “pcharm-community-X.X.tar.gz”.
-
Descomprimir el fichero tar.gz. Podemos utilizar un terminal unix y ejecutar. Por ejemplo:
> tar xzvf pycharm-community-4.0.4.tar.gz -
Ejecutar PyCharm desde la línea de comandos:
> pycharm-community-4.0.4/bin/pycharm.sh
-
En MS Windows habrá que:
-
Ejecutar el fichero pycharm-*.exe para arrancar el proceso de instalación.
-
Seguir las instrucciones y prestar atención a las opciones de instalación.
Carga de imagen y presentación en pantalla
En esta sección vamos a proceder a utilizar Pyton y las bibliotecas que acabamos de instalar para cargar una imagen y presentarla en pantalla.Desde la consola de comandos (terminal en Unix y CMD en Windows), utilizamos el comando pyhton (o mejor ipython) para abrir un intérprete de Python.
Nuestro primer programa OpenCV solo carga de disco y muestra una imagen por pantalla. Obsérvese que todas las funciones de OpenCV están dentro del paquete cv2. Tras ejecutarlo se puede pulsa ENTER sobre la ventana para terminar la ejecución.
import cv2
cv2.imshow("AvatarWindow",m) #Muestra la matriz en una ventana con nombre
cv2.waitKey() #Espera que se pulse una tecla sobre la ventana
cv2.destroyAllWindows() #Cierra todas las ventanas abiertas por OpenCV
from matplotlib import pylab
from matplotlib import pyplot as plt
pylab.ion()
plt.imshow(m)
plt.imshow(cv2.cvtColor(m,cv2.COLOR_BGR2RGB))
Creación de un proyecto OpenCV en PyCharm
Python es un lenguaje interpretado, por lo que no es necesario el paso de compilación de otros lenguajes como Java o C++. Como hemos visto, una de las formas más sencillas de hacer pruebas en pequeños fragmentos de código es utilizar directamente la consola con el intérprete de Python (python) o mejor ipython.Sin embargo, con la ayuda de un entorno como PyCharm, también es fácil crear un proyecto para realizar programas más complejos. Cabe decir que, para pruebas rápidas, en PyCharm también tenemos acceso a una consola de Python (Tools/Windows/Python Console) que se situara en la parte inferior izquierda de la ventana de PyCharm (ver Figura 1).
Para crear un proyecto Python desde PyCharm se debe:
-
Crear un proyecto desde el menú file.
-
Seleccionar el intérprete 3.6.X.
-
Elegir una carpeta vacía para la localización del proyecto (quizás creando una carpeta nueva).
-
Con el menú de contexto, sobre el árbol del proyecto, crear un fichero llamado “Practica1.py”.
-
Añadir al fichero recién creado la línea: print “Hola mundo”
Introducción a las matrices de numpy
En Visión Artificial, una parte muy importante de los algoritmos se centra en estudiar los patrones que aparecen en las matrices que corresponden a imágenes digitalizadas. Es por ello que la matriz es el elemento central de la biblioteca OpenCV. OpenCV está programada en C++ y las matrices que maneja originalmente son exclusivas de OpenCV. Sin embargo, en el recubrimiento que se ha creado de OpenCV para Python esto no es así.Para facilitar la interoperabilidad del módulo de OpenCV con otros paquetes (como matplotlib o scipy), las funciones de OpenCV en Python utilizan los arrays multidimensionales de Numpy. Cada uno de estos arrays es una tabla de elementos (usualmente números), todos del mismo tipo, indexados por una tupla de números positivos (filas, columnas y canal de color en el caso de imágenes en color). En adelante, por comodidad, utilizaremos el término matriz para referirnos a estos arrays multidimensionales.
Para crear una matriz que sea interpretada como imagen se debe utilizar los comandos que Numpy proporciona para crear arrays multidimensionales. El siguiente fragmento crea una matriz inicializada a cero, con 3 planos de 100x100 elementos de tipo byte (ver Figura 3).
import numpy as np
depth=np.uint8
rows=100
cols=200
channels=3
blank_image = np.zeros((rows,cols,channels), depth)
cv2.imshow("BlackWindow",blank_image)
cv2.waitKey()
Python es un lenguaje completamente orientado a objetos con tipado implícito. Es por ello que en el ejemplo anterior la variable “blank_image” representa a un objeto de tipo array multidensional sin que se haya realizado ninguna declaración previa (las variables adquieren el tipo del objeto que se les asigna).
Funciones para creación de arrays de numpy
Numpy dispone de muchas opciones para crear una matriz a partir de listas de Python (elementos separados por comas y entre []):
vec = np.array([1., 2., 1.])
# Matriz (2 x 3). mat.shape = (2,3)
mat = np.array([[1., 2., 3.], [4., 5., 6.]])
Numpy nos ofrece varias formas de inicializar los arrays a partir de las dimensiones:
# Matriz rellena de ceros de 3 filas x 4 columnas.
# En este caso el tipo de los elementos es float64.
m_0 = np.zeros( (3,4) )
# Matriz rellena de unos de 2 filas x 3 columnas x 4 planos.
# En este caso el tipo de los elementos es entero de 16 bits.
m_1 = np.ones( (2,3,4), dtype=np.int16 )
# Matriz con números aleatorios de 4 filas x 3 columnas.
# En este caso el tipo de los elementos es float64
# Apuntar a una parte de la matriz, desde la fila 0 a la 2 (inclusive)
# y desde la columna 1 a la 2 (inclusive)
roi = m_2[0:3, 1:3]
roi[:,:] = -1 # asignar -1 a todos los elementos de la roi en M2.
m_5 = m_2[1,:] # apunta a la parte de la matriz formada por la segunda fila.
m_6 = m_2[:,2] # apunta a la parte de la matriz formada por la tercera columna
Referencias a objetos.
En Python (casi como en Java) todos las variables son referencias a objetos. Por ello, si A apunta a un objeto y hacemos B=A, lo que tenemos es que A y B apunten al mismo objeto.
a=np.zeros((10,20)) #Matriz de 10 filas y 20 columnas
b = a #Matriz b comparte memoria con matriz a
a[0,0]=23 #Al poner el elemento (0,0) a 23
cambia_b = b[0,0]==23 #vale true, porque a y b apuntan a los mismos datos
b = a.copy() #Matriz b es igual a a, pero no comparte memoria.
a[0,0]=2 #Al poner el elemento (0,0) a 23
cambia_b = b[0,0]==2 #vale false, porque a y b apuntan a datos distintos
cambia = (b==a).all() #vale false, porque a y b apuntan a datos distintos
Propiedades y métodos descriptivos de las matrices
A continuación se presentan algunas de las propiedades más utilizadas de los arrays numpy:-
shape: tupla con las dimensiones de la matriz (filas, columnas, planos color)
-
ndim: número de dimensiones en la tupla shape (imágenes en grises ndims=2, y en imágenes en color ndims=3).
-
dtype: tipo de los elementos de la matriz (int8, float64, etc.)
-
“[]” para acceder a los elementos de la matriz. Por ejemplo: m[0,2]=3
-
“:” para acceder a una fila o una columna. Por ejemplo: m[:,0] =0
-
i:j:k para acceder a todos los elementos (slicing) desde el índice i al j (j no incluido) de k en k elementos. Por ejemplo: m[0:2,1:5:2] (submatriz con las filas de 0 a 1 y las columnas 1 y 3.
Tipos matrices e imágenes
Las imágenes en OpenCV con Python son arrays de numpy. Por tanto, utilizaremos las facilidades de ese paquete, incluyendo los tipos definidos en el mismo, para crear imágenes con los formatos más habituales:- dtype=np.uint8 y ndim=2, para imágenes en niveles de gris [0-255] o bitonales {0,255}.
i_8 = np.array([[1, 2, 3],[4, 5, 6]], dtype=np.uint8)
-
dtype=np.uint8 y ndim=3, para imágenes en color, con valores en [0-255], y con tres canales de color, imágenes con 24 bits por píxel (ver Figura 3). En OpenCV por defecto las imágenes son BGR, esto es, el primer canal es el azul, el segundo el verde y el tercero el rojo. Esto es importante cuando queremos usar funciones de OpenCV (cv2) mezcladas con las de otros paquetes como scipy o matplotlib (que necesita los canales ordenados como RGB). Para crear una imagen en color de 24 bits por píxel (3 planos de color) podemos hacer:
- dtype=np.int32 y ndim=2, para matrices de enteros de 32 bits (int64 para 64 bits):
i_24 = np.array([[[1, 2, 3], [4, 5, 6]],
[[7, 8, 9], [10, 11, 12]],
[[13, 14, 15], [16, 17, 18]]], dtype=np.uint8)
m_int = np.array([[1, 2, 3],[4, 5, 6]], dtype=np.int32)
-
dtype=np.float32 y ndim=2, para matrices de reales de 32 bits (o float16, float64, float128):
m_float = np.array([[1, 2, 3],[4, 5, 6]], dtype=np.float32)
Operaciones entre matrices
La clase matriz tiene una serie de métodos que permiten manipularlas. En los siguientes puntos se exploran algunos de estos métodos.Operaciones aritméticas: Suma, resta, producto y división de matrices
Los operadores +,-,* y / están sobrecargados. De manera que siempre que 2 matrices tengan exactamente las mismas dimensiones y tipo realizar operaciones aritméticas entre ellas.Operaciones lógicas: and y or
Los operadores |, & están sobrecargados. De manera que siempre que 2 matrices tengan exactamente las mismas dimensiones y tipo realizar operaciones aritméticas entre ellas.Operaciones de comparación y negación
Los operadores ~, >, <, >= y <= están sobrecargados. De esta forma se pueden obtener todos los píxeles de una imagen que cumplen cierta comparación con una simple llamada a esta función.
# Devuelve una imagen con valores cierto (255) y falso (0), en la que tienen
# valor cierto los píxeles cuyo valor es superior a 200.
pixeles_con_valor_alto = (imagen_gris > 200) #Máscara true false
pixeles_con_valor_alto = pixeles_con_valor_alto * 255 #Mascará en rango [0, 255]
Operaciones básicas de dibujo
OpenCV dispone de las siguientes funciones para realizar dibujos de líneas y áreas:-
line.- Dibuja lineas.
-
rectangle.- Dibujar rectángulos.
-
circle.- Dibuja círculos.
-
ellipse.- Dibuja elipses.
-
polylines.- Dibuja lineas poligonales.
-
fillpoly.- Dibuja polígonos rellenos.
-
putText.- Dibuja textos sobre las imágenes.
Otros tipos de datos
Además de las matrices, cuando se programa en Python se suelen manejar los siguientes tipos de datos:-
String.- Se puede crear utilizando comillas simples o dobles. El acceso a los elementos de un String se realiza utilizando los corchetes y permitiendo los dos puntos para definir subrangos. En los Strings se permiten caracteres de escape similares a los de Java y C. También se permite el uso del % para formatear números desde variables. Las triples comillas se utilizan para definir Strings de varias líneas. String, como clase tiene multitud de métodos útiles (len, max, isnumeric, capitalize...). Además hay muchas funciones core que operan sobre Strings.
S = “Hola”
-
List.- Se crea utilizando corchetes. Como clase tiene multitud de métodos útiles (sort, count, pop, remove, reverse, insert, append). La compresión de listas es una forma eficiente de definir listas (squares = [x**2 for x in range(10)])
l = [1, 2, 5, -3]
-
Tuple.- Las tuplas son listas inmutables. Se definen utilizando paréntesis. Por ejemplo:
t = (5,2)Se pueden definir tuplas de un número ilimitado de elementos. Por ejemplo:
-
(x,y) = (3,2)
-
a,b,c = (“a”,”b”,”c”)
-
t = (3,2,1,2,1,23,4,45,23,2)
También se pueden definir tuplas mixtas, en las que cada elemento es de un tipo. Por ejemplo:Incluso se pueden definir tuplas de un solo elemento, aunque en este caso hay que añadir una coma al final para distinguirla de una expresión matemática común. Por ejemplo a = (2,)
t = (5,2,”Hola”,x)
-
(x,y) = (3,2)
-
Set.- Es una colección de elementos no duplicados en los que el programador no controla el orden. Su uso básico incluye: inserción, pertenencia, eliminación, unión, intersección y diferencia. Al igual que las listas también soporta compresión. Para su declaración se pueden usar las llaves o el constructor “set”.
cartera = {“lapiz”,”cuaderno”,”papel”,”Libro}
-
Dictionary.- Python también permite la declaración de diccionarios en los que se insertan pares clave valor. Las claves deben ser objetos inmutables. Al igual que en los Sets, en la creación de diccionarios también se utilizan las llaves, aunque en este caso los elementos se componen de dos partes, la clave y el valor, separados por dos puntos.
agenda = {“Jose”:916512123,”Andres”:916198212, “Emergencias”:112}
Tipado opcional
Como se habrá observado, el tipado en Python es implicito. Esto quiere decir que todas las variables se asignan a objetos que tienen un tipo concreto. Dicho tipo determina las operaciones que se pueden realizar sobre el objeto.Sin embargo, recientemente (desde la versión 3.5) se ha incorporado al lenguaje la posibilidad de anotar el tipo de las variables que se definan. Esta característica es especialmente recomendada, ya que aporta mucha facilidad para entender el código. Además facilita a los editores la posibilidad de mostrar las posibles acciones a realizar sobre los objetos. Las siguientes lineas muestran algunos ejemplos:
def factorial(valor : int) -> int :
if valor == 0:
return 1
return valor * factorial(valor - 1)
a = [] : List[int]
a.append(3)
Instrucciones de control en Python
Quizás la característica más diferenciadora de Python respecto a otros lenguajes es que la indentación es obligatoria para definir el ámbito de las instrucciones de control. Mientras en Pascal se utilizan las palabrasbegin
y end
, y en C se utilizan las llaves {}
, en Python se utiliza el indentado. Por ello hay que cuidarlo especialmente, ya que de otra forma el programa dará error.Python dispone de la habitual sentencia if.
print "The value is negative."
print i
for x in string:
print x
print i
Declaración de funciones
Aunque Python en un lenguaje en el que todo son objetos, es posible definir funciones sin más que declararlas. Estas funciones no pertenecerán a ningún objeto, pero son objetos en si mismas.La indentación también es obligatoria para definir el ámbito de las funciones.
total = 0
i = first
while i <= last:
total = total + i
i += 1
return total
def f(x): return x % 3 == 0 or x % 5 == 0
...
>>> filter(f, xrange(2, 25))
[3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24]
Creación de clases
La creación de clases en Python es similar a la que encontramos en otros lenguajes. Como características destacables podemos destacar:-
Es obligatorio el uso de
self
para hacer referencia a las propiedades de la clase. En otro caso se consideran variables locales al método.
-
En la declaración de métodos es obligatorio el paso de
self
como primer parámetro. Sin embargo, en la invocación no hace falta pasar dicho parámetro, pues la sintaxis es “objeto.método(parámetros)”
-
Si una propiedad de clase comienza por barra baja se considera privada.
-
De nuevo, la indentación es obligatoria para definir el ámbito de las clases.
-
El constructor se llama __init__
def __init__(self, x = 0, y = 0):
self.x_coord = x
self.y_coord = y
def __str__(self):
return "(" + str(self.y_coord) + ", " +
str(self.y_coord) + ")"
def get_x(self):
return self.x_coord
def get_y(self):
return self.y_coord
def shift(self, x_inc, y_inc):
self.x_coord += x_inc
self.y_coord += y_inc
Estilo del código
En Python se utilizan las siguientes reglas de estilo:-
Las clases utilizan la notación CamelCase.
-
Los nombre de ficheros, variables, propiedades, funciones y métodos se escriben en minúsculas separadas por barras bajas. Por ejemplo: nombre_metodo, nombre_variable, nombre_fichero.
-
Las constantes se escriben en mayúsculas. Por ejemplo: PI, HEIGHT.
-
Los comentarios que explican lo que hace una función se escriben dentro de la función justo tras la cabecera. Para este tipo de comentario en vez de utilizar el carácter de comentario en línea se utiliza el marcador de cadena multilínea (triples comillas).
def factorial(valor : int) -> int :
"""
Función que calcula el factorial de un número.
"""
if valor == 0:
return 1
return valor * factorial(valor - 1)
-
Se ponen espacios antes y después de los operadores.
-
Se pone espacio detrás de las comas.
Bibliografia
- Pyhton for Java programers: http://Python4java.necaiseweb.org/Main/TableOfContents
- A Byte on Python: http://www.swaroopch.com/notes/python/
- Numpy for Matlab users: http://wiki.scipy.org/NumPy_for_Matlab_Users
- A tentative NumPy Tutorial: http://wiki.scipy.org/Tentative_NumPy_Tutorial
- OpenCV help http://docs.opencv.org/