Jugando ✊ piedra, ✋ papel o ✌️ tijeras | Mediapipe – OpenCV – Python

Por Administrador

¡Bienvenidos a un nuevo post lleno de tecnología y diversión! En este, exploraremos cómo crear un juego de piedra, papel o tijeras con visión por computador. Ya sea que seas un programador principiante o un entusiasta experimentado, te aseguro que te divertirás mientras aprendes a hacer tu propio juego utilizando la tecnología de visión artificial. ¡Prepárate para una aventura divertida y llena de desafíos en la que pondrás en práctica tus habilidades de programación!.

CONTENIDO

Jugando piedra, papel o tijeras con Visión Artificial

  • Instalación de packages
  • Imágenes que usaremos para nuestro juego
  • ¡Vamos con la programación!

Jugando piedra, papel o tijeras con Visión Artificial

En esta oportunidad jugaremos con el computador, piedra, papel o tijeras. Antes de empezar con la programación es importante conocer las reglas del juego (aunque por la popularidad de este, puede que no sea necesario explicarlas, pero voy a hacerlo de todos modos, solo porsiacaso 😉).

Figura 1: Reglas de cuando podemos perder o ganar en el juego.

Entonces, ¡exploremos las reglas!. Tenemos tres opciones, piedra que sería un puño, papel que sería la mano extendida y por último, tijeras que es solo levantar los dedos índice y medio. Sabemos que si tanto nosotros como la computadora (o un contrincante humano) emitimos la misma opción, existe un empate. Es por ello que veremos en qué casos podríamos ganar o perder.

Nota: En la figura 1, las ilustraciones que se encuentran rodeadas de azul serán las opciones que podemos escoger, mientras que de verde y rosa las de nuestro contrincante.

  • Si elegimos piedra, solo podremos ganar cuando la computadora elija tijeras y perder con papel.
  • Si elegimos papel, solo podremos ganarle a la piedra, y perder contra tijeras.
  • Por último, si elegimos tijeras, podremos ganarle al papel, pero no a la piedra. 

Instalación de packages

En esta oportunidad necesitaremos instalar Mediapipe con ayuda de pip install mediapipe. Este package a su vez instalará otros módulos como OpenCV o Numpy. Podemos listar los packages instalados usando pip freeze.

Figura 2: Listando los packages instalados, con pip freeze.

Imágenes que usaremos para nuestro juego

Usaremos 5 imágenes, las cuales irán cambiando conforme vamos avanzando en el juego. He creado estas con un alto de 480 píxeles para que se concatenen con la altura de mi video straming, pero podrías adaptarlas a tus necesidades.

¡Vamos con la programación!

Para la realización de este programa, he tomado otro que habíamos hecho anteriormente, se trata de: ✌️ Contando dedos con visión artificial | Mediapipe – OpenCV – Python. Por lo que te recomiendo probar primero ese programa para luego añadir más líneas que nos permitirán construir el juego.

Recuerda que para una explicación más detallada del programa que veremos a continuación, por favor dirígete a este video que he preparado en mi canal, en donde explico paso a pasito cada procedimiento efectuado. ¡Anímate a verlo 😉!. Ahora sí… ¡Vamos con el código!. 

import cv2
import mediapipe as mp
import numpy as np
from math import acos, degrees
import random

def palm_centroid(coordinates_list):
     coordinates = np.array(coordinates_list)
     centroid = np.mean(coordinates, axis=0)
     centroid = int(centroid[0]), int(centroid[1])
     return centroid

def fingers_up_down(hand_results, thumb_points, palm_points, fingertips_points, finger_base_points):
     fingers = None
     coordinates_thumb = []
     coordinates_palm = []
     coordinates_ft = []
     coordinates_fb = []
     for hand_landmarks in hand_results.multi_hand_landmarks:
          for index in thumb_points:
               x = int(hand_landmarks.landmark[index].x * width)
               y = int(hand_landmarks.landmark[index].y * height)
               coordinates_thumb.append([x, y])
          
          for index in palm_points:
               x = int(hand_landmarks.landmark[index].x * width)
               y = int(hand_landmarks.landmark[index].y * height)
               coordinates_palm.append([x, y])
          
          for index in fingertips_points:
               x = int(hand_landmarks.landmark[index].x * width)
               y = int(hand_landmarks.landmark[index].y * height)
               coordinates_ft.append([x, y])
          
          for index in finger_base_points:
               x = int(hand_landmarks.landmark[index].x * width)
               y = int(hand_landmarks.landmark[index].y * height)
               coordinates_fb.append([x, y])
          ##########################
          # Pulgar
          p1 = np.array(coordinates_thumb[0])
          p2 = np.array(coordinates_thumb[1])
          p3 = np.array(coordinates_thumb[2])

          l1 = np.linalg.norm(p2 - p3)
          l2 = np.linalg.norm(p1 - p3)
          l3 = np.linalg.norm(p1 - p2)

          # Calcular el ángulo
          to_angle = (l1**2 + l3**2 - l2**2) / (2 * l1 * l3)
          if int(to_angle) == -1:
               angle = 180
          else:
               angle = degrees(acos(to_angle))
          thumb_finger = np.array(False)
          if angle > 150:
               thumb_finger = np.array(True)
          
          ################################
          # Índice, medio, anular y meñique
          nx, ny = palm_centroid(coordinates_palm)
          cv2.circle(frame, (nx, ny), 3, (0, 255, 0), 2)
          coordinates_centroid = np.array([nx, ny])
          coordinates_ft = np.array(coordinates_ft)
          coordinates_fb = np.array(coordinates_fb)

          # Distancias
          d_centrid_ft = np.linalg.norm(coordinates_centroid - coordinates_ft, axis=1)
          d_centrid_fb = np.linalg.norm(coordinates_centroid - coordinates_fb, axis=1)
          dif = d_centrid_ft - d_centrid_fb
          fingers = dif > 0
          fingers = np.append(thumb_finger, fingers)

          mp_drawing.draw_landmarks(
               frame,
               hand_landmarks,
               mp_hands.HAND_CONNECTIONS,
               mp_drawing_styles.get_default_hand_landmarks_style(),
               mp_drawing_styles.get_default_hand_connections_style())
     return fingers

De la línea 1 a la 80 hemos importado los packages que usaremos, pero además tenemos dos funciones: palm_centroid que nos ayudará a determinar cuando los dedos índice, medio, anular y meñique están levantados o no, y fingers_up_down, que nos devolverá un array de 5 elementos con True o False indicando cuando alguno de ellos está levantado o recogido.

mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_hands = mp.solutions.hands

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

# Pulgar
thumb_points = [1, 2, 4]

# Índice, medio, anular y meñique
palm_points = [0, 1, 2, 5, 9, 13, 17]
fingertips_points = [8, 12, 16, 20]
finger_base_points =[6, 10, 14, 18]

# FINGERS COMBINATIONS
TO_ACTIVATE = np.array([True, False, False, False, False])
# Piedra, papel, tijeras
PIEDRA = np.array([False, False, False, False, False])
PAPEL = np.array([True, True, True, True, True])
TIJERAS = np.array([False, True, True, False, False])

# REGLAS PIEDRA, PAPEL, TIJERAS (0, 1, 2)
WIN_GAME = ["02", "10", "21"]

pc_option = False # Si la pc ha escogido o no
detect_hand = True

THRESHOLD = 10
THRESHOLD_RESTART = 50

count_like = 0
count_piedra = 0
count_papel = 0
count_tijeras = 0
count_restart = 0

# Images to show
image1 = cv2.imread("1.jpg")
image2 = cv2.imread("2.jpg")
image_winner = cv2.imread("3.jpg")
image_tie = cv2.imread("4.jpg")
image_loser = cv2.imread("5.jpg")
# Image to concat
imAux = image1

player = None

