La idea del proyecto es tomar una foto del sol cada 5 minutos, durante un año, entre las horas del día donde, debido a la situación de la cámara, el sol está visible.
Todas las fotos se han tomado con la Raspberry Pi 4, el módulo Camera Board V2 y el objetivo IMX219-D160, con un filtro delante del objetivo, hecho con un trozo de radiografía, a través de scripts de lenguage Python, siguiendo los consejos fotográficos publicados en el artículo Capture the Analemma of the Sun y adaptándolos a la Raspberry Pi 4 y al lenguaje Python.
Para la toma de fotos, lo primero ha sido fijar la zona horaria de la Raspberry con el horario UTC, para así no tener porblemas con el cambio de hora del verano. Las horas de visibilidad del punto focal con ese horario son siempre desde las 7 hasta las 14.
La idea inicial era crear un bucle temporal en python para realizar esa operación. Luego vi que era más práctico crear una tarea cron en la Raspberry que ejecutara un script para la toma de una foto, y que la orden de ejecución de la tarea incluyera el lapso de tiempo. Esta es la linea de la tarea para tomar una foto, cada 5 minutos, entre las 7 y las 14 horas, todos los días del mes, todos los meses y todos los días de la semana:
*/5 07-13 * * * /usr/bin/python3 /home/pi/analemaFoto.py
De esta forma se consigue que, en caso de corte de suministro, el sistema siga tomando la foto al volver a arrancar.
He nombrado cada foto con el día, hora y minuto en que ha sido tomada, con el formato AAAAMMDD-HHMM, para luego poder crear distintas composiciones con las fotos tomadas. De esta forma, para obtener el analema, por ejemplo, de las 11 en punto de la mañana, el patrón de la selección de las fotos será "*1100.jpg". O si queremos el recorrido del sol de un día en particular, el patrón del 17 de marzo de 2021 sería "*20210317*.jpg". En python quedaría así:
from datetime import datetime
# nombre de la foto segun el momento AAAAMMDD-HHMM.jpg
dt = datetime.utcnow()
nfile = dt.strftime('img%Y%m%d-%H%M.jpg')
imagen = '/path/' + nfile
Para que podamos tomar una foto al sol hay que jugar con distintos factores. Podemos poner filtros delante del objetivo, modificar la apertura del foco y la velocidad de disparo, teniendo presente que lo realmente importante es obtener la posición del sol. Aquí podemos ver distintas formas:
Después de varias pruebas, la opción que tomé fue la de usar el filtro, regular la captura con una exposición muy rápida y cerrar el objetivo para que entrara muy poca luz. Todo ello a la máxima resolución del dispositivo. Este es el programa para la toma de fotos:
import picamera
from time import sleep
# Iniciamos la cámara y preparamos su resolución
camera = picamera.PiCamera()
camera.resolution = (3280, 2464)
camera.framerate= 30
camera.shutter_speed = 500
camera.iso = 100
camera.start_preview()
sleep(2)
# imagen en rpi4
imagen_creada = ruta_local + nfolder + nfile
# tomamos la foto
camera.capture(imagen_creada)
camera.stop_preview()
sleep(5)
Y este es el resultado:
Una vez hecha la foto, la subo al servidor NAS donde está montada esta web. Antes de subirla, compruebo que la foto tiene un sol en ella. Para ello utilizo el módulo OpenCV para comprobar si existen píxeles con un color RGB superior a 80,80,80.
import cv2
import numpy as np
# Funcion para saber si hay imagen del sol
def haySol(checkImagen):
imagenChk = cv2.imread(checkImagen)
sought = [80, 80, 80]
npixeles = np.count_nonzero(np.all(imagenChk > sought, axis=2))
if npixeles > 0:
return True
else:
return False
Para subirla al NAS utilizo el módulo pysftp.
import pysftp
# Funcion para el envío del fichero al servidor
def push_file_to_server(local_path, remote_path, file_name):
s = pysftp.Connection(host='192.168.##.##',
username='pi',
password='*************',)
try:
s.chdir(remote_path) # Test if remote_path exists
except IOError:
s.mkdir(remote_path) # Create remote_path
s.chdir(remote_path) # Move to remote path
s.put(local_path + file_name, file_name)
s.close()
if haySol(imagen_creada):
# subimos la foto al nas
push_file_to_server(ruta_local + nfolder, ruta_nas + nfolder, nfile)
Para poder utilizar esta foto en una composición, hay que modificar el umbrar de la foto para que la diferencia blanco/negro sea nítida. Para ello he utilizado el módulo OpenCV, convirtiendo primero la imagen en escala de grises y así poder luego ajustar el umbral.
import cv2
# ruta a la foto
image = cv2.imread('foto.png')
# la convertimos en escala de grises
img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# aplicamos el umbral para que
# todos los pixeles por encima de 220 se cambien a 255
# hay que ajustar este valor dependiendo del filtro
# y los parámetros elegidos al tomar la foto
ret, thresh = cv2.threshold(img, 220, 255, cv2.THRESH_BINARY)
cv2.imwrite('sol-bn.png', thresh)
El resultado es este:
En el programa definitivo, he ajustado el cálculo del umbral a aplicar a cada foto. Este es su estudio: Cálculo del umbral
Para unir las fotos en una composición se utiliza el módulo OpenCV. Por ejemplo, este código lee todas las fotos de una carpeta, ajusta el umbral y las va incluyendo, una a una, en un solo fichero. Se trata de fotos tomadas cada 5 minutos, así que no hay problemas de coincidencia entre ellas.
import cv2
import glob
# funcion para recortar el sol
def recortar_imagen(img_bruto):
imagen = cv2.imread(img_bruto)
# cambiamos a escala de grises
imagen = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)
# modificamos el umbral para recortar el sol
ret, imagen = cv2.threshold(imagen, 150, 255, cv2.THRESH_BINARY)
return imagen
# leemos todas las fotos
primera_foto = True
for img in glob.glob('fotos/img20210317*.jpg'):
if primera_foto:
primera_img = recortar_imagen(img)
primera_foto = False
continue
else:
siguiente_img = recortar_imagen(img)
# añadimos las siguientes fotos sobre la primera
primera_img = cv2.addWeighted(primera_img, 1, siguiente_img, 1, 0)
# guardamos la foto con la suma de todas
cv2.imwrite('suma.png', primera_img)
Este sería el resultado:
Ya solo nos quedaría meclar esta composición con la imagen de fondo que queramos. Para ello utilizo también el módulo OpenVC.
import cv2
# añadimos la composición al fondo
bg = cv2.imread('fondo.png', cv2.IMREAD_COLOR)
fg = cv2.imread('suma.png', cv2.IMREAD_COLOR)
# igualamos el tamaño de las imágenes
dim = (3280, 2460)
resized_bg = cv2.resize(bg, dim, interpolation=cv2.INTER_AREA)
resized_fg = cv2.resize(fg, dim, interpolation=cv2.INTER_AREA)
# mezclamos las imágenes
blend = cv2.addWeighted(resized_bg, 1.0, resized_fg, 1.0, 0.0)
# guadamos la imagen
cv2.imwrite('blended.png', blend)
Y este sería el resultado final: