Crear y modificar imágenes con DALL-E

DALL-E no solo permite crear imágenes a partir de un texto. Se pueden modificar y extender. La propia web tiene herramientas para hacerlo.

Pero como es algo que es más fácil de explicar visualmente que por escrito, podéis ver cómo hacerlo explicado en mi siguiente vídeo de mi canal de Youtube:

Haz click en la imagen para ver el vídeo en mi canal de Youtube

Comprobar lo rápido que ordena Arduino

Vamos a ver cómo medir lo rápido que es capaz de ordenar Arduino un listado de 400 enteros (con más qsort falla en Arduino UNO). Aprovecharemos esto para ver cómo medir el tiempo que le cuesta ejecutar el código en Arduino.

Evitar «interrupciones»

Lo primero es quitarnos de «en medio» cualquier molestia a nuestro código en Arduino

  • Procesos asíncronos como la comunicación por Serial o el Watchdog
  • Interrupciones

En el caso de tener el Watchdog activado debemos asegurarnos de que no salta mientras probamos el bloque de código que queremos medir. Podemos deactivarlo con wdt_disable();

Con Serial debemos vaciar la cache usando Serial.flush(); para volcarla

En el caso de las interrupciones se puede usar noInterrupts(); para evitar que se lancen.

Como medida de seguridad extra podemos añadir un delay para dar tiempo a que todo termine. Aunque es una medida más paranoica que otra cosa debido a que Arduino es una arquitectura de un solo hilo (a diferencia de los procesadores modernos que tienen múltiples hilos). No hay manera de que quede alguna «tarea pendiente». Pero, al menos yo, me quedo más tranquilo.

En el caso de nuestro ejemplo no tenemos ni watchdog ni interrupciones, pero si comunicación usando Serial.

Medir el tiempo

Para medir el tiempo debemos crear dos variables de tipo unsigned long una guardara el tiempo justo antes de ejecutar el código y otra justo después. Para saber el tiempo total de ejecución del código basta con restarlas. Si el codigo le cuesta ejcutarsemenos de 70 minutos podemos usar micros() si se cuesta mas millis().

Ejemplo

Veamos el ejemplo comentado al principio midiendo el coste de ordenar 400 elementos:

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}
int cmp_asc(const void *c1, const void *c2){  
  return *((int *)c1) - *((int *)c2);
}
void setup() {
  Serial.begin(9600);
  Serial.println("Start");
  Serial.flush();
  int array[400] = {32, 162, 26, 82, 85, 220, 157, 149, 142, 255, 160, 66, 151, 37, 204, 0, 242, 92, 34, 24, 124, 14, 19, 173, 187, 160, 223, 225, 59, 151, 237, 245, 125, 35, 143, 125, 210, 53, 129, 136, 126, 174, 88, 138, 17, 132, 68, 116, 154, 7, 77, 31, 167, 59, 147, 165, 232, 59, 184, 169, 175, 116, 77, 110, 118, 211, 19, 111, 142, 247, 0, 120, 229, 138, 43, 173, 210, 98, 218, 75, 142, 101, 204, 14, 30, 130, 102, 142, 214, 176, 153, 178, 3, 199, 135, 191, 149, 22, 230, 64, 15, 165, 225, 187, 85, 204, 69, 144, 147, 9, 49, 134, 208, 171, 151, 231, 217, 37, 42, 68, 135, 102, 212, 216, 51, 177, 137, 242, 117, 154, 241, 32, 119, 210, 5, 180, 63, 209, 180, 39, 106, 11, 232, 6, 216, 249, 223, 113, 29, 78, 210, 138, 69, 2, 63, 37, 91, 146, 155, 243, 92, 174, 143, 122, 102, 130, 203, 168, 186, 255, 17, 167, 162, 41, 158, 81, 56, 213, 209, 11, 248, 108, 146, 82, 230, 159, 132, 17, 168, 165, 161, 190, 4, 53, 181, 149, 52, 64, 219, 215, 72, 4, 79, 186, 8, 16, 40, 32, 82, 115, 56, 138, 46, 126, 255, 124, 21, 85, 211, 13, 21, 21, 111, 227, 88, 128, 247, 158, 188, 210, 196, 190, 24, 38, 198, 81, 168, 245, 174, 40, 74, 236, 78, 68, 48, 44, 130, 34, 133, 118, 215, 242, 168, 21, 123, 84, 77, 140, 30, 83, 94, 29, 94, 138, 46, 223, 228, 13, 2, 70, 87, 74, 47, 100, 193, 86, 80, 237, 130, 142, 152, 239, 113, 114, 133, 160, 217, 34, 161, 214, 168, 92, 216, 178, 67, 188, 110, 136, 183, 147, 127, 209, 88, 102, 133, 196, 15, 66, 237, 189, 208, 0, 98, 147, 116, 130, 214, 231, 58, 150, 227, 155, 117, 133, 42, 98, 114, 254, 17, 80, 113, 63, 215, 190, 35, 171, 89, 180, 91, 26, 147, 39, 126, 66, 34, 1, 139, 87, 183, 129, 153, 106, 219, 245, 143, 182, 62, 99, 27, 82, 198, 234, 158, 122, 16, 119, 254, 241, 170, 186, 197, 192, 46, 133, 179, 54, 236, 35, 34, 97, 48, 150, 19, 26, 235, 17, 15, 182, 201, 151, 30, 40, 94, 188, 192, 149, 220, 250, 16, 35};
  unsigned long timeStart;
  unsigned long timeEnd;
  delay(500);
  timeStart = micros();    
  qsort(array, 400, sizeof(int), cmp_asc);
  timeEnd = micros();  
  Serial.print((timeEnd-timeStart));
  Serial.println();
  
}
void loop()
{
}

Puedes ver el vídeo donde ejecuto el ejemplo y muestro sus resultados:

Haz click para ver el vídeo en mi canal de Youtube

Como ordenar un array en Arduino

A veces tras la librerías oficiales de Arduino nos «tapan» utilidades que tienen las librerías avr-libc del fabricante del controlador. en este caso vamos a usar la función qsort() que implementar el algoritmo quicksort. No entraremos en detalle, simplemente aceptaremos que ordena y lo hace rápido.

qsort(void *base, size_t n_memb, size_t size, cmp_t *cmp)

En este caso la función qsort() requiere 4 parámetros:

  • *base : array de elementos a ordenar
  • n_memb : número de elementos del array
  • size : tamaño de cada elemento
  • cmp : función que realiza la comparación

Como qsort no sabe mágicamente como quieres ordenar lo elementos del array hay que pasar una función de comparación entre dos elementos que tiene que devolver los siguientes valores:

  • Si p1 == p2 devuelve 0
  • Si p1 va antes de p2 devuelve -1 (o culaquier número negativo)
  • Si p2 va después de p1 devuelve 1 (o culaquier número positivo)

La función comparación tiene la sigueinte firma:

int cmp_desc(const void *c1, const void *c2)

Como los parámetros se pasan como un puntero (a void) hay que realizar un cast al tipo de elemento de nuestro array. En nuestro ejemplo int, veamos el código de ejemplo de la función ordenar descendente (primero los números mayores)

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}

Veamos un ejemplo completo con ordenación ascendente y descendente:

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}

int cmp_asc(const void *c1, const void *c2){  
  return *((int *)c1) - *((int *)c2);
}


void setup() {
  Serial.begin(9600);

  int array[10] = {10, 5, 34, 76, 7, 6, 5, 23, 2, 42};
  
  qsort(array, 10, sizeof(int), cmp_asc);
  
  Serial.println("Resultado ascendente: ");
  for(int i = 0; i < 10; i++){
    Serial.print(array[i]);
    Serial.print(", ");
  } 
  Serial.println("");

  qsort(array, 10, sizeof(int), cmp_desc);

  Serial.println("Resultado descendente: ");
  for(int i = 0; i < 10; i++){
    Serial.print(array[i]);
    Serial.print(", ");
  } 
  Serial.println("");
}


void loop()
{
}

Y así de sencillo podemos ordenar cualquier array siempre que podemos hacer una función que compare valores. No necesitamos ningún tipo de librerías externas.

Puedes ver como funciona este código en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Crear prompts de DALL-E y Stable Diffusion con GPT-3

