C#学习——图片或矩阵转STL文件

一、背景

一张图片是由一个一个像素点组成的,每一个像素点有特定的下标及色彩值,这样我们就可以把一张图片看作是准三维点云。之所以叫准三维,是因为每个(x,y)只能对应一个z值。在Matlab程序中,用imread命令导入一张图片,会发现导入的结果是一个矩阵。用surf命令可以很直观地看到这个三维点云构成的三维曲面。实际上,这个矩阵已经能够非常清晰地描述这个三维曲面了。然而,事实就是这么残酷。有些商业软件只能识别特定的三维曲面,比如非常常见的STL文件。STL文件有二进制和ASCII两种编码方式,这两种方式各有优缺点:二进制编码文件体积小,但文本很不直观;ASCII编码非常直观,相应的文件体积较大。通常二进制编码的体积为ASCII编码的五分之一,而不管什么编码都遵循一定的规则。规则写好了,软件识别起来就没有区别了,所以有些比较坑的软件还只支持二进制编码的STL文件。

在逆向工程上,如果测量软件没有相应的功能的话,测出来的结果就会是一个二维矩阵。矩阵下标表示xy位置,值表示高度。三维点云有相应的软件处理成三维曲面,一般这样的软件功能都很强大,当然也不是免费的,体积也会比较大,运气不好还破解不了。因此,有必要采用一种简便的转换方法。

二、解决思路

回到Matlab的surf命令,对得到的三维曲面进行放大,会发现这个曲面是由一个一个四边形构成的。这就给我们提供了一个思路:每四个点可以构成两个三角形,而三角形正好的STL文件的基础。

三、C#代码实现

我们用一个double类型的二维数组来表示一个曲面,数组的下标表示xy坐标,值表示z坐标。四个点可以表示两个三角形,通过二重循环来遍历所有的点,如下图所示:

SLM文件中,一个三角形由一个法向量和三个点构成,点的坐标可以由数组得到,但法向量必须根据这三个点来求。将这个算法写成一个函数,方便调用。

具体实现见代码:

private static double[] Vectorxyz(double x1, double y1, double z1,
    double x2, double y2, double z2,
    double x3, double y3, double z3)
{
    var A = y1 * z2 - y1 * z3 - z1 * y2 + z1 * y3 + y2 * z3 - y3 * z2;
    var B = -x1 * z2 + x1 * z3 + z1 * x2 - z1 * x3 - x2 * z3 + x3 * z2;
    var C = x1 * y2 - x1 * y3 - y1 * x2 + y1 * x3 + x2 * y3 - x3 * y2;
    var vectorLength = Math.Sqrt(A * A + B * B + C * C);

    return new double[] { A / vectorLength, B / vectorLength, C / vectorLength };
}

随后,我们用一个二重循环将所有点构成三角形写入文件。以下代码将三角形以ASCII编码写入STL文件中。代码中,filename为要写入的文件名,如"test.stl" 。inputMatrix为double类型的二维数组,xmax和ymax分别为这个数组第一维和第二维长度。也可以手动指定更小的值,即将其中一部分转换成STL文件。

StreamWriter sw = new StreamWriter(filename);
sw.WriteLine("solid XW STL");
double[] vectorxyz = new double[3];
for (int x = 0; x < xmax - 1; x++)
{
    for (int y = 0; y < ymax; y++)
    {
        vectorxyz = Vectorxyz(x, y, inputMatrix[x, y],
            x, y + 1, inputMatrix[x, y + 1],
            x + 1, y + 1, inputMatrix[x + 1, y + 1]);
        sw.WriteLine($"  facet normal  {vectorxyz[0].ToString("E")}  {vectorxyz[1].ToString("E")}  {vectorxyz[2].ToString("E")}");
        sw.WriteLine("    outer loop");
        sw.WriteLine($"      vertex  {x.ToString("E")}  {y.ToString("E")}  {inputMatrix[x, y].ToString("E")}");
        sw.WriteLine($"      vertex  {x.ToString("E")}  {(y + 1).ToString("E")}  {inputMatrix[x, y + 1].ToString("E")}");
        sw.WriteLine($"      vertex  {(x + 1).ToString("E")}  {(y + 1).ToString("E")}  {inputMatrix[x + 1, y + 1].ToString("E")}");
        sw.WriteLine("    endloop");
        sw.WriteLine("  endfacet");

        vectorxyz = Vectorxyz(x, y, inputMatrix[x, y],
            x + 1, y + 1, inputMatrix[x + 1, y + 1],
            x + 1, y, inputMatrix[x + 1, y]);
        sw.WriteLine($"  facet normal  {vectorxyz[0].ToString("E")}  {vectorxyz[1].ToString("E")}  {vectorxyz[2].ToString("E")}");
        sw.WriteLine("    outer loop");
        sw.WriteLine($"      vertex  {x.ToString("E")}  {y.ToString("E")}  {inputMatrix[x, y].ToString("E")}");
        sw.WriteLine($"      vertex  {(x + 1).ToString("E")}  {(y + 1).ToString("E")}  {inputMatrix[x + 1, y + 1].ToString("E")}");
        sw.WriteLine($"      vertex  {(x + 1).ToString("E")}  {y.ToString("E")}  {inputMatrix[x + 1, y].ToString("E")}");
        sw.WriteLine("    endloop");
        sw.WriteLine("  endfacet");
    }
}
sw.WriteLine("endsolid XW STL");
sw.Close();

以上面上示意图为例,我们将其生成STL文件,如下图所示:

二进制编码与ASCII编码类似,区别在于写入二进制文件,去掉了很多多余的信息,因此能够做到ASCII体积的五分之一,代码就是可以性差。

两者的大小对比如下图所示:

大家可以自己研究一下STL文件的二进制编码方式,按照类似的方法即可实现,也可以评论区留言。

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值