56 votes

corriger la distorsion fisheye par programmation

BOUNTY MISE À JOUR DE STATUT:

J'ai découvert comment mapper un linéaire de la lentille, à partir de coordonnées destination de coordonnées source.

  1. J'ai fait de la lutte pour l'inverser, et à la carte coordonnées source à destination des coordonnées. Qu'est-ce que l'inverse, dans le code, dans le style des fonctions de conversion que j'ai posté?
  2. Je vois aussi que mon undistortion est imparfaite sur certains objectifs - sans doute à ceux qui ne sont pas strictement linéaire. Quel est l'équivalent d'-et-à partir de la source et de la destination des coordonnées pour les lentilles? Encore une fois, que le code juste de formules mathématiques s'il vous plaît!

Question initialement déclaré:

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

Je veux convertir ces points de coordonnées rectilignes. Je veux undistort l'image.

J'ai trouvé cette description de la façon de générer un effet fisheye, mais pas comment inverser la tendance.

Il y a aussi un blog qui explique comment utiliser les outils pour le faire; ce sont des photos de ce que:

Fisheye distorted image

Source de l'image, prise avec un objectif fisheye

Corrected image

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

Comment calculer la distance radiale à partir du centre pour aller de fisheye pour 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;
}

Sinon, j'arrivais à les convertir à l'image de fisheye pour rectiligne avant de trouver les points, mais je suis complètement déconcerté par l' utilisation d'OpenCV documentation. Est-il un moyen simple de le faire dans OpenCV, et le fait d'effectuer assez bien pour le faire à un flux vidéo en direct?

34voto

jmbr Points 2393

La description que vous mentionnez états que la projection par un trou d'épingle de la caméra (qui n'introduit pas de distorsion de l'objectif) est modélisé par

R_u = f*tan(theta)

et la projection d'un commun objectif fisheye caméras (qui est, déformé) est modélisé par

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

Vous savez déjà R_d et thêta et si vous connaissais l'appareil photo de la longueur focale (représenté par f) la correction de l'image serait à l'informatique 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 cherchez. L'estimation de la distance focale f peut être résolu par l'étalonnage de la caméra ou par d'autres moyens tels que la possibilité pour l'utilisateur de fournir de la rétroaction sur la façon dont l'image est corrigée ou à l'aide de connaissances à partir de la scène d'origine.

Afin de résoudre le même problème en utilisant OpenCV, vous devez obtenir l'appareil photo, les paramètres intrinsèques et de la distorsion de l'objectif coefficients. Voir, par exemple, le Chapitre 11 de l'Apprentissage OpenCV (n'oubliez pas de vérifier la correction). Ensuite, vous pouvez utiliser un programme comme celui-ci (écrit avec les liaisons Python pour OpenCV) dans le but 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 __name__ == '__main__':
    main(sys.argv)

Notez également que OpenCV utilise très différente de la distorsion de l'objectif du modèle à la une de la page web lié à.

9voto

Will Points 30630

(Affiche originale, offrant une solution de rechange)

La fonction suivante cartes de destination (rectiligne) en coordonnées source (fisheye-déformée) coordonnées. (Je serais heureux d'aider à inverser)

Je suis arrivé à ce point par essai-et-erreur: je n'ai pas fondamentalement comprendre pourquoi ce code fonctionne, des explications et de l'amélioration de la précision apprécié!

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, elle a réussi à undistorts les images utilisées comme exemples (je n'ai fait aucune tentative de la qualité de l'interpolation):

wait - it takes a very long time to load from the free image hosting!

(Et c'est à partir de l'article du blog, pour la comparaison:)

Using Panotools

4voto

comingstorm Points 11392

Si vous pensez que vos formules sont exactes, vous pouvez comput une formule exacte avec la trigo, comme suit:

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 @jmbr dit, la caméra de distorsion dépend de l'objectif et le zoom. Plutôt que de compter sur une formule fixe, vous voudrez peut-être essayer un polynôme d'extension:

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

En jouant d'abord Un, puis de l'ordre supérieur des coefficients, vous pouvez calculer raisonnable de fonction locale (la forme de l'expansion prend avantage de la symétrie du problème). En particulier, il devrait être possible de calculer initiale coefficients de l'approximation de l'théorique de la fonction ci-dessus.

Aussi, pour obtenir de bons résultats, vous aurez besoin d'utiliser un filtre d'interpolation pour générer votre image corrigée. Aussi longtemps que la distorsion n'est pas trop grande, vous pouvez utiliser le type de filtre que vous voudriez utiliser pour redimensionner l'image de façon linéaire sans trop de problème.

Edit: selon votre demande, l'équivalent facteur d'échelle 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 aux côtés de tan(Rin/f), vous pouvez voir qu'ils sont très similaires dans leur forme. Fondamentalement, la distorsion de la tangente devient sévère avant le péché(w) devient très différent de w.

La formule d'inversion doit être quelque chose comme:

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

3voto

Barry Vant-Hull Points 21

J'ai pris ce que JMBR fait et fondamentalement renversée. Il prit le rayon de la déformation de l'image (Rd, qui est, la distance en pixels depuis le centre de l'image) et a trouvé une formule pour le Ru, le rayon de la distortion de l'image.

Vous voulez aller dans l'autre sens. Pour chaque pixel de la non faussée (image traitée), vous voulez savoir ce que le pixel correspondant est dans la déformation de l'image. En d'autres termes, compte tenu de (xu, yu) --> (xd, yd). Vous replacez ensuite chaque pixel dans une image avec son pixel correspondant de l'image déformée.

À partir du moment où JMBR fait, je fais l'inverse, de trouver des Rd en fonction de Ru. J'obtiens:

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

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

La longueur focale de mon appareil photo était de 2,5 mm. La taille de chaque pixel sur mon CCD de 6 um carré. f est donc 2500/6 = 417 pixels. Ce qui peut être trouvé par essai et erreur.

Trouver Rd vous permet de trouver le pixel correspondant dans l'image déformée à l'aide des coordonnées polaires.

L'angle de chaque pixel à partir du point central est le même:

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

Ensuite,

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

Assurez-vous que vous savez quel quadrant vous êtes dans.

Voici le code C#, 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;
      }
}

3voto

Roman Zenka Points 1327

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

Utilisez 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
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X