2010-03-19 5 views
48

BOUNTY STATUS UPDATE:Korrektur Fischaugenverzeichnung programmatisch

I discovered how to map a linear lens von destination Koordinaten in Koordinaten source.

Wie berechnen Sie den radialen Abstand von der Mitte von Fisheye zu geradlinig?

  • 1). Ich habe Mühe, es umzukehren und Quellkoordinaten auf Zielkoordinaten abzubilden. Was ist der umgekehrte Code im Stil der Konvertierungsfunktionen, die ich gepostet habe?

  • 2). Ich sehe auch, dass meine Unverzerrung auf einige Linsen nicht perfekt ist - vermutlich diejenigen, die nicht streng linear sind. Was ist das Äquivalent zu Quell- und Zielkoordinaten für diese Objektive? Wieder mehr Code als nur mathematische Formeln bitte ...


Question as originally stated:

Ich habe einige Punkte, die Positionen in einem Bild mit einem Fisheye-Objektiv beschreiben.

Ich möchte diese Punkte in geradlinige Koordinaten konvertieren. Ich möchte das Bild entziffern.

Ich habe this description gefunden, wie man einen Fisheye-Effekt erzeugt, aber nicht, wie man es umkehrt.

Es gibt auch eine blog post, die beschreibt, wie man Werkzeuge dafür verwendet; diese Bilder davon:

(1): SOURCEOriginal photo link

Eingang: Originalbild mit Fischaugen-Verzerrungen zu beheben.

(2): DESTINATIONOriginal photo link

Output: Korrigierte Bild (technisch auch mit Perspektivenkorrektur, aber das ist ein separater Schritt).

Wie berechnen Sie den radialen Abstand von der Mitte von Fisheye zu geradlinig?

Meine Funktion Stub sieht wie folgt aus:

Point correct_fisheye(const Point& p,const Size& img) { 
    // to polar 
    const Point centre = {img.width/2,img.height/2}; 
    const Point rel = {p.x-centre.x,p.y-centre.y}; 
    const double theta = atan2(rel.y,rel.x); 
    double R = sqrt((rel.x*rel.x)+(rel.y*rel.y)); 
    // fisheye undistortion in here please 
    //... change R ... 
    // back to rectangular 
    const Point ret = Point(centre.x+R*cos(theta),centre.y+R*sin(theta)); 
    fprintf(stderr,"(%d,%d) in (%d,%d) = %f,%f = (%d,%d)\n",p.x,p.y,img.width,img.height,theta,R,ret.x,ret.y); 
    return ret; 
} 

Alternativ könnte ich irgendwie das Bild von Fisheye umwandeln geradlinig, bevor die Punkte zu finden, aber ich bin von der OpenCV documentation völlig benebelt. Gibt es einen einfachen Weg, dies in OpenCV zu tun, und ist es gut genug, um es zu einem Live-Video-Feed zu machen?

+0

ich nicht ganz bekommt, was du bist Auf der Suche nach. Das Fischauge bildet von einer Kugel zur Bildebene. Die umgekehrte Zuordnung wäre vom Bild zurück zu einer Kugel, oder? Nach welcher geradlinigen Koordinate suchen Sie? – mtrw

+0

@mtrw Meine Quelle Bild ist Fisheye verzerrt, und ich möchte es zu entziffern – Will

+0

So ist das Bild auf http://photo.net/learn/fisheye/ was Sie suchen? – mtrw

Antwort

29

Die description you mention besagt, dass der Vorsprung durch eine Stift-Loch-Kamera (eine, die nicht eine Linsenverzerrung einführt) durch

modelliert
R_u = f*tan(theta) 

und den Vorsprung durch gemeinsames Fischaugenobjektiv Kameras (das heißt, verzerrt) ist zur Berechnung R_u in Bezug auf R_D und Theta modellierte durch

R_d = 2*f*sin(theta/2) 

Sie wissen bereits R_D und Theta, und wenn Sie das Kamerabrennweite wissen (durch f), dann liefe das Bild zu korrigieren. Mit anderen Worten,

R_u = f*tan(2*asin(R_d/(2*f))) 

ist die Formel, die Sie suchen. Das Abschätzen der Brennweite f kann durch Kalibrieren der Kamera oder anderer Mittel, wie z. B. durch Feedback des Benutzers, wie gut das Bild korrigiert wird, oder durch Verwendung von Wissen aus der ursprünglichen Szene erfolgen.

