C # - распознавание черных точек по фотографии - PullRequest
0 голосов
/ 10 мая 2018

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

Это для школьного задания.

Ответы [ 2 ]

0 голосов
/ 11 мая 2018

Основы для обработки данных изображений можно найти в других вопросах , поэтому я не буду вдаваться в подробности, но специально для проверки порогов я бы сделал это, собрав красный, зеленый и синий байты каждого пикселя (как указано в ответе, который я связал), а затем просто объедините их в 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

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

[Редактировать]

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

0 голосов
/ 10 мая 2018

Поскольку это для школьной работы, я предоставлю вам только алгоритм высокого уровня.

Поскольку фон гарантированно будет белым, вам повезло.

Сначала вам нужно определить порог на уровне черного, который вы хотите считать цветом черной точки.

#ffffff чисто белый, а #000000 чисто черный. Я бы посоветовал некоторым, где #383838 быть вашим порогом.

Затем вы создаете двумерный массив bool, чтобы отслеживать, какой пиксель вы уже посетили.

Теперь мы можем начать смотреть на картинку.

Вы читаете пиксель по одному за раз по горизонтали и видите, является ли пиксель> пороговым. Если да, тогда вы делаете DFS или BFS, чтобы найти всю область, где сосед пикселя также> порог.

В ходе этого процесса вы будете отмечать массив bool, который мы создали ранее, чтобы указать, что вы уже посетили пиксель.

, поскольку это точка окружности, вы можете просто взять минимальную, максимальную координаты x и y и вычислить центральную точку.

Как только вы закончите с одной точкой, вы продолжите цикл по пикселю изображения и найдете точки, которые вы не посещали (false в массиве bool)

Поскольку у точек на фотографии есть небольшие точки на краю, который не связан с большой точкой, вам, возможно, придется выполнить некоторые математические операции, чтобы увидеть, является ли радиус> некоторым числом, чтобы считать эту точку действительной. Или вместо соседа с радиусом 1 вы делаете соседнюю BFS / DFS с 5-10 пикселями, чтобы включить те, которые действительно близки к основной точке.

...