【问题标题】:How to measure length of line which is drawn on image? C#如何测量在图像上绘制的线的长度? C#
【发布时间】:2018-07-03 16:04:31
【问题描述】:

我想编写一个应用程序来测量在显微镜下检查的标本碎片。我认为最好的方法是捕获图像并在样本的选定部分上绘制,然后以像素为单位计算绘制线的值(然后将此值转换为适当的单位)。

是否有任何东西可以帮助解决已经实施的此类问题或任何工具/包或允许此类计算的东西?

如果其他编程语言允许以更简单的方式或仅以某种方式解决此问题,我也愿意学习其他编程语言的解决方案。

【问题讨论】:

  • 您的目标是什么:Winforms、WPF、ASP..?您应该始终正确标记您的问题,以便人们可以在问题页面上看到它! Winforms:在 PictureBox 中显示并编码鼠标事件:Down、Move 和 Up。只需几行即可获得像素..
  • 从起点到终点,您可以计算距离(以像素为单位)。但是将其转换为毫米(或您想要的任何长度单位)是一个挑战!
  • 我建议看看 OpenCV。它是一个 C++ 计算机视觉库,也具有 Python 绑定(如果您来自 C#,我建议使用 Python over C++)。我相信它提供了一个线段检测器作为其 API 的一部分。如果您愿意制作一个交互式应用程序,您可以避免使用 CV,只需通过鼠标单击捕获线段的开始和结束位置。然后使用距离公式乘以您选择从像素转换为实际单位的任何比例。
  • “挑战”是在图像被放大显示时了解缩放系数并信任 dpi 值。
  • 如果知道起点和终点,可以使用勾股定理中的 Delta-x 和 Delta-y 来确定长度。

标签: c# wpf


【解决方案1】:

这是一个非常基本的示例,用于测量在 winforms 中绘制到图像上的分段线。

它使用PictureBox 显示图像,使用Label 显示当前结果,为了更好地衡量,我添加了两个Buttons 清除所有点并撤消/删除最后一个。

我收集到List<Point> 中的像素位置:

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

两个编辑按钮相当简单:

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();
}

注意我是如何触发Paint事件的

其余的代码也很简单;我调用一个函数来计算和显示所有段长度的总和。请注意,我至少需要 两个 点才能执行此操作或显示第一行..

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";
}

一些注意事项:

  • 图像显示时没有任何缩放。 PictureBox 有一个 SizeMode 属性,使缩放显示变得简单。在这种情况下,我建议不要存储鼠标的直接像素位置,而是存储“未缩放”值,并使用“重新缩放”的值列表进行显示。通过这种方式,您可以放大和缩小,并且仍然可以将点固定在正确的位置。

  • 为此,您应该使用List&lt;PointF&gt; 来保持精度。

  • 缩放时,例如通过扩大PictureBox,可能在将其嵌套在Panel 中之后,确保将纵横比保持等于Image 的纵横比或执行full calculation 以包括剩余空间或顶部;在SizeMode.Normal 中,图像将始终与 TopLeft 齐平,但在其他模式下,它并不总是如此。

  • 对于实际距离的计算,即物理距离只需除以实际 dpi 值。

让我们看看我们的行动:

更新:

为了有机会创造更接近的拟合度和更好的精度,我们显然需要放大图像。

以下是必要的更改:

我们添加一个“浮点”列表:

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

并用它来存储未缩放的鼠标位置在鼠标向下:

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

我们将所有其他出现的points 替换为pointsF

Paint 事件总是计算缩放点到当前缩放级别:

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

进行缩放的函数如下所示:

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

它使用一个类级别变量float zoom = 1f;,该变量在轨迹栏的Scroll 事件中与图片框的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");
}

图片框嵌套在Panel 中,AutoScroll 开启。现在我们可以在添加片段的同时进行缩放和滚动:

【讨论】:

  • 请注意,如果您从事“科学”工作,这远非完美!这在很大程度上取决于操作员的准确性。
  • 当然。下一步将是实现放大,让他有机会超越显示像素精度..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 2021-03-10
  • 1970-01-01
  • 1970-01-01
  • 2019-09-21
相关资源
最近更新 更多