Działający autofocus na gumce

Mała aktualizacja moich postępów w budowie autofocusa na gumce. Oto obecna konstrukcja. Jako napęd zastosowałem tani silniczek krokowy PF35T-48L4. Dla poprawienia trakcji założyłem mu dwa kółka zamiast jednego i dwie gumki. Silniczek jest napędzany z Adafruit DC & Stepper Motort Hat – pozwala ona na sterowanie dwoma silnikami krokowymi. Zasilanie silnika na razie z czterech paluszków, ale to tylko testy.

img_4279

 

Nad oprogramowaniem jeszcze pracuję ale jak na razie działa ono super – jest dość powolne, ale jest w stanie ustawić ostrość tak dokładnie że ręcznie nie jestem w stanie tego powtórzyć. Gumka okazała się bardzo wdzięcznym sposobem napędzania obiektywu i przy odpowiednim sposobie poruszania silnikiem pozwala na bardzo dokładne ustawienie ostrości. Miałem pewne obawy czy ten silniczek, który w końcu ma tylko 48 kroków na obrót będzie wystarczająco dokładny, ale w połączeniu ze sporym przełożeniem dokładność jest raczej wystarczająca. Jednak zamówiłem silniczek 28BYJ-48 – ten ma też 48 kroków ale jednocześnie ma przekładnię 1:64 co daje znacznie większą precyzję.

Wziąłem sobie także do serca ideę zaproponowaną przez jednego z czytelników i po zakończeniu działań z gumką mam zamiar spróbować zbudować AF napędzany paskiem zębatym. Zakupiłem już niezbędny pasek, łożyska, zębatki itp. ale będzie to wymagało znacznie bardziej skomplikowanej konstrukcji mechanicznej niż obecnie. Cóż, poćwiczę sobie obróbkę drewna 😉

https://www.youtube.com/watch?v=Ejaw1uHHKFo

Dla ciekawskich poniżej obecna wersja algorytmu ustawiania ostrości. Python jest dla mnie nowością, więc proszę się nie śmiać z jakichś głupich konstrukcji:

#!/usr/bin/python

import atexit
import collections
import itertools
import urllib 
import time
import cv2
from Adafruit_MotorHAT import Adafruit_MotorHAT, Adafruit_StepperMotor
import numpy as np

resolutionX = 1024
resolutionY = 768
windowX = 160
windowY = 80
maxPosition = 800
fastMinDifference = 50
slowMinDifference = 20
startDirection = Adafruit_MotorHAT.FORWARD
memoryLength = 800
fastFocusWindow = 3 
slowFocusWindow = 6 
verySlowFocusWindow = 200 
minSlowStepsBeforeFocus = 5
numOfFastScans = 2
numOfSlowScans = 2

mh = Adafruit_MotorHAT()
def turnOffMotors():
    mh.getMotor(1).run(Adafruit_MotorHAT.RELEASE)
    mh.getMotor(2).run(Adafruit_MotorHAT.RELEASE)
    mh.getMotor(3).run(Adafruit_MotorHAT.RELEASE)
    mh.getMotor(4).run(Adafruit_MotorHAT.RELEASE)

atexit.register(turnOffMotors)

myStepper = mh.getStepper(48, 1)
myStepper.setSpeed(20)

class sliceable_deque(collections.deque):
    def __getitem__(self, index):
        if isinstance(index, slice):
            return type(self)(itertools.islice(self, index.start,
                                               index.stop, index.step))
        return collections.deque.__getitem__(self, index)

def createBuffer(window):
    return sliceable_deque(maxlen=window)

def addSample(sample, holder):
    holder.append(sample)

def rollingMax(holder):
    return reduce(lambda res, x: res if res > x else x, holder) 

def rollingMin(holder):
    return reduce(lambda res, x: res if res < x else x, holder) 

def rollingAverage(holder):
    return reduce(lambda res, x: res + x, holder) / len(holder)

def rollingSum(holder):
    return reduce(lambda res, x: res + x, holder)

def rollingAllPositive(holder):
    return reduce(lambda res, x: res if x > 0 else False , holder, True) 

