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.
- 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é?
- 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:
Source de l'image, prise avec un objectif fisheye
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?
Réponses
Trop de publicités?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é à.
(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):
(Et c'est à partir de l'article du blog, pour la comparaison:)
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 )
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;
}
}
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