Cómo le enseñé a mi computadora a jugar en parejas usando OpenCV y Deep Learning

Un poco de diversión con visión por computadora y CNN con una pequeña base de datos.

, CNN, , . , , . , Spot it! ( , ).





, , : , . Spot it!, . . , . 55 , , .









Pruébelo usted mismo: ¿cuál es el símbolo común en las tarjetas que se muestran arriba?
: , ?

?

data science . , . 330 . . : (CNN)? !









, , ? , : . , . . , : , ( ) ( ) , ( ) ( ) . .





Lab . L , a , b — . OpenCV:





import cv2
import imutils
imgname = 'picture1'
image = cv2.imread(f’{imgname}.jpg’)
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
      
      







De izquierda a derecha: imagen original, componente de luz, componente a y componente b
: , , a b

, :





clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
cl = clahe.apply(l)
limg = cv2.merge((cl,a,b))
final = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
      
      







De izquierda a derecha: imagen original, componente de luz, con mayor contraste, convertido de nuevo a RGB
: , , , RGB

:





resized = cv2.resize(final, (800, 800))
#  
cv2.imwrite(f'{imgname}processed.jpg', blurred)
      
      



!









. OpenCV. -, ( , 190), . :





image = cv2.imread(f’{imgname}processed.jpg’)
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
thresh = cv2.threshold(gray, 190, 255, cv2.THRESH_BINARY)[1]
#  
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
output = image.copy()
#    
for c in cnts:
    cv2.drawContours(output, [c], -1, (255, 0, 0), 3)
      
      







Imagen procesada convertida a blanco y negro, dividida por umbral y con contornos
, -,

, , : . , .





#   ,  
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
#     
mask = np.zeros(gray.shape,np.uint8)
mask = cv2.drawContours(mask, [cnts], -1, 255, cv2.FILLED)
#    
fg_masked = cv2.bitwise_and(image, image, mask=mask)
#   (  )
mask = cv2.bitwise_not(mask)
bk = np.full(image.shape, 255, dtype=np.uint8)
bk_masked = cv2.bitwise_and(bk, bk, mask=mask)
#     
final = cv2.bitwise_or(fg_masked, bk_masked)
      
      







Máscara, fondo, primer plano, fusionado
, , ,

! , — . , . :





#       (  )
gray = cv2.cvtColor(final, cv2.COLOR_RGB2GRAY)
thresh = cv2.threshold(gray, 195, 255, cv2.THRESH_BINARY)[1]
thresh = cv2.bitwise_not(thresh)
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:10]
#   
i = 0
for c in cnts:
    if cv2.contourArea(c) > 1000:
        #  ,  
        mask = np.zeros(gray.shape, np.uint8)
        mask = cv2.drawContours(mask, [c], -1, 255, cv2.FILLED)
        #  
        fg_masked = cv2.bitwise_and(image, image, mask=mask)
        mask = cv2.bitwise_not(mask)
        bk = np.full(image.shape, 255, dtype=np.uint8)
        bk_masked = cv2.bitwise_and(bk, bk, mask=mask)
        finalcont = cv2.bitwise_or(fg_masked, bk_masked)
        #    
        output = finalcont.copy()
        x,y,w,h = cv2.boundingRect(c)
        # squares io rectangles
        if w < h:
            x += int((w-h)/2)
            w = h
        else:
            y += int((h-w)/2)
            h = w
        #    
        roi = finalcont[y:y+h, x:x+w]
        roi = cv2.resize(roi, (400,400))
        #  
        cv2.imwrite(f"{imgname}_icon{i}.jpg", roi)
        i += 1
      
      



Dividido por el umbral, con ciertos contornos, imágenes de un fantasma y un corazón (recortadas por una máscara)
, , ( )

! . , , 57 ( 57 ). :





symbols
 ├── test
 │   ├── anchor
 │   ├── apple
 │   │   ...
 │   └── zebra
 ├── train
 │   ├── anchor
 │   ├── apple
 │   │   ...
 │   └── zebra
 └── validation
     ├── anchor
     ├── apple
     │   ...
     └── zebra
      
      



, ( 2500)! , GitHub. , …





(CNN)





. CNN. CNN .





. . softmax 57 .





:





# 
from keras import layers
from keras import models
from keras import optimizers
from keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
# ,    57  (    )
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(400, 400, 3)))
model.add(layers.MaxPooling2D((2, 2)))  
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5)) 
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(57, activation='softmax'))
model.compile(loss='categorical_crossentropy',       optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
      
      



. — . , , , . Keras:





#  
train_dir = 'symbols/train'
validation_dir = 'symbols/validation'
test_dir = 'symbols/test'
#     ImageDataGenerator  Keras (  )
train_datagen = ImageDataGenerator(rescale=1./255, rotation_range=40, width_shift_range=0.1, height_shift_range=0.1, shear_range=0.1, zoom_range=0.1, horizontal_flip=True, vertical_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir, target_size=(400,400), batch_size=20, class_mode='categorical')
validation_generator = test_datagen.flow_from_directory(validation_dir, target_size=(400,400), batch_size=20, class_mode='categorical')
      
      



, :





Fantasma original a la izquierda, otras imágenes muestran ejemplos de aumento de datos
,

, .





history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=100, validation_data=validation_generator, validation_steps=50)
#     
model.save('models/model.h5')
      
      



, , . :





Resultados del modelo base

, . ( ) . .





Resultados finales del modelo

: . , 0,995 .





. , , , . :





  • - : .





  • ( ).





  • . ( ).





GitHub , main.py.





:






? , ! , . ! :





¿Monigote de nieve?  ¿Dónde?
? ?

? : , ! : 55 . 1485 . 140 . , !





No creo que sea realmente difícil crear un modelo 100%. Esto se puede hacer mediante el aprendizaje por transferencia, por ejemplo. Para comprender qué está haciendo el modelo, podemos renderizar las capas para la imagen de prueba. ¡Qué probar la próxima vez!






¡Espero que hayas disfrutado leer esta publicación!








All Articles