Um das gleiche Problem mit OpenCV zu lösen, müssten Sie die intrinsischen Parameter und Linsenverzerrungskoeffizienten der Kamera ermitteln. Siehe zum Beispiel Kapitel 11 von Learning OpenCV (vergessen Sie nicht, die correction zu überprüfen). Dann können Sie ein Programm wie dieses verwenden (geschrieben mit den Python-Bindings für OpenCV), um die Linsenverzerrung zu umkehren:

#!/usr/bin/python 

# ./undistort 0_0000.jpg 1367.451167 1367.451167 0 0 -0.246065 0.193617 -0.002004 -0.002056 

import sys 
import cv 

def main(argv): 
    if len(argv) < 10: 
    print 'Usage: %s input-file fx fy cx cy k1 k2 p1 p2 output-file' % argv[0] 
    sys.exit(-1) 

    src = argv[1] 
    fx, fy, cx, cy, k1, k2, p1, p2, output = argv[2:] 

    intrinsics = cv.CreateMat(3, 3, cv.CV_64FC1) 
    cv.Zero(intrinsics) 
    intrinsics[0, 0] = float(fx) 
    intrinsics[1, 1] = float(fy) 
    intrinsics[2, 2] = 1.0 
    intrinsics[0, 2] = float(cx) 
    intrinsics[1, 2] = float(cy) 

    dist_coeffs = cv.CreateMat(1, 4, cv.CV_64FC1) 
    cv.Zero(dist_coeffs) 
    dist_coeffs[0, 0] = float(k1) 
    dist_coeffs[0, 1] = float(k2) 
    dist_coeffs[0, 2] = float(p1) 
    dist_coeffs[0, 3] = float(p2) 

    src = cv.LoadImage(src) 
    dst = cv.CreateImage(cv.GetSize(src), src.depth, src.nChannels) 
    mapx = cv.CreateImage(cv.GetSize(src), cv.IPL_DEPTH_32F, 1) 
    mapy = cv.CreateImage(cv.GetSize(src), cv.IPL_DEPTH_32F, 1) 
    cv.InitUndistortMap(intrinsics, dist_coeffs, mapx, mapy) 
    cv.Remap(src, dst, mapx, mapy, cv.CV_INTER_LINEAR + cv.CV_WARP_FILL_OUTLIERS, cv.ScalarAll(0)) 
    # cv.Undistort2(src, dst, intrinsics, dist_coeffs) 

    cv.SaveImage(output, dst) 


if __name__ == '__main__': 
    main(sys.argv) 

Beachten Sie auch, dass OpenCV verwendet eine sehr unterschiedliche Linsenverzerrung Modell der in der Bahn Seite, mit der Sie verbunden sind.

+0

Dies beruht auf Zugriff auf die betreffende Kamera. Ich weiß nicht, ich bekomme nur ein Video. Darüber hinaus kommt es mir vor, dass Kameras in Serie produziert werden und es nicht viel Abwechslung individuell sicher sein kann? Die im Originalartikel verlinkten Werkzeuge erfordern nicht, dass jemand mit einem Schachbrett vor der Kamera steht !? – Will

+2

Die Kameraparameter variieren bei der gleichen Kamera nur durch das Zoomen des Zooms. Außerdem können Sie sich auf automatische Kalibrierungstechniken verlassen, anstatt das Schachbrett zu verwenden. Wie auch immer, ich habe meine Antwort bearbeitet, um den ersten Teil Ihrer Frage zu beantworten, die Ihnen die Formel liefert, nach der Sie gesucht haben. – jmbr

+0

Danke jmbr! Die Welt fängt an, einen Sinn zu ergeben. Ich kann eigentlich R_u = f * tan (2 * asin (R_d/(2 * f))) nicht bekommen, um mir etwas anderes als NaN zu geben. Ich habe kein Wissen über Trigonometrie und dessen, dass mich das jetzt zurückhält :( – Will

3

Wenn Sie denken, Ihre Formeln genau sind, können Sie eine exakte Formel mit trig Comput, etwa so:

Rin = 2 f sin(w/2) -> sin(w/2)= Rin/2f 
Rout= f tan(w)  -> tan(w)= Rout/f 

(Rin/2f)^2 = [sin(w/2)]^2 = (1 - cos(w))/2 -> cos(w) = 1 - 2(Rin/2f)^2 
(Rout/f)^2 = [tan(w)]^2 = 1/[cos(w)]^2 - 1 

-> (Rout/f)^2 = 1/(1-2[Rin/2f]^2)^2 - 1 

Doch wie @jmbr sagt, die eigentliche Kamera Verzerrung wird auf der Linse abhängen und dem Zoom . Anstatt sich auf eine feste Formel verlassen, möchten Sie vielleicht eine Polynomausdehnung versuchen:

Rout = Rin*(1 + A*Rin^2 + B*Rin^4 + ...) 

, indem zuerst ein zwicken, dann Koeffizienten höherer Ordnung, können Sie jede vernünftige lokale Funktion berechnen (die Form der Erweiterung nutzt der Symmetrie des Problems). Insbesondere sollte es möglich sein, Anfangskoeffizienten zu berechnen, um die obige theoretische Funktion zu approximieren.

Um gute Ergebnisse zu erzielen, müssen Sie einen Interpolationsfilter verwenden, um das korrigierte Bild zu erzeugen. Solange die Verzerrung nicht zu groß ist, können Sie die Art von Filter verwenden, mit dem Sie das Bild ohne Probleme linear neu skalieren würden.

Edit: wie pro Ihren Antrag, das entspricht Skalierungsfaktor für die obige Formel:

(Rout/f)^2 = 1/(1-2[Rin/2f]^2)^2 - 1 
-> Rout/f = [Rin/f] * sqrt(1-[Rin/f]^2/4)/(1-[Rin/f]^2/2) 

Wenn Sie die obige Formel neben tan (Rin/f) plotten, können Sie sehen, dass sie sehr ähnlich sind in gestalten. Grundsätzlich wird die Verzerrung von der Tangente stark, bevor sin (w) sich stark von w unterscheidet.

Die inverse Formel so ähnlich sein sollte:

Rin/f = [Rout/f]/sqrt(sqrt(([Rout/f]^2+1) * (sqrt([Rout/f]^2+1) + 1)/2) 
7

(Originalplakat, Bereitstellung einer Alternative)

Die folgende Funktion bildet Ziel (geradliniger) Koordinaten Quelle (fisheye zerrtes) -Koordinaten. (ich würde Hilfe schätzt es bei der Umkehr)

ich durch Versuch und Irrtum zu diesem Punkt gekommen: Ich nicht grundsätzlich kein begreifen, warum dieser Code funktioniert, Erklärungen und eine verbesserte Genauigkeit geschätzt!

def dist(x,y): 
    return sqrt(x*x+y*y) 

def correct_fisheye(src_size,dest_size,dx,dy,factor): 
    """ returns a tuple of source coordinates (sx,sy) 
     (note: values can be out of range)""" 
    # convert dx,dy to relative coordinates 
    rx, ry = dx-(dest_size[0]/2), dy-(dest_size[1]/2) 
    # calc theta 
    r = dist(rx,ry)/(dist(src_size[0],src_size[1])/factor) 
    if 0==r: 
     theta = 1.0 
    else: 
     theta = atan(r)/r 
    # back to absolute coordinates 
    sx, sy = (src_size[0]/2)+theta*rx, (src_size[1]/2)+theta*ry 
    # done 
    return (int(round(sx)),int(round(sy))) 

Wenn es mit einem Faktor von 3,0 verwendet, ist es die Bilder erfolgreich undistorts als Beispiele (I machte keinen Versuch, auf Qualität Interpolation):

Toten Link

(Und das ist aus die Blog-Post, zum Vergleich :)

Using Panotools

+0

Der Grund, warum Ihr Code funktioniert, ist, dass Sie (rx, ry) um einen Faktor theta skalieren (was jetzt ein Verhältnis ist, kein Winkel) Wiki-Artikel sagt) ein "lineares Mapping" vom Betrachtungswinkel zum Bildoffset, ich glaube, dass der atan (r)/r korrekt ist. – comingstorm

+1

Die Rückseite Ihres Mappings sollte um einen Faktor von tan (r ')/r' skalieren , wo r 'ist der Radius von der Mitte des unverzerrten Bildes – comingstorm

+1

Und der Grund für beide dieser Arbeit ist, dass, wenn Sie Vektor v' = v * k (| v |) haben, und Sie wollen | v '| = f (| v |), nehmen Sie den absoluten Wert der ersten Gleichung: | v '| = | v | * k (| v |) = f (| v |) - so dass k (| v |) = f (| v |)/| v | – comingstorm

2

ich blind impl emented die Formeln von here, so kann ich nicht garantieren, dass es tun würde, was Sie brauchen.

Verwenden Sie auto_zoom, um den Wert für den Parameter abzurufen.


def dist(x,y): 
    return sqrt(x*x+y*y) 

def fisheye_to_rectilinear(src_size,dest_size,sx,sy,crop_factor,zoom): 
    """ returns a tuple of dest coordinates (dx,dy) 
     (note: values can be out of range) 
crop_factor is ratio of sphere diameter to diagonal of the source image""" 
    # convert sx,sy to relative coordinates 
    rx, ry = sx-(src_size[0]/2), sy-(src_size[1]/2) 
    r = dist(rx,ry) 

    # focal distance = radius of the sphere 
    pi = 3.1415926535 
    f = dist(src_size[0],src_size[1])*factor/pi 

    # calc theta 1) linear mapping (older Nikon) 
    theta = r/f 

    # calc theta 2) nonlinear mapping 
    # theta = asin (r/(2 * f)) * 2 

    # calc new radius 
    nr = tan(theta) * zoom 

    # back to absolute coordinates 
    dx, dy = (dest_size[0]/2)+rx/r*nr, (dest_size[1]/2)+ry/r*nr 
    # done 
    return (int(round(dx)),int(round(dy))) 


def fisheye_auto_zoom(src_size,dest_size,crop_factor): 
    """ calculate zoom such that left edge of source image matches left edge of dest image """ 
    # Try to see what happens with zoom=1 
    dx, dy = fisheye_to_rectilinear(src_size, dest_size, 0, src_size[1]/2, crop_factor, 1) 

    # Calculate zoom so the result is what we wanted 
    obtained_r = dest_size[0]/2 - dx 
    required_r = dest_size[0]/2 
    zoom = required_r/obtained_r 
    return zoom 
3

fand ich diese PDF-Datei und ich habe bewiesen, dass die Mathematik richtig ist (mit Ausnahme der Linie vd = *xd**fv+v0 which should say vd = **yd**+fv+v0).

http://perception.inrialpes.fr/CAVA_Dataset/Site/files/Calibration_OpenCV.pdf

Es verwendet nicht alle aktuellen Koeffizienten, die OpenCV zur Verfügung hat, aber ich bin sicher, dass es ziemlich leicht angepasst werden könnte.

double k1 = cameraIntrinsic.distortion[0]; 
double k2 = cameraIntrinsic.distortion[1]; 
double p1 = cameraIntrinsic.distortion[2]; 
double p2 = cameraIntrinsic.distortion[3]; 
double k3 = cameraIntrinsic.distortion[4]; 
double fu = cameraIntrinsic.focalLength[0]; 
double fv = cameraIntrinsic.focalLength[1]; 
double u0 = cameraIntrinsic.principalPoint[0]; 
double v0 = cameraIntrinsic.principalPoint[1]; 
double u, v; 


u = thisPoint->x; // the undistorted point 
v = thisPoint->y; 
double x = (u - u0)/fu; 
double y = (v - v0)/fv; 

double r2 = (x*x) + (y*y); 
double r4 = r2*r2; 

double cDist = 1 + (k1*r2) + (k2*r4); 
double xr = x*cDist; 
double yr = y*cDist; 

double a1 = 2*x*y; 
double a2 = r2 + (2*(x*x)); 
double a3 = r2 + (2*(y*y)); 

double dx = (a1*p1) + (a2*p2); 
double dy = (a3*p1) + (a1*p2); 

double xd = xr + dx; 
double yd = yr + dy; 

double ud = (xd*fu) + u0; 
double vd = (yd*fv) + v0; 

thisPoint->x = ud; // the distorted point 
thisPoint->y = vd; 
3

Ich nahm, was JMBR tat und im Grunde umgekehrt. Er nahm den Radius des verzerrten Bildes (Rd, dh den Abstand in Pixeln von der Mitte des Bildes) und fand eine Formel für Ru, den Radius des unverzerrten Bildes.

Sie wollen in die andere Richtung gehen. Für jedes Pixel im unverzerrten (verarbeiteten Bild) möchten Sie wissen, was das entsprechende Pixel im verzerrten Bild ist. Mit anderen Worten, gegeben (xu, yu) -> (xd, yd). Sie ersetzen dann jedes Pixel im unverzerrten Bild durch das entsprechende Pixel aus dem verzerrten Bild.

Beginnend, wo JMBR tat, mache ich das Umgekehrte und finde Rd als eine Funktion von Ru. Ich bekomme:

Rd = f * sqrt(2) * sqrt(1 - 1/sqrt(r^2 +1)) 

wobei f die Brennweite in Pixeln (Ich werde später erklären) und r = Ru/f.

Die Brennweite für meine Kamera war 2,5 mm. Die Größe jedes Pixels auf meinem CCD betrug 6 um. f war daher 2500/6 = 417 Pixel. Dies kann durch Versuch und Irrtum gefunden werden.

Mit Finding Rd können Sie das entsprechende Pixel im verzerrten Bild mithilfe von Polarkoordinaten finden.

Der Winkel jedes Pixels von dem Mittelpunkt ist das gleiche:

theta = arctan((yu-yc)/(xu-xc)) wo xc, yc die Mittelpunkte sind.

Dann

xd = Rd * cos(theta) + xc 
yd = Rd * sin(theta) + yc 

Stellen Sie sicher wissen, welche Sie Quadranten sind.

Hier ist der C# -Code I

verwendet
public class Analyzer 
{ 
     private ArrayList mFisheyeCorrect; 
     private int mFELimit = 1500; 
     private double mScaleFESize = 0.9; 

     public Analyzer() 
     { 
      //A lookup table so we don't have to calculate Rdistorted over and over 
      //The values will be multiplied by focal length in pixels to 
      //get the Rdistorted 
      mFisheyeCorrect = new ArrayList(mFELimit); 
      //i corresponds to Rundist/focalLengthInPixels * 1000 (to get integers) 
      for (int i = 0; i < mFELimit; i++) 
      { 
       double result = Math.Sqrt(1 - 1/Math.Sqrt(1.0 + (double)i * i/1000000.0)) * 1.4142136; 
       mFisheyeCorrect.Add(result); 
      } 
     } 

     public Bitmap RemoveFisheye(ref Bitmap aImage, double aFocalLinPixels) 
     { 
      Bitmap correctedImage = new Bitmap(aImage.Width, aImage.Height); 
      //The center points of the image 
      double xc = aImage.Width/2.0; 
      double yc = aImage.Height/2.0; 
      Boolean xpos, ypos; 
      //Move through the pixels in the corrected image; 
      //set to corresponding pixels in distorted image 
      for (int i = 0; i < correctedImage.Width; i++) 
      { 
       for (int j = 0; j < correctedImage.Height; j++) 
       { 
        //which quadrant are we in? 
        xpos = i > xc; 
        ypos = j > yc; 
        //Find the distance from the center 
        double xdif = i-xc; 
        double ydif = j-yc; 
        //The distance squared 
        double Rusquare = xdif * xdif + ydif * ydif; 
        //the angle from the center 
        double theta = Math.Atan2(ydif, xdif); 
        //find index for lookup table 
        int index = (int)(Math.Sqrt(Rusquare)/aFocalLinPixels * 1000); 
        if (index >= mFELimit) index = mFELimit - 1; 
        //calculated Rdistorted 
        double Rd = aFocalLinPixels * (double)mFisheyeCorrect[index] 
             /mScaleFESize; 
        //calculate x and y distances 
        double xdelta = Math.Abs(Rd*Math.Cos(theta)); 
        double ydelta = Math.Abs(Rd * Math.Sin(theta)); 
        //convert to pixel coordinates 
        int xd = (int)(xc + (xpos ? xdelta : -xdelta)); 
        int yd = (int)(yc + (ypos ? ydelta : -ydelta)); 
        xd = Math.Max(0, Math.Min(xd, aImage.Width-1)); 
        yd = Math.Max(0, Math.Min(yd, aImage.Height-1)); 
        //set the corrected pixel value from the distorted image 
        correctedImage.SetPixel(i, j, aImage.GetPixel(xd, yd)); 
       } 
      } 
      return correctedImage; 
     } 
}