Home Of MetaWops.

OS X. iOS. Gadgets. Social Media. Basketball. Music. Synths. Electronics.

Elektronik, Raspberry Pi

Focus Stacking mit dem Raspberry Pi

Focus Stacking ErgebnisSehe gerade, dass ich über mein aktuelles Bastelprojekt noch gar nicht gebloggt habe — obwohl ich schon zwei (mehr oder weniger ähnliche) Videos von ersten Tests auf YouTube dazu hochgeladen hatte (eins, zwei). Da es heute aufregende neue Bilder zum Projekt gibt, fasse ich mal kurz zusammen.

Mein Freund Heinrich hat mich drauf gebracht. Er schickte mir eine Mail mit einem Link zu einem Blog Beitrag, wo jemand einen alten, ausgedienten Flachbettscanner benutzte, um damit Focus Stacking Bilder zu machen. Da Heinrich ein Fotografie Freak ist und ich Spaß am Elektronik-Basteln und Programmieren (und auch am Fotografieren) habe, passte das perfekt! Wollten wir auch machen! Einen alten Flachbettscanner (von Vobis!) hatte ich auch noch im Keller stehen, der musste herhalten. Aber der Reihe nach.

Focus Stacking?

Focus Stacking nennt man das Verfahren, mit dem man viele Einzelbilder, die jeweils nur eine sehr geringe Schärfentiefe von einem Objekt haben zu einem Bild mit sehr großer Schärfentiefe zusammenfasst. Typischerweise bietet sich das bei Makroaufnahmen an, denn mit einem einzelnen Makrofoto von z.B. einem Insekt schafft man es i.d.R. nur, einen kleinen Teil z.B. des Facettenauges scharf zu bekommen. Lösung: man macht einfach viele Bilder und fährt dabei quasi mit der Schärfenebene durchs Objekt indem man sich mit der Kamera in sehr kleinen Abständen auf das Objekt zubewegt. Bei gleich bleibender Focus Einstellung natürlich. Die dabei entstehenden Bilder schmeißt man dann in eine Focus Stacking Software (z.B. Helicon Focus) und die zaubert daraus ein einziges Bild in dem alles scharf ist.

Was bisher geschah

Der Scanner wurde auseinandergenommen, die Anschlusskabel des Steppermotors freigelegt. Da ich nicht an den Motor herankam und auch keine Beschriftungen ablesen konnte, musste ich selbst herausfinden, was das für ein Modell ist. Nach etwas Recherche und ein wenig geschicktem Hantieren mit dem Messgerät stellte sich heraus, dass das nicht soo schwer ist. Und dank Messgerät am im Scanner verbauten Netzteil konnte man annehmen, dass dieser mit 12V betrieben wird. Eine Steppermotor Treiber Platine (sowas kostet ca. 9 Euro) hatte ich schon vorab geordert und nun stellte sich nach ein paar vorsichtigen Experimenten heraus, dass die zum Glück zu diesem Motor passte. Mit ihr kann man sogar noch viel mehr anstellen (Stichwort: micro steps), aber das ist etwas für die Zukunft.

Die Ansteuerung meiner Kamera (frisch gekaufte Sony Alpha 77) über deren Draht-Fern-Auslöser-Anschluss war auch recht schnell reverse engineered. Eine kleine Schaltung mit einem Transistor ebenfalls dank des Original Beitrags flugs aufgebaut. Das Studium des Datenblattes zur verwendeten Motor-Treiber-Platine war schon etwas intensiver. Aber schließlich war’s verstanden und in einem Python Skript umgesetzt. Das ganze läuft auf dem Raspberry Pi, der dann die Elektronik und somit den Stepper Motor und den Kamera Auslöser steuert. Im Prinzip macht das Programm nur (in einer Schleife): einen Schritt vorwärts, 2 Sekunden warten (Kamera Wackeln abwarten), Foto machen, 0.3 Sekunden warten. Fertig. Dieser „eine Schritt vorwärts“ entspricht dann ungefähr 0,05mm, was schon ganz ordentlich ist. Mit dem micro steps feature des Treibers könnte man das noch um den Faktor 16 verringern, aber da muss man erstmal sehen, ob es das überhaupt braucht.

Optisches Setup

Die Kamera sitzt also auf dem Scanner Schlitten und fährt aufs Objekt zu. An der Kamera vorne hat Heinrich heute eine abenteuerliche Konstruktion mit zahlreichen Adapterringen und Tubus-Stücken angebracht. Als Objektiv verwendeten wir dann schließlich ein Vergrößerer Objektiv in Retro-Stellung. Den Vergrößerungsfaktor kann ich gar nicht sagen, dafür ist Heinrich der Experte. Wir haben aber auch einen feinen Balgen hier und können das bei Bedarf noch steigern. Für heute reichte uns das Setup aber erstmal. So sieht das dann aus:

Als Lichtquelle stand uns heute nur meine ringförmige Arbeitsplatz Lupenleuchte zur Verfügung und in den Fotos unten ist jetzt auch nichts farbkorrigiert. Wir wollen die Beleuchtung noch mit ringförmigen weiß-LEDs verbessern, die wir direkt um das Objektiv anordnen. Doch dazu später in einem anderen Beitrag mehr, wenn wir so weit sind. Überhaupt haben wir noch einige Ideen für Verbesserungen und ich denke, wir werden erst ruhen, wenn wir die Schnake „im Kasten“ haben — von vorne bis hinten scharf. 😉

Heut stand uns allerdings erstmal nur eine kleine Platine Modell (der coole Bus Pirate von Dangerous Prototypes; auch mal einen Blog Artikel wert …) und auf ihr haben wir uns auf einen Chip und dessen Beinchen konzentriert. Und — Alter Schwede! — sind da mit diesem optischen Setup und der Focus Stacking Technik Dinge zu entdecken! Hier ein paar der Ausgangsfotos mit geringer Schärfentiefe, wie man sie dann in die Software steckt:

Da haben wir 100 Bilder (im Original mit 24 Megapixel pro Bild, 6000 x 4000 Pixel; hier heruntergerechnet auf 1500 x 1000 Pixel) gemacht und sind von Bild zu Bild tatsächlich nur einen einzigen Step des Stepper Motors weitergefahren, also ca. 0,05mm. Dann einfach die 100 Bilder in die genial einfache Software Helicon Focus geworfen und auf den „mach mal“ Button geklickt. Schon nach kurzer Zeit (man kann der Software beim Arbeiten zusehen) erhält man dann dieses beeindruckende Ergebnis:

Focus Stacking Ergebnis

Im Original auch 6000 x 4000 Pixel, hier leicht verkleinert auf 4500 x 3000 Pixel, weil WordPress die Originaldatei zu groß findet und nicht annehmen will. Abgesehen davon: ich sitze hier vor einem 27″ Display mit 2560 x 1440 Pixel Auflösung und wenn ich das 6000 x 4000 Bild in 1:1 Größe anzeige, kann ich da noch drin herumfahren und irre Details sehen! Man muss jetzt noch das Licht besser setzen, dass man z.B. auch die Beschriftung auf dem Chip Gehäuse besser lesen kann. Man kann sehr schön sehen, wie da der schwarze, grobe „Asphalt“ der Chip Oberfläche gelasert wird und die Beschriftung leicht tiefer sitzt. Auch der Unterschied bei den silbernen Beinchen des Chips und dem Lötzinn mit dem sie an der Platine fixiert sind ist krass. Und wie die Leiterbahnen unterirdisch im roten Plastik Trägermaterial verschwinden … Hach, einfach geil! 🙂 Macht Appetit auf mehr! Und wir werden weitere Bilder machen! Denn dieser Chip ist ja nun relativ platt, eben, plan. So ein Insekt hat da mehr Tiefe. Bin sehr gespannt, ob/wie uns das gelingt. Aber die Ergebnisse von heute mit dem Chip auf der Platine waren schon mal extrem motivierend! 🙂 Stay tuned!

Ergänzung am 6.11.13: Hier die aktuelle Version des Python Scripts, welches ich zur Steuerung des Motors und des Kamera Auslösers geschrieben habe. Ob das mit den Micro Steps (Funktion setStepSize) funktioniert, habe ich noch nicht probiert; nur mal erst die Funktion anhand des Datenblatts implementiert.

# -------------------------------------------------
# program for controlling the stepper motor in
# an old flatbed scanner and the camera shutter
# of my Sony Alpha 77 (via shutter remote cable)
# to make focus stacking pictures.
#
# original idea: David Hunt
# original source: http://www.davidhunt.ie/?p=2826
#
# adapted by: Stefan Wolfrum
# date: Fall 2013
# last update: 2013-11-03
#
# the used motor driver board is a Pololu A4988:
# http://www.pololu.com/catalog/product/1182
# datasheet:
# http://www.pololu.com/file/0J450/a4988_DMOS_microstepping_driver_with_translator.pdf
#
# usage:
# shutter.py -- without any commandline parameters
#               uses the following default values:
#               100 pictures, 1 motor step, 2s wait
#
# shutter.py 200 -- take 200 pictures instead
#
# shutter.py 100 4 -- take 100 pictures but make
#                     4 motor steps between them
#
# shutter.py 100 4 3.0 -- same as above but wait
#                         3s instead of default 2.
#
# in general there are four possible calls, with
# zero, one, two or three parameters:
#
# shutter.py
# shutter.py
# shutter.py
# shutter.py
#
# where no value for a parameter is given its
# default value (see above) is used.
# -------------------------------------------------

import time
import sys

try:
    import RPi.GPIO as GPIO
except RuntimeError:
    print("Error importing RPi.GPIO!  This is probably because you need superuser privileges.  You can achieve this by using 'sudo' to run your script")

# set the Raspberry Pi pin layout scheme we want to use:
# ------------------------------------------------------
GPIO.setmode(GPIO.BCM)

# these are the Raspberry Pi pins we use:
# ---------------------------------------
cameraShutterPin = 17
cameraWakeupPin = 21
motorStepPin = 22
motorSleepPin = 25
motorDirectionPin = 7
motorResetPin = 8
MS1Pin = 9
MS2Pin = 10
MS3Pin = 11

# define our pins as outputs:
# ---------------------------
GPIO.setup(cameraShutterPin, GPIO.OUT)
GPIO.setup(cameraWakeupPin, GPIO.OUT)
GPIO.setup(motorStepPin, GPIO.OUT)
GPIO.setup(motorSleepPin, GPIO.OUT)
GPIO.setup(motorDirectionPin, GPIO.OUT)
GPIO.setup(motorResetPin, GPIO.OUT)
GPIO.setup(MS1Pin, GPIO.OUT)
GPIO.setup(MS2Pin, GPIO.OUT)
GPIO.setup(MS3Pin, GPIO.OUT)

# more global variables/constants:
# --------------------------------
delayAfterStep = 0.01

stepSizeFULL = 0
stepSizeHALF = 1
stepSizeQUARTER = 2
stepSizeEIGHTH = 3
stepSizeSIXTEENTH = 7
microStepSizeBits = ['000', '100', '010', '110', '111']

motorDirectionFORWARD = 0
motorDirectionBACKWARD = 1

# several function definitions
# ============================

# set the microstep resolution:
# -----------------------------
def setStepSize(stepSize):
    if stepSize == stepSizeFULL:
        GPIO.output(MS1Pin, int(microStepSizeBits[0][0:1]))
        GPIO.output(MS2Pin, int(microStepSizeBits[0][1:2]))
        GPIO.output(MS3Pin, int(microStepSizeBits[0][2:3]))
    elif stepSize == stepSizeHALF:
        GPIO.output(MS1Pin, int(microStepSizeBits[1][0:1]))
        GPIO.output(MS2Pin, int(microStepSizeBits[1][1:2]))
        GPIO.output(MS3Pin, int(microStepSizeBits[1][2:3]))
    elif stepSize == stepSizeQUARTER:
        GPIO.output(MS1Pin, int(microStepSizeBits[2][0:1]))
        GPIO.output(MS2Pin, int(microStepSizeBits[2][1:2]))
        GPIO.output(MS3Pin, int(microStepSizeBits[2][2:3]))
    elif stepSize == stepSizeEIGHTH:
        GPIO.output(MS1Pin, int(microStepSizeBits[3][0:1]))
        GPIO.output(MS2Pin, int(microStepSizeBits[3][1:2]))
        GPIO.output(MS3Pin, int(microStepSizeBits[3][2:3]))
    elif stepSize == stepSizeSIXTEENTH:
        GPIO.output(MS1Pin, int(microStepSizeBits[4][0:1]))
        GPIO.output(MS2Pin, int(microStepSizeBits[4][1:2]))
        GPIO.output(MS3Pin, int(microStepSizeBits[4][2:3]))

# set the motor's direction:
# --------------------------
def setDirection(direction):
    GPIO.output(motorDirectionPin, direction)

# move the motor the given number of steps
# in the currently set direction:
# ----------------------------------------
def move(numberOfSteps):
    for j in range(0, numberOfSteps, 1):
        GPIO.output(motorStepPin, GPIO.LOW)
        GPIO.output(motorStepPin, GPIO.HIGH)
        time.sleep(delayAfterStep)

# shoot a picture:
# ----------------
def shootPicture():
    # close camera shutter (push finger down)
    GPIO.output(cameraShutterPin, GPIO.HIGH)
    time.sleep(0.3)
    # open camera shutter (release finger)
    GPIO.output(cameraShutterPin, GPIO.LOW)
    time.sleep(0.05)

def wakeupBoard():
    # set the motor driver board's sleep pin to HIGH for normal operation
    GPIO.output(motorSleepPin, GPIO.HIGH)
    # set the motor reset pin to HIGH:
    GPIO.output(motorResetPin, GPIO.HIGH)

def sleepBoard():
    # set motor driver board to sleep to prevent the coils to drain current:
    GPIO.output(motorSleepPin, GPIO.LOW)
    GPIO.output(motorResetPin, GPIO.LOW)

# MAIN PROGRAM
# ============

# some initial output pin settings
# --------------------------------
# for safety reasons: release the camera shutter initially:
GPIO.output(cameraShutterPin, GPIO.LOW)

wakeupBoard()

# set the resolution of the stepper motor:
setStepSize(stepSizeFULL)

# set the direction of the stepper motor:
setDirection(motorDirectionFORWARD)

# this is our main loop where we move the camera stepwise
#   and take a photo after each step:

numberOfPictures = 100
numberOfSteps = 1
sleepTimeAfterStep = 2.0
sleepTimeAfterPicture = 0.3
returnToOrigin = True

# handle command line arguments:
# ------------------------------

argc = len(sys.argv)
# 'first' argument is always the script name and that's argv[0]
# so our first 'real' parameter after the script name is argv[1]
# but if there's only this one additional parameter argc==2 !
if argc == 2:
    numberOfPictures = int(sys.argv[1])
elif argc == 3:
    numberOfPictures = int(sys.argv[1])
    numberOfSteps = int(sys.argv[2])
elif argc == 4:
    numberOfPictures = int(sys.argv[1])
    numberOfSteps = int(sys.argv[2])
    sleepTimeAfterStep = float(sys.argv[3])

# do some confirmational printout
# -------------------------------
print
print "shutter.py - stepper motor & camera shutter controlling utility"
print "inspired by David Hunt"
print "this program witten by Stefan Wolfrum in 2013"
print "version " + str(version)
print
print "invoked with the following parameters:"
print "--------------------------------------"
print "* " + str(numberOfPictures) + " pictures"
print "* " + str(numberOfSteps) + " motor step(s) between pictures"
print "* " + str(sleepTimeAfterStep) + " second(s) sleep time after each motor step"
print "* " + str(sleepTimeAfterPicture) + " second(s) sleep time after each picture"
if returnToOrigin:
    print "* camera will return to origin"
else:
    print "* camera will NOT return to origin"
print
print "starting process ..."

for j in range(0, numberOfPictures, 1):
    print "taking picture #" + str(j+1)
    shootPicture()
    time.sleep(sleepTimeAfterPicture)
    move(numberOfSteps)
    time.sleep(sleepTimeAfterStep)

if returnToOrigin:
    print "returning to origin ..."
    setDirection(motorDirectionBACKWARD)
    for j in range(0, numberOfPictures, 1):
        move(numberOfSteps)

# clean up the hardware
sleepBoard()

# last thing to do: clean up the GPIO system:
GPIO.cleanup()

print "finished."
  1. Hier noch nachgeschoben ein Link zur 6000 x 4000 Pixel Version des Ergebnis Bildes: http://f.cl.ly/items/453v1Q1B3j011i1r1n1s/2013-11-02_18-45-50%20M=C%20R=40%20S=4.jpg

  2. Heinrich Kunel

    Möchte noch ein paar Details zum optischen Setup beisteuern:
    Zum Einsatz kam ein Vergrößerungsobjektiv Minolta C.E. Rokkor 2,8/30mm in Retrostellung an Zwischenringen – wie Stefan schon erläutert hat. Das Objektiv ist für den Einsatz an Farbvergrößerern konzipiert und wurde ursprünglich für das Pocketformat 13x17mm gerechnet. Es ist aufgebaut aus 6 Linsen in 4 oder 5 Gruppen. Die optische Korrektion ist ausgezeichnet und resultiert in einer Leistung, die die entsprechenden Objektive von Schneider Kreuznach von Rodenstock (Componon bzw. Rodagon mit jeweils 4,0/28mm und auch 6 Linsen in 4 Gruppen) gerechnet für das Halbformat 18x24mm noch etwas übertrifft. Bin gespannt wie sich das Objektiv schlägt, wenn wir es mit noch größeren Auszügen am Balgengerät einsetzen. bis dahin ;o)

  3. Afif

    Dear Stefan
    Many thank for the well done job and for sharing your experience. I am very new to Python and I’ll ask you a very basic question. I could understand everything from your code except the returnToOrigin instruction. First you defined it as TRUE. Then, if I understood it correctly, as a first step in your photo session you print that the camera will return to origin (because it’s TRUE) but mechanically nothing will happen (why?). Then you make your loop and then returnToOrigin. Wile I understand the last returnToORigin, I can’t figure out why you used the first returnToOrigin instruction. Thanks for you help

  4. Hi Afif
    and thanks for your question which I’m happy to answer. 🙂
    The „returnToOrigin“ is only a boolean variable. Its default value (set in line 179) is True. I planned to make another commandline parameter for this value so that the program can be invoked with four parameters, the forth being the option to let the camera return to it’s origin after all the photos are shot.
    However, I didn’t implement this, yet.
    Instead, the camera always returns to it’s starting position after the work is done because the variable is set to True and there’s no way for the user who starts the program to change that.
    The if statement that starts in line 212 only prints out whether the camera will return or not. (But it will always, for now.)
    So, to sum it up: before the first photo is shot nothing happens. Just the text „* camera will return to origin“ will be printed out. Then the photos are taken and then the camera returns to its origin (where its position was at the beginning).
    Unfortunately, there is no „null position“ of the scanner’s carriage and there’s no sensor or switch, either, that fires when some „null position“ is reached. So all I can do in terms of „returning to an origin“ is: moving the carriage the exact amount of steps backward that it moved forward when taking the photos. And that’s exactly what I do at the end so that I can start over with the next round of photos and don’t have to manually (i.e., by hand) move the carriage backwards (which might also damage the stepper motors? Not sure here, though).
    Does that answer your question?

  5. Afif

    Yes it answers my question. Thank for the detailed and quick response. I am planning to do exactly do the same as you did in terms of moving the camera, and, I also recovered a slider from a DVD/RW slider to use it for moving small specimen (as reported in some youtube video). Thanks again and best wishes in your next experiences

  6. Okay dann werde ich es auch mal Bauen
    Danke für die Dokumentation !

  7. Nick

    Hey Stefan, ein sehr Schönes Projekt!
    Ich habe da eine Frage zu der Verwendung des A4988 Driver Boards mit dem Raspberry.
    Ich habe gerade ein großes Schulprojekt am laufen und komme mit der Ansteuerung nicht so recht klar.
    Ist es möglich mir ein Bild zu schicken wie das Board an das Pi angeschlossen werden muss ? Dein Programmcode war schon sehr Hilfreich. Leider kann man auf den Bildern nicht genau erkennen wie es angeschlossen wurde. Ich wäre dir sehr dankbar!

    MfG Nick

Schreibe eine Antwort

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

Theme von Anders Norén

%d Bloggern gefällt das: