简体   繁体   English

找到二维曲线的双切线的算法

[英]Algorithm to find the bitangent of a 2D curve

I'm looking for an algorithm to calculate the bitangent of a curve, ie.我正在寻找一种算法来计算曲线的双切线,即。 the line below a curve that intersects it at exactly two points:一条曲线下方的线恰好与它相交于两点: 示例 1示例 2

Specifically, I am looking for the two x -values where the bitangent intersects the curve.具体来说,我正在寻找双切线与曲线相交的两个x值。 These are examples of actual curves that I am studying, I have manually drawn the tangent I would like to find.这些是我正在研究的实际曲线的示例,我已经手动绘制了我想找到的切线。

Referencing this post from the Mathematica Stack Exchange almost verbatim, I understand that I am looking for two distinct x values for which a generic line and the function describing this curve have几乎逐字引用 Mathematica Stack Exchange 中的这篇文章,我知道我正在寻找两个不同的x值,它们的通用线和描述该曲线的 function 具有

  1. The same y value (ie. the line touches the curve)相同的y值(即线接触曲线)
  2. The same derivative (ie. the line is tangent to the curve)相同的导数(即直线与曲线相切)

This works if we know the function f(x) that describes the curve, because we know the equation of a generic line is y = mx + b .如果我们知道描述曲线的 function f(x) ,则此方法有效,因为我们知道通用线的方程为y = mx + b We can then set up the following system of equations and solve:然后我们可以建立以下方程组并求解:

  1. y 1 = f(x 1 ) , y 1 = f(x 1 ) ,

  2. y 2 = f(x 2 ) , y 2 = f(x 2 ) ,

  3. f'(x 1 ) = f'(x 2 ) , f'(x 1 ) = f'(x 2 ) ,

  4. f'(x 1 ) = (y 2 - y 1 )/(x 2 - x 1 ) f'(x 1 ) = (y 2 - y 1 )/(x 2 - x 1 )

The issue I am having is two fold.我遇到的问题有两个方面。 I don't know the function of the line, as it comes from a measurement, and even if I did, I don't know how to solve a system of equations using C# / MathNet.我不知道该行的 function,因为它来自测量,即使我知道,我也不知道如何使用 C# / MathNet 求解方程组。 The only thing I can say definitively about the curve is the slope of the bitangent will be positive, and the x-axis will run from -150 to 700.关于曲线,我唯一可以明确地说的是双切线的斜率为正,x 轴将从 -150 运行到 700。

Other things I've tried is using a modified convex hull algorithm, and fitting a cubic spline by manually selecting the knot points, but both these attempts were insufficiently accurate.我尝试过的其他事情是使用修改后的凸包算法,并通过手动选择结点来拟合三次样条,但是这两种尝试都不够准确。

