web-dev-qa-db-fra.com

correction de la distorsion fisheye par programme

MISE À JOUR DU STATUT DE BOUNTY:

j'ai découvert comment cartographier une lentille linéaire, des coordonnées destination aux coordonnées source .

Comment calculez-vous la distance radiale du centre pour aller du fisheye au rectiligne?

  • 1). J'ai du mal à l'inverser et à mapper les coordonnées source aux coordonnées de destination. Quel est l'inverse, dans le code dans le style des fonctions de conversion que j'ai publiées? 

  • 2). Je constate également que ma distorsion est imparfaite sur certains objectifs - probablement ceux qui ne sont pas strictement linéaires. Quelle est l'équivalent des coordonnées source et destination de ces lentilles? Encore une fois, plus de code que de simples formules mathématiques s'il vous plaît ... 


Question comme indiqué à l'origine:

J'ai quelques points qui décrivent les positions sur une photo prise avec un objectif fisheye.

Je veux convertir ces points en coordonnées rectilignes. Je veux dénaturer l'image.

J'ai trouvé cette description comment générer un effet fisheye, mais pas comment l'inverser.

Il y a aussi un article de blog qui décrit comment utiliser les outils pour le faire; ces images en sont issues:

(1) : SOURCELien photo d'origine

Entrée: Image originale avec une distorsion fish-eye à corriger.

(2) : DESTINATIONLien photo d'origine

Sortie: Image corrigée (techniquement aussi avec correction de perspective, mais c'est une étape distincte).

Comment calculez-vous la distance radiale du centre pour aller du fisheye au rectiligne?

Mon stub de fonction ressemble à ceci:

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;
}

Alternativement, je pourrais en quelque sorte convertir l'image de fisheye en rectiligne avant de trouver les points, mais je suis complètement dérouté par le documentation OpenCV . Existe-t-il un moyen simple de le faire dans OpenCV et fonctionne-t-il suffisamment bien pour le faire dans un flux vidéo en direct?

49
Will

Le description que vous mentionnez indique que la projection par une caméra à trou d'épingle (qui n'introduit pas de distorsion de l'objectif) est modélisée par

R_u = f*tan(theta)

et la projection par des caméras à objectif fisheye courantes (c'est-à-dire déformées) est modélisée par

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

Vous connaissez déjà R_d et thêta et si vous connaissiez la distance focale de la caméra (représentée par f), alors corriger l'image reviendrait à calculer R_u en termes de R_d et thêta. En d'autres termes,

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

est la formule que vous recherchez. L'estimation de la distance focale f peut être résolue en étalonnant l'appareil photo ou d'autres moyens tels que laisser l'utilisateur fournir des informations sur la correction de l'image ou en utilisant les connaissances de la scène d'origine.

Afin de résoudre le même problème en utilisant OpenCV, vous devez obtenir les paramètres intrinsèques de l'appareil photo et les coefficients de distorsion de l'objectif. Voir, par exemple, le chapitre 11 de Learning OpenCV (n'oubliez pas de vérifier correction ). Ensuite, vous pouvez utiliser un programme tel que celui-ci (écrit avec les liaisons Python pour OpenCV) afin d'inverser la distorsion de l'objectif:

#!/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 __== '__main__':
    main(sys.argv)

Notez également qu'OpenCV utilise un modèle de distorsion d'objectif très différent de celui de la page Web à laquelle vous êtes lié.

33
jmbr

(Affiche originale, fournissant une alternative)

La fonction suivante mappe les coordonnées de destination (rectilignes) aux coordonnées source (distordues par le fisheye). (J'apprécierais de l'aide pour l'inverser)

Je suis arrivé à ce point par essais et erreurs: je ne comprends pas fondamentalement pourquoi ce code fonctionne, explications et précision améliorée appréciées !

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)))

Lorsqu'il est utilisé avec un facteur de 3,0, il déforme avec succès les images utilisées comme exemples (je n'ai fait aucune tentative d'interpolation de qualité):

Lien mort

(Et cela vient du blog, à titre de comparaison :)

Using Panotools

8
Will

Si vous pensez que vos formules sont exactes, vous pouvez calculer une formule exacte avec trig, comme ceci:

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

Cependant, comme le dit @jmbr, la distorsion réelle de l'appareil photo dépendra de l'objectif et du zoom. Plutôt que de compter sur une formule fixe, vous voudrez peut-être essayer une expansion polynomiale:

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

En ajustant d'abord A, puis les coefficients d'ordre supérieur, vous pouvez calculer n'importe quelle fonction locale raisonnable (la forme de l'expansion profite de la symétrie du problème). En particulier, il devrait être possible de calculer les coefficients initiaux pour approximer la fonction théorique ci-dessus.

De plus, pour de bons résultats, vous devrez utiliser un filtre d'interpolation pour générer votre image corrigée. Tant que la distorsion n'est pas trop importante, vous pouvez utiliser le type de filtre que vous utiliseriez pour redimensionner l'image sans problème.

Modifier: selon votre demande, le facteur d'échelle équivalent pour la formule ci-dessus:

(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)

Si vous tracez la formule ci-dessus à côté de tan (Rin/f), vous pouvez voir qu'ils sont de forme très similaire. Fondamentalement, la distorsion de la tangente devient grave avant que le péché (w) ne devienne très différent de w.

La formule inverse devrait être quelque chose comme:

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

J'ai implémenté aveuglément les formules de ici , donc je ne peux pas garantir qu'il ferait ce dont vous avez besoin.

Utilisation auto_zoom pour obtenir la valeur du paramètre zoom.


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
Roman Zenka

J'ai pris ce que JMBR a fait et je l'ai inversé. Il a pris le rayon de l'image déformée (Rd, c'est-à-dire la distance en pixels du centre de l'image) et a trouvé une formule pour Ru, le rayon de l'image non déformée.

Vous voulez aller dans l'autre sens. Pour chaque pixel de l'image non déformée (image traitée), vous voulez savoir quel est le pixel correspondant dans l'image déformée. En d'autres termes, étant donné (xu, yu) -> (xd, yd). Vous remplacez ensuite chaque pixel de l'image non déformée par le pixel correspondant de l'image déformée.

Partant de là où JMBR a fait, je fais l'inverse, trouvant Rd en fonction de Ru. Je reçois:

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

où f est la distance focale en pixels (je l'expliquerai plus tard), et r = Ru/f.

La distance focale de mon appareil photo était de 2,5 mm. La taille de chaque pixel de mon CCD était de 6 um carré. f était donc 2500/6 = 417 pixels. Cela peut être trouvé par essais et erreurs.

Finding Rd vous permet de trouver le pixel correspondant dans l'image déformée en utilisant les coordonnées polaires.

L'angle de chaque pixel par rapport au point central est le même:

theta = arctan( (yu-yc)/(xu-xc) ) où xc, yc sont les points centraux.

Alors,

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

Assurez-vous de savoir dans quel quadrant vous vous trouvez.

Voici le code C # que j'ai utilisé

 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;
      }
}
3
Barry Vant-Hull

J'ai trouvé ce fichier pdf et j'ai prouvé que les calculs sont corrects (sauf pour la ligne vd = *xd**fv+v0 which should say vd = **yd**+fv+v0).

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

Il n'utilise pas tous les derniers coefficients d'OpenCV mais je suis sûr qu'il pourrait être adapté assez facilement.

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
Myke Smyth