Loading

Spin-Image

Spin-Image

Spin-Image(自转图像)是一种表面表示技术,主要用于三维场景下的表面匹配和目标识别。

Spin-Image使用面向对象的坐标系统对任意表面的全局属性进行编码。

Spin-Image 生成算法

  1. 定义Oriented Point: Oriented Point是带有方向的三维表面点(由曲面顶点的三维位置\(p\)和曲面法线\(n\)所组成)

  2. 以Oriented Point为轴生成一个圆柱坐标系:点\(x\)在此坐标系表示为\((\alpha,\beta)\),其中\(\alpha\)为点\(x\)到到中心轴线\(L\)的(非负)垂直距离,\(\beta\)为点\(x\)到切平面\(P\)的符号距离。

    image-20220516145444977

  3. 定义Spin-Image的参数:Spin-Image是一个具有一定大小(行列数),分辨率(二维网格大小)的二维图像。

    Spin-Image 大小(size)使用以下公式进行计算:

    \[i_{\max }=\frac{2 \beta_{\max }}{b}+1 ,\quad j_{\max }=\frac{a_{\max }}{b}+1 \]

    其中\(\alpha_{max},\beta_{max}\)为所有不同oriented point基的\(\alpha ,| \beta|\)的最大值,其中\(b\)为桶的宽度。

    分辨率,即桶的宽度,通常取网格所有边的平均值:

    \[b=r=\frac{1}{N} \sum_{i=1}^{N}\left|e_i\right| \]

  4. Snip map(\(S_0\)):将圆柱体中的三维坐标投影到二维Spin-Image之中,这一过程可以理解为一个Spin-Image绕法向量\(n\)旋转360度,扫到的三维空间上的点会落到Spin-Image,使用如下公式:

    \[\begin{gathered} S_{O}: R^{3} \rightarrow R^{2} \\ S_{O}(x) \rightarrow(a, \beta)=\left(\sqrt{\|x-p\|^{2}-\left(n \cdot(x-p))^{2}\right.}, n \cdot(x-p)\right) \end{gathered} \]

    我们仅仅需要一个oriented point即可以生成一个spin-Image,我们生成一个2维数组用来表示生成的spin-Image。为了生成spin-Image,我们将2D的点\((\alpha,\beta)\)的结果累加到离散的桶之中。

    同时我们需要考虑数据中的噪声影响,因此2-D点的贡献值通过双线性插值扩散到周围的4个桶之中。

    image-20220516153200534

    2-D点\((\alpha,\beta)\)使用下面的公式累加到离散的桶:

    \[i=\left\lfloor\frac{\beta_{\max }-\beta}{b}\right\rfloor \quad j=\left\lfloor\frac{a}{b}\right\rfloor \]

    其中,用来计算对应网格强度的双线性的权重为:

    \[a=(\beta_{\max }-\beta)-i b\ \ \ b=\alpha-j b \]

  5. 这样我们便可以获得spin-Image,如下图所示:

    image-20220516170112268

Spin-Image实现(unity)

生成图片参数

 public float CalcMeshResolution(Vector3[] vertices, int[] triangles)
 {
      float totalLength = 0;
      int verticesNums = vertices.Length;
      for (int i = 0; i < verticesNums / 3; i += 3)
      {
          Vector3 p0 = vertices[triangles[i * 3]];
          Vector3 p1 = vertices[triangles[i * 3 + 1]];
          Vector3 p2 = vertices[triangles[i * 3 + 2]];

          totalLength += (p0 - p1).magnitude;
          totalLength += (p0 - p2).magnitude;
          totalLength += (p1 - p2).magnitude;
      }
      Debug.Log("Calc Resolution Finish!");
      return totalLength / (verticesNums / 3);
 }

public void CreateSpinImageParameters(Mesh modelMesh)
{
      binSize = CalcMeshResolution(modelMesh.vertices, modelMesh.triangles);
      //calc spin-image size
      Vector3 boundMin = modelMesh.bounds.min;
      Vector3 boundMax = modelMesh.bounds.max;
      Vector3 boundVector = boundMax - boundMin;
      betaMax = Mathf.Sqrt(Vector3.Dot(boundVector, boundVector));
      alphaMax = Mathf.Max(Mathf.Max(boundVector.x, boundVector.y), boundVector.z);
      Debug.Log("CalcParameters!");
}

这里计算\(\alpha_{max}\)\(\beta_{max}\)(即图片的大小)的计算我并没有采用计算所有oriented point中的最大值;而是采取了一种粗略估计的方法,根据当前模型的包围盒大小,取包围盒的最大/最小值点的距离(一般来说可以会比上面方法计算的值大)。最后取两个值的最大值作为宽高(即等宽高)

计算网格强度(CPU)

按照上面提到的公式对对应桶的网格强度进行计算,我们将根据强度的最大值将网格强度映射到0到1,并使用灰度图进行显示。

计算权重ab的公式这里使用:

\[a=\frac{ (\beta_{\max }-\beta)}{b}-i\ , \ b=\frac{\alpha}{b}-j \]

    public void CreateSpinImageFromCPU(OrientedPoint op)
    {
        CreateSpinImageParameters(currModelMesh);
        int width = Mathf.CeilToInt((2 * betaMax) / binSize + 1);
        int height = Mathf.CeilToInt(alphaMax / binSize + 1);
        imageWidth = Mathf.Max(width,height);
        float[] idensity = new float[imageWidth * imageWidth];
        foreach (var point in currModelMesh.vertices)
        {
            //spin-map
            Vector3 xp = point - op.Position;
            float beta = Vector3.Dot(op.Normal, xp);
            float alpha = Mathf.Sqrt(Vector3.Dot(xp, xp) - Mathf.Pow(beta, 2));
            //spin-bin
            int i = Mathf.FloorToInt((betaMax - beta) / binSize);        //col
            int j = Mathf.FloorToInt(alpha / binSize);                   //row
            //bilinearWeight
            float a = (betaMax - beta) / binSize - i;
            float b = alpha / binSize - j;

            float ij = (1 - a) * (1 - b);
            float i1j = a * (1 - b);
            float ij1 = (1 - a) * b;
            float i1j1 = a * b;
            //unity reverse y
            i = imageWidth - i;
            idensity[i * imageWidth + j] += ij;
            idensity[(i + 1) * imageWidth + j] += i1j;
            idensity[i * imageWidth + j + 1] += ij1;
            idensity[(i + 1) * imageWidth + j + 1] += i1j1;
        }

        textureData = new Color32[imageWidth * imageWidth];


        float maxIdensity = 0;
        foreach (var item in idensity)
        {
            maxIdensity = Mathf.Max(maxIdensity, item);
        }


        for (int i = 0; i < imageWidth * imageWidth; ++i)
        {
            textureData[i] = Color.white - new Color(idensity[i], idensity[i], idensity[i], 0) / maxIdensity;
        }

        Texture2D tempTexture = new(imageWidth, imageWidth, TextureFormat.ARGB32, true);
        tempTexture.SetPixels32(textureData);
        tempTexture.Apply();
        spinImage = tempTexture;

        Debug.Log("CPU Generated!");
    }

计算网格强度(GPU)

采用HLSL的computer shader进行并行计算网格强度,但是在GPU并行上有个问题得注意:对网格强度的计算需要采用原子操作,但是HLSL并没有提供浮点数的原子操作,我们需要自己实现浮点数的原子操作,如下:

globallycoherent RWByteAddressBuffer g_BinBuffer : register(u0);
void InterlockedAddFloat(uint addr, float value)
{
    uint comp;
    uint orig = g_BinBuffer.Load(addr * 4);
    [allow_uav_condition]
    do
    {
        g_BinBuffer.InterlockedCompareExchange(addr * 4, comp = orig, asuint(asfloat(orig) + value), orig);
    }
    while (orig != comp);
}

采用字节地址缓冲区,浮点数使用32位进行存储,读取时候使用asfloat函数将数据视为浮点数进行读取,然后进行浮点数的加法,最后将得到的结果视为uint类型;最后采用InterlockedCompareExchange原子函数进行来实现原子操作。

在GPU计算也是主要分为两趟Pass

  • 第一趟:在一维数组上计算网格强度。
  • 第二趟:将得到的网格强度映射到0-1,最后并赋给对于的图片像素位置。

HLSL核心代码:

#pragma kernel CreateSpinImageData
#pragma kernel CreateSpinImage
[numthreads(512,1,1)]
void CreateSpinImageData(uint3 DTid : SV_DispatchThreadID)
{
    if (DTid.x >= g_PointNums)
    {
        return;
    }
    
    float3 currPoint = g_MeshPointsBuffer[DTid.x];
    //spin-map
    float3 xp = currPoint - g_OrientedPointBuffer[0].position;
    float beta = dot(g_OrientedPointBuffer[0].normal, xp);
    float alpha = sqrt(dot(xp, xp) - pow(beta, 2));
    
    //Bin
    uint i = floor((g_BetaMax - beta) / g_BinSize);
    uint j = floor(alpha / g_BinSize);
    
    //BilinearWeight
    float a = (g_BetaMax - beta) - i * g_BinSize;
    float b = alpha - j * g_BinSize;
    
    float ij = (1 - a) * (1 - b);
    float i1j = a * (1 - b);
    float ij1 = (1 - a) * b;
    float i1j1 = a * b;
    
    //unity reverse y
    i = g_ImageWidth - i;
    
    InterlockedAddFloat(i * g_ImageWidth + j, ij);
    InterlockedAddFloat((i + 1) * g_ImageWidth + j, i1j);
    InterlockedAddFloat(i * g_ImageWidth + j + 1, ij1);
    InterlockedAddFloat((i + 1) * g_ImageWidth + j + 1, i1j1);
}

[numthreads(32, 32, 1)]
void CreateSpinImage(uint3 DTid : SV_DispatchThreadID)
{
    uint index = DTid.y * g_ImageWidth + DTid.x;
    
    float data = asfloat(g_BinBuffer.Load(index * 4));
    float r = 1.0f - data / g_MaxIdensity;
    float4 res = float4(r, r, r, 1.0f);
    g_SpinImage[DTid.xy] = (res);
}

参考资料

Spin Images(Georgios Papadimitriou)

三维计算机视觉(七)--Spin image_Eason.wxd的博客-CSDN博客

HLSL InterLockedAdd Float

posted @ 2022-05-29 22:53  Ligo丶  阅读(343)  评论(0编辑  收藏  举报