Wykrywamy ostrość

Dziś trochę więcej o wykrywaniu ostrości i moich eksperymentach z kamerką. Ale najpierw zdjęcie stanu obecnego zabawy:

img_4245

To tymczasowa konfiguracja – byłem ciekawy czy zamiast silnika krokowego dało by się użyć servo. Nie, nie da się – ma za małą dokładność by ustawić precyzyjnie ostrość – servo ma zakres ruchów +-90o i teoretycznie 450 położeń w tym zakresie. Jako że obiektyw wymaga 90o od jednego końca ostrości do drugiego to przełożenie 2:1 dało by jakieś 900 możliwych ustawień ostrości. Jednak w rzeczywistości servo ma pełno miejsc gdzie są „dziury” i rzeczywista liczba możliwych położeń jest mniejsza. Do tego dochodzi problem napędu na gumkę który powoduje że przełożenie 2:1 nie pokrywa całego zakresu ustawiania ostrości. Dlatego wrócę jednak do silników krokowych – niestety zepsułem jeden i czekam aż mi nowe z Chin przyjdą. Jednak jako że nie mam tych silniczków to postanowiłem robić eksperymenty korzystając z servo. W tym celu napisałem mały programik – na internecie znalazłem cały zestaw różnych sposobów mierzenia ostrości obrazka, zaimplementowałem pięć różnych i sprawdzałem jakie wyniki dają w czasie szukania ostrości. O tym jak działają te algorytmy nie będę pisał, bo to cały skomplikowany problem, ale dla zainteresowanych kod:

#!/usr/bin/python
from Adafruit_PWM_Servo_Driver import PWM
import time
import cv2
import urllib
import numpy as np

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, ksize):
    gx = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize)
    gy = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize)
    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

pwm = PWM(0x40)
stream = urllib.urlopen('http://127.0.0.1:8081')
jpegData = ''
servoMin = 150
servoMax = 600
pwm.setPWMFreq(10)
servo = servoMin
while (True):
 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[192:384, 341:683,:]
        grayscale = cv2.cvtColor(smaller, cv2.COLOR_BGR2GRAY)
        focusMeasure1 = laplacian(grayscale)
        focusMeasure2 = modifiedLaplacian(grayscale)
        focusMeasure3 = tenengrad(grayscale,3)
        focusMeasure4 = normalizedGraylevelVariance(grayscale)
        focusMeasure5 = canny(grayscale)
        print "Focus: {:.2f},{:.2f},{:.2f},{:.2f},{:.0f}".format(focusMeasure1, focusMeasure2, focusMeasure3, focusMeasure4, focusMeasure5)
        pwm.setPWM(0, 0, servo+5)
        time.sleep(0.05)
        pwm.setPWM(0, 0, servo)
        servo = servo+1

Produkuje on następujące wyniki (po środku był moment ostrości):

Focus: 6.25,334.66,257.09,4.21,57
Focus: 6.29,333.23,247.55,3.93,54
Focus: 6.57,330.79,246.81,3.74,50
Focus: 7.09,325.72,251.18,3.82,50
Focus: 7.06,324.92,245.72,3.81,51
Focus: 6.77,322.43,245.89,3.81,51
Focus: 6.08,322.87,241.82,3.73,47
Focus: 7.63,320.52,255.82,3.52,44
Focus: 8.09,322.99,252.84,3.18,38
Focus: 34.43,320.52,1395.14,3.37,29
Focus: 209.77,316.49,4543.62,4.38,106
Focus: 52.82,320.57,1508.88,3.57,34
Focus: 14.44,326.01,437.19,3.32,38
Focus: 5.87,327.79,223.55,3.55,46
Focus: 5.05,329.02,212.45,3.62,45
Focus: 4.85,329.17,211.92,3.86,51
Focus: 4.32,330.16,212.61,4.35,59
Focus: 4.27,324.45,214.65,4.61,60
Focus: 4.26,323.95,211.89,4.59,60
Focus: 3.97,326.88,215.01,4.75,59
Focus: 4.18,318.56,226.96,5.31,69

Jak widać nie każdy z algorytmów jest dobry a w szczególności ten „zmodyfikowany laplacian” wydaje się być nie nie wart. Ale najprostszy (laplacian) oraz dwa bardziej skomplikowane (tenegrad oraz canny) wydają się być dobre do szukania ostrości. Cóż, teraz zostaje jeszcze tylko napisać kawałek kodu który będzie umiał znaleźć najlepszą ostrość biorąc pod uwagę to że całość jest napędzana gumką – to ostatnie jest najtrudniejszym wyzwaniem.

 

Marek Cyzio Opublikowane przez: