Clasificador de Imágenes: Tierra y Luna
por Andrés Olano trejos @ga_olano
- 58
- 0
- 0
Introducción
El objetivo de este proyecto es poner en práctica los conocimientos adquiridos en transferencia de aprendizaje mediante el reconocimiento de imágenes capturadas del planeta tierra y de la luna. Si bien la transferencia de aprendizaje es algo ya probado con otros tipos de ejemplos, la intención fue llevarlo a un código mejor estructurado y preparado para que sea alimentado posteriormente con más ejemplos (por ejemplo otros planetas y satélites) para que su capacidad de clasificación aumente.
Dificultad: No se pudo almacenar el modelo generado en un archivo (ej. .keras) para ser reutilizado en posteriores funcionalidades de clasificación.

Materiales
El proyecto ha sido trabajado con :
- Python 3.12.1
- Visual Studio Code
- Tensorflow
- Numpy
- Matplotlib
- PIL
- Keras
- Otras librerías complementarias
- Diversas imágenes obtenidas de Internet
- Kaggle (https://www.kaggle.com/models/google/mobilenet-v2/TensorFlow2/tf2-preview-feature-vector/4)
Paso 1: Tratamiento de las imágenes
El proceso de tratamiento de las imágenes se ha realizado mediante la construcción de funciones y métodos encargados del copiado de archivos, descompresión y limpieza de ficheros
def copiar_archivo(origen, destino):
# Ruta del archivo original
origen = origen # Ruta de destino en la carpeta tierra
destino = destino
try:
# Copiar el archivo en Windows
shutil.copy(origen, destino)
print(f"Archivo {origen} copiado exitosamente a la carpeta {destino} en Windows.")
except FileNotFoundError:
print(f"Error: No se encontró el archivo {origen} en la raíz del sistema Windows.")
except PermissionError:
print(f"Error de permisos: No se tienen los permisos necesarios para copiar el archivo {origen} en Windows.")
except shutil.SameFileError:
print("Error: El archivo de origen y destino son el mismo en el sistema Windows.")
except Exception as e:
print(f"Ocurrió un error al copiar el archivo en Windows: {str(e)}")
def descomprimir_archivo(archivoZip, destino):
# Ruta del archivo comprimido
archivo_comprimido = archivoZip
# Ruta de destino para la descompresión
destino = destino
try:
# Descomprimir el archivo
shutil.unpack_archive(archivo_comprimido, destino)
print(f"Archivo {archivo_comprimido} descomprimido exitosamente en la carpeta {destino}.")
except FileNotFoundError:
print(f"Error: No se encontró el archivo {archivo_comprimido} en la carpeta {destino}.")
except shutil.ReadError:
print("Error: El archivo no es un archivo comprimido válido o está corrupto.")
except PermissionError:
print("Error de permisos: No se tienen los permisos necesarios para descomprimir el archivo.")
except Exception as e:
print(f"Ocurrió un error al descomprimir el archivo: {str(e)}")
def eliminar_archivo(archivo):
# Ruta del archivo a eliminar
archivo_a_eliminar = archivo
try:
# Eliminar el archivo
os.remove(archivo_a_eliminar)
print(f"Archivo {archivo_a_eliminar} eliminado exitosamente de la carpeta.")
except FileNotFoundError:
print(f"Error: No se encontró el archivo {archivo_a_eliminar} en la carpeta.")
except PermissionError:
print("Error de permisos: No se tienen los permisos necesarios para eliminar el archivo.")
except Exception as e:
print(f"Ocurrió un error al eliminar el archivo: {str(e)}")

Paso 2: Proceso de Entrenamiento
Mediante la transferencia de aprendizaje se aplica el proceso de entrenamiento, mediante una función parametrizable que permite recibir, entre otras cosas la cantidad de épocas con el fin de probar diferentes configuraciones.
def entrenarMobilenetv2(datasetFolder, validation_split, epochs, modelNameSave,
outputLayers):
# Limpiar la consola
os.system('cls' if os.name == 'nt' else 'clear')
print("****** Inicio del Proceso de Entrenamiento ******\n\n")
datagen = ImageDataGenerator(
rescale = 1. / 255,
rotation_range = 10,
width_shift_range = 0.15,
height_shift_range = 0.15,
shear_range = 5,
zoom_range = [0.7 , 1.3],
validation_split = validation_split
)
print(f"{getDateTime()} Creación del Set Generador -> OK")
data_gen_entrenamiento = datagen.flow_from_directory(
datasetFolder,
target_size = (224, 224),
batch_size = 32,
shuffle = True,
subset = "training"
)
print(f"{getDateTime()} Creación del Set Training -> OK")
data_gen_pruebas = datagen.flow_from_directory(
datasetFolder,
target_size = (224, 224),
batch_size = 32,
shuffle = True,
subset = "validation"
)
print(f"{getDateTime()} Creación del Set Validation -> OK")
for imagenes,etiquetas in data_gen_entrenamiento:
for i in range(10):
plt.subplot(2,5,i+1)
plt.imshow(imagenes[i])
break
plt.show()
print(f"{getDateTime()} Mostrar Imagenes de Prueba -> OK")
# url = "https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/4"
url = "https://www.kaggle.com/models/google/mobilenet-v2/TensorFlow2/tf2-preview-feature-vector/4"
base_model = hub.KerasLayer(url, trainable=False)
input_layer = layers.Input(shape=(224, 224, 3))
x = layers.Lambda(lambda img: base_model(img, training=False), output_shape=(1280,))(input_layer) # <--- Añadido output_shape
x = layers.Dense(128, activation="relu")(x)
output_layer = layers.Dense(2, activation="softmax")(x)
modelo = Model(inputs=input_layer, outputs=output_layer)
print(f"{getDateTime()} Creación del Modelo -> OK")
modelo.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss="categorical_crossentropy",
metrics=["accuracy"]
)
print(f"{getDateTime()} Compilación del Modelo -> OK")
entrenamiento = modelo.fit(
data_gen_entrenamiento,
epochs=epochs,
validation_data=data_gen_pruebas,
verbose=1 # Desactivar la salida de las épocas
)
print(f"{getDateTime()} Entrenamiento del Modelo -> OK")
print("****** Fin del Proceso de Entrenamiento ******")
return modelo

Paso 3: Proceso de pruebas
Mediante una función se pasa el modelo ya creado del paso 2 y la imagen a clasificar. Esto permite la reutilización de la función para probar con diferentes imágenes
def predecirModelo(ruta,modelo):
try:
# Cargar el modelo previamente entrenado
modelo_cargado = modelo
# Cargar y preprocesar la imagen
img = Image.open(ruta)
img = img.convert("RGB")
img = np.array(img).astype(float)/255.0
img = cv2.resize(img, (224, 224)).astype(np.float32)
# Crear la capa Lambda con output_shape especificado
input_layer = layers.Input(shape=(224, 224, 3))
x = layers.Lambda(lambda img: modelo_cargado.layers[1](img, training=False),
output_shape=(128,))(input_layer)
# Realizar la predicción
# prediccion = modelo_cargado.predict(img.reshape(-1, 224, 224, 3))
prediccion = modelo_cargado.predict(np.expand_dims(img, axis=0)) # Añadir dimensión de lote print(prediccion)
return np.argmax(prediccion[0], axis=-1)
except Exception as e:
print(f"Error durante la predicción: {str(e)}")
return -1

Paso 4: Consolidación
import sys
# Procesar el archivo de datos
copiar_archivo('PTierra.zip','dataset/tierra/PTierra.zip')
descomprimir_archivo('dataset/tierra/PTierra.zip','dataset/tierra/')
eliminar_archivo('dataset/tierra/PTierra.zip')
copiar_archivo('Luna.zip','dataset/luna/Luna.zip')
descomprimir_archivo('dataset/luna/Luna.zip','dataset/luna/')
eliminar_archivo('dataset/luna/Luna.zip')
modelo = entrenarMobilenetv2(datasetFolder="dataset",
validation_split=0.2,
epochs = 20,
modelNameSave='planetsModel.keras',
outputLayers=2
)
El proceso entrega los siguientes resultados:
****** Inicio del Proceso de Entrenamiento ******
250423-23:01:51 Creación del Set Generador -> OK
Found 541 images belonging to 2 classes.
250423-23:01:51 Creación del Set Training -> OK
Found 135 images belonging to 2 classes.
250423-23:01:51 Creación del Set Validation -> OK
250423-23:01:54 Mostrar Imagenes de Prueba -> OK
250423-23:01:56 Creación del Modelo -> OK
250423-23:01:56 Compilación del Modelo -> OK
Epoch 1/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 29s 1s/step - accuracy: 0.7206 - loss: 0.5557 - val_accuracy: 0.9333 - val_loss: 0.1684
Epoch 2/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 16s 927ms/step - accuracy: 0.9212 - loss: 0.2235 - val_accuracy: 0.8889 - val_loss: 0.2425
Epoch 3/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 980ms/step - accuracy: 0.9217 - loss: 0.1669 - val_accuracy: 0.9259 - val_loss: 0.1485
Epoch 4/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 1s/step - accuracy: 0.9292 - loss: 0.1736 - val_accuracy: 0.9185 - val_loss: 0.1777
Epoch 5/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 1s/step - accuracy: 0.9305 - loss: 0.1675 - val_accuracy: 0.9259 - val_loss: 0.1854
Epoch 6/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 1s/step - accuracy: 0.9663 - loss: 0.0946 - val_accuracy: 0.9481 - val_loss: 0.1357
Epoch 7/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 983ms/step - accuracy: 0.9752 - loss: 0.0805 - val_accuracy: 0.9333 - val_loss: 0.1476
Epoch 8/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 990ms/step - accuracy: 0.9728 - loss: 0.0856 - val_accuracy: 0.9333 - val_loss: 0.1546
Epoch 9/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 999ms/step - accuracy: 0.9863 - loss: 0.0574 - val_accuracy: 0.9259 - val_loss: 0.1569
Epoch 10/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 1s/step - accuracy: 0.9794 - loss: 0.0774 - val_accuracy: 0.9259 - val_loss: 0.1677
Epoch 11/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 977ms/step - accuracy: 0.9864 - loss: 0.0520 - val_accuracy: 0.9333 - val_loss: 0.1647
Epoch 12/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 1s/step - accuracy: 0.9763 - loss: 0.0492 - val_accuracy: 0.9556 - val_loss: 0.1604
Epoch 13/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 1s/step - accuracy: 0.9878 - loss: 0.0469 - val_accuracy: 0.9556 - val_loss: 0.1709
Epoch 14/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 969ms/step - accuracy: 0.9929 - loss: 0.0398 - val_accuracy: 0.9407 - val_loss: 0.1590
Epoch 15/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 17s 998ms/step - accuracy: 0.9871 - loss: 0.0423 - val_accuracy: 0.9556 - val_loss: 0.1117
Epoch 16/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 16s 968ms/step - accuracy: 0.9785 - loss: 0.0430 - val_accuracy: 0.9333 - val_loss: 0.1825
Epoch 17/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 16s 941ms/step - accuracy: 0.9964 - loss: 0.0225 - val_accuracy: 0.9481 - val_loss: 0.1308
Epoch 18/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 16s 965ms/step - accuracy: 0.9783 - loss: 0.0540 - val_accuracy: 0.9333 - val_loss: 0.2238
Epoch 19/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 16s 939ms/step - accuracy: 0.9856 - loss: 0.0401 - val_accuracy: 0.9185 - val_loss: 0.2073
Epoch 20/20
17/17 ━━━━━━━━━━━━━━━━━━━━ 16s 944ms/step - accuracy: 0.9917 - loss: 0.0218 - val_accuracy: 0.9259 - val_loss: 0.2504
250423-23:07:43 Entrenamiento del Modelo -> OK
****** Fin del Proceso de Entrenamiento ******
La función retorna un modelo entrenado.

Paso 5: Prueba del modelo
rint("********* Inicio del Ejercicio de Predicción *********")
ruta = "Luna01.JPG"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
ruta = "Tierra01.JPG"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
ruta = "Luna02.png"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
ruta = "Tierra02.JPG"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
ruta = "Luna03.JPG"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
ruta = "Luna04.JPG"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
ruta = "Tierra03.JPG"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
ruta = "Tierra04.JPG"
print(f"Imagen:{ruta} - Resultado: {predecirModelo("Testing/"+ruta, modelo)}")
Resultados
********* Inicio del Ejercicio de Predicción *********
1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 654ms/step
Imagen:Luna01.JPG - Resultado: 0
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 77ms/step
Imagen:Tierra01.JPG - Resultado: 1
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 97ms/step
Imagen:Luna02.png - Resultado: 0
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 81ms/step
Imagen:Tierra02.JPG - Resultado: 1
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 81ms/step
Imagen:Luna03.JPG - Resultado: 0
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 88ms/step
Imagen:Luna04.JPG - Resultado: 0
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 83ms/step
Imagen:Tierra03.JPG - Resultado: 1
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 80ms/step
Imagen:Tierra04.JPG - Resultado: 1
El modelo identifica correctamente las imágenes presentadas

0 comentarios
Entra o únete Gratis para comentar