Generación de Imágenes con Sistemas Distribuidos - CesarGMtz/Final_Eval GitHub Wiki
Este proyecto consiste en implementar un sistema distribuido local con tres equipos conectados en red, capaz de procesar 600 imágenes de gran formato aplicando diversas transformaciones con técnicas de paralelización (OpenMP). Además, se comparan el rendimiento y el costo anual del sistema frente a una solución en la nube (AWS). El proyecto incluye una interfaz gráfica que permite seleccionar las imágenes, visualizar resultados y mostrar métricas clave del procesamiento.
-
Función:
- Convierte a escala de grises y aplica espejo horizontal.
-
Parámetros:
- char mask[10]: Nombre base del archivo de salida.
- char path[80]: Ruta del archivo BMP original.
- const char *output_dir: Ruta del folder de salida.
- int *leidas: Puntero a entero para contar los bytes leídos.
- int *escritas: Puntero a entero para contar los bytes escritos.
-
Devuelve:
- Actualiza *leidas y *escritas, y genera la imagen en escala de grises.
-
Función:
- Convierte a escala de grises y aplica espejo horizontal.
-
Parámetros:
- char mask[10]: Nombre base del archivo de salida.
- char path[80]: Ruta del archivo BMP original.
- const char *output_dir: Ruta del folder de salida.
- int *leidas: Puntero a entero para contar los bytes leídos.
- int *escritas: Puntero a entero para contar los bytes escritos.
-
Devuelve:
- Actualiza *leidas y *escritas, y genera la imagen en escala de grises invertida horizontalmente.
-
Función:
- Convierte a escala de grises y aplica espejo vertical.
-
Parámetros:
- char mask[10]: Nombre base del archivo de salida.
- char path[80]: Ruta del archivo BMP original.
- const char *output_dir: Ruta del folder de salida.
- int *leidas: Puntero a entero para contar los bytes leídos.
- int *escritas: Puntero a entero para contar los bytes escritos.
-
Devuelve:
- Actualiza *leidas y *escritas, y genera la imagen en escala de grises invertida verticalmente.
-
Función:
- Aplica espejo horizontal a imagen a color.
-
Parámetros:
- char mask[10]: Nombre base del archivo de salida.
- char path[80]: Ruta del archivo BMP original.
- const char *output_dir: Ruta del folder de salida.
- int *leidas: Puntero a entero para contar los bytes leídos.
- int *escritas: Puntero a entero para contar los bytes escritos.
-
Devuelve:
- Actualiza *leidas y *escritas, y genera la imagen invertida horizontalmente.
-
Función:
- Aplica espejo horizontal a imagen a color.
-
Parámetros:
- char mask[10]: Nombre base del archivo de salida.
- char path[80]: Ruta del archivo BMP original.
- const char *output_dir: Ruta del folder de salida.
- int *leidas: Puntero a entero para contar los bytes leídos.
- int *escritas: Puntero a entero para contar los bytes escritos.
-
Devuelve:
- Actualiza *leidas y *escritas, y genera la imagen invertida verticalmente.
-
Función:
- Aplica efecto de desenfoque en dos pasadas (horizontal y vertical).
-
Parámetros:
- char mask[10]: Nombre base del archivo de salida.
- char path[80]: Ruta de la imagen BMP original.
- int kernel: Tamaño del kernel de desenfoque (De 55 a 155).
- const char *output_dir: Ruta del folder de salida.
- int *leidas: Puntero a entero para contar los bytes leídos.
- int *escritas: Puntero a entero para contar los bytes escritos.
-
Devuelve:
- Actualiza *leidas y *escritas, y genera la imagen con el blur especificado.
Esta transformación convierte cada píxel de la imagen de color (RGB) a una escala de grises. El valor de intensidad se calcula utilizando la fórmula perceptual:
pixel = 0.21 * r + 0.72 * g + 0.07 * b;
Esto se implementa en la función gray_img del archivo transformation_proc.h. El procedimiento consiste en leer cada píxel, aplicar la fórmula para convertirlo a una intensidad de gris, y luego escribirlo tres veces (para cada canal RGB) en la imagen de salida.
Estas transformaciones invierten la imagen en uno de sus ejes. La versión a color conserva la información RGB:
- En invH_color_img, se invierte el orden de los píxeles por fila (horizontalmente).
for (int i = 0; i < alto; i++) {
unsigned char *src_row_start = arr_in + (i * row_size_padded);
unsigned char *dest_row_start = arr_out + (i * row_size_padded);
for (int j = 0; j < ancho; j++) {
b = src_row_start[j * 3 + 0];
g = src_row_start[j * 3 + 1];
r = src_row_start[j * 3 + 2];
dest_row_start[(ancho - 1 - j) * 3 + 0] = b;
dest_row_start[(ancho - 1 - j) * 3 + 1] = g;
dest_row_start[(ancho - 1 - j) * 3 + 2] = r;
}
for (int p = 0; p < padding; p++) {
dest_row_start[(ancho * 3) + p] = src_row_start[(ancho * 3) + p];
}
}
- En invV_color_img, se invierte el orden de las filas (verticalmente).
for (int i = 0; i < alto; i++) {
unsigned char *src_row_start = arr_in + ((alto - 1 - i) * row_size_padded);
unsigned char *dest_row_start = arr_out + (i * row_size_padded);
memcpy(dest_row_start, src_row_start, row_size_padded);
}
Ambas funciones leen la imagen en memoria y luego reordenan los píxeles antes de escribir la imagen final. Esto requiere conocer la estructura de almacenamiento de los datos BMP y manejar correctamente el "padding" de bytes al final de cada fila.
En estas funciones, primero se convierte la imagen a escala de grises, como en gray_img, y posteriormente se aplica el espejo, igual que en las versiones a color. Esto está implementado en invH_gray_img e invV_gray_img. A pesar de que el resultado visual parece sencillo, estas transformaciones requieren una manipulación precisa de arreglos de bytes y un conocimiento profundo del formato de archivo BMP.
El desenfoque consiste en suavizar la imagen al promediar los píxeles vecinos. En este caso, el usuario puede ingresar un tamaño de "kernel" entre 55 y 155. El valor introducido se usa para definir el radio del kernel con el que se aplica el efecto. El desenfoque se hace en dos pasadas:
- Promedio horizontal de los valores RGB de los píxeles vecinos.
- Promedio vertical de los resultados anteriores.
Esto reduce el ruido y produce un efecto visual de suavizado. El proceso está implementado en blur_img, que utiliza múltiples arreglos temporales para los tres canales de color (R, G, B) y recorre los píxeles vecinos para realizar el cálculo promedio.
int kernelRadius = (kernel - 1) / 2;
for (int y = 0; y < alto; y++) {
for (int x = 0; x < ancho; x++) {
long bSum = 0, gSum = 0, rSum = 0;
int count = 0;
for (int kx = -kernelRadius; kx <= kernelRadius; kx++) {
int nx = x + kx;
if (nx >= 0 && nx < ancho) {
int index =( y * ancho + nx) * 3;
bSum += arr_in[index + 0];
gSum += arr_in[index + 1];
rSum += arr_in[index + 2];
count++;
}
}
int index = (y * ancho + x) * 3;
arr_temp[index + 0] = (unsigned char)(bSum / count);
arr_temp[index + 1] = (unsigned char)(gSum / count);
arr_temp[index + 2] = (unsigned char)(rSum / count);
}
}
for (int y = 0; y < alto; y++) {
for (int x = 0; x < ancho; x++) {
long bSum = 0, gSum = 0, rSum = 0;
int count = 0;
for (int ky = -kernelRadius; ky <= kernelRadius; ky++) {
int ny = y + ky;
if (ny >= 0 && ny < alto) {
int index = (ny * ancho + x) * 3;
bSum += arr_temp[index + 0];
gSum += arr_temp[index + 1];
rSum += arr_temp[index + 2];
count++;
}
}
int index = (y * ancho + x) * 3;
arr_out[index + 0] = (unsigned char)(bSum / count);
arr_out[index + 1] = (unsigned char)(gSum / count);
arr_out[index + 2] = (unsigned char)(rSum / count);
}
}
Dado que el procesamiento de 600 imágenes implica aplicar seis transformaciones distintas a cada una (un total de 3600 operaciones), se utilizó la biblioteca OpenMP para distribuir el trabajo en varios hilos (threads). Esto se hizo usando la siguiente directiva:
#pragma omp parallel for reduction(+:total_leidas, total_escritas)
Esta línea permite que el bucle que recorre las 600 imágenes sea ejecutado por varios hilos en paralelo, dividiendo las iteraciones de manera balanceada. Además, se usa reduction para acumular correctamente el total de bytes leídos y escritos, evitando condiciones de carrera. Se fijó un número de hilos mediante:
#define NUM_THREADS 13
omp_set_num_threads(NUM_THREADS);
El tiempo total de ejecución se mide usando omp_get_wtime(), lo que permite obtener con precisión el rendimiento del procesamiento paralelo.

