Вопрос:
Я хотел бы написать приложение, которое будет измерять фрагменты образца, исследованного под микроскопом. Я думал, что лучший способ – захватить изображение и нарисовать отдельные части образца, а затем подсчитать значение рисованной линии в пикселях (а затем преобразовать это значение в соответствующий блок).
Есть ли что-нибудь, что помогает решить уже выпущенную проблему или какой-либо инструмент/пакет или что-то такое, что позволяет такие вычисления?
Я также охотно узнаю о решениях на других языках программирования, если они позволят решить эту проблему более простым способом или просто каким-то образом.
Лучший ответ:
Это очень простой пример измерения сегментированной линии, нарисованной на изображении в 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 чтобы сделать масштабированный дисплей простым. В таком случае я рекомендую хранить не прямые пиксельные местоположения мыши, а значения “unzoomed” и использовать “rezoomed” список значений для отображения. Таким образом, вы можете увеличивать и уменьшать масштаб и по-прежнему удерживать очки в правильных местах.
-
Для этого вы должны использовать List<PointF> для сохранения точности.
-
При масштабировании, например, путем увеличения PictureBox, возможно, после его вложения в Panel, убедитесь, что соотношение сторон равно или равно пропорции Image или для полного вычисления, чтобы включить дополнительное пространство слева или сверху; в 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; который устанавливается вместе с 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»); }
Картинка находится внутри Panel с AutoScroll. Теперь мы можем масштабировать и прокручивать, добавляя сегменты: