Это большая проблема моделирования.У меня есть следующие рекомендации / идеи:
- Разделить изображение на RGB, затем обработать.
- предварительная обработка.
- Динамический поиск параметров.
- Добавить ограничения.
- Будьте уверены в том, что вы пытаетесь обнаружить.
Более подробно:
1: Как отмечено в другихответы, преобразованные прямо в оттенки серого, отбрасывают слишком много информации - любые круги с яркостью, аналогичной фону, будут потеряны.Гораздо лучше рассмотреть цветовые каналы либо изолированно, либо в другом цветовом пространстве.Здесь есть два основных способа: выполнить HoughCircles
на каждом предварительно обработанном канале в отдельности, затем объединить результаты или обработать каналы, затем объединить их и затем выполнить HoughCircles
.В моей попытке ниже, я попробовал второй метод, разделение на каналы RGB, обработка, а затем объединение.Остерегайтесь чрезмерного насыщения изображения при объединении, я использую cv.And
, чтобы избежать этой проблемы (на этом этапе мои круги всегда являются черными кольцами / дисками на белом фоне).
2: Предварительная обработка довольно сложнаи что-то его часто лучше всего поиграть.Я использовал AdaptiveThreshold
, который является действительно мощным методом свертки, который может усиливать края изображения, устанавливая пороговые значения пикселей на основе их локального среднего (аналогичные процессы также происходят на ранних этапах визуальной системы млекопитающих).Это также полезно, так как снижает уровень шума.Я использовал dilate/erode
только с одним проходом.И я сохранил другие параметры, как у вас.Кажется, использование Canny
до HoughCircles
очень помогает в поиске «заполненных кругов», так что, вероятно, лучше сохранить его. Эта предварительная обработка довольно тяжелая и может привести к ложным срабатываниям с несколько более «круглыми кружочками»,но в нашем случае это, возможно, желательно?
3: Как вы отметили, параметр HoughCircles param2
(ваш параметр LOW
) необходимо настроить для каждого изображения, чтобы получить оптимальное решение, вфакт из документов :
Чем оно меньше, тем больше ложных кругов можно обнаружить.
Проблема в том, что сладкое пятно будетбыть разным для каждого изображения.Я думаю, что лучший подход здесь состоит в том, чтобы установить условие и выполнить поиск по различным значениям param2
, пока это условие не будет выполнено.На ваших изображениях показаны непересекающиеся круги, а когда param2
слишком мало, мы обычно получаем множество перекрывающихся кругов.Поэтому я предлагаю поискать:
максимальное количество не перекрывающихся и не содержащихся кругов
Поэтому мы продолжаем вызывать HoughCircles с различными значениями param2
доэто встреченоЯ делаю это в моем примере ниже, просто увеличивая param2
, пока он не достигнет порогового значения.Было бы намного быстрее (и довольно легко сделать), если вы выполняете двоичный поиск, чтобы найти, когда это встречается, но вы должны быть осторожны с обработкой исключений, поскольку opencv часто выдает ошибки для невинно выглядящих значений param2
(вхотя бы на моей установке).Другим условием, с которым нам было бы очень полезно сравнивать, было бы количество кругов.
4: Есть ли еще какие-то ограничения, которые мы можем добавить к модели?Чем больше вещей мы можем сказать нашей модели, тем легче задача, которую мы можем сделать, чтобы обнаружить круги.Например, знаем ли мы:
- Количество кружков.- полезна даже верхняя или нижняя граница.
- Возможные цвета кругов, или фона, или "некруглых".
- Их размеры.
- Где они могут быть на изображении.
5: Некоторые из капель на ваших изображениях можно назвать вольными кругами!Рассмотрим два «некруглых шарика» на вашем втором изображении, мой код не может их найти (хорошо!), Но… если я «сделаю фотошоп», чтобы они были более круглыми, мой код может их найти ...Может быть, если вы хотите обнаружить вещи, которые не являются кругами, лучше использовать другой подход, например Tim Lukins
.
Проблемы
При интенсивной предварительной обработке AdaptiveThresholding
и `Canny 'объекты изображения могут сильно искажаться, что может привести к обнаружению ложных окружностей или неправильным отчетам о радиусах. Например, на большом твердом диске после обработки может появиться кольцо, поэтому HughesCircles может найти внутреннее кольцо. Кроме того, даже документы отмечают, что:
... обычно функция хорошо определяет центры окружностей, однако может не найти правильные радиусы.
Если вам нужно более точное обнаружение радиусов, я предлагаю следующий подход (не реализован):
- На исходном изображении - трассировка лучей от сообщенного центра круга в расширяющемся кресте (4 луча: вверх / вниз / влево / вправо)
- Делать это отдельно в каждом канале RGB
- Объедините эту информацию для каждого канала для каждого луча разумным образом (т. Е. При необходимости отразите, сместите, масштабируйте и т. Д.)
- возьмите среднее значение для первых нескольких пикселей на каждом луче, используйте его, чтобы определить, где происходит значительное отклонение на луче.
- Эти 4 точки являются оценками точек на окружности.
- Используйте эти четыре оценки, чтобы определить более точный радиус и центральное положение (!).
- Это можно обобщить, используя расширяющееся кольцо вместо четырех лучей.
Результаты
Код в конце довольно хорошо работает, эти примеры были сделаны с кодом, как показано:
Обнаруживает все круги на вашем первом изображении:
Как выглядит предварительно обработанное изображение до применения фильтра canny (хорошо видны разноцветные круги):
Обнаруживает все (кроме двоичных объектов) на втором изображении:
Изменено второе изображение (капли округлены, а большой овал сделан более круглым, что улучшает обнаружение), все обнаружено:
Довольно хорошо обнаруживает центры на этой картине Кандинского (я не могу найти концентрические кольца из-за граничных условий).
Код:
import cv
import numpy as np
output = cv.LoadImage('case1.jpg')
orig = cv.LoadImage('case1.jpg')
# create tmp images
rrr=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
ggg=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
bbb=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
processed = cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
storage = cv.CreateMat(orig.width, 1, cv.CV_32FC3)
def channel_processing(channel):
pass
cv.AdaptiveThreshold(channel, channel, 255, adaptive_method=cv.CV_ADAPTIVE_THRESH_MEAN_C, thresholdType=cv.CV_THRESH_BINARY, blockSize=55, param1=7)
#mop up the dirt
cv.Dilate(channel, channel, None, 1)
cv.Erode(channel, channel, None, 1)
def inter_centre_distance(x1,y1,x2,y2):
return ((x1-x2)**2 + (y1-y2)**2)**0.5
def colliding_circles(circles):
for index1, circle1 in enumerate(circles):
for circle2 in circles[index1+1:]:
x1, y1, Radius1 = circle1[0]
x2, y2, Radius2 = circle2[0]
#collision or containment:
if inter_centre_distance(x1,y1,x2,y2) < Radius1 + Radius2:
return True
def find_circles(processed, storage, LOW):
try:
cv.HoughCircles(processed, storage, cv.CV_HOUGH_GRADIENT, 2, 32.0, 30, LOW)#, 0, 100) great to add circle constraint sizes.
except:
LOW += 1
print 'try'
find_circles(processed, storage, LOW)
circles = np.asarray(storage)
print 'number of circles:', len(circles)
if colliding_circles(circles):
LOW += 1
storage = find_circles(processed, storage, LOW)
print 'c', LOW
return storage
def draw_circles(storage, output):
circles = np.asarray(storage)
print len(circles), 'circles found'
for circle in circles:
Radius, x, y = int(circle[0][2]), int(circle[0][0]), int(circle[0][1])
cv.Circle(output, (x, y), 1, cv.CV_RGB(0, 255, 0), -1, 8, 0)
cv.Circle(output, (x, y), Radius, cv.CV_RGB(255, 0, 0), 3, 8, 0)
#split image into RGB components
cv.Split(orig,rrr,ggg,bbb,None)
#process each component
channel_processing(rrr)
channel_processing(ggg)
channel_processing(bbb)
#combine images using logical 'And' to avoid saturation
cv.And(rrr, ggg, rrr)
cv.And(rrr, bbb, processed)
cv.ShowImage('before canny', processed)
# cv.SaveImage('case3_processed.jpg',processed)
#use canny, as HoughCircles seems to prefer ring like circles to filled ones.
cv.Canny(processed, processed, 5, 70, 3)
#smooth to reduce noise a bit more
cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 7, 7)
cv.ShowImage('processed', processed)
#find circles, with parameter search
storage = find_circles(processed, storage, 100)
draw_circles(storage, output)
# show images
cv.ShowImage("original with circles", output)
cv.SaveImage('case1.jpg',output)
cv.WaitKey(0)