La interfaz gráfica del sistema fue desarrollada utilizando PyQt5, con el objetivo de facilitar la interacción del usuario con el sistema distribuido de procesamiento de imágenes. Esta interfaz permite gestionar de manera intuitiva la entrada, el procesamiento y el monitoreo del progreso de las imágenes, así como mostrar información relevante sobre el rendimiento del sistema.
- Selección de carpeta de entrada

La interfaz permite al usuario seleccionar la carpeta que contiene las imágenes a procesar. Esta acción activa automáticamente un conteo de los archivos BMP con nombre img_<número>.bmp, preparando al sistema para su procesamiento.
- Visualización de carpeta de salida

Una vez iniciado el procesamiento, la interfaz indica la ruta de la carpeta donde se almacenarán las imágenes procesadas, facilitando su localización posterior.
- Selector de kernel para desenfoque

Se incluye un slider que permite ajustar el valor del kernel utilizado en el efecto de desenfoque. El usuario puede seleccionar un valor entre 55 y 155, como fue requerido en el reto.
- Progreso estimado

Se presenta una barra de progreso que estima visualmente el avance del procesamiento con base en los registros que genera el archivo arc1.txt, actualizado por el backend del sistema.
- Área de mensajes y métricas

La interfaz cuenta con un campo de texto de solo lectura que muestra:
- La cantidad total de imágenes detectadas.
- El nombre del archivo actualmente procesado.
- Mensajes de estado y errores del procesamiento.
- Métricas al finalizar el procesamiento:
- Localidades leídas y escritas.
- Tasa de procesamiento (MB/segundo).
- Comparativa de costos entre equipos locales y AWS.
- Botones de control
- Iniciar procesamiento: ejecuta el programa en C encargado del procesamiento distribuido.
- Reiniciar sistema: detiene cualquier procesamiento activo, limpia el entorno e inicializa nuevamente la interfaz.
- Submenú de equipo
El menú "Equipo" contiene los nombres y matrículas de los miembros responsables del desarrollo de la interfaz gráfica:

La interfaz está compuesta por tres clases principales:
- MainWindow: contiene la lógica de interacción del usuario y controla el ciclo de procesamiento.
- ProcessorThread: ejecuta el binario en C responsable del procesamiento, capturando errores en tiempo de ejecución.
- ProgressMonitorThread: realiza el seguimiento de la carpeta de salida para actualizar la barra de progreso y mostrar información del proceso en curso.
ub0 (Master) | ub1 (Esclavo) | ub2 (Esclavo) |
---|---|---|
![]() |
![]() |
![]() |
En este caso, lo más interesante sobre la configuración que le dimos a las VMs es la parte del Network Adapter, en el cual tuvimos que habilitar 2, uno en NAT para el acceso a internet, y, otro, en bridge, para la conexión local por medio del switch.
Para lograr la conexión entre los equipos, lo primero que se hizo fue definir un segmento de red sobre el cual trabajar y, con ello, se asignaron IPs a cada equipo:
RED: 192.168.20.0 /24
UB0: 192.168.20.1
UB1: 192.168.20.2
UB2: 192.168.20.3
Una vez habiendo asignado las direcciones IP, cada equipo tuvo que configurar su archivo 00-installer-config.yaml, para esto se ejecutó sudo nano /etc/netplan/00-installer-config.yaml
y se agregó lo siguiente:
network:
version: 2
ethernets:
ens33: # Depende el protocolo del equipo
addresses: [192.168.20.1/24] # IP del equipo
dhcp4: no
Una vez guardado el archivo, se aplica la configuración ejecutando sudo netplan apply
, esto se puede confirmar ejecutando ip a
, revisando que aparezca la IP definida:

Luego, para facilitar las referencias a los demás equipos, se edita el archivo hosts ejecutando sudo nano /etc/hosts
, agregando, en este caso:
192.168.20.1 ub0
192.168.20.2 ub1
192.168.20.3 ub2
Después de configurar netplan, lo que se hace es instalar los paquetes necesarios para ssh, el servidor nfs y openmpi, con:
sudo apt install -y nfs-common openssh-server openmpi-bin openmpi-common libopenmpi-dev
Se puede revisar el estatus (activo o inactivo) del servicio ssh y el servidor nfs de esta forma:
Para ssh:
sudo systemctl status ssh
Para el servidor nfs:
sudo systemctl status nfs-server.service
Cabe mencionar que se puede cambiar “status” por “start” o “stop” en los anteriores comandos, para activar o desactivar los servicios deseados.
Luego, se crea la carpeta compartida, en este caso llamada “mirror”:
sudo mkdir /mirror
Después, se crea el usuario compartido:
sudo useradd -m -u 1001 -d /mirror/mpiu -s /bin/bash mpiu
Se debe configurar una llave SSH desde el usuario “mpiu” creado en server1, para esto primero cambia al usuario creado:
su - mpiu
Si no existe una clave o no se ha generado ninguna:
ssh-keygen -t rsa
Una vez que se ha creado, copia la llave del nodo maestro al usuario creado en los nodos esclavos:
ssh-copy-id mpiu@ub2
ssh-copy-id mpiu@ub1
Una vez se haya copiado, comprueba que puedes hacer ssh sin contraseña desde los esclavos al maestro y viceversa:
ssh mpiu@ub0
ssh mpiu@ub2
Se debe editar el fstab para montar el recurso NFS automáticamente cada que se bootee la computadora:
sudo nano /etc/fstab
Agrega la línea dentro del fstab, poniendo la ip del master en donde se indica la dirección:
192.168.20.1:/mirror /mirror nfs defaults 0 0
Luego, para guardar la configuración automática ejecuta:
sudo mount -a
En caso de que el maestro ya haya copiado su ssh-key y no puedas hacer ssh sin contraseña desde esclavo a master, tendras que generar tu ssh-key y copiarla al master, para eso pasate al usuario creado:
su - mpiu
una vez dentro, ocupa el siguiente comando:
ssh-keygen -t rsa
Ahora copiala al master y ya deberias poder hacer ssh sin contraseña:
ssh-copy-id mpiu@ub0
Este documento explica las diferencias clave entre dos versiones de un procesador de imágenes en C: una versión secuencial paralelizada con OpenMP, y otra versión distribuida con MPI + OpenMP. Ambas versiones aplican transformaciones sobre imágenes BMP.
Sin MPI | Con MPI |
---|---|
#include <omp.h> |
#include <omp.h> #include <mpi.h>
|
Se añade mpi.h
para utilizar las funciones de inicialización, comunicación y sincronización entre nodos.
Sin MPI | Con MPI |
---|---|
No aplica |
MPI_Init , MPI_Comm_rank , MPI_Comm_size
|
Estas funciones inicializan el entorno MPI y determinan qué proceso es el maestro (rank == 0
) y cuántos procesos están disponibles (size
).
Sin MPI | Con MPI |
---|---|
Todos los procesos leen argv[]
|
Solo el proceso maestro (rank == 0 ) los lee |
Se evita que todos los procesos accedan simultáneamente a los argumentos. Se hace un MPI_Bcast
para compartir los datos con los demás nodos.
Sin MPI | Con MPI |
---|---|
Todos los procesos escriben en el archivo | Solo el maestro escribe el encabezado; solo el nodo más lento escribe resultados |
Esto previene condiciones de carrera y evita que múltiples procesos sobrescriban el archivo simultáneamente.
Sin MPI | Con MPI |
---|---|
for (int i = 0; i < total_imgs; i++) |
for (int i = rank; i < total_imgs; i += size) |
La carga se reparte entre nodos: nodo 0 procesa 0,3,6,...; nodo 1 procesa 1,4,7,...; y así sucesivamente. Esto logra paralelismo distribuido.
Sin MPI | Con MPI |
---|---|
Verifica existencia del archivo localmente | Agrega rank al mensaje de advertencia |
Se especifica qué nodo no encontró el archivo, útil para depuración distribuida.
Sin MPI | Con MPI |
---|---|
Cada proceso calcula y escribe resultados | Solo el nodo más lento escribe métricas finales |
Se usa MPI_Gather
para recopilar tiempos desde todos los nodos. Luego, se selecciona el nodo con mayor tiempo de ejecución para reportar los resultados.
Sin MPI | Con MPI |
---|---|
Termina con return 0
|
Llama a MPI_Finalize() antes de terminar |
Es obligatorio cerrar correctamente el entorno MPI al final del programa.
Este documento explica las diferencias clave entre dos versiones de un procesador de imágenes en C: una versión monolítica en main.c paralelizada con OpenMP, y otra donde las funciones están separadas en un archivo modular (transformation_proc.h) preparadas para integración con entornos distribuidos (como MPI).
Sin MPI | Con transformation_proc |
---|---|
#include <omp.h> #include "transformation_proc.h" |
#include <stdio.h> Funciones definidas como extern en transformation_proc.h |
Las funciones de procesamiento se separan en un módulo externo, permitiendo reutilización desde distintos contextos (OpenMP, MPI, etc.).
Sin MPI | Con transformation_proc |
---|---|
No aplica | No aplica directamente, pero las funciones están diseñadas para poder usarse desde procesos MPI |
Aunque el módulo no llama MPI_Init, su diseño modular permite ser integrado fácilmente en sistemas MPI.
Sin MPI | Con transformation_proc |
---|---|
main.c gestiona argv[] y define rutas e índices | Las funciones como gray_img reciben parámetros específicos como mask, path, output_dir |
Cada función es independiente y parametrizable, facilitando su ejecución individual desde cualquier nodo.
Sin MPI | Con transformation_proc |
---|---|
main.c abre y escribe el archivo completo | Las funciones no escriben en el log directamente, pero devuelven datos (*leidas, *escritas) para que el log lo maneje el controlador externo |
Esto separa la lógica de procesamiento de la lógica de logging, ideal para entornos distribuidos.
Sin MPI | Con transformation_proc |
---|---|
#pragma omp parallel for sobre un bucle con todas las transformaciones | Cada transformación (gray_img, invH_color_img, etc.) es una función autónoma, invocable de forma paralela desde cualquier bucle o proceso |
El diseño modular permite paralelizar la ejecución a nivel de función.
Sin MPI | Con transformation_proc |
---|---|
main.c abre los archivos de entrada antes de procesar | Cada función valida apertura de archivos, memoria y escritura individualmente |
Cada función implementa manejo de errores robusto y localizado, útil en ejecución distribuida.
Sin MPI | Con transformation_proc |
---|---|
main.c acumula total_leidas y total_escritas de forma global | Cada función recibe punteros int *leidas, int *escritas para devolver sus propios totales |
Esto permite obtener métricas granulares por transformación y por nodo/hilo en sistemas distribuidos.
Sin MPI | Con transformation_proc |
---|---|
main.c libera recursos al final del programa | Cada función gestiona su propia memoria (malloc y free) y cierre de archivos |
Cada función es autocontenida, evitando fugas de memoria en ejecuciones concurrentes.
Este documento presenta las diferencias entre dos versiones de una interfaz gráfica en PyQt5 para procesar imágenes con un ejecutable externo: una que llama un archivo secuencial (a.exe
o a.out
), y otra que lanza el procesamiento distribuido con MPI (mpiexec
).
Sin MPI | Con MPI |
---|---|
Llama directamente a.exe o a.out desde subprocess.run
|
Ejecuta con mpiexec e incluye argumentos como -n , --hostfile
|
No requiere archivo machinefile
|
Usa script check_hosts.sh para generar y contar nodos |
Command: [exe_path, input, output, arc1.txt, kernel, total]
|
Command: ["mpiexec", "-n", N, "--hostfile", "machinefile", exe_path, input, output, arc1.txt, kernel, total]
|
Sin MPI | Con MPI |
---|---|
No genera machinefile
|
Llama un script bash para generar y verificar nodos disponibles |
Solo crea r_img y limpia archivos |
También verifica hosts y prepara entorno distribuido |
Sin MPI | Con MPI |
---|---|
No aplica | Usa salida de check_hosts.sh para obtener num_procs (número de procesos activos) |
Valor fijo en el comando | Valor dinámico detectado automáticamente |
Sin MPI | Con MPI |
---|---|
Se limpia y se lee al final del procesamiento | Igual, pero el formato esperado cambia ligeramente en las claves ("Localidades leídas" vs "Total de localidades leidas" ) |
Se imprimen resultados incluyendo precios de equipos | Se imprimen resultados más concisos, sin tabla de costos |
Sin MPI | Con MPI |
---|---|
Ejecuta directamente al binario | Ejecuta a través de mpiexec usando configuración del cluster |
No usa machinefile
|
Requiere el archivo machinefile generado dinámicamente |
Sin MPI | Con MPI |
---|---|
self.processor_thread = ProcessorThread(command, ...) |
Igual, pero command es el comando completo con mpiexec
|
No hace detección de nodos antes de ejecutar | Hace detección de nodos antes de lanzar procesamiento |
Sin MPI | Con MPI |
---|---|
Usa hilo de monitoreo basado en conteo de archivos de salida | Igual, sin diferencias significativas |
Escala por total_expected_images * 6
|
Igual |
Este archivo check_hosts.sh
existe únicamente en la versión con MPI debido a que su función es preparar el entorno de ejecución distribuida. En un entorno con MPI, es necesario especificar un archivo llamado machinefile
que indica cuántos "slots" (procesos) estarán disponibles por nodo para ejecutar el procesamiento paralelo. Este script automatiza la generación de ese archivo, adaptándose a los nodos que estén disponibles en el momento de la ejecución.
-
HOSTS_SLOTS: Un diccionario que define cuántos slots tiene cada nodo del clúster (
ub0
,ub1
,ub2
). -
MACHINEFILE="../src/machinefile": Define la ruta del archivo
machinefile
que se necesita para lanzarmpiexec
. -
> "$MACHINEFILE": Limpia el contenido previo del archivo
machinefile
. -
Ping a cada host: Usa
ping
para comprobar si el nodo está activo y responde. Si responde, se agrega almachinefile
. - TOTAL_SLOTS: Acumula el total de slots disponibles entre todos los nodos activos.
-
Última línea: Imprime el total de slots disponibles, el cual puede ser capturado por la interfaz en Python para definir cuántos procesos debe lanzar
mpiexec
.
Este enfoque permite que el sistema sea dinámico y tolerante a fallos: si un nodo no está disponible, simplemente se omite y los demás nodos disponibles continúan con el procesamiento.
Sistema | Caracteísticas |
---|---|
Dell Precision | ![]() |
Asus TUF | ![]() |
Asus ZenBook | ![]() |
Sistema Distribuido | ![]() |
Es claro en las imágenes que el procesamiento resulta más rápido en máquinas físicas que en el clúster distribuido, donde la carga se reparte entre varios nodos. Se llegó a la conclusión de que esta diferencia de rendimiento puede estar relacionada principalmente con dos factores clave:
-
Virtualización de los nodos: Los nodos del clúster se ejecutan sobre máquinas virtuales, lo que introduce una capa adicional de abstracción entre el sistema operativo y el hardware. Esto puede limitar el acceso directo a los recursos del procesador, memoria y disco, afectando negativamente el rendimiento. Además, cuando varias VMs comparten el mismo host físico, pueden competir por los mismos recursos, generando cuellos de botella.
-
Implementación simultánea de OpenMP y MPI: Aunque la combinación de OpenMP (paralelismo a nivel de hilos) y MPI (paralelismo distribuido) permite aprovechar múltiples niveles de concurrencia, su implementación requiere un diseño muy cuidadoso. Si no se balancea correctamente la carga entre hilos y nodos, o si hay una sobrecarga por las comunicaciones entre procesos MPI, el rendimiento puede degradarse. Esto es especialmente notorio en entornos virtualizados, donde las comunicaciones entre nodos pueden ser más lentas que en hardware físico dedicado.
En resumen, aunque el enfoque distribuido ofrece escalabilidad, en este caso el entorno de ejecución (máquinas virtuales con recursos compartidos) y la complejidad de la doble paralelización pueden reducir los beneficios esperados frente a una ejecución en máquinas físicas bien optimizadas.
Lenovo | Lanix | PC Gaming | |
---|---|---|---|
CPU | Intel Core i7-14700 | Intel Core i3-1215U | AMD Ryzen 5 |
RAM | 16GB | 16GB | 32GB |
SO | Linux | Linux | Linux |
Nucleos | 20 | 6 | 6 |
Consumo | 300W | 55W | 600W |
Precio | Lenovo | Lanix | PC Gaming |
---|---|---|---|
Local | $9.36 | $1.72 | $18.72 |
AWS | $368.64 | $39.94 | $326.4 |
Este proyecto demostró cómo es posible construir un sistema de procesamiento de imágenes eficiente y funcional utilizando únicamente recursos locales y tecnologías de código abierto. Al implementar tanto una versión paralela con OpenMP como una distribuida con MPI, se comprobó que es viable acelerar tareas pesadas como el procesamiento de imágenes mediante la colaboración entre varios equipos conectados en red.
Además de la parte técnica, el desarrollo incluyó una interfaz gráfica amigable que permite al usuario gestionar el sistema sin necesidad de conocimientos avanzados, haciendo más accesible su uso. También se evaluó el desempeño frente a soluciones en la nube, destacando que, aunque servicios como AWS ofrecen potencia, un sistema local bien optimizado puede representar una alternativa mucho más económica en el largo plazo.
En conjunto, esta solución no solo resuelve un problema técnico, sino que también propone un modelo sostenible y escalable, combinando eficiencia, bajo costo y facilidad de uso.