En una muestra de «delegar el trabajo a otros» vamos a intentar que sea GPT-3. El que nos cree el prompt para dibujar con DALL-E o con Stable Diffusion. Para ello vamos a jugar «al teléfono roto», le pediremos a GTP-3 que nos describa una obra de arte famosa, de la descripción quitaremos cualquier referencia a la obra original y le pasaremos ese texto a DALL-E y Stable Diffusion a ver que dibujan.

Antes de empezar or voy a ahorrar las pruebas que estuve haciendo hasta encontrar «las palabras mágicas» para que GPT-3 me describa el aspecto de algo y no me cuente su historia:

Describe what XXXX looks like in 50 words

Donde XXXX es lo que queremos que describa. La parte final de «in 50 words» es necesario para limitar el tamaño del resultado y que se centre en lo importante ya que DALL-E tiene un prompt limitado a 400 caracteres.

Pintura

Empezaremos por un cuadro famoso, la Mona Lisa

Describe what the Mona Lisa looks like in 50 words

Que GTP-3 completa con:

The Mona Lisa is a portrait of a woman with a soft expression and subtle colors. She is seated with her hands folded in her lap, and her gaze seems to follow the viewer around the room.

Tras «limpiarlo» le pasamos a DALL-E:

A portrait of a woman with a soft expression and subtle colors. She is seated with her hands folded in her lap, and her gaze seems to follow the viewer around the room.

Que crea como resultado:

DALL-E

DALL-E hace una curiosa reinterpretación, aunque lo de las manos no parece haberlo entendido.

Stable Diffusion

Stable Diffusion parece haber entendido mejor la tarea dando versiones más parecidas a lo que tenia en mente.

Escultura

Vamos a por la escultura, el David de Miguel Ángel:
Describe what michelangelo’s david looks like in 50 words

Que obtiene:
Michelangelo’s David is a life-size marble sculpture of the Biblical hero David, created between 1501 and 1504. The statue depicts David in the moment before he defeats Goliath, and is considered one of the most famous sculptures in the world.

Dude si dejar la referencia a David o no, decidi dejarla porque no apunta exactamente al David de Miguel Ángel.
A life-size marble sculpture of the Biblical hero David, created between 1501 and 1504. The statue depicts David in the moment before he defeats Goliath, and is considered one of the most famous sculptures in the world.

DALL-E

DALL-E en este caso ha sabido captar el sentido sin copiar la obra. Ademas que esa barba le pega más a un personaje bíblico que el lampiño David de Miguel Angel

Stable Diffusion

Stable Diffusion se ha empeñado en sacar desnudos, muchos más parecidos al de Miguel Angel

Arquitectura

Vamos a por la arquitectura, la Torre Eiffel.

Describe what the Eiffel Tower looks like in 50 words

The Eiffel Tower is a large iron lattice tower located on the Champ de Mars in Paris, France. Built in 1889, it is one of the most recognizable structures in the world. The tower is 324 metres (1,063 ft) tall and has two platforms, the first at 187 metres (614 ft), and the second at 276 metres (906 ft). There are stairs and elevators to the top.

Tras limpiar todas la referencias (que son bastantes):

A large iron lattice tower built in 1889. The tower is 324 metres (1,063 ft) tall and has two platforms, the first at 187 metres (614 ft), and the second at 276 metres (906 ft). There are stairs and elevators to the top.

DALL-E

Para DALL-E Torre Eiffel no hay más que una.

Stable Diffusion

Stable Diffusion da resultados mucho más originales y esta claro que no sabe que hablamos de la Torre Eiffel

Para ver mas ejemplos, en este caso de un dragón, podéis ver el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en Youtube

Generador de funciones / señales barato y simple usando Arduino

Ya vimos como convertir de forma barata de PWM a analógico. Ahora que tenemos una salida analógica ¿Podemos usarla para generar distintos tipos de señales? Vamos aintentar generar señales de distinto tipo sinusoidal, triangular, cuadrada (aunque para eso tenemos PWM), triangular, ….

Sin embargo vamos a tener alguna limitaciones:

  • Las limitaciones son que el valor mínimo de salida es 0v y el máximo 5v.
  • La frecuencia no puede ser muy alta. La base de nuestro sistema es un señal cuadrada funciona a 32 kHz y convierte la anchura de la señal (0-100%) a voltaje (0-5v). Para que esto ocurra necesita algunos pulsos, ademas que para reconstruir la señal necesita varios puntos. ¿Cuántos? Depende del tipo de señal, pero tomando la sinusoidal como referencia en mis pruebas el limite es unos 500-600HZ con la primera versión del programa y unos 1500-2000Hz con la segunda versión.

Ya vimos como generar distintas funciones para las ondas, ahora hay que añadir un cambio, las generaremos devolviendo un valor entre 0 y 1. Las funciones modificadas son las siguientes:

//Distintas señales a elegir
double signal = tf; //dientes de sierra
double signal = (sin(tf*2*PI)+1)/2 ; //seno
double signal = sin(tf*PI); //seno positiva
double signal = (sin(tf*2*PI) > 0) ? 1: 0; //cuadrada
double signal = (sin(tf*2*PI) >= 0) ? 2*tf: 2-(2*tf); //triangular

Solo podemos usar una cada vez. Puedes definir tus propias funciones mientras que devuelvan un valor entre 0 y 1. Donde 0 representa 0v y 1 es igual a 5v, cualquier valor intermedio representa un voltaje proporcional. Por ejemplo 0.5 representa 2.5v

Veamos el código:

float t = 0;
float f = 100; //frecuencia
float p = 1/f; //periodo
unsigned long oldMicros = 0;
unsigned long nowMicros = 0;

const byte PRESCALER2 = 0b001;

void setup() {
  //ajusta la frec. salida PWM pin 3
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
}

double dt = 0;
double tf = 0;
double signal = 0;

void loop() {
  nowMicros = micros();
  dt = double(nowMicros - oldMicros)/1000000;
  
  t += dt;
  tf = t*f;
  //Distintas señales a elegir
  //signal = tf; //dientes de sierra
  signal = (sin(tf*2*PI)+1)/2 ; //seno  
  //signal = sin(tf*PI); //seno positiva
  //signal = (sin(tf*2*PI) > 0) ? 1: 0; //cuadrada
  //signal = (sin(tf*2*PI) >= 0) ? 2*tf: 2-(2*tf); //triangular
  
  byte value = byte(255 * signal);
  analog(value);

  oldMicros = nowMicros;
  
  //cuando alcanza un periodo se reinicia
  if(t >= p){ 
    t = 0;
  }
}

//compensa el desajuste de la salida analogica
void analog(byte value){
  value += (255 - value) >> 4; 
  analogWrite(3, value); 
}

Con esta versión se puede alcanzar una frecuencia máxima (probada con una señal sinusoidal) de entre 500-600Hz.

Alcanzando mayores frecuencias

Para intentar exprimir al máximo nuestro Arduino vamos a optimizar el código todo lo posible. Para ello vamos a reducir los cálculos necesarios en cada iteración. Lo haremos precalculando en un array los valores de la onda en varios puntos. Luego en cada iteración calcularemos que punto nos toca mostrar del array. Esta forma da lugar a ondas más «sucias» con más ruido. Pero también permite mayores frecuencias.

float f = 1000; //frecuencia
float p = 1/f; //periodo
float t = 0;
int i = 0;
unsigned long oldMicros = 0;
unsigned long nowMicros = 0;
const int WAVE_POINTS = 2000; //puntos generados

byte wave[WAVE_POINTS]; //valores generados
double waveDt = p/WAVE_POINTS; //tiempo entre cada punto 

const byte PRESCALER2 = 0b001;

void setup() {
  //ajusta la frec. salida PWM pin 3
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
  
  //Precalculamos la onda
  for(i = 0; i < WAVE_POINTS; i++){
    t += waveDt;
    double tf = t*f;
    //double signal = tf; //dientes de sierra
    //double signal = (sin(tf*2*PI)+1)/2 ; //seno  
    //double signal = sin(tf*PI); //seno positiva
    //double signal = (sin(tf*2*PI) > 0) ? 1: 0; //cuadrada
    double signal = (sin(tf*2*PI) >= 0) ? 2*tf: 2-(2*tf); //triangular
    wave[i] = analog(byte(255 * signal));
  }
}

double dt = 0;

void loop() {
  nowMicros = micros();
  dt = double(nowMicros - oldMicros)/1000000;

  t += dt/waveDt;
  analogWrite(3, wave[int(t)]);

  oldMicros = nowMicros;    

  //reinicia al recorrer todos los puntos
  if(t > WAVE_POINTS){ 
    t = 0;
  }
}

//compensa el desajuste de la salida analogica
byte analog(byte value){
  value += (255 - value) >> 4; 
  return value;
}

Con esta versión se puede alcanzar una frecuencia máxima (probada con una señal sinusoidal) de entre 1500-2000Hz usando 2000 puntos para la tabla donde se precalculan los valores.

Esta versión tiene el problema de que ocupa casi todas la memoria SRAM de la placa, aunque se pueden reducir el número de puntos se reduce la calidad de la señal.

Ventajas y desventajas

Frente a cualquier generador de funciones barato tiene la desventaja de que la señal es más ruidosa y es posible que no alcance frecuencias tan elevadas.

Por otro lado en la parte de las ventajas esta el precio y en que la forma de la onda y su frecuencia es completamente programable, pudiendo hacerlo que nosotros queremos.

Puede ver un vídeo explicativo donde profundizo más en el tema en mi canal de Youtube:

Haz click para ver el vídeo en Youtube

Crear un cadáver exquisito usando text-to-image (DALL-E)

Un cadáver exquisito es una técnica creativa en la que varios autores construyen un texto, un dibujo o pintura de forma colaborativa pero sin saber que han hecho los otros autores. El resultado no se ve hasta que la obra esta terminada. A veces se le puede dar un tema, otras no. Vamos a simular estre proceso con DALL-E usando dos fotos, una de un asno y otra de un coche.

Subiremos ambas fotos, las uniremos y borraremos la parte central, dejando solo una pequeña parte que DALL-E pueda usar como pista para continuar ambas imágenes. La preparación tiene este aspecto:

Preparando el lienzo para DALL-E

Hay que tener claro que DALL-E solo recibe información del cuadrado que indica el editor, así que no tenemos que preocuparnos por lo que haya fuera.

En este caso queremos dejar volar la imaginación de DALL-E y como texto le vamos a pasar solo «photo»

Mi resultado favorito fue este:

El cadáver exquisito creado por DALL-E

Si deseas ver este proceso con más detalle (y los resultados descartados) puedes verlo en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Arduino convertir PWM a analógico por un módico precio.

Antes de leer este post aviso, esta solución es barata pero muy mala, con una salida plagada de ruido. Para aplicaciones que necesiten mejor conversión es mejor adquirir un DAC que lo haga. Sin embargo para «cacharrear» es una buena solución.

Una de las pegas que encuentro a Arduino UNO es no tener una verdadera salida analógica, que permita devolver un voltaje entre 0v y 5v. Lo más parecido que tenemos es PWM que codifica el valor en forma de la anchura (duración) del pulso. Sin embargo es posible convertir esa señal a voltaje. Vamos a ver cómo conseguir una verdadera salida analógica, variación de voltaje, en Arduino UNO conectando un circuito RC (resistencia condensador) muy sencillo a la salida PWM. La parte teórica del asunto es que el circuito RC actúa como un filtro de paso bajo para filtrar la señal PWM y convertirla en un voltaje constante (más o menos). Usando mi simulador de filtros veamos gráficamente lo que ocurre cuando aplicamos a una señal cuadrada un filtro paso bajo con una frecuencia de corte unas 50 veces menor que la señal cuadrada (En el simulador elegimos como señal una onda cuadrada, filtro paso bajo, una frecuencia para el filtro de 0.0075 Hz y quitamos todas las fuentes de ruido el ruido). El resultado es una señal que tras un periodo tiende a ser estabilizarse alrededor de un valor:

Ese valor será proporcional al tamaño en anchura de la parte alta del pulso cuadrado. Por lo que podemos convertir una pulso PWM, que modula su valor como la anchura de la parte alta de la señal, en un voltaje.

El circuito tiene la siguiente forma: (ahora veremos de donde salen esos valores)

Filtro paso bajo RC

Como ya vimos, para calcular la frecuencia de un filtro paso bajo podemos usar la siguiente formula:

f = 1 / (2* Pi * R * C)

Siendo f la frecuencia, C la capacidad del condensador y R la resistencia.

Para nuestro caso f = 1 / (2 * Pi * 4400 * 10^-7) = 361.71 Hz

Pero hemos dicho que la frecuencia de corte ha de ser unas 50 veces menor que la funcionamiento…pero eso es en la teoría. Haciendo pruebas en la vida real va mejor con alrededor de 100 veces. Por lo que PWM debería funcionar a unos 36 kHz. El problema esta que PWM no trabaja a esa frecuencia….a no ser que la cambiemos como vimos en esta entrada Por lo que fijaremos su frecuencia en unos 32 kHz.

El código será el siguiente:

const byte PRESCALER2 = 0b001;
void setup() {
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
}

void loop() {
  analogWrite(3, 255); //5v
  delay(5000);
  analogWrite(3, 204); //4v
  delay(5000);
  analogWrite(3, 153); //3v
  delay(5000);
  analogWrite(3, 102); //2v
  delay(5000);
  analogWrite(3, 51); //1v
  delay(5000);
  analogWrite(3, 0); //0v  
  delay(3000);
}

El resultado tiene bastante ruido y los valores resultantes no son muy exactos. Por lo que no se puede usar para casos donde se requiera gran precisión en el valor.

Para ajustar más los valores podemos usar una pequeña corrección para subir el valor del la señal PWM en los valores más bajos (son los que más se descuadran) y que se aproxime al valor de voltaje que debería tener:

void analog(byte value){
  value += (255 - value) >> 4; 
  analogWrite(3, value); 
}

El ejemplo anterior con esta pequeña corrección:

const byte PRESCALER2 = 0b001;
void setup() {
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
}

void loop() {
  analog(255); //5v
  delay(5000);
  analog(204); //4v
  delay(5000);
  analog(153); //3v
  delay(5000);
  analog(102); //2v
  delay(5000);
  analog(51); //1v
  delay(5000);
  analog(0); //0v  
  delay(3000);
}

void analog(byte value){
  value += (255 - value) >> 4; 
  analogWrite(3, value); 
}

Puedes ver un vídeo sobre este artículo, con demostración del funcionamiento, en mi canal de Youtube:

Haz click para ver el video en Youtube

Modificar la frecuencia del PWM en Arduino.

Vamos a ver cómo modificar la frecuencia a la que trabaja el PWM de Arduino UNO. Para esto es necesario cambiar la frecuencia de uno de los tres timers que posee. Cada uno de ellos controla la frecuencia de dos pines PWM y cumple una función distinta:

TimerPines PWMRegistroFunciones
Timer 0D5, D6TCCR0Bmicros(), milis() y delay()
Timer 1D9, D10TCCR1BServo
Timer 2D3, D11TCCR2Btone()

En la columna «registro» se indica el registro cuyos tres últimos bits controlan el «prescaler» da cada timer. Sin entrar en detalle de como funciona, este prescaler nos permite ajustar la frecuencia de la señal PWM actuando como un divisor de la misma. En las siguientes tablas podemos ver los valores correspondientes. Recordar que los bits CS*2, CS*1, CS*0 corresponden a los tres bits de menor peso del registro de 8 bits.

Timer 0 – TCCR0B

CS02CS01CS00DivisorFrecuencia
000Parado
001162500 Hz
01087812.50 Hz
01164976.56 Hz
100256244.14 Hz
101102461.04 Hz

Timer 1 – TCCR1B

CS12CS11CS10DivisorFrecuencia
000Parado
001131372,55 Hz
01083921,16 Hz
01164490,20 Hz
100256122,55 Hz
101102430,64 Hz

Timer 2 – TCCR2B

CS22CS21CS20DivisorFrecuencia
000Parado
001131372,55 Hz
01083921,16 Hz
01132980,39 Hz
10064490,20 Hz
101128245,10 Hz
110256122,55 Hz
111102430,64 Hz

Ejemplo

En nuestro ejemplo nos vamos a centrar en los pines D3 y D11. ¿Por qué estos dos? Para evitar problemas innecesarios, ambos usan el timer 2 que va asociado únicamente a la función tone(). Además es el timer que más nos permite jugar con sus frecuencias.

El prescaler lo ajustaremos con la siguiente instrucción:

TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;

Donde PRESCALER2 indicara la configuración de los bits CS22, CS21 y CS20. Veamos el código del ejemplo:

const byte PRESCALER2 = 0b001;

void setup() {
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
}

void loop() {
  analogWrite(3, 128);
  delay(1000);
}

Si quieres profundizar más en este tema y saber más sobre Arduino puedes echar un vistazo a mi libro.

Si quiere ver el ejemplo funcionando puedes mirar este vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

¿Puede DALL-E borrar las marcas de agua de las fotos?

Tras leer el titulo poca introducción necesita esta entrada. Tan solo necesito una foto con marca de agua, para evitarme problemas voy a usar una foto mía (tomada por mi, no de mi). Y añadirle una marca de agua poco sutil.

Foto de un burro con marca de agua

Ahora la subo a DALL-E y con mucho cuidado borro la marca de agua

Ahora pidamos a DALL-E la foto de un burro «photo of a donkey» y elijamos el mejor resultado (según mi punto de vista):

¿Y si le pedimos un caballo? «photo of a horse«

Pero un caballo y un burro están «próximos» y si le pido algo muy distinto como un coche «photo of a car«

Parece ser que el contexto de la foto pesa mucho y se impone a nuestros deseos. ¿Y si no nos complicamos la vida y pedimos simplemente una foto? «photo»

¡Correcto! DALL-E tiene suficiente información en la imagen como para completarla.

Aunque todas las imágenes parezcan iguales hay diferencias entre ellas.

Quizás este sistema es muy descarado, me gustaría quitar la marca de agua y evitarme una denuncia del autor. ¿Y si genero una variación a partir de la imagen con marca de agua?. Como siempre me quedo con el mejor resultado de los cuatro:

Es difícil que el autor original defienda que esta es su misma foto (el burro no esta ni en la misma pose). Ademas DALL-Eme ha movido la marca de agua a un sitio donde molesta menos, no pasa nada por que ya sabemos como eliminarla.

Definitivamente no es que DALL-E permita eliminar marcas de agua, es que permite generar imágenes nuevas a partir de fotos con marca de agua y que ni el propio autor seria capaz de reconocer.

Autómatas celulares y evolución

Vamos a usar los autómatas celulares para modelar una versión muy simple de evolución y luego jugar con ello.

Para simular la evolución necesitamos «algo» en cada individuo (celda) que haga la función de código genético que será sometido a un proceso de selección competitiva según lo adaptado que este al entorno. Los más adaptados tendran más oportunidades de replicarse, pero durante la misma se verán sometidos al cruce con el código genético de otro individuo y a mutaciones.

Veamos en detalle cada una de estas parte.

Genes, genotipo y fenotipo

Vamos a empezar a describir los genes de nuestro individuo. En nuestro caso cada gen es un único bit, por lo que puede tener 2 valores 0 y 1. Cada individuo tiene 8 genes por lo que entre todos serán un único byte. Hay 256 posibles combinaciones de genes (genotipos). Para representar el aspecto (fenotipo) de cada individuo usaremos el nivel de gris representado por el valor del byte que representa sus genes. Este color ira desde negro para el valor mínimo (0) a blanco para el valor máximo (255), pasando por distintos tonos de grises.

Función fitness

Es la función que devuelve un valor que puntúa lo bien adaptado que está un individuo al entorno. Tenemos todo un artículo dedicado a ella.

En este caso tendremos dos funciones fitness para poder jugar con ellas. Para ambas tomaremos el valor del bit representado por los genes. Por ejemplo, el genotipo 00000010 valdrá 2 y el genotipo 00000110 valdrá 6. Esto hace que no todos los genes tengan la misma importancia. También jugaremos con eso.

Para lo que no tengan claro como funcionan los numeros binarios pueden mirar la siguiente tabla para saber cuanto vale cada gen (el valor total del genotipo seria la suma de los valores de todos los bits que estén a 1)

Nº de bit76543210
Valor1286432168421

Hemos dicho que habrá dos funciones fitness una considera como más aptos a los individuos con mayor valor en sus genes. La otra a los individuos con menor valor. Ya veremos cómo jugamos con esto.

En la interfaz está la opción de cambiar la función fitness. Hay tres posibilidades: higth, que da preferencia a los bits de mayor valor; los, que da preferencia a los de menor valor y two, que usa ambas funciones fitness

Selección

Para que haya evolución necesitamos un proceso de selección, en cada iteración cada uno de nuestros individuos elegirá a uno de sus vecinos para cruzarse con él. La selección se hará con un mecanismo de ruleta, es decir, al azar, pero cada vecino tiene diferente probabilidad de salir elegido según el resultado de la función fitness. Los más adaptados son más probables.

Cruce

Una vez seleccionado el otro progenitor, se realiza el cruce de sus genes. El proceso consiste en tomar los 4 bits superiores del individuo, los 4 bits inferiores del vecino y con unirlos para formar el genotipo de descendiente. Esto creará un individuo con un código genético nuevo, mezcla de ambos individuos, que ocupará el lugar de su progenitor.

Mutación

Añadiremos mutaciones, cada cruce tiene cierta probabilidad de sufrir una mutación en uno de sus genes. Al producirse una mutación se cambia el valor de ese gen. Si es 0 pasa a ser 1 y viceversa.

La interfaz permite elegir la probabilidad de que está mutación ocurra en cada cruce. Se puede ajustar su valor entre 0 y 5% aunque lo recomendable es en 0,01% y 0,05%.

Creando el mundo

Cada uno de los individuos del mundo se crean con un valor entre 0 y 127. Esto se hace así para que haya un gen, el de mayor peso (gen 7), que este a 0 en todos ellos. La idea es que ese gen solo se puede poner a 1 a través de una mutación. Visualmente significa que solo puede haber cuadrados entre negro y gris claro. Para que haya cuadrados blancos tiene que producirse una mutación.

Jugando con todo esto

Empecemos por un punto importante, hemos dicho que, al principio, ningún individuo tendrá el gen 7 a 1. De tal manera que si tenemos individuos blancos será gracias a una mutación. Así que si iniciamos el autómata como mutaciones a 0 y fitness a «Hight» veremos que no es capaz de alcanzarlo, si añadimos unas pocas mutaciones lo alcanza sin problemas.

Gen de mayor peso a 0: Con mutaciones – Sin mutaciones

Ahora teniendo todo en blanco y con mutaciones vamos a cambiar la función fitness a «Low» vemos que cambia a negro, los individuos han sido capaces de adaptarse al entorno. Probemos los mismo, partiendo del estado con la mayoría de los individuos blancos, pero con las mutaciones a 0, el resultado es que nada cambia. Sin mutaciones y una población altamente especializada no hay variedad genética como par adaptarse al entorno.

De Hight a Low: Sin mutaciones – Con mutaciones

Veamos otro caso distinto probemos fitness a «Two» que indica que hay una función fitness distinta para cada zona del tablero. Al ejecutarlo veremos un fenómeno que se llama «especiación» en el cual a partir de una especie se crean dos separadas genéticamente.

Especiación

Ya hemos dicho antes que la incapacidad de adaptarse a un entorno es por la falta de variedad genética, al estar en un entorno dominado por muy poca variedad de genes y suprimir la mutaciones como fuente de variación la especie queda atrapada y no puede adaptarse. Pero en el caso anterior tenemos el tablero dividido en dos, hay variedad, aunque suprimamos las mutaciones debería de poder adaptarse y así es. Es capaz de adaptarse a cualquier cambio en el entorno, pero solo una vez.

¿Qué pasa si nos vamos al lado contrario aumentamos mucho las mutaciones?. Que vemos que los ganes varían tanto y tan rápido que es imposible mantener la población estable. Pensad que el simulador solo deja poner una tasa de mutación del 5% (1 de cada 20 individuos tiene una mutación) que parece una tasa baja y aun así se puede ver visualmente como afecta e impide que la población se adapte completamente al entorno.

Podéis probar vosotros mismos aquí y si queréis echar un vistazo al código fuente podéis hacerlo aqui.

Puedes ver un vídeo con más explicaciones en mi canal de Youtube:

Haz click para ver mi vídeo en Youtube