Camerakalibratie en Het Halfdiagonalen Vermoeden

24
CAMERAKALIBRATIE EN HET HALFDIAGONALEN VERMOEDEN Yannis De Cleene Bart De Kleijn

description

Hoe kan je twee camera's kalibreren tegenover elkaar?

Transcript of Camerakalibratie en Het Halfdiagonalen Vermoeden

CAMERAKALIBRATIEEN HET HALFDIAGONALEN VERMOEDEN

Yannis De CleeneBart De Kleijn

Universiteit Antwerpen

Inhoudstafel

Inleiding3Homogene punten4Homografie5Stereovisie53D naar 3D projectie6Ons vermoeden9Code10Bijlage 111Bijlage 218Bronnen20

Inleiding

Vooraanstaande experts op het vlak van artificile intelligentie dachten initiel dat de kalibratie tussen cameras niet meer dan enkele matrices waren. 40 jaar later bestaat er nog steeds geen foutloze methode om een computer te laten zien. Sindsdien is er een heel nieuw onderzoeksgebied ontstaan, Computer Vision, waarin ontzettend veel nieuwe methoden en successen zijn gevonden. Ze zijn er in geslaagd om autos volledig autonoom op de baan te laten rijden door de verwerking van drie-dimensionele data uit beeldmateriaal en sensoren. Ook op het vlak van View Geometry zijn er heel wat doorbraken geweest de laatste jaren. Hier onderzoeken ze hoe het uitzicht van objecten veranderen wanneer ze van verschillende posities worden bekeken en zo de parameters van de cameras veranderen. En van de meest bekende methoden is die van de ervaren pioniers in het vak, Hartley en Zisserman. Hun techniek en alle alternatieven zijn gebaseerd op de theorie van homologe punten en homografie. Een groot struikelblok bij de berekeningen is de lange tijd om ze uit te voeren.

In het labo visie hebben we eerst onderzoek gedaan naar al die huidige methoden met hun voor- en nadelen. Vervolgens hebben we nagedacht over een nieuwe techniek en zijn de betrouwbaarheid en voorwaarden gaan testen. De techniek die we hebben onderzocht noemen we zelf het halfdiagonalen vermoeden en steunt op de projectie van een 3D object naar een 2D vlak. Dit creert een perspectief waar volgens ons parameters uit gehaald kunnen worden die ons meer kunnen vertellen over welke projectie dit perspectief tot stand is gekomen. Dit onderzoek is de eerste stap naar een methode waar de berekeningstijd dramatisch verminderd kan worden zonder veel nauwkeurigheid op te geven.

Homogene punten

Voor we de matrix met de parameters van de camera en projectie kunnen opstellen moeten we kort homogene punten bespreken. Deze cordinaten worden gebruikt om het concept van oneindig voor te kunnen stellen omdat dit niet bestaat in een Euclidisch stelsel. Men heeft ontdekt dat berekeningen binnen Projective Geometry sterk vereenvoudigt kunnen worden door een systeem te gebruiken waar oneindig kan weergegeven worden met eindige cordinaten. We leggen eerst de schrijfwijze uit en geven dan een voorbeeld van projectie.

Om een homogeen punt te specifieren heb je steeds een extra cordinaat nodig. Zo heb je er twee nodig voor een punt op een geprojecteerde lijn en drie voor een geprojecteerd vlak. Een punt (x,y,1) of (2x, 2y,2) is een combinatie van homogene cordinaten om het punt (x,y) voor te stellen op een vlak. De cartesiaanse cordinaten kunnen terug verkregen worden door de eerste twee termen te delen door de derde. We verduidelijken de projectie met een basic voorbeeld.

We hebben een homogeen punt (x,y,w) van een punt in het xy-vlak. De lijn die gevormd wordt door de oorsprong en het punt (x,y,w) snijdt het w=1 vlak in het punt (x/w,y/w,1).

De transformatie bekijkt een twee-dimensioneel homogeen punt als een punt in een drie-dimensionele ruimte en projecteert dit punt op het w=1 vlak. Dit is de basis waar stereovisie en epipolaire relatie op steunt. Het vlak w wordt het projectievlak. De resultaten op de verschillende projectievlakken zullen nodig zijn om later via de matrices de relatie te vinden tussen verschillende cameras. Door gebruik te maken van homogene punten kunnen veel voorkomende operaties zoals translatie, rotatie en projectie uitgevoerd worden als berekeningen met matrices.

Homografie

Om camerakalibratie goed te begrijpen is het belangrijk dat men homografie verstaat. Dit is de manier waarop twee cameras dezelfde beeldpunten afbeelden en de manier waarop ze tegenover elkaar opgesteld staan. De cameras kunnen worden gezien van uit een assenstelsel met de oorsprong in een vast montagepunt. Ook kunnen we de cameras voorstellen tegenover elkaar door de oorsprong van de assenstelsels in de camera-oorsprong te leggen. Door dit te doen wordt het probleem herleidt tot een transformatie tussen twee 3D assenstelsels.

Stereovisie

Computer Vision is dat domein van toegepaste wetenschappen waarin men probeert om de computer de inhoud van beeldmateriaal in de ruimte te laten interpreteren. Een camera maakt bijvoorbeeld zoals in het vorige hoofdstuk een projectie van de 3D wereld op een 2D vlak en gaat zich als het ware orinteren door de gegevens dat hij hieruit kan extraheren. Dit beeld bevat echter niet genoeg informatie om de structuur of orintatie in de omgeving te achterhalen. Als men een tweede camera plaatst en die twee fotos vergelijkt met elkaar, dan wordt dit wel mogelijk. Dit principe is analoog aan het principe waarop het menselijke dieptezicht is gebaseerd. Het spreekt natuurlijk voor zich dat de gebruikte opnametechniek een grote factor is in het orinteren van de cameras en dat dit zeer interessante toepassingen met zich mee kan brengen. Voor het proces uit te leggen gaan we er echter van uit dat de computer als input twee digitale fotos heeft die ontwikkeld zijn met dezelfde opnametechniek. Er is niet geweten waar de camera zich bevindt of wat de karakteristieken zijn van het gebruikte toestel.

Normaal gezien ligt het geprojecteerde beeld achter het brandpunt van de lens. We gaan ervan uit dat we een recente camera gebruiken en dat de projectie voor het brandpunt van dezelfde kwaliteit is. Hierdoor vergemakkelijken we het principe. We voeren een assenstelsel in met de oorsprong in het brandpunt, de X- en Y-as evenwijdig en de Z-as loodrecht met het projectievlak. Dit vlak heeft als vergelijking Z=1. We bekomen dan een projectievergelijking met b een constante, pu en P zoals op de bovenstaande foto, C de translatievector en Rt de orintatievector wanneer we de vergelijking willen uitdrukken volgens een vast stelsel.

De projectievergelijking die we bekomen moet eigenlijk nogmaal getransformeerd worden om het pixelrooster in rekening te brengen. Deze transformatie gebeurt volgens de kalibratiematrix K waarin we alle technische aspecten terug vinden van de camera. Hierdoor krijgen we de volgende vergelijking:

Hierin is p gelijk aan pu, maar dan uitgedrukt in pixelcordinaten. Het punt p is gekend en hieruit willen we het punt P berekenen. Dit wordt uitgelegd in het volgende hoofdstuk. Zowel C, R als K moeten uit de fundamentele matrix bepaald worden.

We gaan ervan uit dat we dus twee digitale fotos hebben getrokken vanuit twee verschillende posities. De eerste camera gebruiken we als oorsprong van een vast referentiestelsel. De punten p1 en p2 zijn de snijpunten tussen het punt P en de brandpunten van de cameras en dus ook de pixelcordinaten. We zien dat het punt p2 dat in het tweede beeld gelegen is en dat overeenkomt met het punt p1 in het eerste beeld, op de rechte L1 gelegen is. Die rechte wordt de epipolaire rechte van p1 genoemd. We willen de vergelijking van deze rechte vinden zodat niet het hele vlak moet doorzocht worden, maar volstaat het enkel langsheen de epipolaire rechte te zoeken. Als de twee punten bekend zijn, kan nu de fundamentele matrix F berekend worden, waarover later meer. Met deze matrix kunnen de corresponderende punten gezocht worden door correlatie.

3D naar 3D projectie

Wanneer men de locatie van de camera in de wereld wil weten kan men deze voorstellen als het assenstelsel van de camera in referentie tot het wereldassenstelsel van de ruimte waarin de camera zich begeeft. Wanneer de transformatie tussen de camera en het wereld assenstelsel gevonden is kan men met behulp van de transformatiematrix cameracordinaten naar wereldcordinaten omzetten en met behulp van de inverse transformatiematrix wereldcordinaten terug naar cameracordinaten omzetten. Dit zal later van toepassing zijn om de verticale verplaatsing in z en horizontale verplaatsing in x en y van de camera ten opzichte van de wereld te bepalen. Het wereldassenstelsel mag men ook in een andere camera leggen. De transformatiematrix wordt aangeduid met H en word took wel de homografie genoemd.

De cordinaten van een punt P voorgesteld in het assenstelsel A kan men voorstellen als het punt P in het assenstelsel B maal een rotatiematrix plus een translatievector. Wanneer we deze samenvoegen en uitbreiden tot een 4x4 matrix bekomen we de homografie tussen A en B.

In het algemene geval kan elke verzameling homogene cordinaten op deze manier worden getransformeerd tot een nieuw stel homogene cordinaten. Als we tot slot delen door het bekomen gewicht sn van elk punt bekomen we de nieuwe cordinaten in standaardvorm.

De inverse transformatiematrix bekomen we door de rotatiematrix te transponeren en de tegengestelde translatievector te roteren en tot slot deze te combineren.

Telkens wanneer men een camera gebruikt voor beeldopnames moet men goed weten welke parameters men in beschouwing moet nemen. De focale lengte en het optisch centrum wijken altijd met een bepaalde hoeveelheid af van het ideale ontwerp. Dit kan echter gecompenseerd worden wanneer we alle camera parameters kennen. De belangerijkste parameters eigen aan de camera zijn, zijn fx, fy, cx, cy en en worden de intrinsieke camera parameters genoemd. De intrinsieke parameters kunnen voor berekeningen in matrixvorm worden geschreven.

fx en fy stellen de focale lengte in de x en y richting voor met een eenheid van pixels/cm met betrekking tot de sensor. We bekomen sx en sy door de lengte van de sensor in x en y richting te delen door het aantal pixels in x en y richting. Dit geeft een maat in mm/pixel of mm aangezien pixel dimensieloos is. Formule ... geeft ons de focale lengte in x en y richting. is de skew factor en wordt ter vereenvoudiging 0 genomen.

Vervolgens vinden we de pixelcordinaten cx en cy van het optisch centrum door de oorsprong van het beeldvlak, genomen in de rechter bovenoek, te verschuiven over een aantal pixels in de x en y richting. Ideaal gezien zou het optisch centrum in het midden van het beeldvlak liggen maar dit is zelden waar. Een goede callibratie helpt ons bij het vinden van de ware cordinaten van het optisch centrum tegenover de oorsprong van het beeldvlak evenals de best passende focale lengte van de lens.

Wanneer we met de camera een beeld nemen van de wereld zal elke punt in de ruimte gepro- jecteerd worden op het 2D beeldvlak. Elk punt met cordinaten x ,y, z in de ruimte komt na deze projectie overeen met een punt i, j in het beeldvlak dat afgerond binnen de grenzen van een pixel valt.

Als we de cordinaten van alle punten van een lichaam, zoals eerder de hoeken van een kubus, in matrixvorm noteren kunnen we deze met behulp van de intrinsieke matrix en de projectie matrix afbeelden op een 2D vlak.

Of korter:

Met de beeld i,j cordinaten voor elk punt:

Op deze manier kunnen we een model projecteren met rotatieparameters x, y, z en trans- latieparameters X, Y, Z. Vervolgens kunnen we dan zoeken hoe hard deze projectie afwijkt van de werkelijkheid en de parameters aanpassen tot we de werkelijke transformatie- parameters hebben waarmee we door terugrekenen de locatie van de multicopter kennen.

Ons vermoeden

ThresholdingOm onze algoritmen te vergemakkelijken gaan we het aangeboden vierkante vlak eerst treshholden. Dit geeft een zwart-wit beeld waarin de hoeken gemakkelijk te onderscheiden zijn. In dit beeld zoeken we vervolgens via Harris detectoren of FAST corners de hoeken van het vierkant om de diagonalen en de vluchtlijnen te construeren.

Door hoeken overstaande hoeken te verbinden vinden we d diagonalen en zo ook het geprojecteerde middelpunt en door de richting van de zijden door te trekken vinden we 2 snijpunten die we vluchtpunten V1 en V2 noemen. Wanneer we beiden verbinden bekomen we een horizonlijn. Ons vermoeden was dat de normaalvector van het aangeboden vlak de horizon loodrecht snijdt. Dit is aangetoond door Inge Coudron.

Code

Het programma is geschreven in python en gaat telkens van een gekend model (vierkant) een projectie doen. Hierdoor bekomen we de cordinaten van de beeldpunten waarmee we de lengte van de halfdiagonalen en hun onderlinge verhoudingen kunnen bepalen. Tot slot wordt over verschillende parameters van de projectie getereerd en de resultaten hiervan worden in een grafiek gezet. Men kan vaststellen dat de verhoudingen bij het variren van een parameter lineair en soms kwadratisch gaan veranderen. Door met projecties van een model te werken ipv met zelf gemaakte beelden hebben we geen meetfouten en een subpixel nauwkeurigheid. We moeten echter wel rekening houden met de beperking dat men bij een rotatie van 90 rond de z-as / 180 rond x- of y-as hetzelfde beeld bekomt. We denken echter wel dat men van uit het beeld kan terugrekenen naar de projectieparameters. Als we een vierkante tegel verwarmen kan deze worden waargenomen door zowel de RGB, ToF en IR camera en kunnen zo de vectoren worden van de cameracenters tot het center van de tegel en vervolgens ook hun onderlinge homografie. De code is te vinden in bijlage 1 en de resultaten in bijlage 2.

Bijlage 1

#!/usr/bin/pythonfrom math import *import matplotlib.pyplot as pltimport numpy as npimport cv2from fProject import fProjectimport time

def projectAndMeasure(x): def findLine(pointOne, pointTwo): a = (pointTwo[1] - pointOne[1]) / (pointTwo[0] - pointOne[0]) b = pointOne[1] - (a * pointOne[0]) return (a, b)

def findIntersect(lineOne, lineTwo): x = (lineTwo[1] - lineOne[1]) / (lineOne[0] - lineTwo[0]) y = (lineOne[0] * x) + lineOne[1] return (x, y)

def findAngle(lineOne, lineTwo): A = lineOne[0] - lineTwo[0] B = sqrt(lineOne[0]**2 + 1) C = sqrt(lineTwo[0]**2 + 1)

# Cosine rule alphaRadians = acos(-((A**2 - B**2 - C**2) / (2 * B * C))) alphaDegrees = degrees(alphaRadians) return (alphaRadians, alphaDegrees)

def plotLine(image, pointOne, pointTwo): cv2.line(image,(int(pointOne[0]),int(pointOne[1])),(int(pointTwo[0]),int(pointTwo[1])),(255,0,0),1) return 1

def distance(pointOne, pointTwo): distance = sqrt((pointOne[0]-pointTwo[0])**2 + (pointOne[1]-pointTwo[1])**2) return distance

P_M = np.array([[-1,-1,1,1],[1,-1,-1,1],[0,0,0,0],[1,1,1,1]])

f=813 cx=340 cy=224 K = np.array([[f,0,cx],[0,f,cy],[0,0,1]])

y=fProject(x,P_M,K)

#print y

projectie = np.zeros((480, 640,3), np.uint8) plotLine(projectie, (y[0], y[1]), (y[2], y[3])) plotLine(projectie, (y[2], y[3]), (y[4], y[5])) plotLine(projectie, (y[4], y[5]), (y[6], y[7])) plotLine(projectie, (y[6], y[7]), (y[0], y[1])) #cv2.imshow('projectie',projectie) #cv2.waitKey(0)

West = One = y[0], y[1] North = Two = y[2], y[3] East = Three = y[4], y[5] South = Four = y[6], y[7]

''' im = cv2.imread('blob.png') imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY) ret,thresh = cv2.threshold(imgray,127,255,0) contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) cnt = contours[0] One = West = float(cnt[cnt[:,:,0].argmin()][0][0]),float(cnt[cnt[:,:,0].argmin()][0][1]) Two = East = float(cnt[cnt[:,:,0].argmax()][0][0]),float(cnt[cnt[:,:,0].argmax()][0][1]) Three = North = float(cnt[cnt[:,:,1].argmin()][0][0]),float(cnt[cnt[:,:,1].argmin()][0][1]) Four = South = float(cnt[cnt[:,:,1].argmax()][0][0]),float(cnt[cnt[:,:,1].argmax()][0][1])

#print North, East, South, West '''

lineTwoOne = findLine(Two,One) lineThreeFour = findLine(Three,Four) lineTwoFour = findLine(Two, Four) lineTwoThree = findLine(Two, Three) lineOneFour = findLine(One, Four) lineOneThree = findLine(One, Three)

intersections=[0,0,0] intersects={'middle': (0,0),'1': (0,0),'2': (0,0)}

intersections[1] = findIntersect(lineTwoOne, lineThreeFour) intersections[2] = findIntersect(lineTwoFour, lineOneThree) intersections[0] = findIntersect(lineTwoThree, lineOneFour)

i=1 for intersectPoint in intersections: #print West[0],intersectPoint[0],East[0],North[1],intersectPoint[1],South[1]

if (West[0] < intersectPoint[0] < East[0]) and (North[1] < intersectPoint[1] < South[1]): intersects['middle'] = intersectPoint else: intersects[str(i)] = intersectPoint i = i+1

horizon = findLine(intersects['1'], intersects['2'])

ricoVector = -(1 / horizon[0]) movedVector = intersects['middle'][1] - (ricoVector * intersects['middle'][0])

vectorLine = ricoVector, movedVector

vectorPoint = findIntersect(vectorLine, horizon)

# Create a black image img = np.zeros((480, 640,3), np.uint8)

# Draw a diagonal blue line with thickness of 1 px plotLine(img, One, Two) plotLine(img, One, Three) plotLine(img, One, Four) plotLine(img, Three, Two) plotLine(img, Four, Two) plotLine(img, Three, Four) plotLine(img, vectorPoint, intersects['middle'])

#cv2.imshow('image',img) #time.sleep(0.1) #cv2.destroyAllWindows()

M1 = distance(One, intersects['middle']) M2 = distance(Two, intersects['middle']) M3 = distance(Three, intersects['middle']) M4 = distance(Four, intersects['middle']) #print M1 #print M2 #print M3 #print M4 #print "-------------------------------------"

return (M1/M2,M1/M3,M1/M4,M2/M3,M2/M4,M3/M4)

class Measure: def __init__(self): self.m1 = [] self.m2 = [] self.m3 = [] self.m4 = [] self.m5 = [] self.m6 = [] self.a = []

def iterateOverXRot(self,start,end): for i in range(start,end): measurement = projectAndMeasure(np.array([pi/360*i,pi/360*30,pi/360*45,0,0,20])) self.m1.append(measurement[0]) self.m2.append(measurement[1]) self.m3.append(measurement[2]) self.m4.append(measurement[3]) self.m5.append(measurement[4]) self.m6.append(measurement[5]) self.a.append(i)

return (self.a,self.m1,self.m2,self.m3,self.m4,self.m5,self.m6)

def iterateOverYRot(self,start,end): for i in range(start,end): measurement = projectAndMeasure(np.array([pi/360*30,pi/360*i,pi/360*45,0,0,20])) self.m1.append(measurement[0]) self.m2.append(measurement[1]) self.m3.append(measurement[2]) self.m4.append(measurement[3]) self.m5.append(measurement[4]) self.m6.append(measurement[5]) self.a.append(i)

return (self.a,self.m1,self.m2,self.m3,self.m4,self.m5,self.m6)

