Основы для обработки данных изображений можно найти в других вопросах , поэтому я не буду вдаваться в подробности, но специально для проверки порогов я бы сделал это, собрав красный, зеленый и синий байты каждого пикселя (как указано в ответе, который я связал), а затем просто объедините их в Color c = Color.FromArgb(r,g,b)
и проверьте, чтобы он был "темным", используя c.GetBrightness() < brightnessThreshold
.Значение 0,4 было хорошим порогом для вашего тестового изображения.
Вы должны сохранить результат этого обнаружения порога в массиве, в котором каждый элемент является значением, которое указывает, прошла или нет проверка порога.Это означает, что вы можете использовать что-то простое, например, двумерный массив Boolean
с высотой и шириной исходного изображения.
Если у вас уже есть способы сделать все это, тем лучше.Просто убедитесь, что у вас есть какой-то массив, в котором вы можете легко найти результат этой бинаризации.Если метод, который вы используете, дает вам результат в виде изображения, вы, скорее всего, получите простой одномерный байтовый массив, но тогда ваш поиск будет просто иметь формат, подобный imagedata[y * stride + x]
.Это функционально идентично тому, как происходит внутренний поиск в двумерном массиве, поэтому он не будет менее эффективным.
Теперь, как я уже сказал в своем комментарии, реальные вещи здесьбыл бы алгоритм для определения того, какие пиксели должны быть сгруппированы в один «шарик».
Общее использование этого алгоритма состоит в том, чтобы циклически проходить каждый пиксель на изображении, а затем проверять, удаляет ли A) пороги B) его нет ни в одном из существующих обнаруженных BLOB-объектов.Если пиксель соответствует требованиям, создайте новый список всех пикселей, прошедших через пороговые значения, подключенных к этому, и добавьте этот новый список в список обнаруженных больших двоичных объектов.Я использовал класс Point
для сбора координат, делая каждый из моих BLOB-объектов List<Point>
, а мою коллекцию BLOB-объектов * List<List<Point>>
.
Что касается самого алгоритма, то вы делаете две коллекции.очков.Одним из них является полный набор соседних точек, которые вы строите (список точек ), другой - текущий сканируемый край ( текущий список краев ).Текущий список ребер начнется с вашей исходной точки, и следующие шаги будут повторяться, пока в вашем текущем списке ребер есть элементы :
- Добавьте все элементы из текущего списка ребер в полный список точек .
- Создайте новую коллекцию для вашего следующего ребра ( следующий реберный список ).
- Для каждой точки в вашем текущем списке ребер , получите список его непосредственно соседних точек (исключая любые, которые будут выходить за границы изображения), и проверьте всеэти точки, если они сбрасывают порог, и если их еще нет ни в списке точек , ни в списке следующих ребер .Добавьте точки, которые проходят проверку, в следующий список ребер .
- После того, как этот цикл прохождения по текущему списку ребер завершится, замените исходный текущий список ребер по следующему списку ребер .
... и, как я уже говорил, выполняйте эти шаги до тех пор, пока ваш текущий список ребер послеэтот последний шаг не пустой.
Это создаст ребро, которое расширяется до совпадения со всеми пикселями, очищающими порог, и добавит их все в список.В конце концов, когда все соседние пиксели окажутся в основном списке, новый сгенерированный список ребер станет пустым, и алгоритм завершится.Затем вы добавляете свой новый список точек в список больших двоичных объектов, и любые пиксели, которые вы зацикливаете после этого, могут быть обнаружены как уже присутствующие в этих больших двоичных объектах, поэтому алгоритм для них не повторяется.
Есть два способа сделать соседние пункты;вы либо получите четыре очка, либо все восемь.Разница в том, что использование четырех не заставит алгоритм совершать диагональные прыжки, а использование восьми будет.(Дополнительный эффект заключается в том, что один из них заставляет алгоритм расширяться в форме ромба, а другой - в виде квадрата.) Поскольку у вас, кажется, есть несколько рассеянных пикселей вокруг ваших пятен, я советую вам получить все восемь.
Как Стив указал в своем ответе , очень быстрый способ проверки наличия в коллекции точки - создать двумерный массив Boolean
с размерами изображения.Например, Boolean[,] inBlob = new Boolean[height, width];
, который вы синхронизируете со списком фактических точек.Поэтому, когда вы добавляете точку, вы также помечаете позицию [y, x]
в логическом массиве как true
.Это сделает довольно тяжелые проверки типа if (collection.contains(point))
столь же простыми, как if (inBlob[y,x])
, что требует никаких итераций вообще .
У меня был List<Boolean[,]> inBlobs
, который я синхронизировал сList<List<Point>> blobs
Я построил, и в алгоритме расширяющегося фронта я сохранил такой Boolean[,]
как для следующего списка ребер , так и для списка точек (последний из которых был добавлен кinBlobs
в конце).
Как я прокомментировал, как только у вас есть ваши капли, просто зациклите точки внутри них на блоб и получите минимумы и максимумы для X и Y, так что вы получитеграницы капли.Затем просто возьмите средние из них, чтобы получить центр капли.
Дополнительно:
Если все ваши точки гарантированно находятся на значительном расстоянии друг от друга, оченьСамый простой способ избавиться от пикселей с плавающей кромкой - это взять границы краев каждого шарика, расширить их все на определенный порог (для этого я взял 2 пикселя), а затем перебрать эти прямоугольники и проверить, пересекаются ли они, и объединить их.что делать.Класс Rectangle
имеет как IntersectsWith()
для простых проверок, так и статический Rectangle.Inflate
для увеличения размера прямоугольника.
Вы можете оптимизировать использование памяти методом fill с помощьюсохранение только краевых точек (точек сопоставления порогов с несовпадающими соседями в любом из четырех основных направлений) в главном списке.Окончательные границы и, следовательно, центр останутся прежними.Важно помнить, что, исключая кучу точек из списка больших двоичных объектов, вы должны отметить все из них в массиве Boolean[,]
, который используется для проверки уже обработанных пикселей.В любом случае это не требует дополнительной памяти.
Полный алгоритм, включая оптимизацию, в действии на вашей фотографии с использованием 0,4 в качестве порога яркости:
![Detected blobs](https://i.stack.imgur.com/fzicl.png)
Голубой цвет - это обнаруженные капли, красный - обнаруженный контур (с использованием метода, оптимизированного для памяти), а одиночные зеленые пиксели указывают центральные точки всех капель.
[Редактировать]
С тех пор, как я опубликовал это, прошел почти год, и я думаю, что я мог бы также сослаться на реализацию, которую я сделал для .Фактически мне удалось использовать его самостоятельно примерно через месяц после того, как я написал его, когда воссоздали алгоритм сжатия видео старой игры для DOS , в которой использовались фрагментированные кадры различий.