So my questions are:所以我的问题是:

  1. How can I find the function of this curve (ideally using MathNet / C#)如何找到这条曲线的 function(最好使用 MathNet / C#)
  2. How can I solve the above system of equations (also ideally using MathNet / C#)如何求解上述方程组(最好也使用 MathNet / C#)

Any other algorithm ideas / advice is appreciated!感谢任何其他算法的想法/建议!

Thanks谢谢

Other related posts on the Mathematica Stack Exchange Mathematica Stack Exchange 上的其他相关帖子

Since the function is not known analytically the only viable approach is working numerically.由于 function 在分析上未知,唯一可行的方法是在数值上工作。 The idea of using convex hull seems promising.使用凸包的想法似乎很有希望。 Based on your example data it seems it should work as follows:根据您的示例数据,它似乎应该按如下方式工作:

  • compute the lower convex hull eg with Graham Scan which in fact computes separately the upper and lower one (or eventually the left/right one depending on the implementation)计算下凸包,例如使用Graham Scan ,它实际上分别计算上凸包和下凸包(或最终取决于实现的左/右包)
  • take the longer hull segments取更长的船体部分
  • maybe the longer one is what you are looking for or eventually you may need to take some number of longer ones (eg 2 to 5) and create a ranking of them based on some suitable idea eg:也许更长的就是你正在寻找的,或者最终你可能需要一些更长的时间(例如2到5)并根据一些合适的想法为它们创建一个排名,例如:
    • the max y-distance between the points over a segment and the segment itself段上的点与段本身之间的最大 y 距离
    • take the most central one取最中心的一个
    • take the one with the higher slope取斜率较高的那个

You need to experiment with your data (and know its physical meaning) to see what is the most suitable ranking criteria to select the one segment you need among the longer ones.您需要对您的数据进行试验(并了解其物理含义),以查看最适合 select 的排名标准是您需要的较长部分中的一个。

Input data输入数据

Well as you did not shared the original input data in numerical form I extracted it from the images.好吧,由于您没有以数字形式共享原始输入数据,我从图像中提取了它。 This part is meant for others... First I edited them in paint a bit:这部分是为其他人准备的……首先我用油漆对它们进行了一些编辑:

  1. fill the background by Black color用黑色填充背景

    that removes many of the bi tangent points删除了许多双切点

  2. crop inside在里面裁剪

    that removes axises and scales/grid删除轴和刻度/网格

  3. manually clear the remnant pixels of what has been left from the bitangent手动清除双切线剩余的像素

Then I apply some DIP to shrink all vertical and horizontal lines to single point here the results:然后我应用一些DIP将所有垂直和水平线缩小到单点,结果如下:

情节0 情节1

and then I extracted xy of any non background pixels (first number is x , second y in pixels):然后我提取了任何非背景像素的 xy (第一个数字是x ,第二个y以像素为单位):

int data0_xy[]=
    {
      33, 447,  41, 451,  49, 462,  56, 470,  64, 473,  72, 477,  80, 481,  87, 489,  95, 492, 103, 495, 111, 496, 118, 500, 126, 504, 134, 507, 142, 506, 149, 506,
     157, 510, 165, 514, 173, 512, 180, 512, 188, 512, 196, 515, 204, 515, 212, 513, 219, 512, 227, 514, 235, 514, 243, 513, 250, 512, 258, 511, 266, 512, 274, 511,
     281, 508, 289, 505, 297, 505, 305, 506, 312, 505, 320, 500, 328, 500, 336, 500, 343, 501, 351, 497, 359, 495, 367, 497, 374, 499, 382, 500, 390, 498, 398, 498,
     406, 501, 413, 502, 421, 501, 429, 501, 437, 502, 444, 504, 452, 506, 460, 504, 468, 504, 475, 504, 483, 505, 491, 505, 499, 503, 506, 500, 514, 501, 522, 503,
     530, 499, 537, 497, 545, 495, 553, 496, 561, 495, 569, 490, 576, 486, 584, 486, 592, 485, 600, 483, 607, 478, 615, 475, 623, 472, 631, 471, 638, 468, 646, 461,
     654, 457, 662, 454, 669, 452, 677, 450, 685, 443, 693, 436, 700, 431, 708, 429, 716, 425, 724, 417, 732, 410, 739, 405, 747, 400, 755, 397, 763, 387, 770, 380,
     778, 375, 786, 369, 794, 363, 801, 355, 809, 346, 817, 342, 825, 336, 832, 326, 840, 318, 848, 310, 856, 306, 863, 300, 871, 291, 879, 281, 887, 274, 894, 269,
     902, 263, 910, 252, 918, 244, 926, 237, 933, 230, 941, 224, 949, 215, 957, 206, 964, 202, 972, 197, 980, 187, 988, 179, 995, 173,1003, 169,1011, 162,1019, 155,
    1026, 148,1034, 141,1042, 136,1050, 133,1057, 126,1065, 119,1073, 115,1081, 113,1089, 108,1096, 102,1104,  97,1112,  97,1120,  94,1127,  87,1135,  84,1143,  82,
    1151,  84,1158,  80,1166,  77,1174,  74,1182,  72,1189,  73,1197,  72,1205,  69,1213,  67,1220,  68,1228,  68,1236,  64,1244,  62,1252,  61,1259,  63,1267,  60,
    1275,  57,1283,  53,1290,  49,1298,  49,1306,  47,1314,  40,1321,  35,1329,  31,1337,  27,1345,  20,1352,  10,
    };
int data1_xy[]=
    {
      33, 533,  41, 533,  49, 532,  56, 531,  64, 533,  72, 532,  80, 530,  87, 533,  95, 530, 103, 533, 111, 531, 118, 532, 126, 531, 134, 531, 142, 531, 149, 529,
     157, 530, 165, 530, 173, 529, 180, 526, 188, 529, 196, 527, 204, 525, 212, 526, 219, 526, 227, 525, 235, 523, 243, 520, 250, 522, 258, 520, 266, 520, 274, 517,
     281, 519, 289, 516, 297, 515, 305, 513, 312, 511, 320, 510, 328, 509, 336, 507, 343, 503, 351, 503, 359, 501, 367, 500, 374, 496, 382, 496, 390, 493, 398, 490,
     406, 488, 413, 485, 421, 482, 429, 480, 437, 476, 444, 469, 452, 467, 460, 466, 468, 461, 475, 456, 483, 451, 491, 449, 499, 442, 506, 438, 514, 432, 522, 426,
     530, 420, 537, 411, 545, 402, 553, 400, 561, 395, 569, 386, 576, 380, 584, 372, 592, 363, 600, 354, 607, 344, 615, 336, 623, 327, 631, 323, 638, 315, 646, 300,
     654, 299, 662, 291, 669, 284, 677, 273, 685, 266, 693, 256, 700, 250, 708, 243, 716, 237, 724, 227, 732, 217, 739, 210, 747, 197, 755, 185, 763, 184, 770, 151,
     778, 165, 786, 158, 794, 145, 801, 136, 809, 111, 817, 119, 825, 112, 832, 105, 840,  94, 848,  76, 856,  80, 863,  78, 871,  66, 879,  60, 887,  40, 894,  28,
     902,  38, 910,  42, 918,  41, 926,  46, 933,  47, 941,  41, 949,  42, 957,  39, 964,  48, 972,  52, 980,  49, 988,  57, 995,  51,1003,  67,1011,  78,1019,  85,
    1026,  90,1034,  96,1042, 106,1050, 114,1057, 123,1065, 132,1073, 126,1081, 137,1089, 157,1096, 169,1104, 175,1112, 169,1120, 189,1127, 191,1135, 204,1143, 209,
    1151, 221,1158, 229,1166, 235,1174, 226,1182, 241,1189, 244,1197, 257,1205, 264,1213, 260,1220, 273,1228, 275,1236, 280,1244, 285,1252, 288,1259, 290,1267, 292,
    1275, 290,1283, 297,1290, 298,1298, 298,1306, 301,1314, 300,1321, 304,1329, 304,1337, 298,1345, 297,1352, 297,
    };

So now we have more or less the same data as you do...所以现在我们有或多或少和你一样的数据......

Bitangent双切线

Well as your data is not that big you can try bruteforce approach.好吧,由于您的数据不是那么大,您可以尝试蛮力方法。

  1. loop through all point pairs遍历所有点对

    simple O(n^2) search.简单的O(n^2)搜索。 Do not test point pairs twice so the second loop will not test points already done in first loop.不要测试点对两次,这样第二个循环就不会测试第一个循环中已经完成的点。

  2. detect if valid bitangent检测是否有效的双切线

    so if we compute normal vector to tested bitangent (perpendicular vector to it pointing towards data points).因此,如果我们计算测试的双切线的法线向量(指向数据点的垂直向量)。 Then all the vectors p(i)-p(bitangent) must have non negative (or positive if you want exactly 2 hits) dot product with our normal.那么所有向量p(i)-p(bitangent)必须与我们的法线具有非负(或正,如果你想要恰好 2 个命中)点积。 This lead to another O(n) loop resulting in O(n^3) approach.这导致另一个O(n)循环导致O(n^3)方法。 If any dot product crosses zero throw away this bitangent and test the next one.如果任何点积过零,则丢弃该双切线并测试下一个。 If no dot product crosses you found bitangent so add the actaul points from first two loops as a new bitangent to the list.如果没有点积交叉,您会发现双切线,因此将前两个循环中的实际点作为新的双切线添加到列表中。

This will find all the bitangents on the lowwer side of data (or above it if you flip the normal vector or crossing condition).这将在数据的低端(如果翻转法线向量或交叉条件,则在其上方)找到所有双切线。 So now you can apply your heuristics to select bitangent you want.所以现在你可以将你的启发式应用到你想要的 select bitangent。

I do not code in C# so here simple C++ example:我不在 C# 中编码,所以这里简单的 C++ 示例:

const int n2=sizeof(data_xy)/sizeof(data_xy[0]);    // numbers in data
const int n=n2>>1;                                  // points in data
int bitangent[100],bitangents;                      // 2 poit indexes per bitangent, number of indexes in bitangent[]
// O(n^3) bruteforce bitangent search
int nx,ny,i2,j2,k2,dx,dy;
bitangents=0;
for (i2=0;i2<n2;i2+=2)      // loop all points (bitangent start)
 for (j2=i2+2;j2<n2;j2+=2)  // loop all yet untested points (bitangent end)
    {
    // normal
    ny=-(data_xy[j2+0]-data_xy[i2+0]);
    nx=+(data_xy[j2+1]-data_xy[i2+1]);
    // test overlap
    for (k2=0;k2<n2;k2+=2)
     if ((k2!=i2)&&(k2!=j2))
        {
        dx=data_xy[k2+0]-data_xy[i2+0];
        dy=data_xy[k2+1]-data_xy[i2+1];
        if ((dx*nx)+(dy*ny)<0) { k2=-1; break; } // if dot product is negative overlap occurs so throw solution away
        }
    if (k2>=0)
        {
        bitangent[bitangents]=i2; bitangents++;
        bitangent[bitangents]=j2; bitangents++;
        }
    }

I render like this (VCL):我这样渲染(VCL):

void draw()
    {
    int i2,j2,x,y,r=3;
    bmp->Canvas->Brush->Color=clWhite;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));

    // render data lines
    bmp->Canvas->Pen->Color=clBlack;
    for (i2=0;i2<n2;i2+=2)
        {
        x=data_xy[i2+0];
        y=data_xy[i2+1];
        if (!i2) bmp->Canvas->MoveTo(x,y);
        else     bmp->Canvas->LineTo(x,y);
        }

    // render bitangents lines
    bmp->Canvas->Pen->Color=clBlue;
    for (i2=0;i2<bitangents;i2+=2)
        {
        j2=bitangent[i2+0];
        x=data_xy[j2+0];
        y=data_xy[j2+1];
        bmp->Canvas->MoveTo(x,y);
        j2=bitangent[i2+1];
        x=data_xy[j2+0];
        y=data_xy[j2+1];
        bmp->Canvas->LineTo(x,y);
        }

    // render data points
    bmp->Canvas->Pen->Color=clBlack;
    bmp->Canvas->Brush->Color=clRed;
    for (i2=0;i2<n2;i2+=2)
        {
        x=data_xy[i2+0];
        y=data_xy[i2+1];
        bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
        }
    // render bitangents points
    bmp->Canvas->Pen->Color=clBlack;
    bmp->Canvas->Brush->Color=clAqua;
    for (i2=0;i2<bitangents;i2++)
        {
        j2=bitangent[i2];
        x=data_xy[j2+0];
        y=data_xy[j2+1];
        bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
        }

    Form1->Canvas->Draw(0,0,bmp);
    }

And here output for your data:在这里 output 为您的数据:

输出0 输出1

As you can see I did not need any floating point operation or variable:) To keep this as simple as possible I used static arrays (no dynamic allocation).如您所见,我不需要任何浮点运算或变量:) 为了尽可能简单,我使用了 static arrays(无动态分配)。 As you can see there are quite more bitangents than you think at first.如您所见,双切线比您最初想象的要多得多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM