Image en chaîne devant apparaître en Projection cylindrique

je veux déformer une image plate d'une manière qui semble être la projection provenant d'un cylindre.

j'ai une image plate comme celle-ci:

I have this flat image

et je veux le montrer comme quelque chose comme ceci en image 2D:

output image should be something like this

je suis un peu en retrait sur les projections géométriques. J'ai visité quelques autres questions comme ce mais je ne comprends pas comment vais-je représenter ces coordonnées cylindriques (thêta et rho) en coordonnées x,y dans le plan cartésien (x,y). Vous pouvez m'aider avec un exemple élaboré? Je le code pour iPhone et je n'utilise aucune bibliothèque tierce partie comme OpenCV etc.

Merci beaucoup.

25
demandé sur Community 2012-08-18 14:25:41

1 réponses

C'est une réponse en 2 parties, le calcul et le code

Mathématiques

j'aime ce problème parce que la projection impliquée est intéressante mais les maths peuvent encore être résolues à la main sans trop de difficulté. Pour commencer, il est important de comprendre pourquoi exactement l'image se déforme comme elle le fait. Disons que nous avons une image plate avec un cylindre concave assis en face de lui.

Demo Image 1

la première étape consiste à réaliser une projection orthographique déplaçant l'image sur la surface courbée.

Demo Image 2

ensuite ces points sont projetés avec perspective sur le plan de l'image. Notez dans ce cas que l'image entière rétrécit parce que toutes les parties du cylindre avaient une plus grande coordonnée z que le plan d'image. Dans votre cas, le cylindre touche le plan de l'image à gauche et à droite de sorte qu'aucun rétrécissement produire. Quand les points sont projetés en arrière remarquent qu'ils ne forment plus une ligne plate sur le plan de l'image, il y a une courbe en raison de la coordonnée z du cylindre variant avec X.

Demo Image 3

le premier truc est que nous voulons en fait représenter ce processus à l'envers. Vous pourriez d'abord penser que vous voulez prendre chaque pixel dans votre image originale et le déplacer dans votre nouvelle image. Cela fonctionne beaucoup mieux si vous cochez pour chaque pixel de votre image où il est apparu dans l'ancienne image et définir sa couleur. Cela signifie que vous avez besoin de faire 3 choses.

  1. Paramétrez votre cylindre
  2. projeter un rayon de la caméra, en passant par chaque point de la nouvelle image et trouver ses coordonnées x, y, z sur le cylindre
  3. utilisez une projection orthographique pour ramener ce rayon dans le plan de l'image (cela signifie simplement laisser tomber la composante z)

le suivi de tout peut être un peu délicat, alors je vais essayer d'utiliser une terminologie cohérente. Tout d'abord, je suppose que vous voulez garantir que votre cylindre touche votre image sur les bords. Si cela est vrai, alors les 2 paramètres gratuits que vous pouvez choisir sont le rayon du cylindre et la distance focale.

L'équation d'un cercle dans le plan zx est

x^2+(z-z0)^2 = r^2

, en supposant que le centre du cercle se trouve sur l'axe des z. Si le bord du cylindre va toucher le bord du plan d'image qui a une largeur w et une distance focale f alors

omega^2+(f-z0)^2 = r^2 //define omega = width/2, it cleans it up a bit
z0 = f-sqrt(r^2-omega^2)

maintenant nous connaissons tous les paramètres du cylindre nous passons à l'étape 2, projetons des lignes de la caméra, à travers le plan d'image à xim vers le cylindre à xc. Voici un schéma rapide de la terminologie.

Demo4

nous connaissons la ligne que nous projetons part de l'origine et traverse le plan de l'image à xim. Nous pouvons écrire son équation comme

x = xim*z/f

puisque nous voulons la coordonnée x quand elle passe à travers le cylindre combiner les équations

xim^2*z^2/f^2 + z^2 - 2*z*z0 +z0^2 - r^2 = 0

vous pouvez utiliser l'équation quadratique pour résoudre pour z et ensuite se brancher de nouveau dans l'équation linéaire pour obtenir X. Les deux solutions correspondent aux deux endroits où la ligne touche le cercle, puisque nous nous intéressons seulement à celui qui se produit après le plan d'image, et que l'on aura toujours une plus grande coordonnée x, utilisez-b + sqrt(...). Puis

xc = xim*z/f;
yc = yim*z/f;

la dernière étape de la suppression de la projection orthographique est facile il suffit de laisser tomber le composant z et vous avez terminé.

Code

je sais que vous avez dit que vous n'utilisiez pas openCV mais je vais l'utiliser dans ma démonstration comme un conteneur d'image. Toutes les opérations sont effectuées sur un pixel par pixel, donc il ne devrait pas être difficile pour vous de convertir ceci pour travailler sur n'importe quel conteneur d'image que vous utilisez. J'ai d'abord fait une fonction qui convertit les coordonnées de l'image dans l'image finale de coordonnées dans l'image d'origine. OpenCV place son image d'origine en haut à gauche, c'est pourquoi je commence par soustraire w/2 et h/2 et je termine en les rajoutant dans

cv::Point2f convert_pt(cv::Point2f point,int w,int h)
{
//center the point at 0,0
cv::Point2f pc(point.x-w/2,point.y-h/2);

//these are your free parameters
float f = w;
float r = w;

float omega = w/2;
float z0 = f - sqrt(r*r-omega*omega);

float zc = (2*z0+sqrt(4*z0*z0-4*(pc.x*pc.x/(f*f)+1)*(z0*z0-r*r)))/(2* (pc.x*pc.x/(f*f)+1)); 
cv::Point2f final_point(pc.x*zc/f,pc.y*zc/f);
final_point.x += w/2;
final_point.y += h/2;
return final_point;
}

maintenant tout ce qui reste est d'échantillonner chaque point dans la nouvelle image à l'ancienne. Il y a plusieurs façons de faire cela et je fais le plus simple une que je connais ici, une interpolation bilinéaire. En outre, cela est seulement mis en place pour travailler sur une échelle de gris, ce qui fonctionne sur la couleur est simple il suffit d'appliquer le processus à tous les 3 canaux. J'ai juste pensé que ce serait un peu plus clair de cette façon.

for(int y = 0; y < height; y++)
{
    for(int x = 0; x < width; x++)
    {
        cv::Point2f current_pos(x,y);
        current_pos = convert_pt(current_pos, width, height);

        cv::Point2i top_left((int)current_pos.x,(int)current_pos.y); //top left because of integer rounding

        //make sure the point is actually inside the original image
        if(top_left.x < 0 ||
           top_left.x > width-2 ||
           top_left.y < 0 ||
           top_left.y > height-2)
        {
            continue;
        }

        //bilinear interpolation
        float dx = current_pos.x-top_left.x;
        float dy = current_pos.y-top_left.y;

        float weight_tl = (1.0 - dx) * (1.0 - dy);
        float weight_tr = (dx)       * (1.0 - dy);
        float weight_bl = (1.0 - dx) * (dy);
        float weight_br = (dx)       * (dy);

        uchar value =   weight_tl * image.at<uchar>(top_left) +
        weight_tr * image.at<uchar>(top_left.y,top_left.x+1) +
        weight_bl * image.at<uchar>(top_left.y+1,top_left.x) +
        weight_br * image.at<uchar>(top_left.y+1,top_left.x+1);

        dest_im.at<uchar>(y,x) = value;
    }
}

voici un exemple de sortie pour f = w / 2 et r = W.

enter image description here

60
répondu Hammer 2012-08-22 16:23:05