def rollingAnyPositive(holder):
    return reduce(lambda res, x: res if res or x > 0 else False , holder, False) 

def rollingAllNegative(holder):
    return reduce(lambda res, x: res if x < 0 else False , holder, True) 

def rollingAnyNegative(holder):
    return reduce(lambda res, x: res if res or x < 0 else False , holder, False) 

def rollingAllZero(holder):
    return reduce(lambda res, x: res if x == 0 else False , holder, True) 

def rollingDeltas(holder):
    if len(holder) < 2:
        return [0]
    one = holder[:len(holder) - 1]
    two = holder[1:]
    zipped = zip(one, two)
    return [ (b - a) for (a, b) in zipped ]

m = np.array([-1, 2, 1], dtype=np.float64)
g = cv2.getGaussianKernel(3, -1, cv2.CV_64F)

def laplacian(image):
    return cv2.Laplacian(grayscale, cv2.CV_64F).var()

def modifiedLaplacian(image):
    lx = cv2.sepFilter2D(image, cv2.CV_64F, m, g)
    ly = cv2.sepFilter2D(image, cv2.CV_64F, g, m)
    fm = cv2.add(cv2.absdiff(lx, 0) , cv2.absdiff(ly, 0))
    return cv2.mean(fm)[0]

def tenengrad(image):
    gx = cv2.Sobel(image, cv2.CV_64F, 1, 0, 3)
    gy = cv2.Sobel(image, cv2.CV_64F, 0, 1, 3)
    fm = np.multiply(gx, gx) + np.multiply(gy, gy)
    return cv2.mean(fm)[0]

def normalizedGraylevelVariance(image):
    mu, sigma = cv2.meanStdDev(image)
    return (sigma * sigma / mu)[0][0]

def canny(image):
    canny = cv2.Canny(image, 255, 175)
    nonZero = cv2.countNonZero(canny) 
    return nonZero

measure2 = createBuffer(memoryLength)

startSliceX = resolutionX / 2 - windowX / 2
startSliceY = resolutionY / 2 - windowY / 2
endSliceX = startSliceX + windowX
endSliceY = startSliceY + windowY

numberOfDirectionChanges = 0
mode = 0
stepNum = 0
stepsSinceLastSwitch = 0

direction = startDirection

if startDirection == Adafruit_MotorHAT.FORWARD:
    position = 0
    myStepper.step(100, Adafruit_MotorHAT.BACKWARD, Adafruit_MotorHAT.SINGLE)
    myStepper.step(4, Adafruit_MotorHAT.FORWARD, Adafruit_MotorHAT.SINGLE)
else:
    position = maxPosition
    myStepper.step(100, Adafruit_MotorHAT.FORWARD, Adafruit_MotorHAT.SINGLE)
    myStepper.step(4, Adafruit_MotorHAT.BACKWARD, Adafruit_MotorHAT.SINGLE)

time.sleep(2)

def switchDirection():
    global direction
    global numberOfDirectionChanges
    global stepsSinceLastSwitch
    if direction == Adafruit_MotorHAT.FORWARD:
        print "Backward"
        direction = Adafruit_MotorHAT.BACKWARD
    else:
        print "Forward"
        direction = Adafruit_MotorHAT.FORWARD
    releaseTension()
    numberOfDirectionChanges += 1
    stepsSinceLastSwitch = 0    

def adjustPosition(howMuch):
    global position
    if direction == Adafruit_MotorHAT.FORWARD:
        position += howMuch
    else:
        position -= howMuch

def releaseTension():
    myStepper.oneStep(direction, Adafruit_MotorHAT.SINGLE)
    myStepper.oneStep(direction, Adafruit_MotorHAT.SINGLE)
    myStepper.oneStep(direction, Adafruit_MotorHAT.SINGLE)
    myStepper.oneStep(direction, Adafruit_MotorHAT.SINGLE)
    adjustPosition(4*8) 

def fast():
    myStepper.oneStep(direction, Adafruit_MotorHAT.SINGLE)
    adjustPosition(8) 

def slow():
    myStepper.oneStep(direction, Adafruit_MotorHAT.MICROSTEP)
    adjustPosition(1) 

verySlowCounter = 0
verySlowThreshold = 5

MODE_FAST=1
MODE_SLOW=2
MODE_VERY_SLOW=3
mode = MODE_FAST

stream = urllib.urlopen('http://127.0.0.1:8081')
jpegData = ''

def verySlow():
    global verySlowCounter
    verySlowCounter += 1
    if verySlowCounter >= verySlowThreshold:
        myStepper.oneStep(direction, Adafruit_MotorHAT.MICROSTEP)
        adjustPosition(1)
        verySlowCounter = 0 

notInFocus = True 

while (notInFocus):
    jpegData += stream.read(1024)
    jpegStart = jpegData.find('\xff\xd8')
    jpegEnd = jpegData.find('\xff\xd9')
    if jpegStart != -1 and jpegEnd != -1:
        jpg = jpegData[jpegStart:jpegEnd + 2]
        jpegData = jpegData[jpegEnd + 2:]
        image = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
        smaller = image[startSliceY:endSliceY, startSliceX:endSliceX, :]
        grayscale = cv2.cvtColor(smaller, cv2.COLOR_BGR2GRAY)
        addSample(tenengrad(grayscale), measure2)
        deltas2 = rollingDeltas(measure2)
        accel2 = rollingDeltas( deltas2 )
        max2 = rollingMax(measure2)

        print "Focus: {:.0f},{:.0f},{:.0f},{:.0f},{:.0f},{:.0f},{:.0f},{:.0f},{:.0f}".format( measure2[-1],  deltas2[-1], accel2[-1], max2, position, mode, direction, fastMinDifference,slowMinDifference)

        # check if we reached max focus

        if mode == MODE_VERY_SLOW and stepsSinceLastSwitch >= minSlowStepsBeforeFocus and measure2[-1] >= max2*0.99:
            notInFocus = False 
            if direction == Adafruit_MotorHAT.FORWARD:
                direction = Adafruit_MotorHAT.BACKWARD
            else:
                direction = Adafruit_MotorHAT.FORWARD
            myStepper.oneStep(direction, Adafruit_MotorHAT.SINGLE)
            myStepper.oneStep(direction, Adafruit_MotorHAT.SINGLE)
            break

        if mode == MODE_FAST:
            if stepsSinceLastSwitch >= 2*fastFocusWindow and rollingSum( deltas2[-(2*fastFocusWindow):-fastFocusWindow] ) > fastMinDifference and rollingSum( deltas2[-fastFocusWindow:] ) < fastMinDifference: 
                switchDirection()
            else:
                if numberOfDirectionChanges < numOfFastScans:
                    fast()
                else:
                    mode = MODE_SLOW
        elif mode == MODE_SLOW:
            if stepsSinceLastSwitch >= 2*slowFocusWindow and rollingSum( deltas2[-(2*slowFocusWindow):-slowFocusWindow] ) > slowMinDifference and rollingSum( deltas2[-slowFocusWindow:] ) < slowMinDifference: 
                switchDirection()
            elif stepsSinceLastSwitch >= min( memoryLength, maxPosition) and rollingAverage( deltas2 ) < 0: 
                slowMinDifference = rollingMax( deltas2 )
                switchDirection()
            else:
                if numberOfDirectionChanges < numOfSlowScans + numOfFastScans:
                    slow()
                else:
                    mode = MODE_VERY_SLOW
        else:
            if stepsSinceLastSwitch >= verySlowFocusWindow and rollingSum( deltas2[-verySlowFocusWindow:] ) < 0 and rollingSum( deltas2[-verySlowFocusWindow/2:] ) < 0 and rollingSum( deltas2[-verySlowFocusWindow/3:] ) < 0: 
                switchDirection()
            else:
                verySlow()

        stepNum = stepNum + 1
        stepsSinceLastSwitch = stepsSinceLastSwitch + 1

        if position > maxPosition or position < 0:
            switchDirection()
            if mode == MODE_FAST:
                fastMinDifference = min( fastMinDifference, rollingMax( deltas2 ) )
            else:
               slowMinDifference = min( slowMinDifference, rollingMax( deltas2 ) )

 

Marek Cyzio Opublikowane przez: