web-dev-qa-db-fra.com

Calcul des normales de surface à partir d'une image de profondeur à l'aide d'un produit croisé de pixels voisins

Comme le titre l'indique, je veux calculer les normales de surface d'une image de profondeur donnée en utilisant le produit croisé des pixels voisins. J'aimerais utiliser Opencv pour cela et éviter d'utiliser PCL cependant, je ne comprends pas vraiment la procédure, car mes connaissances sont assez limitées dans le sujet. Par conséquent, je serais reconnaissant si quelqu'un pouvait fournir des conseils. Pour mentionner ici que je n'ai pas d'autres informations que l'image de profondeur et l'image RVB correspondante, donc pas d'informations sur la matrice de la caméra K.

Ainsi, disons que nous avons l'image de profondeur suivante:

enter image description here

et je veux trouver le vecteur normal à un point correspondant avec une valeur de profondeur correspondante comme dans l'image suivante:

enter image description here

Comment puis-je faire cela en utilisant le produit croisé des pixels voisins? Cela ne me dérange pas si les normales ne sont pas très précises.

Merci.


Mise à jour:

Ok, j'essayais de suivre la réponse de @ timday et de porter son code sur Opencv. Avec le code suivant:

Mat depth = <my_depth_image> of type CV_32FC1
Mat normals(depth.size(), CV_32FC3);

for(int x = 0; x < depth.rows; ++x)
{
    for(int y = 0; y < depth.cols; ++y)
    {

        float dzdx = (depth.at<float>(x+1, y) - depth.at<float>(x-1, y)) / 2.0;
        float dzdy = (depth.at<float>(x, y+1) - depth.at<float>(x, y-1)) / 2.0;

        Vec3f d(-dzdx, -dzdy, 1.0f);
        Vec3f n = normalize(d);

        normals.at<Vec3f>(x, y) = n;
    }
}

imshow("depth", depth / 255);
imshow("normals", normals);

J'obtiens le bon résultat suivant (j'ai dû remplacer double par float et Vecd en Vecf, je ne sais pas pourquoi cela ferait une différence bien que):

enter image description here

23
ThT

Vous n'avez pas vraiment besoin d'utiliser le produit croisé pour cela, mais voyez ci-dessous.

Considérez que votre image de plage est une fonction z (x, y).

La normale à la surface est dans la direction (-dz/dx, -dz/dy, 1). (Où par dz/dx, je veux dire le différentiel: le taux de changement de z avec x). Et puis les normales sont classiquement normalisées à la longueur unitaire.

Soit dit en passant, si vous vous demandez d'où vient (-dz/dx, -dz/dy, 1) ... si vous prenez les 2 vecteurs tangents orthogonaux dans le plan parallèle aux axes x et y, ce sont (1 , 0, dzdx) et (0,1, dzdy). La normale est perpendiculaire aux tangentes, elle devrait donc être (1,0, dzdx) X (0,1, dzdy) - où 'X' est un produit croisé - qui est (-dzdx, -dzdy, 1). Il y a donc votre produit dérivé normal normal, mais il n'est pas nécessaire de le calculer si explicitement dans le code lorsque vous pouvez simplement utiliser l'expression résultante pour la normale directement.

Un pseudocode pour calculer une normale de longueur unitaire en (x, y) serait quelque chose comme

dzdx=(z(x+1,y)-z(x-1,y))/2.0;
dzdy=(z(x,y+1)-z(x,y-1))/2.0;
direction=(-dzdx,-dzdy,1.0)
magnitude=sqrt(direction.x**2 + direction.y**2 + direction.z**2)
normal=direction/magnitude

Selon ce que vous essayez de faire, il peut être plus judicieux de remplacer les valeurs NaN par un nombre important.

En utilisant cette approche, à partir de votre image de plage, je peux obtenir ceci:

enter image description here

(J'utilise ensuite les directions normales calculées pour faire un ombrage simple; notez l'aspect "steppy" en raison de la quantification de l'image de la plage; idéalement, vous auriez une précision supérieure à 8 bits pour les données de la plage réelle).

Désolé, pas le code OpenCV ou C++, mais juste pour être complet: le code complet qui a produit cette image (GLSL incorporé dans un fichier Qt QML; peut être exécuté avec la qmlscene de Qt5) est ci-dessous. Le pseudocode ci-dessus peut être trouvé dans la fonction main() du shader de fragment:

import QtQuick 2.2

Image {
  source: 'range.png'  // The provided image

  ShaderEffect {
    anchors.fill: parent
    blending: false

    property real dx: 1.0/parent.width
    property real dy: 1.0/parent.height
    property variant src: parent

    vertexShader: "
      uniform highp mat4 qt_Matrix;
      attribute highp vec4 qt_Vertex;
      attribute highp vec2 qt_MultiTexCoord0;
      varying highp vec2 coord;
      void main() {
        coord=qt_MultiTexCoord0;
        gl_Position=qt_Matrix*qt_Vertex;
      }"

   fragmentShader: "
     uniform highp float dx;
     uniform highp float dy;
     varying highp vec2 coord;
     uniform sampler2D src;
     void main() {
       highp float dzdx=( texture2D(src,coord+vec2(dx,0.0)).x - texture2D(src,coord+vec2(-dx,0.0)).x )/(2.0*dx);
       highp float dzdy=( texture2D(src,coord+vec2(0.0,dy)).x - texture2D(src,coord+vec2(0.0,-dy)).x )/(2.0*dy);
       highp vec3 d=vec3(-dzdx,-dzdy,1.0);
       highp vec3 n=normalize(d);
       highp vec3 lightDirection=vec3(1.0,-2.0,3.0);
       highp float shading=0.5+0.5*dot(n,normalize(lightDirection));
       gl_FragColor=vec4(shading,shading,shading,1.0);
     }"
  }
}
24
timday