Overview
Just like our eyes, cameras can focus at different distances, but not all distances at once. Hold your hand up in front of your face and look at something far away -- your hand will appear blurry. Now focus your eyes on your hand and the background will blur. Camera lenses and sensors do the same thing.
Focus Stacking
But we don't have to put up with reality! We can take a series of photos with the MEMENTO, each with a different focal length, and then use some clever software to combine them into a single, in-focus-at-all-distances image. This is especially useful when taking close-up macro photographs, such as the flowers seen here.
In this guide we'll cover focus stacking as well as two other neat tricks -- rack focus and split focus -- you can do with the MEMENTO.
These are the extremes of the far- and near-focus shots taken to stack into the single image seen above. Hover your mouse over the two thumbnails to see the larger image comparison.
Below is the sequence of images used for the full focus stack.
Page last edited March 08, 2024
Text editor powered by tinymce.
Focus Tricks
You'll create focus-stacked macro photos such as the flowers seen here by shooting a series of images at varied focal distances and combining them with clever software.
Rack Focus
You can also use focal distance to draw attention to different subjects in a scene. In cinematography, a rack focus is a technique used to do exactly this. With the MEMENTO you can take a series of images with incrementally varied focal distance and then combine them into a GIF or video.
Split Diopter
Another popular focus trick used in films is the split diopter shot, which brings half of the frame into near focus and half into far focus. This is traditionally achieved with a special half-frame lens element secured in front of the main lens, but you can use a simple matting technique to combine two focused frames into one.
Page last edited March 08, 2024
Text editor powered by tinymce.
Install CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.
CircuitPython Quickstart
Follow this step-by-step to quickly get CircuitPython running on your board.
import storage storage.erase_filesystem()
Your board will reboot after running this.
Click the link above to download the latest CircuitPython UF2 file.
Save it wherever is convenient for you.
Plug your board into your computer, using a known-good data-sync cable, directly, or via an adapter if needed.
Double-click the reset button (highlighted in red above), and you will see the RGB status LED(s) turn green (highlighted in green above). If you see red, try another port, or if you're using an adapter or hub, try without the hub, or different adapter or hub.
For this board, tap reset and wait for the LED to turn purple, and as soon as it turns purple, tap reset again. The second tap needs to happen while the LED is still purple.
If you do not see the LED turning purple, you will need to reinstall the UF2 bootloader. See the Factory Reset page in this guide for details.
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.
You will see a new disk drive appear called CAMERABOOT. Drag the adafruit-circuitpython-adafruit_esp32s3_camera-etc.uf2 file to CAMERABOOT.
Page last edited March 08, 2024
Text editor powered by tinymce.
Code the Focus Stacker
The focus stacking mode has been added to the main "fancy camera" example. You can see this page on using the features as originally created.
There are two variables to adjust in the code -- FOCUS_STEPS and FOCUS_START.
The FOCUS_STEPS default to 20 which means the lens will adjust 20 steps per photo taken, until the maximum near focus value of 255 has been reached. Set this to 5 or 10 for finer focal steps (and more images saved to your SD card.)
FOCUS_START is set to 30 by default, meaning the first photo taken won't be at maximum far focus, which is often much farther than needed for typical medium shots. Set this down to 0 for full far focus.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, and the code.py file. To get everything you need, click on the Download Project Bundle button below, and uncompress the .zip file.
Connect your computer to the board via a known good USB power+data cable. A new flash drive should show up as CIRCUITPY.
Drag the contents of the uncompressed bundle directory onto your board CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
# SPDX-FileCopyrightText: 2024 John Park for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense
'''
Focus stacking example. Set FOCUS_STEPS (5-10 is a good range) for 0-255 range
Set STACK to True, or set to False to have JPEG mode take snapshots as usual.
'''
import time
import bitmaptools
import displayio
import gifio
import ulab.numpy as np
import adafruit_pycamera
pycam = adafruit_pycamera.PyCamera()
# pycam.live_preview_mode()
settings = (
None,
"resolution",
"effect",
"mode",
"led_level",
"led_color",
"timelapse_rate"
)
curr_setting = 0
print("Starting!")
pycam.tone(440, 0.1)
last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
timelapse_remaining = None
timelapse_timestamp = None
STACK = True # mode placeholder
FOCUS_STEPS = 20 # number of focus steps to increment during bracket from 0-255
FOCUS_START = 30 # optionally, start the focus closer
focus_stacking = False
while True:
if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0:
# alpha blend
new_frame = pycam.continuous_capture()
bitmaptools.alphablend(
onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED
)
pycam.blit(onionskin)
elif pycam.mode_text == "GBOY":
bitmaptools.dither(
last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED
)
pycam.blit(last_frame)
elif pycam.mode_text == "LAPS":
if timelapse_remaining is None:
pycam.timelapsestatus_label.text = "STOP"
else:
timelapse_remaining = timelapse_timestamp - time.time()
pycam.timelapsestatus_label.text = f"{timelapse_remaining}s / "
# Manually updating the label text a second time ensures that the label
# is re-painted over the blitted preview.
pycam.timelapse_rate_label.text = pycam.timelapse_rate_label.text
pycam.timelapse_submode_label.text = pycam.timelapse_submode_label.text
# only in high power mode do we continuously preview
if (timelapse_remaining is None) or (
pycam.timelapse_submode_label.text == "HiPwr"
):
pycam.blit(pycam.continuous_capture())
if pycam.timelapse_submode_label.text == "LowPwr" and (
timelapse_remaining is not None
):
pycam.display.brightness = 0.05
else:
pycam.display.brightness = 1
pycam.display.refresh()
if timelapse_remaining is not None and timelapse_remaining <= 0:
# no matter what, show what was just on the camera
pycam.blit(pycam.continuous_capture())
# pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken
try:
pycam.display_message("Snap!", color=0x0000FF)
pycam.capture_jpeg()
except TypeError as e:
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
pycam.display.refresh()
pycam.blit(pycam.continuous_capture())
timelapse_timestamp = (
time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1
)
else:
pycam.blit(pycam.continuous_capture())
pycam.keys_debounce()
if pycam.shutter.long_press:
print("FOCUS")
print(pycam.autofocus_status)
pycam.autofocus()
print(pycam.autofocus_status)
if pycam.shutter.short_count:
print("Shutter released")
if pycam.mode_text == "STOP":
pycam.capture_into_bitmap(last_frame)
pycam.stop_motion_frame += 1
try:
pycam.display_message("Snap!", color=0x0000FF)
pycam.capture_jpeg()
except TypeError as e:
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
if pycam.mode_text == "GBOY":
try:
f = pycam.open_next_image("gif")
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
continue
with gifio.GifWriter(
f,
pycam.camera.width,
pycam.camera.height,
displayio.Colorspace.RGB565_SWAPPED,
dither=True,
) as g:
g.add_frame(last_frame, 1)
if pycam.mode_text == "GIF":
try:
f = pycam.open_next_image("gif")
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
continue
i = 0
ft = []
pycam._mode_label.text = "RECORDING" # pylint: disable=protected-access
pycam.display.refresh()
with gifio.GifWriter(
f,
pycam.camera.width,
pycam.camera.height,
displayio.Colorspace.RGB565_SWAPPED,
dither=True,
) as g:
t00 = t0 = time.monotonic()
while (i < 15) or not pycam.shutter_button.value:
i += 1
_gifframe = pycam.continuous_capture()
g.add_frame(_gifframe, 0.12)
pycam.blit(_gifframe)
t1 = time.monotonic()
ft.append(1 / (t1 - t0))
print(end=".")
t0 = t1
pycam._mode_label.text = "GIF" # pylint: disable=protected-access
print(f"\nfinal size {f.tell()} for {i} frames")
print(f"average framerate {i/(t1-t00)}fps")
print(f"best {max(ft)} worst {min(ft)} std. deviation {np.std(ft)}")
f.close()
pycam.display.refresh()
if pycam.mode_text == "JPEG":
pycam.tone(200, 0.1)
if STACK:
focus_stacking = True
print("Start focus stack!")
pycam.autofocus_vcm_step = FOCUS_START
saved_settings = pycam.get_camera_autosettings()
pycam.set_camera_exposure(saved_settings["exposure"])
pycam.set_camera_gain(saved_settings["gain"])
pycam.set_camera_wb(saved_settings["wb"])
else:
try:
pycam.display_message("Snap!", color=0x0000FF)
pycam.capture_jpeg()
pycam.live_preview_mode()
except TypeError as e:
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
except RuntimeError as e:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)
if focus_stacking:
vcm_step = pycam.autofocus_vcm_step
vcm_step = min(255, vcm_step + FOCUS_STEPS)
if vcm_step < 255:
pycam.capture_jpeg()
pycam.tone(1600 + (vcm_step*10), 0.05)
pycam.autofocus_vcm_step = vcm_step
pycam.display_message(str(vcm_step), color=0xFF00FF)
pycam.live_preview_mode()
print("Now at focus", pycam.autofocus_vcm_step)
else:
focus_stacking = False
print("Done stacking!")
pycam.autofocus_vcm_step = FOCUS_START
pycam.camera.exposure_ctrl = True
pycam.set_camera_gain(None) # go back to autogain
pycam.set_camera_wb(None) # go back to autobalance
pycam.set_camera_exposure(None) # go back to auto shutter
pycam.live_preview_mode()
time.sleep(0.01)
if pycam.card_detect.fell:
print("SD card removed")
pycam.unmount_sd_card()
pycam.display.refresh()
if pycam.card_detect.rose:
print("SD card inserted")
pycam.display_message("Mounting\nSD Card", color=0xFFFFFF)
for _ in range(3):
try:
print("Mounting card")
pycam.mount_sd_card()
print("Success!")
break
except OSError as e:
print("Retrying!", e)
time.sleep(0.5)
else:
pycam.display_message("SD Card\nFailed!", color=0xFF0000)
time.sleep(0.5)
pycam.display.refresh()
if pycam.up.fell:
print("UP")
key = settings[curr_setting]
if key:
print("getting", key, getattr(pycam, key))
setattr(pycam, key, getattr(pycam, key) + 1)
if pycam.down.fell:
print("DN")
key = settings[curr_setting]
if key:
setattr(pycam, key, getattr(pycam, key) - 1)
if pycam.right.fell:
print("RT")
curr_setting = (curr_setting + 1) % len(settings)
if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelapse_rate":
curr_setting = (curr_setting + 1) % len(settings)
print(settings[curr_setting])
# new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1)
# pycam.set_resolution(pycam.resolutions[new_res])
pycam.select_setting(settings[curr_setting])
if pycam.left.fell:
print("LF")
curr_setting = (curr_setting - 1 + len(settings)) % len(settings)
if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelaps_rate":
curr_setting = (curr_setting + 1) % len(settings)
print(settings[curr_setting])
pycam.select_setting(settings[curr_setting])
# new_res = max(1, pycam.get_resolution()-1)
# pycam.set_resolution(pycam.resolutions[new_res])
if pycam.select.fell:
print("SEL")
if pycam.mode_text == "LAPS":
pycam.timelapse_submode += 1
pycam.display.refresh()
if pycam.ok.fell:
print("OK")
if pycam.mode_text == "LAPS":
if timelapse_remaining is None: # stopped
print("Starting timelapse")
timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate]
timelapse_timestamp = time.time() + timelapse_remaining + 1
# dont let the camera take over auto-settings
saved_settings = pycam.get_camera_autosettings()
# print(f"Current exposure {saved_settings=}")
pycam.set_camera_exposure(saved_settings["exposure"])
pycam.set_camera_gain(saved_settings["gain"])
pycam.set_camera_wb(saved_settings["wb"])
else: # is running, turn off
print("Stopping timelapse")
timelapse_remaining = None
pycam.camera.exposure_ctrl = True
pycam.set_camera_gain(None) # go back to autogain
pycam.set_camera_wb(None) # go back to autobalance
pycam.set_camera_exposure(None) # go back to auto shutter
The code works the same way as the primary camera example for the adafruit_pycamera library, as covered here, with the following additions:
Variables
The STACK variable is set to True, which means pressing the shutter release in JPEG mode will begin shooting the multiple focus images. Set this False to use regular snapshot mode.
FOCUS_STEPS specifies how many steps to adjust the lens per shot. The voice coil motor can be set from 0-255 (far-to-near focus length) so a step setting of 10 would generate 25 steps. Settings from 5-20 are typical, although you can go all the way down to 1 if you want to capture with the highest focal granularity.
FOCUS_START is used to set the farthest position in case you are shooting a medium or close range shot, you won't need a lot of frames captured at the far distances. 0 is the farthest possible focus, objects 12" away from the camera will be in focus starting around the 95 mark, 4" is in focus at about 130.
focus_stacking is used to hold the state while the set of photos is being shot.
STACK = True # mode placeholder FOCUS_STEPS = 20 # number of focus steps to increment during bracket from 0-255 FOCUS_START = 30 # optionally, start the focus closer focus_stacking = False
Focus Stack
With the MEMENTO in JPEG mode and the STACK set True, when you press the short shutter release here's what happens:
-
pycam.autofocus_vcm_stepis set to theFOCUS_STARTvalue -- this moves the lens to its farthest focal distance (physically closer to the sensor) by energizing the voice coil motor a precise amount - the exposure, gain, and white balance values are set and locked so there is no perceived flickering between frames
pycam.autofocus_vcm_step = FOCUS_START saved_settings = pycam.get_camera_autosettings() pycam.set_camera_exposure(saved_settings["exposure"]) pycam.set_camera_gain(saved_settings["gain"]) pycam.set_camera_wb(saved_settings["wb"])
Focus, Shoot, Repeat
Next the focus stacking loop begins.
- a photo is saved to SD card
- the
pycam.autofocus_vcm_stepis adjusted by theFOCUS_STEPSamount, which moves the lens farther from the sensor, thus bringing closer objects in focus
This repeats until the lens has moved to it's nearest focal position.
vcm_step = pycam.autofocus_vcm_step
vcm_step = min(255, vcm_step + FOCUS_STEPS)
if vcm_step < 255:
pycam.capture_jpeg()
pycam.tone(1600 + (vcm_step*10), 0.05)
pycam.autofocus_vcm_step = vcm_step
pycam.display_message(str(vcm_step), color=0xFF00FF)
pycam.live_preview_mode()
print("Now at focus", pycam.autofocus_vcm_step)
Reset
When the focus stack has been captured, the MEMENTO resets the following:
- lens returns to its farthest focus distance
- exposure control is set back to auto
- gain control returns to auto
- white balance returns to auto
pycam.autofocus_vcm_step = FOCUS_START pycam.camera.exposure_ctrl = True pycam.set_camera_gain(None) # go back to autogain pycam.set_camera_wb(None) # go back to autobalance pycam.set_camera_exposure(None) # go back to auto shutter pycam.live_preview_mode()
Page last edited March 08, 2024
Text editor powered by tinymce.
Focus Stacking
Set your MEMENTO on a stable surface or tripod mount, compose your picture, and then press the shutter release button. It will start by taking a photo at the farthest focus, then adjust the lens by the lens voice motor coil increment you selected, and repeat until is has taken all the photos up to the nearest possible focus.
Remove the microSD card from the MEMENTO and place it into your computer's SD card reader.
Copy the images to a directory on your computer.
One way to focus stack your images is with the web app Focus Stacking Online
Click the Add button and navigate to your image set.
Select the whole set and click OK.
Next, click the Stack! button and wait a few minutes for the images to be processed.
When it completes you can save your image by clicking the Download button.
Options
You can use Photoshop to create focus stacked images using this technique.
Another popular option is the cross-platform, open source focus-stack by Petteri Aimonen.
Page last edited March 08, 2024
Text editor powered by tinymce.
Rack Focus
A rack focus is great for shifting attention between subjects without moving the camera. With a shallow depth of focus, the rack focus makes it clear where we should look, since blur quickly envelopes everything but the subject at the intended focal depth.
Shoot a Rack Focus Shot
Set the MEMENTO on a stable surface or attach it to a tripod. Line up your subjects, and press the shutter release button.
The MEMENTO will snap pictures at the farthest to nearest depths.
Frames to GIFs
Remove the microSD card from the MEMENTO and insert it into an SD card reader on your computer. Copy the image files to a local directory. Then, you can turn the individual frames into a movie using video editing software, or stitch them into a GIF. This page shows you how to do just that!.
Page last edited March 08, 2024
Text editor powered by tinymce.
Split Focus
We can create a type of "impossible" focus shot using just two photos. Similar to the macro shot focus stacking we did earlier, but this time using the two ideal frames and a simple matte.
Far Focus
The first frame captures Lars in focus at the back of the totally not creepy abandoned warehouse.
Matte
We'll use a matte (also called a mask or alpha channel) to composite the two images. In this matte, the white pixels represent full opacity of the top layer (Slappy in focus). The black pixels represent full transparency of the top layer, allowing the bottom layer (Lars in focus) to be seen.
Different image editing software will vary as far as the specifics, but the principle of compositing layers with a matte/mask/alpha channel will still apply.
You can see an example in Photoshop here.
Page last edited March 08, 2024
Text editor powered by tinymce.