En la siguiente sección tenemos un conjunto de constantes y variables. De estas quisiera destacar WIN_GAME. Que es la combinación que tendremos que conseguir para ganarle al computador.

Figura 3: Le ganaremos al computador con estas combinaciones. Nosotros estamos siendo representados por las ilustraciones con el aura celeste.

Recuerda que si tanto nosotros como la computadora elegimos la misma opción, será un empate. Por ello en la figura 3 tomamos en cuenta solo las combinaciones en las que podemos ganar. Tenemos en azul las opciones que podemos escoger, mientras que en verde las que escogería el computador. Si no hubiera un empate, y si lo que hemos escogido no está dentro de estas combinaciones, entonces perderíamos.

Tomemos en cuenta también que para piedra hemos asignado el número 0, papel 1, mientras que tijeras el 2. Por ello WIN_GAME muestra dichas combinaciones en números (que han sido convertidas a strings para simplificar las comparaciones).

with mp_hands.Hands(
     model_complexity=1,
     max_num_hands=1,
     min_detection_confidence=0.5,
     min_tracking_confidence=0.5) as hands:

     while True:
          ret, frame = cap.read()
          if ret == False:
               break
          frame = cv2.flip(frame, 1)
          height, width, _ = frame.shape
          frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
          results = hands.process(frame_rgb)

          if results.multi_hand_landmarks:
               fingers = fingers_up_down(results, thumb_points, palm_points, fingertips_points, finger_base_points)
               #print("fingers:", fingers)

               if detect_hand == True:
                    if not False in (fingers == TO_ACTIVATE) and pc_option == False:
                         if count_like >= THRESHOLD:
                              pc = random.randint(0, 2)
                              print("pc:", pc)
                              pc_option = True
                              imAux = image2
                         count_like += 1
                    
                    if pc_option == True:
                         if not False in (fingers == PIEDRA):
                              if count_piedra >= THRESHOLD:
                                   player = 0
                              count_piedra += 1
                         elif not False in (fingers == PAPEL):
                              if count_papel >= THRESHOLD:
                                   player = 1
                              count_papel += 1
                         elif not False in (fingers == TIJERAS):
                              if count_tijeras >= THRESHOLD:
                                   player = 2
                              count_tijeras += 1
          if player is not None:
               detect_hand = False
               if pc == player:
                    imAux = image_tie
               else:
                    if (str(player) + str(pc)) in WIN_GAME:
                         imAux = image_winner
                    else:
                         imAux = image_loser
               count_restart += 1
               if count_restart > THRESHOLD_RESTART:
                    pc_option = False
                    detect_hand = True
                    player = None

                    count_like = 0
                    count_piedra = 0
                    count_papel = 0
                    count_tijeras = 0
                    count_restart = 0
                    imAux = image1
          
          n_image = cv2.hconcat([imAux, frame])
          cv2.imshow("n_image", n_image)
          if cv2.waitKey(1) & 0xFF == 27:
               break
cap.release()
cv2.destroyAllWindows()

Y este es todo el programa. Por ello, el último apartado que veremos es la visualización de resultados:

Figura 4: Según lo programado, podremos empezar a jugar una vez que demos like 👍.

En la figura 4 podemos ver que lo primero que nos pide el juego para empezar a jugar, es un like. Una vez que se realice este gesto pasaremos a la siguiente immagen, en donde podremos elegir de entre piedra, papel o tijeras:

Figura 5: Listos para elegir piedra, papel o tijeras.

En la figura 5 tenemos que ya podemos elegir de entre estas tres opciones. Así que, si en esta ocasión vamos por tijeras el resultado será….

Figura 6: Resultado del juego, en esta ocasión.

Como puedes ver en la figura 6, el jugador le ha ganado al computador (esta vez). Por lo que se visualiza la imagen de un gatito muy contento con nuestro resultado.

Y bien, esto ha sido todo por el tutorial de hoy. ¡Espero que te haya gustado! 😊 Nos vemos en el siguiente… ¡Qué te vaya súper bien!.