简体   繁体   English

如何测量绘制在图像上的线的长度? C#

[英]How to measure length of line which is drawn on image? C#

I would like to write an application that will measure fragments of a specimen examined under a microscope. 我想编写一个应用程序,用于测量在显微镜下检查过的标本的碎片。 I thought that the best way would be to capture the image and draw on selected parts of the specimen then count the value of the drawn line in pixels (and later to convert this value into the appropriate unit). 我认为最好的方法是捕获图像并在样本的选定部分上绘制,然后以像素为单位计算绘制的线的值(然后将其转换为适当的单位)。

Is there anything that helps solve such issue already implemented or any tool/package or something that allows such calculations? 是否有任何有助于解决已经实施的问题的工具,任何工具/软件包或允许进行此类计算的工具?

I will also willingly learn about solutions in other programming languages if they allow to solve this problem in a easier way or just in some way. 如果其他编程语言的解决方案允许以简单的方式或某种方式解决该问题,我也将乐于学习。

This is a very basic example of measuring a segmented line drawn onto an image in winforms. 这是测量winforms中绘制到图像上的分段线的非常基本的示例。

It uses a PictureBox to display the image, a Label to display the current result and for good measure I added two Buttons the clear all points and to undo/remove the last one. 它使用PictureBox来显示图像,使用Label来显示当前结果,并且为达到良好的效果,我添加了两个Buttons来清除所有点并撤消/删除最后一个点。

I collect to pixel positions in a List<Point> : 我收集到List<Point>像素位置:

List<Point> points = new List<Point>();

The two edit buttons are rather simple: 这两个编辑按钮非常简单:

private void btn_Clear_Click(object sender, EventArgs e)
{
    points.Clear();
    pictureBox1.Invalidate();
    show_Length();
}

private void btn_Undo_Click(object sender, EventArgs e)
{
    if (points.Any())points.Remove(points.Last());
    pictureBox1.Invalidate();
    show_Length();
}

Note how I trigger the Paint event by invalidating the image whenever the points collection changes.. 请注意,每当点集合更改时,如何通过使图像无效来触发Paint事件。

The rest of the code is also simple; 其余代码也很简单。 I call a function to calculate and display the sum of all segment lengths. 我调用一个函数来计算和显示所有段长度的总和。 Note that I need at least two points before I can do that or display the first line.. 请注意,我至少需要两个点之前,我可以做到这一点或显示第一行..

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    points.Add(e.Location);
    pictureBox1.Invalidate();
    show_Length();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    if (points.Count > 1) e.Graphics.DrawLines(Pens.Red, points.ToArray());
}

void show_Length()
{
    lbl_len.Text = (pointsF.Count) + " point(s), no segments. " ;

    if (!(points.Count > 1)) return;

    double len = 0;
    for (int i = 1; i < points.Count; i++)
    {
        len += Math.Sqrt((points[i-1].X - points[i].X) * (points[i-1].X - points[i].X) 
                    +  (points[i-1].Y - points[i].Y) * (points[i-1].Y - points[i].Y));
    }
    lbl_len.Text = (points.Count-1) + " segments, " + (int) len + " pixels";
}

A few notes: 一些注意事项:

  • The image is displayed without any zooming. 图像显示时没有任何缩放。 PictureBox has a SizeMode property to make zoomed display simple. PictureBox具有SizeMode属性,以使缩放显示变得简单。 In such a case I recommend to store not the direct pixel locations of the mouse but 'unzoomed' values and to use a 'rezoomed' list of values for the display. 在这种情况下,我建议不要存储鼠标的直接像素位置,而应存储“未缩放”的值,并使用“已缩放”的值列表进行显示。 This way you can zoom in and out and still have the points stick to the right spots. 这样,您可以放大和缩小,但仍将点固定在正确的位置。

  • For this you ought to use a List<PointF> to keep precision. 为此,您应该使用List<PointF>保持精度。

  • When zooming eg by enlarging the PictureBox , maybe after nesting it in a Panel , make sure to either keep the aspect ratio equal to that of the Image or to do a full calculation to include the extra space left or top; 在缩放时,例如通过放大PictureBox ,可能是在将它嵌套在Panel ,请确保将其长宽比保持为与Image相同,或者进行完整的计算以包括左侧或顶部的额外空间; in SizeMode.Normal the image will always sit flush TopLeft but in other modes it will not always do so. SizeMode.Normal ,图像将始终与TopLeft齐平放置,但在其他模式下,它并不总是如此。

  • For the calculation of actual ie physical distances simply divide by the actual dpi value. 为了计算实际距离,即物理距离,只需除以实际dpi值即可。

Let's see what we have in action: 让我们看看我们在做什么:

在此处输入图片说明

Update: 更新:

To get a chance to create cloers fits and better precision we obviously need to zoom in on the image. 为了有机会创建Clos拟合和更好的精度,我们显然需要放大图像。

Here are the necessary changes: 以下是必要的更改:

We add a list of 'floating points': 我们添加一个“浮点数”列表:

List<PointF> pointsF = new List<PointF>();

And use it to store the un-zoomed mouse positions in the mouse down: 并使用它在鼠标向下存储未缩放的鼠标位置:

pointsF.Add( scaled( e.Location, false));

We replace all other occurances of points with pointsF . 我们取代所有其他occurances pointspointsF

The Paint event always calculates the scaled points to the current zoom level: Paint事件始终会计算到当前缩放级别的缩放点:

if (pointsF.Count > 1)
{
    points = pointsF.Select(x => Point.Round(scaled(x, true))).ToList();
    e.Graphics.DrawLines(Pens.Red, points.ToArray());
}

And the function to do the scaling looks like this: 进行缩放的函数如下所示:

PointF scaled(PointF p, bool scaled)
{
    float z = scaled ? 1f * zoom : 1f / zoom;
    return new PointF(p.X * z, p.Y * z);
}

It uses a class level variable float zoom = 1f; 它使用一个类级别的变量float zoom = 1f; which gets set along with the picturebox's Clientsize in the Scroll event of a trackbar: 它在轨迹ClientsizeScroll事件中与图片框的Clientsize一起设置:

private void trackBar1_Scroll(object sender, EventArgs e)
{
    List<float> zooms = new List<float>()
    { 0.1f, 0.2f, 0.5f, 0.75f, 1f, 2, 3, 4, 6, 8, 10};
    zoom = zooms[trackBar1.Value];

    int w = (int)(pictureBox2.Image.Width * zoom);
    int h = (int)(pictureBox2.Image.Height * zoom);

    pictureBox2.ClientSize = new Size(w, h);
    lbl_zoom.Text = "zoom: " + (zoom*100).ToString("0.0");
}

The picturebox is nested inside a Panel with AutoScroll on. 图片框嵌套在具有AutoScroll功能的Panel内。 Now we can zoom and scroll while adding segments: 现在,我们可以在添加细分时进行缩放和滚动:

在此处输入图片说明

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

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