def iterateOverXTrans(self,start,end): for i in range(start,end): measurement = projectAndMeasure(np.array([pi/360*30,pi/360*30,pi/360*45,i,0,20])) self.m1.append(measurement[0]) self.m2.append(measurement[1]) self.m3.append(measurement[2]) self.m4.append(measurement[3]) self.m5.append(measurement[4]) self.m6.append(measurement[5]) self.a.append(i)

return (self.a,self.m1,self.m2,self.m3,self.m4,self.m5,self.m6)

def iterateOverYTrans(self,start,end): for i in range(start,end): measurement = projectAndMeasure(np.array([pi/360*30,pi/360*30,pi/360*45,0,i,20])) self.m1.append(measurement[0]) self.m2.append(measurement[1]) self.m3.append(measurement[2]) self.m4.append(measurement[3]) self.m5.append(measurement[4]) self.m6.append(measurement[5]) self.a.append(i)

return (self.a,self.m1,self.m2,self.m3,self.m4,self.m5,self.m6)

def iterateOverXYTrans(self,start,end): for i in range(start,end): measurement = projectAndMeasure(np.array([pi/360*30,pi/360*30,pi/360*45,i,i,20])) self.m1.append(measurement[0]) self.m2.append(measurement[1]) self.m3.append(measurement[2]) self.m4.append(measurement[3]) self.m5.append(measurement[4]) self.m6.append(measurement[5]) self.a.append(i)

return (self.a,self.m1,self.m2,self.m3,self.m4,self.m5,self.m6)

fig1 = plt.figure()x = Measure()values = x.iterateOverXRot(20,40)

disZ = fig1.add_subplot(111)disZ.plot(values[0], values[1], 'ro', values[0], values[2], 'bo', values[0], values[3], 'go', values[0], values[4], 'r--', values[0], values[5], 'b--', values[0], values[6], 'g--')disZ.grid(True)disZ.set_xlabel('Rotation along X')disZ.set_ylabel('Relations')disZ.axis([20, 40, 0.5, 1.5])

fig2 = plt.figure()y = Measure()values = y.iterateOverYRot(20,40)

rotY = fig2.add_subplot(111)rotY.plot(values[0], values[1], 'ro', values[0], values[2], 'bo', values[0], values[3], 'go', values[0], values[4], 'r--', values[0], values[5], 'b--', values[0], values[6], 'g--')rotY.grid(True)rotY.set_xlabel('Rotation along Y')rotY.set_ylabel('Relations')rotY.axis([20, 40, 0.5, 1.5])

fig3 = plt.figure()xt = Measure()values = xt.iterateOverXTrans(0,40)

transX = fig3.add_subplot(111)transX.plot(values[0], values[1], 'ro', values[0], values[2], 'bo', values[0], values[3], 'go', values[0], values[4], 'r--', values[0], values[5], 'b--', values[0], values[6], 'g--')transX.grid(True)transX.set_xlabel('Translate along X')transX.set_ylabel('Relations')transX.axis([0, 40, 0.5, 1.5])

fig4 = plt.figure()yt = Measure()values = yt.iterateOverYTrans(-10,50)

transY = fig4.add_subplot(111)transY.plot(values[0], values[1], 'ro', values[0], values[2], 'bo', values[0], values[3], 'go', values[0], values[4], 'r--', values[0], values[5], 'b--', values[0], values[6], 'g--')transY.grid(True)transY.set_xlabel('Translate along Y')transY.set_ylabel('Relations')transY.axis([0, 40, 0.5, 1.5])

fig5 = plt.figure()xyt = Measure()values = xyt.iterateOverXYTrans(0,40)

transXY = fig5.add_subplot(111)transXY.plot(values[0], values[1], 'ro', values[0], values[2], 'bo', values[0], values[3], 'go', values[0], values[4], 'r--', values[0], values[5], 'b--', values[0], values[6], 'g--')transXY.grid(True)transXY.set_xlabel('Translate along X and Y')transXY.set_ylabel('Relations')transXY.axis([0, 40, 0.5, 1.5])

plt.show()

Bijlage 2

Bronnen

http://www.cmmc.be/reconlab/paper3dreconstructie.pdf

5