绘制曲线周围的信封

11
在我的C# WinForms应用程序中,我有一个图片框,用于显示2个曲线(来自电压/电流测量)。X轴是电压,Y轴是电流。电压轴范围为-5到5,但电流轴的比例要小得多,范围为-10微安至10微安。任务是查看第二条曲线是否在第一条曲线的10%以内。
为了进行视觉检查,我尝试在第一条曲线(蓝色)周围绘制一个包络线。该曲线只是一个PointF数组。目前,由于我不知道如何正确地绘制蓝色曲线周围的正确包络线,所以我只绘制了另外两条曲线,这些曲线是通过将原始曲线的X点添加和减去10%而得出的。当然,这是一个糟糕的方法,但至少对于明显垂直的曲线部分,它起作用。但是,一旦曲线处于非垂直部分,这个技巧就不再起作用了,正如下图所示:

enter image description here

这是我用来绘制信封的代码:

public Bitmap DrawEnvelope(double[,] pinData, float vLimit, float iLimit)
{
    g = Graphics.FromImage(box);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;

    PointF[] u = new PointF[pinData.GetLength(0)]; //Up line
    PointF[] d = new PointF[pinData.GetLength(0)]; //Down Line
    List<PointF> joinedCurves = new List<PointF>();

    float posX = xMaxValue * (vLimit / 100);
    float minX = posX * -1;


    for (int i = 0; i < pinData.GetLength(0); i++)
    {
        u[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + minX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
    }

    for (int i = 0; i < pinData.GetLength(0); i++)
    {
        d[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + posX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
    }


    Pen pengraph = new Pen(Color.FromArgb(50, 0 ,0 ,200), 1F);
    pengraph.Alignment = PenAlignment.Center;

    joinedCurves.AddRange(u);
    joinedCurves.AddRange(d.Reverse());

    PointF[] fillPoints = joinedCurves.ToArray();
    SolidBrush fillBrush = new SolidBrush(Color.FromArgb(40, 0, 0, 250));
    FillMode newFillMode = FillMode.Alternate;

    g.FillClosedCurve(fillBrush, fillPoints, newFillMode, 0);

    g.Dispose();
    return box;
}

我添加了绿色圆圈,它们表示第二条曲线(红色)可能与原始曲线有超过10%的差异的区域。

如果有人能指导我,让我知道如何在原始曲线周围获得一个漂亮的包络线,那就太好了。

更新 因为我太菜了,无法实现到目前为止给出的答案,所以我放了一个赏金,看看是否有人可以友善地向我展示解决此问题的编程方法。


2
信封是否应该是+-10%的区域?如果是,那么我认为您需要更明确地了解实际上"在10%内"意味着什么。每个电压下的当前10%?每个电流下的电压10%?还是其他什么?如果这来自某个学校/大学的任务,那么实际措辞是什么?如果这是出于商业原因,那么基本要求是什么?一旦清楚了这些,您的"信封"应该会更清晰明了。 - Gareth McCaughan
2
你能否重新绘制原始曲线,将线条粗细加大并降低不透明度呢? - Dan
@GarethMcCaughan 我自己也不知道"增加10%"是什么意思!我最好的猜测是将电压和电流点都增加10%以形成上边线,下边线也增加10%... - Dumbo
1
如果10%的要求来自于(比如说)一项作业任务,那么你应该(1)告诉我们它到底是什么,(2)在问题中添加“作业”标签。如果问题描述真的很不清楚,你可以考虑联系出题人并要求澄清。 - Gareth McCaughan
1
将以下关于编程的内容从英语翻译成中文。仅返回翻译后的文本:(如果它来自实际问题,那么让我们更多地了解一下来自真实世界的问题;也许这会让正确的标准更加明显。) - Gareth McCaughan
显示剩余3条评论
5个回答

3
你可以尝试找出每对点之间的梯度,并计算通过中点的正交线上的两个相邻点。然后,你将有另外两条由一组点定义的线,你可以使用它们来绘制包络线。

请您提供一小段代码,这样我就能看到我需要做什么了吗? - Dumbo

1

这完全取决于您想要信封的尺寸方式。

您可以通过计算每个点上的曲线斜率来估算/计算斜率,计算该点到下一个点和前一个点的斜率,取平均值,然后计算与斜率垂直的向量。

将此向量添加到曲线的点上,这将给出信封的右侧边缘。

从曲线的点中减去此向量,这将给出信封的左侧边缘。

如果点之间距离过远或者点之间出现非常突然的变化,则该方法将失败。


嗯...我刚刚注意到尼古拉斯也想到了同样的事情。 - Emond
谢谢你的提示...有没有机会看看代码应该是什么样子的?至少对于一些数据点? - Dumbo
我需要知道你想要如何绘制信封。 - Emond
我想你的意思是信封可以有哪些值,如果我没错的话!我的意思是原始曲线应该被某些指示包围,以便找出结果曲线(红色曲线)是否在其百分比范围内。我认为的是计算(例如10%)另外两条曲线,其值比原始曲线大或小n%,以形成一种边界。但问题是,如果第一对点增加了n%,则边界将从不同位置开始。请让我知道我是否清楚:P - Dumbo
一个百分比是什么?x值,y值?你想要展示什么?信封代表什么? - Emond

1
这可能是一个愚蠢的建议。也许你可以让winforms为你绘制信封,而不是自己手动绘制。试着用一支宽度较大的笔将信封绘制成一条线。或许这样会有所帮助。
如果你看一下这个msdn上关于改变笔宽的例子,你可能就会明白我的意思了。

http://msdn.microsoft.com/en-us/library/3bssbs7z.aspx


1

你最好的选择是迭代你的点数组,并且每次计算两个相邻点的垂直向量(参见计算二维向量的叉积以获取实现线索)。沿着这些垂直向量的任意方向进行投影,以生成你信封的两个点数组。

这个函数使用线段中点粗略地生成它们(只要点数高并且偏移量不太小,绘制时应该看起来还可以):

private void GetEnvelope(PointF[] curve, out PointF[] left, out PointF[] right, float offset)
        {
            left = new PointF[curve.Length - 1];
            right = new PointF[curve.Length - 1];

            for (int i = 1; i < curve.Length; i++)
            {
                PointF normal = new PointF(curve[i].Y - curve[i - 1].Y, curve[i - 1].X - curve[i].X);
                float length = (float)Math.Sqrt(normal.X * normal.X + normal.Y * normal.Y);
                normal.X /= length;
                normal.Y /= length;

                PointF midpoint = new PointF((curve[i - 1].X + curve[i].X) / 2F, (curve[i - 1].Y + curve[i].Y) / 2F);
                left[i - 1] = new PointF(midpoint.X - (normal.X * offset), midpoint.Y - (normal.Y * offset));
                right[i - 1] = new PointF(midpoint.X + (normal.X * offset), midpoint.Y + (normal.Y * offset));
            }
        }

谢谢,这已经很接近我想要的了。 - Dumbo

0

2个(可能不正确的)可能性。

  1. 做你最初为了得到苍白的蓝色宽区域所做的事情,但也在垂直方向上做(不仅仅是水平方向)

  2. 按照丹的建议用非常粗的线条(浅蓝色)画一遍,然后再画一遍,然后在其上面再画原来的(细)线条。


谢谢兄弟,你花了十分之一世纪的时间来回答。 - Dumbo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接