Foro de elhacker.net

Programación => Scripting => Mensaje iniciado por: Once en 9 Abril 2017, 05:41 am



Título: Visión artificial con Python
Publicado por: Once en 9 Abril 2017, 05:41 am
Hola chicos, tengo  planeado hacer una serie de entregas acerca de visión artificial con Python y OpenCV en mi blog (http://elblogdeonce.blogspot.com), y me gustaría si no hay problema, también publicarlas en el foro. Cabe aclarar que se aceptan sugerencias y recomendaciones.


(https://1.bp.blogspot.com/-iTt55tSnYlc/WOlUVzAEvHI/AAAAAAAAABw/hsBOEC3ucII_A0fKbCzN3QikFdAlFpPdACLcB/s1600/header.jpg)


El objetivo de esta entrada (y en general de esta serie de entradas) es llevar la visión artificial a cualquier usuario de Python gracias  a las libreria OpenCV y Numpy. En esta entrada en concreto se abordará un poco de la teoría sobre la visión artificial pero lo suficiente como para comprender el material de las próximas entradas.



Representación digital de las imágenes

Antes de comenzar, debemos tener en cuenta cómo están representadas las imágenes o dicho de otra manera, cómo es una imagen para el computador.

  • Lo primero y más importante es que una imagen no es más que una matriz numérica (para imágenes en blanco y negro o a escala de grises) o varias matrices para imágenes a color. A cada una de estas matrices las llamamos componente.
  • Cada entrada de ésta matriz es un número que representa el color que debe tener la imagen en esa coordenada y que llamamos píxel y al valor de cada píxel lo llamamos nivel de gris.

Sistema de visión artificial

Un sistema de visión artificial generalmente se puede dividir en cinco etapas principales:

  • Adquisición imágenes: Es la etapa donde se forma y adquiere la imagen (rayos x, cámaras tradicionales, cámaras infrarojas, ultrasonido, etc).
  • Pre-procesamiento: Es la etapa que busca mejorar la calidad de la imagen para que pueda ser usada después.
  • Segmentación: Es la etapa donde se separan los distintos objetos presentes en la imagen.
  • Extracción de caracteristicas:  Es la etapa donde se obtienen las caracteristicas de los objetos previamente segmentados (color, dimensiones, textura).
  • Toma de desiciones: Es la etapa donde, a partir de los resultados de la etapa anterior se toma una decisión, como por ejemplo mover un brazo robótico a una coordenada específica.


Representación del color
Modelo de color

Un modelo de color es un modelo mate mático que permite representar los colores en forma numérica.

Existen varios modelos de color que aunque se puede representar la misma imagen usando varios modelos, en visión artificial es útil muchas veces representar las imágenes usando modelos diferentes al RGB ya que distintos canales de distintos modelos pueden resaltar detalles diferentes aunque la información completa no cambie.

Algunos modelos
RGB
El modelo RGB (Red, Green and Blue) es un modelo en el que los colores pueden ser representados por un vector de tres posiciones que representan los niveles de intensidad de rojo, verde y azul para cada pixel. El modelo RGB es un modelo aditivo.
CMYK
El modelo CMYK (Cyan, Magenta, Yellow, Key) es un modelo en que los colores pueden ser representados por un vector de cuatro posiciones que a su vez representan los niveles de cian, magenta, amarillo y negro para cada pixel. Éste es un modelo sustractivo.


El histograma
El histograma es un gráfico que nos muestra las frecuencias de ocurrencia de los niveles de gris en una imagen.

El histograma es una herramienta muy importante (como veremos en las siguientes entradas) ya que nos permite hacernos una idea de qué operaciones debemos hacer a la imagen para mejorar su calidad, especialmente durante la etapa de preprocesamiento.


(https://1.bp.blogspot.com/-NEqUI6qhphI/WOmmOySSBFI/AAAAAAAAACw/I9_lH-J0Z34U9JVcCOAYj0gBbQdiLEQkQCLcB/s320/histograma.png)
Imagen y su histograma.

Como se puede observar en la imagen anterior, en el eje de las x está representado el nivel de gris que puede tomar cada píxel (para una imagen de 8 bits, hay
2^8= 256 formas de representar los niveles de gris) y en el eje de las y está representado la cantidad de píxeles que hay por cada nivel de gris.

¿Qué es el ruido?
El ruido en las imágenes es información no deseada que contamina las imágenes. El ruido puede ser varios tipos y ser generado en la adquisición de la imagen por defectos del sensor o por errores o interferencias durante la transmisión de la imagen.

El ruido puede llegar a ser un gran problema cuando estamos haciendo visión por computador, especialmente en la etapa de segmentación, por eso uno de los principales objetivos de la etapa de preprocesamiento es intentar reducir el ruido de una imagen.

Algunos tipos de ruido.
Ruido gaussiano
Es el ruido cuya aparición sigue una distribución de probabilidad normal (o gaussiana).

Este tipo de ruido suele aparecer durante la adquisición de la imagen y se debe principalmente a condiciones de baja iluminación y/o altas temperaturas. Y durante la transmisión a causa del ruido electrónico en los circuitos.

(https://2.bp.blogspot.com/-Y5fzdFjTlAc/WOmRbAwfV9I/AAAAAAAAACQ/GiHBuoE2fKwxql97mFlEW-t6jWyr8ujmgCLcB/s1600/gaussian_free.jpg)
Imagen sin ruido. Wikipedia

(https://3.bp.blogspot.com/-hGurQIMBlI4/WOmRbLM2A6I/AAAAAAAAACU/T5eZGSKh5rI3LL3QohJot_KSxlfGw4srwCLcB/s1600/gaussian_noise.jpg)
Imagen ruido gaussiano. Wikipedia.


Ruido sal y pimienta
También conocido como ruido impulsivo es ruido que consiste en pixeles  blancos sobre regiones negras (ruido tipo sal) y/o pixeles negros sobre regiones claras (ruido tipo pimienta).

Este tipo de ruido se debe a problemas en la conversión de la imagen en el sensor y/o errores en la transmisión.

(https://1.bp.blogspot.com/-fFB06GQx4LQ/WOmTa-cgIGI/AAAAAAAAACg/WZQUjC_mQFg7khC5FK_2lQ5dwDFqq-PkwCLcB/s1600/impulsivo.png)
Imagen con ruido impulsivo.


Referencias
[1] https://en.wikipedia.org/wiki/Gaussian_noise
[2] https://en.wikipedia.org/wiki/Image_noise

Fuente: https://elblogdeonce.blogspot.com.co/2017/04/parte-i-vision-artificial-un-poco-de.html

Saludos!


Título: Re: Visión artificial con Python
Publicado por: Once en 11 Abril 2017, 07:31 am
Fuente: http://elblogdeonce.blogspot.com.co/2017/04/parte-ii-vision-artificial-primeros.html

Una introducción a OpenCV y Numpy

(https://1.bp.blogspot.com/-eUVwuMmsIxs/WOqHPCnm1vI/AAAAAAAAADo/o7SiNDpLMogFO2BUuTsQ51wZK3JArHQsgCLcB/s1600/logo.png)

En esta segunda entrega sobre visión artificial con Python trataremos una introducción a OpenCV y Numpy. Crearemos imágenes desde cero y abriremos imágenes ya existentes.

¿Qué es OpenCV?

OpenCV (Open source Computer Vision) es un libreria bajo la licencia BSD diseñada y optimizada para hacer visión por computador y aprendizaje automático. Está disponible para los lenguajes C, C++, Java y Python. Además puede correr sobre Windows, Linux, MAC, IOS y Android.

La documentación de esta libreria se puede consultar (y descargar) desde acá.

¿Qué es NumPy?

Numpy es una libreria optimizada para hacer computación científica con Python, especialmente algebra lineal (matricial) que es lo que nos interesará para hacer visión por computador.

¿Es necesario usar estas librerias?

No, la respuesta es un rotundo no. La visión por computar es básicamente operaciones matriciales, operaciones que podemos implementar no solo en Python puro y duro, sino que en casi cualqueir otro lenguaje.

La ventaja, entonces, está en que estas librerias fueron creadas y son mantenidas por verdaderos expertos en el tema (científicos de la computación, matemáticos, etc) lo que da como resultado algoritmos realmente optimizados.

Configurando el entorno

Durante esta serie de entregas necesitaremos básicamente tres librerias:

OpenCV: Para hacer visión por computador.
Numpy: Para hacer operaciones matriciales.
Matplotlib: Para graficar (especialmente histogramas)

Todas estas librerias están disponibles para descargar usando pip.

Instalando los paquetes necesarios


Código
  1. # pip install opencv-python
  2.  
  3. # pip install numpy
  4.  
  5. # pip install matplotlib

¿Estamos listos? Entonces... comencemos

Como vimos en la entrega anterior, una imagen no es más que un sistema de matrices donde cada entrada representa un nivel de gris para cada píxel.

Creando nuestra primer imagen

Como ya hemos mencionado antes una imagen no es más que una matriz de valores numéricos. Demostremos esto con un simple ejemplo.

Código
  1. import numpy as np
  2. import cv2
  3.  
  4. # Creamos una matriz de unos de 100 filas y 100 columnas
  5. imagen = np.ones((100, 100, 3), np.uint8)
  6. # Mostramos la imagen
  7. cv2.imshow("Mi primer imagen", imagen)
  8. # Guardamos la imagen donde esta el script
  9. cv2.imwrite("Mi primer imagen.png", imagen)
  10. cv2.waitKey()  # Pausa la ejecuación hasta que se presione una tecla
  11. cv2.destroyAllWindows()  # Cierra todas las ventanas abiertas

Una vez ejecutamos el script anterior tenemos que el resultado es el siguiente:

(https://4.bp.blogspot.com/-qvuHl9VSzvs/WOpV_ilnRcI/AAAAAAAAADE/kHUc7JBJ99wFtG06vqDJaNroBHK4N1u3gCLcB/s1600/mi_primer_imagen.png)
Imagen creada con Numpy

En las primeras dos líneas importamos numpy (y le asignamos el alias np, para no tener que estar escribiendo numpy todo el rato) y OpenCV (cv2).

Luego creamos una matriz de unos con la función ones de numpy. Si imprimimos imagen (print(numpy)) veremos que, efectivamente, es una matriz llena de unos.

Citar
numpy.ones(shape, dtype=None): Crea un array de dimensión sahape donde cada entrada es del tipo dtype.

shape: es la forma que tendrá el array, si solo tiene un elemento será una lista, dos elementos (n, m) representan una matriz con n filas y m columnas. Y con tres elementos  (n, m, z) n filas, m columnas y z representa la cantidad de matrices o canales que tendrá el array.

En este ejemplo creamos una matriz llena de unos con 100 filas, 100 columnas y un solo canal donde todas las entradas son números enteros de 8 bits.

Citar
cv2.imshow(nombre, img): Muestra la imagen img. nombre es una cadena que representará la ventana donde se muestra la imagen. img debe ser una matriz.

cv2.imwrite(nombre, img): Guarda en el disco duro la matriz img como una imagen con el nombre nombre

Entonces... ¿cómo se representa una imagen a color?.

En el ejemplo anterior creamos una imagen de un solo canal (una sola matriz) lo que significa que solo podemos representar un color. Para este caso el gris o escala de grises.

Si queremos representar una imagen a color, vamos a necesitar más canales (ya no solo una matriz) dependiendo del modelo de color con el que queremos trabajar.

Código
  1. import numpy as np
  2. import cv2
  3.  
  4. # Creamos la matriz de uno 100 filas
  5. # 100 columnas y 3 canales
  6. imagen = np.ones((100, 100, 3), np.uint8)
  7. # agregamos color a la imagen
  8. # A todas las columas pero primeras 25 filas
  9. # Asigna el valor (255, 0, 0)
  10. imagen[:, 0:25] = (255, 0, 0)
  11. # A todas las columnas pero desde la fila
  12. # 25 hasta la 50 asigna el valor (0, 255, 0)
  13. imagen[:, 25:50] = (0, 255, 0)
  14. # A todas las columnas pero desde la fila
  15. # 50 hasta la 75 asigna el valor (0, 0, 255)
  16. imagen[:, 50:75] = (0, 0, 255)
  17.  
  18. # Guardamos la imagen en el disco duro
  19. cv2.imwrite("imagen_color.png", imagen)
  20. # Mostramos la imagen
  21. cv2.imshow("imagen", imagen)
  22. # Esperamos que se presione una tecla
  23. cv2.waitKey()
  24. # Cerramos todas las ventanas
  25. cv2.destroyAllWindows()

El resultado es el siguiente:


(https://3.bp.blogspot.com/-OQuaebOJAkg/WOp63YrlkCI/AAAAAAAAADU/T0hUbxnsUfsVikldYPOhFfkdDAuTq6F0QCLcB/s1600/imagen_color.png)
Imagen a color generada con Numpy.

Y ahora sí obtenemos una imagen a color usando solamente numpy.

El cambio en este código está en la forma del array shape (100, 100, 3) donde el tres representa la cantidad de canales de la imagen, como trabajamos con el modelo RGB necesitamos tres canales, uno para el rojo, otro para el verde y un último para el azul.

OpenCV por defecto trabajo con el modelo BGR que es el mismo modelo RGB, pero con los valores invertidos.

Otra cosa nueva es la forma de asignar los colores a la imagen, esta forma se llama indexing y es muy parecido al slicing solo que para arrays multidimencionales. El formato es el siguiente:

Citar
matriz[a:b, c:d] = tupla

Donde a representa la primer fila a seleccionar y b la última fila a seleccionar. c representa la primer columa a seleccionar y d la última columna a seleccionar.

Si no se especifica a o c se selecciona desde el primer elemento.
Si no se especifica b o d se selecciona hasta el último elemento.

tupla debe ser una tupla o lista de elemetos a asignar a la matriz en la posicion señanala. Debe tener la misma longitud que canales la imagen. El primer elemento es asignado a la primer matriz y así sucesivamente hasta el último valor que es asignado a la última matriz.

Leer una imagen con OpenCV

OpenCV cuenta con una función que nos permite leer una imagen desde el disco y nos devuelve un array de numpy.

Citar
cv2.imread(name, flag): Lee la imagen name y devuelve una matriz.

Si el parámetro flag es positivo OpenCV interpretará la imagen como si tuviera tres canales y por lo tanto devolvera una matriz con tres canales.

Si el parámetro es cero  OpenCV leerá la imagen en esacala de grises y por lo tanto devolverá una matriz de un solo canal.

Si el parámetro es negativo OpenCV leerá la imagen tal cual como es y los canales de la matriz resultante dependen de la imagen a leer.


Obteniendo el tamaño de una imagen


Luego de abrir una imagen con OpenCV, el resultado es un array de numpy, por lo que tenemos todas las ventajas que nos brinda los objetos de numpy, uno de ellos es la propiedad shape.

La propiedad shape devuelve una lista que indica la cantidad de filas, columnas y canales que tiene un array respectivamente. Si la lista solo contiene dos elementos, indica que el array solo tiene un solo canal.

Su uso es muy simple:
Código
  1. import numpy as np
  2. import cv2
  3.  
  4. #  Leemos la imagen
  5. img = cv2.imread("imagen.jpg")
  6. #  Imprimimos el tamanio
  7. print(img.shape)
  8.  

Comprendiendo el flag de la función cv2.imread()

Haciendo un par de cambios al ejemplo anterior, podemos comprender mejor el significado del falg en la función cv2.imread()
Código
  1. import numpy as np
  2. import cv2
  3.  
  4. img = cv2.imread("logo.png")
  5. print("Sin flag:", img.shape)
  6.  
  7. img = cv2.imread("logo.png", -1)
  8. print("flag negativo:", img.shape)
  9.  
  10. img = cv2.imread("logo.png", 0)
  11. print("flag cero:", img.shape)
  12.  
  13. img = cv2.imread("logo.png", 1)
  14. print("flag positivo:", img.shape)

El resultado será algo así:

Citar
Sin flag: (99, 82, 3)
flag negativo: (99, 82, 4)
flag cero: (99, 82)
flag positivo: (99, 82, 3)

En todos los casos tenemos una matriz de 99 filas y 82 columnas, pero con distintos canales. Analicemos el significado.

flag negativo: La imagen se interpreta tal cual como es, por lo que tenemos tres canales para el color y uno más para la transparencia.
flag cero: La imagen se interpreta a escala de grises y por lo tanto sólo se necesita un canal.
flag positivo: La imagen se interpreta como si sólo tuviera tres canales (elimina la transparencia).

(https://3.bp.blogspot.com/-HfUntH3XcNg/WOxf2Xon0UI/AAAAAAAAAD8/esf4K_yAHHMEqwRSPI4_LOpHByp9An4TQCLcB/s320/flagsw.png)
Diferencias según el flag.

Saludos.
Once.


Título: Re: Visión artificial con Python
Publicado por: PUAROT en 11 Abril 2017, 19:48 pm
Genial esta serie de entregas !!!

Espero que sigan! hacen falta este tipo de iniciativas

 ;-) ;-) ;-)


Un saludo y enhorabuena


Título: Re: Visión artificial con Python
Publicado por: PUAROT en 11 Abril 2017, 20:52 pm
Aprovecho para preguntarte ....

Hace un tiempo intente instalar OpenCV en Mac y no fuí capaz, me decía que no tenia instalado los xcode, y como no tengo actualizado a la ultima version, no me dejaba instalarlo.
Por otra parte he intentado instalar con pip3 ya que utilizo python 3 tanto en MAC como en la Raspberry pi 2  y me dice que no encuentra el paquete

Con pñip3 search he encontrado opencv-python ... es el mismo?
Lo que encuentro para instalar opencv ya sea para MAC o para raspberry pi, es mas complicado que el simple pip

 


Título: Re: Visión artificial con Python
Publicado por: Once en 11 Abril 2017, 21:50 pm
Hola PUAROT, primero, gracias por responder y sí, por el momento están planeadas un par de entregas más.

Con respecto a tu duda, no hay problema si lo instalas con pip3 de hecho yo también estoy trabajando con Python 3.6.

Muchas gracias por el comentario, sí opencv-python es cómo se encuentra OpenCV en el pip, la embarré cuando escribí el artículo. De nuevo gracias por haber notado ese error (espero no hallan más).

Si tienes algún otro problema o duda no dudes en comentarlo.

Saludos!


Título: Re: Visión artificial con Python
Publicado por: tincopasan en 13 Abril 2017, 08:59 am
está bueno que tengas la iniciativa de mostrar el uso de este módulo, y ya que usaste:
Citar
# Guardamos la imagen en el disco duro
cv2.imwrite("imagen_color.png", imagen)

habría que mecionar que se puede cambiar el formato(la extensión) con la misma sentencia:
Código
  1. cv2.imwrite("imagen_color.jpg",imagen)
y con esto convertimos de png a jpg ya que se pueden usar basicamente bmp,tiff,jpg,png. con la consiguiente variación de tamaño(peso,kb) para cada caso.


Título: Re: Visión artificial con Python
Publicado por: Once en 16 Abril 2017, 08:42 am
Hola tincopasan, gracias por la anotación, la verdad en esta entrada no pretendía explicar esa función, solo mostrar que efectivamente, las imágenes no son más que matrices de números, pero pensándolo bien, creo que quedaría mejor introducir también la función acá, gracias de nuevo y cuando tenga algo de tiempo, edito la entrega.

Saludos!


Título: Introducción a NumPy
Publicado por: Once en 12 Julio 2017, 13:25 pm
Fuente: http://elblogdeonce.blogspot.com.co/2017/07/parte-iii-vision-artificial.html (http://elblogdeonce.blogspot.com.co/2017/07/parte-iii-vision-artificial.html)

(https://4.bp.blogspot.com/-YZ7RMSH0L60/WWVtUNGfu6I/AAAAAAAAAEw/w7zFxNl58t4I4A1JVczDnLTH_1Uut36SgCEwYBhgL/s1600/numpy_logo.png)

Aunque en la entrada anterior ([Parte II: visión artificial] Primeros pasos con OpenCV y Numpy) se habló un poco sobre numpy éste se merece una entrada completa (y más) debido a que el corazón de la visión artificial son las operaciones matriciales lo que hace de numpy una parte imprescindible incluso cuando usamos librerias que hacen gran parte del trabajo como OpenCV. El objetivo de ésta entrada es familiarizarnos un poco más con NumPy y así poder dejar sentado el terreno para comenzar a hacer visión artificial.

¿Cómo recorrer una matriz?
Uno de los principales problemas/confusiones que he notado es a la hora de recorrer una matriz. La confusión es simple, suelen tratar a las matices como sistemas coordenados...

(https://2.bp.blogspot.com/-dFF820MU7sI/WWUeYJr1CkI/AAAAAAAAAEc/PiLbl2_HOoYcsxHZaqnci-nh3nHgGp_UgCLcBGAs/s320/matriz.png)
Figura: estructura de una matriz.

Por convención a cada sección horizontal de la matriz la llamamos una fila (verde) y a cada sección vertical una columna (azul). Además, la numeración de las filas y columnas comienza desde la esquina superior izquierda (el origen), las filas crecen hacía abajo y las columnas hacía la derecha.

Para acceder a un elemento, se debe especificar la fila y la columna (generalmente en ese orden exacto) donde se encuentra el elemento.

Y acá está el problema, cuando accedemos a un elemento de una matriz lo hacemos mediante dos coordenadas (fila, columna) por lo que debemos tener en cuenta que las filas nos mueven por la matriz verticalmente y que las columnas nos mueven por la matriz horizontalmente. Contrario a como nos movemos en un sistema coordenado donde por lo general la primer componente nos mueve horizontalmente (el eje de las x), y la segunda verticalmente (el eje de las y).

Tener esto en cuenta nos va a ahora MUCHOS dolores de cabeza en un futuro.

¿Cómo crear una matriz?

Numpy nos proporciona varios métodos para crear matrices:

A partir de una lista
Para crear una matriz a partir de una lista tenemos el método numpy.array() que recibe como parametro una lista (que puede ser una lista de lista en el caso de las matrices)

Código
  1. >>> import numpy as np
  2. >>> x = np.array([1, 2, 3])  # Vector
  3. >>> x
  4. array([1, 2, 3])
  5. >>> X = np.array([[1, 2, 3], [4, 5, 6]])  # Matriz 2x3
  6. >>> X
  7. array([[1, 2, 3],
  8.       [4, 5, 6]])

Métodos predefinidos
Numpy también nos proporciona unos métodos predefinidos para crear matrices, por ejemplo la matriz identidad, matrices de unos o de ceros:

Código
  1. >>> import numpy as np
  2. >>> identidad = np.identity(3)
  3. >>> identidad
  4. array([[ 1.,  0.,  0.],
  5.       [ 0.,  1.,  0.],
  6.       [ 0.,  0.,  1.]])
  7. >>> ceros = np.zeros((3, 3))
  8. >>> ceros
  9. array([[ 0.,  0.,  0.],
  10.       [ 0.,  0.,  0.],
  11.       [ 0.,  0.,  0.]])
  12. >>> unos = np.ones((3, 3))
  13. >>> unos
  14. array([[ 1.,  1.,  1.],
  15.       [ 1.,  1.,  1.],
  16.       [ 1.,  1.,  1.]])

El método numpy.identity(n) genera la matriz identidad de dimensiones nxn
Los métodos numpy.zeros(tamaño), numpy.ones(tamaño) crean matrices de ceros y unos respectivamente.

¿Operaciones con matrices?
Una de las cosas que más conmociona a las personas cuando se enfrentan por primer vez con numpy es que éste usa los mismos operadores de Python.
Así que veamos un repaso de éstos operadores:


Operador   Uso   Operación
+   a + b   Suma
-   a - b   Resta
*   a * b   Multiplicación
/   a / b   División
//   a // b   División entera
**   a ** b   Potencia
%   a % b   Módulo
<   a < b   Menor que
<=   a <= b   Menor o igual que
==   a == b   Igualdad
>   a > b   Mayor que
>=   a >= b   Mayor o igual que
!=   a != b   Distinto

Éstos son sólo algunos de los operadores de Python que además podemos usar con objetos de numpy. Esto se logra sobreescribiendo los operadores (algo de lo que hablaremos luego en una entrada aparte).

Hay dos cosas que debemos tener en cuenta:

  • Al menos uno de los operandos debe ser un objeto numpy: Así es, no es necesario que los dos operandos sean objetos de numpy, basta con se sólo a, o sólo b sean objetos de numpy para que se pueda realizar la operación sin errores.
  • Éstas operaciones son elemento a elemento: Acá tenemos dos casos:
    • Tanto a como b son objetos de numpy: En este caso, las operaciones se realizan elemento a elemento, es decir, el primero de a con el primero de b, el segundo de a con el segundo de b... por lo que debemos tener en cuenta que tanto a, como b deben tener las mismas dimensiones.
    • Hay un objeto de numpy y un número: En este caso, se aplica la operación deseada a toda la matriz con el número indicado.
      El resultado va a ser una nueva matriz con el resultado de aplicar la operación indicada.


Si queremos la multiplicación habitual de matrices, a partir de Python 3.5 se introdujo el operador @ (PEP 465) que nos permite justo eso, realizar la multiplicación "real" entre matrices.

Un pequeño ejemplo
Código
  1. >>> import numpy as np
  2. >>> a = np.ones((3, 3))  # Crea una matriz de unos 3x3
  3. >>> a
  4. array([[ 1.,  1.,  1.],
  5.       [ 1.,  1.,  1.],
  6.       [ 1.,  1.,  1.]])
  7. >>> a = a * 2  # Multiplica cada componente de a por 2
  8. >>> a
  9. array([[ 2.,  2.,  2.],
  10.       [ 2.,  2.,  2.],
  11.       [ 2.,  2.,  2.]])
  12. >>> b = np.ones((3, 3)) ** 5  # Eleva cada componente a la quintapotencia
  13. >>> b
  14. array([[ 1.,  1.,  1.],
  15.       [ 1.,  1.,  1.],
  16.       [ 1.,  1.,  1.]])
  17. >>> b = b + 5  # Suma a cada componente de b 5
  18. >>> b
  19. array([[ 6.,  6.,  6.],
  20.       [ 6.,  6.,  6.],
  21.       [ 6.,  6.,  6.]])
  22. >>> a @ b  # Multiplicación matricial entre a y b
  23. array([[ 36.,  36.,  36.],
  24.       [ 36.,  36.,  36.],
  25.       [ 36.,  36.,  36.]])
  26. >>> b > 5  # Componentes mayores que 5
  27. array([[ True,  True,  True],
  28.       [ True,  True,  True],
  29.       [ True,  True,  True]], dtype=bool)
  30. >>> b < 3  # Componentes menores que 3
  31. array([[False, False, False],
  32.       [False, False, False],
  33.       [False, False, False]], dtype=bool)
  34. >>>

Indexing y slicing
Al igual que las lista en Python, los objetos de numpy también soportan tanto el indexing, como el slicing.

Indexing
El indexing nos permite acceder a un elemento indicando sus coordenadas.

Si estamos trabajando con un vector (matriz con solo una columna o solo una fila) para acceder a un elemento sólo necesitamos su índice:
Código
  1. >>> import numpy as np
  2. >>> x = np.array([1, 2, 3])
  3. >>> x[0]
  4. 1
  5. >>> x[1]
  6. 2
  7. >>> x[-1]
  8. 3
  9. >>>

El indexing para vectores funciona igual que para listas.

Si estamos trabajando con matrices, para acceder a un elemento, necesitamos dos coordenadas (a veces tres) la fila y la columna donde se encuentra el elemento que necesitamos:

Código
  1. >>> import numpy as np
  2. >>> a = np.identity(3)
  3. >>> a
  4. array([[ 1.,  0.,  0.],
  5.       [ 0.,  1.,  0.],
  6.       [ 0.,  0.,  1.]])
  7. >>> a[1, 1]
  8. 1.0
  9. >>> a[1,-1]
  10. 0.0
  11. >>> a[0,0]
  12. 1.0

Los índices negativos seleccionan un elemento comenzando desde el final del array, siendo -1 la posición del último elemento, -2 la del penultimo y así...

Es por eso que en el caso del array, x[-1] devuelve 3 ya que este es el último elemento del array; y en el caso de la matriz, a[1, -1] devuelve 0.0 ya que éste es el elemento que se encuentra en la primer fila y en la última columna.

Slicing
El slicing es una variación del indexing en la que en lugar de obtener un solo elemento, nos permite obtener un rango de elementos.

La sintaxis es igual a la del slicing para listas:
Código
  1. >>> import numpy as np
  2. >>> x = np.arange(10)
  3. >>> x
  4. array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  5. >>> x[:5]
  6. array([0, 1, 2, 3, 4])
  7. >>> x[5:]
  8. array([5, 6, 7, 8, 9])
  9. >>> x[-1:0:-1]
  10. array([9, 8, 7, 6, 5, 4, 3, 2, 1])
  11. >>> x[-1:0:-2]
  12. array([9, 7, 5, 3, 1])

Se indica el rango que se quiere obtener usando a:b (dos puntos) donde tanto a como b son enteros. a indica el inicio, si se omite, se toma 0 y b indica el final, si se omite, se toma -1.

Hay que tener en cuenta que el rango que devuelve el slicing va desde a hasta  b - 1.

Además, hay un tercer parámetro opcional, también separado por : (dos puntos) que indica el paso (o salto).

Para las matrices la sintaxis es la misma, solo que debemos hacer el slicing tanto para las filas como para las columnas, separadas por una , (coma):


Código
  1. >>> import numpy as np
  2. >>> a = np.identity(3)
  3. >>> a
  4. array([[ 1.,  0.,  0.],
  5.       [ 0.,  1.,  0.],
  6.       [ 0.,  0.,  1.]])
  7. >>> a[1:, 1:]
  8. array([[ 1.,  0.],
  9.       [ 0.,  1.]])
  10. >>> a[:1, :1]
  11. array([[ 1.]])
  12. >>> a[:2, :2]
  13. array([[ 1.,  0.],
  14.       [ 0.,  1.]])
  15. >>>

Estos son solo ejemplos de indexing y slicing básicos, para ver operaciones más avanzadas y aclarar posibles dudas, referirse a la documentación oficial


Modificar los valores de una matriz
Para modificar los valores de una matriz podemos hacerlo de a un solo elemento usando indexing, o de a varios elementos usando slicing.

Eso sí, siempre debemos tener en cuenta el tamaño de los datos que queremos escribir coincidan con el tamaño seleccionado en la matriz de destino.

Código
  1. >>> import numpy as np
  2. >>> a = np.identity(3)
  3. >>> a
  4. array([[ 1.,  0.,  0.],
  5.       [ 0.,  1.,  0.],
  6.       [ 0.,  0.,  1.]])
  7. >>> a[1, 1] = 10
  8. >>> a
  9. array([[  1.,   0.,   0.],
  10.       [  0.,  10.,   0.],
  11.       [  0.,   0.,   1.]])
  12. >>> a[1:, 1:] = np.ones((3, 3))
  13. Traceback (most recent call last):
  14.  File "", line 1, in
  15. ValueError: could not broadcast input array from shape (3,3) into shape (2,2)
  16. >>> a[1:, 1:] = np.ones((2, 2))
  17. >>> a
  18. array([[ 1.,  0.,  0.],
  19.       [ 0.,  1.,  1.],
  20.       [ 0.,  1.,  1.]])

En éste caso, en la línea 12 ocurre un error porque estamos intentando asignar una matriz 3x3 a una zona de la matriz a de tamaño 2x2.

Uso de máscaras
Otra forma de modificar los elementos de una matriz es mediante el uso de máscaras.

Una máscara es una matriz booleana (de falsos y verdaderos) donde las componentes verdaderas indican los elementos que van a ser modificados.

La máscara se pasa como si ésta fuera un índice:

Código
  1. >>> x = np.arange(25)  # Crea un vector
  2. >>> X = x.reshape(5, 5)  # Convierte el vector a matriz de 5x5
  3. >>> X
  4. array([[ 0,  1,  2,  3,  4],
  5.       [ 5,  6,  7,  8,  9],
  6.       [10, 11, 12, 13, 14],
  7.       [15, 16, 17, 18, 19],
  8.       [20, 21, 22, 23, 24]])
  9. >>> mascara = (X % 2 == 0)  # Genera máscara posición de los elementos pares
  10. >>> mascara
  11. array([[ True, False,  True, False,  True],
  12.       [False,  True, False,  True, False],
  13.       [ True, False,  True, False,  True],
  14.       [False,  True, False,  True, False],
  15.       [ True, False,  True, False,  True]], dtype=bool)
  16. >>> X[mascara] = 0  # Cambia todos los pares por cero
  17. >>> X
  18. array([[ 0,  1,  0,  3,  0],
  19.       [ 5,  0,  7,  0,  9],
  20.       [ 0, 11,  0, 13,  0],
  21.       [15,  0, 17,  0, 19],
  22.       [ 0, 21,  0, 23,  0]])

¿Paso de matrices como valor o referencia?
Otro de los inconvenientes (más que inconveniente es un dolor de trasero) en especial para quien apenas está comenzando con Python e incluso para los distraidos es que, a veces, despues de llamar a una función y de pasarle a esta una matriz sobre la que realizaremos una operación suceden cosas extrañas. (brujería podrian pensar algunos) y el culpable está en como Python pasa los parametros por valor y por referencia.

Parametros por valor
Decimos que un parametro se pasa por valor cuando a una función se le pasa el valor (valga la redundancia) de una variable, o en otras palabras, una copia de una variable. Por lo tanto los cambios que se hagan sobre este parametro se hacen sobre la copia.

Parametros por referencia
Decimos que un parametro se pasa por referencia cuando a una función se le pasa la dirección en memoria de una variable en lugar de una copia de la variable. Por lo tanto los cambios que se hagan sobre este parametro, se hacen en la dirección de memoria a la que apunta y por lo tanto se hacen sobre la variable original.


Teniendo ésto en cuenta, ahora debemos comprender como toma Python los parametros por valor y por referencia.

Parametros por valor y referencia en Python
A Python no podemos especificarle explícitamente que valores queremos pasar por valor y cuales por referencia, en su lugar debemos recordar:

Los objetos mutables se pasan por referencia.
Los objetos inmutables se pasan por valor.

Los objetos mutables son aquellos que pueden cambiar su valor.
Los objetos inmutables son aquellos que no pueden cambiar su valor.

Aunque ésta definición no está completa (no es el objetivo de la entrada) es suficiente para comprender la "magía negra" que está por ocurrir. (Eso sí, teniendo en cuenta que las matrices de numpy son objetos mutables).

Código
  1. import numpy as np
  2.  
  3. def funcion(matriz):
  4.    matriz[1, 1] = 10
  5.  
  6. mi_matriz = np.ones((3, 3))
  7. print("Antes de llamar la función:")
  8. print(mi_matriz)
  9. funcion(mi_matriz)
  10. print("Después de llamar la función:")
  11. print(mi_matriz)

Citar
Antes de llamar la función:
[[ 1.  1.  1.]
 [ 1.  1.  1.]
 [ 1.  1.  1.]]
Después de llamar la función:
[[  1.   1.   1.]
 [  1.  10.   1.]
 [  1.   1.   1.]]

Ésto significa que los cambios que realizamos a la matriz (incluso con distinto nombre) dentro de la función son visibles (afectan a la variable fuera de la función) fuera de la función.

Si no queremos que esto suceda debemos crear explícitamente una copia de la matriz y pasar a la función ésta copia en lugar de la matriz original.

Para copiar una matriz, podemos hacer uso del método copy() que tienen los objetos de numpy:

Código
  1. import numpy as np
  2.  
  3. def funcion(matriz):
  4.    matriz[1, 1] = 10
  5.  
  6. mi_matriz = np.ones((3, 3))
  7. print("Antes de llamar la función:")
  8. print(mi_matriz)
  9. funcion(mi_matriz.copy())
  10. print("Después de llamar la función:")
  11. print(mi_matriz)

Citar
Antes de llamar la función:
[[ 1.  1.  1.]
 [ 1.  1.  1.]
 [ 1.  1.  1.]]
Después de llamar la función:
[[  1.   1.   1.]
 [  1.   1.   1.]
 [  1.   1.   1.]]

Ésta vez pasamos una copia de la matriz y no la matriz como tal, por lo que aunque se sigue pasando la copia de la matriz por referencia se está modificando la copia pero no la original.

Algunos métodos interesantes de numpy
numpy.amin(matriz): Devuelve el menor elemento de la matriz.
numpy.amax(matriz): Devuelve el mayor elemento de la matriz.
numpy.arange(matriz): Similar a range()
numpy.sum(matriz): Suma todos los elementos de la matriz.

Algunos métodos de las matrices (objetos ndarray)
matriz.ravel(): Devuelve un vector con los elementos de la matriz.
matriz.copy(): Copia la matriz.
matriz.reshape(tamaño): Cambia el tamaño de la matriz.

Referirse a la documentación para una lista completa de los métodos y propiedades: ir

Saludos!
Once.