Как измерить длину линии, которая нарисована на изображении? C # - PullRequest
0 голосов
/ 02 июля 2018

Я хотел бы написать приложение, которое будет измерять фрагменты образца, исследованного под микроскопом. Я подумал, что лучшим способом было бы сделать снимок и нарисовать на выбранных частях образца, а затем посчитать значение нарисованной линии в пикселях (а затем преобразовать это значение в соответствующую единицу).

Есть ли что-нибудь, что помогает решить такую ​​проблему, уже реализовано или какой-либо инструмент / пакет или что-то, что позволяет такие вычисления?

Я также охотно узнаю о решениях на других языках программирования, если они позволят решить эту проблему более простым или просто каким-либо образом.

1 Ответ

0 голосов
/ 02 июля 2018

Это очень простой пример измерения сегментированной линии, нарисованной на изображении в 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<PointF>, чтобы сохранить точность.

  • При масштабировании, например, увеличив PictureBox, возможно, после его вложения в Panel, убедитесь, что вы сохранили соотношение сторон, равное Image, или выполните полный расчет , чтобы включить дополнительное пространство слева или сверху; в SizeMode.Normal изображение всегда будет стоять на одном уровне TopLeft , но в других режимах это не всегда будет так.

  • Для расчета фактических, то есть физических расстояний, просто поделите на фактическое значение dpi.

Посмотрим, что у нас в действии:

enter image description here

Обновление:

Чтобы получить возможность подгонки клёров и повышения точности, нам, очевидно, нужно увеличить изображение.

Вот необходимые изменения:

Мы добавляем список «плавающих точек»:

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;, которая устанавливается вместе с Clientsize в графическом окне в событии Scroll трекбара:

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

Picturebox вложен в Panel с включенным AutoScroll. Теперь мы можем увеличивать и масштабировать при добавлении сегментов:

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...