OpenCV обнаруживает капли на изображении

Мне нужно найти (и нарисовать прямоугольник)/получить макс и минимальный радиус на изображении. (образцы ниже)

проблема заключается в том, чтобы найти правильные фильтры для изображения, которые позволят преобразовать Canny или Threshold, чтобы выделить капли. то я буду использовать findContours для поиска прямоугольников.

Я пробовал:

  • Threshold - с разным уровнем

  • blur->erode->erode->grayscale->canny

  • измените тон изображения с различными "строками"

и ect. лучший результат - обнаружить кусок (20-30%) капли. и эта информация не позволяет рисовать прямоугольник. также, спасибо за тени, не связанные с точками blob, были обнаружены, что также мешает обнаружить область.

как я понимаю, мне нужно найти счетчик, который имеет жесткий контраст (не гладкий, как в тени). Есть ли способ сделать это с помощью openCV?

Обновление

случаи отдельно: изображение 1, изображение 2, изображение 3, изображение 4, изображение 5, изображение 6, изображение 7, изображение 8, изображение 9, изображение 10, изображение 11, изображение 12

Еще одно обновление

Я считаю, что у blob есть контрастная область на краю. Итак, я попытался сделать преимущество сильнее: я создал 2 gray scale Mat: A and B, применил Gaussian blur для второго - B (чтобы немного уменьшить шум), затем я сделал несколько расчетов: идет каждый пиксель и найдите максимальную разницу между Xi,Yi от "A" и соседними точками от "B":

введите описание изображения здесь

и примените разницу max к Xi,Yi. поэтому я получаю что-то вроде этого:

введите описание изображения здесь

Я на правильном пути? Кстати, могу ли я достичь чего-то подобного с помощью методов OpenCV?

Обновить Image Denoising помогает уменьшить уровень шума, Sobel - выделить контуры, затем Threshold + findContours и custome convexHull получает что-то похожее, которое я ищу, но это не хорошо для некоторых капель.

Ответы

Ответ 1

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

первый результат

второй результат

третий результат

Имейте в виду, что это очень простой подход и только доказывает точку. Это потребует экспериментов, испытаний и доработки. Идея состоит в том, чтобы использовать Sobel и суммировать все полученные пиксели. Это, деленное на размер изображения, должно дать вам базовую оценку высокой частоты. ответ изображения. Теперь, экспериментально, я нашел значения clipLimit для фильтра CLAHE, которые работают в 2 тестовых примерах, и нашел линейную функцию высокая частота. ответ входа с фильтром CLAHE, что дает хорошие результаты.

sobel = get_sobel(img)
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557

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

import cv2
import numpy as np


def unsharp_mask(img, blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
    gaussian = cv2.GaussianBlur(img, (5,5), 0)
    return cv2.addWeighted(img, imgWeight, gaussian, gaussianWeight, 0)


def smoother_edges(img, first_blur_size, second_blur_size = (5,5), imgWeight = 1.5, gaussianWeight = -0.5):
    img = cv2.GaussianBlur(img, first_blur_size, 0)
    return unsharp_mask(img, second_blur_size, imgWeight, gaussianWeight)


def close_image(img, size = (5,5)):
    kernel = np.ones(size, np.uint8)
    return cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)


def open_image(img, size = (5,5)):
    kernel = np.ones(size, np.uint8)
    return cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)


def shrink_rect(rect, scale = 0.8):
    center, (width, height), angle = rect
    width = width * scale
    height = height * scale
    rect = center, (width, height), angle
    return rect


def clahe(img, clip_limit = 2.0):
    clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(5,5))
    return clahe.apply(img)


def get_sobel(img, size = -1):
    sobelx64f = cv2.Sobel(img,cv2.CV_64F,2,0,size)
    abs_sobel64f = np.absolute(sobelx64f)
    return np.uint8(abs_sobel64f)


img = cv2.imread("blobs4.jpg")
# save color copy for visualizing
imgc = img.copy()
# resize image to make the analytics easier (a form of filtering)
resize_times = 5
img = cv2.resize(img, None, img, fx = 1 / resize_times, fy = 1 / resize_times)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# use sobel operator to evaluate high frequencies
sobel = get_sobel(img)
# experimentally calculated function - needs refining
clip_limit = (-2.556) * np.sum(sobel)/(img.shape[0] * img.shape[1]) + 26.557

# don't apply clahe if there is enough high freq to find blobs
if(clip_limit < 1.0):
    clip_limit = 0.1
# limit clahe if there not enough details - needs more tests
if(clip_limit > 8.0):
    clip_limit = 8

# apply clahe and unsharp mask to improve high frequencies as much as possible
img = clahe(img, clip_limit)
img = unsharp_mask(img)

# filter the image to ensure edge continuity and perform Canny
# (values selected experimentally, using trackbars)
img_blurred = (cv2.GaussianBlur(img.copy(), (2*2+1,2*2+1), 0))
canny = cv2.Canny(img_blurred, 35, 95)

# find first contours
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

# prepare black image to draw contours
canvas = np.ones(img.shape, np.uint8)
for c in cnts:
    l = cv2.arcLength(c, False)
    x,y,w,h = cv2.boundingRect(c)
    aspect_ratio = float(w)/h

    # filter "bad" contours (values selected experimentally)
    if l > 500:
        continue
    if l < 20:
        continue
    if aspect_ratio < 0.2:
        continue
    if aspect_ratio > 5:
        continue
    if l > 150 and (aspect_ratio > 10 or aspect_ratio < 0.1):
        continue
    # draw all the other contours
    cv2.drawContours(canvas, [c], -1, (255, 255, 255), 2)

# perform closing and blurring, to close the gaps
canvas = close_image(canvas, (7,7))
img_blurred = cv2.GaussianBlur(canvas, (8*2+1,8*2+1), 0)
# smooth the edges a bit to make sure canny will find continuous edges
img_blurred = smoother_edges(img_blurred, (9,9))
kernel = np.ones((3,3), np.uint8)
# erode to make sure separate blobs are not touching each other
eroded = cv2.erode(img_blurred, kernel)
# perform necessary thresholding before Canny
_, im_th = cv2.threshold(eroded, 50, 255, cv2.THRESH_BINARY)
canny = cv2.Canny(im_th, 11, 33)

# find contours again. this time mostly the right ones
_, cnts, _ = cv2.findContours(canny.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# calculate the mean area of the contours' bounding rectangles
sum_area = 0
rect_list = []
for i,c in enumerate(cnts):
    rect = cv2.minAreaRect(c)
    _, (width, height), _ = rect
    area = width*height
    sum_area += area
    rect_list.append(rect)
mean_area = sum_area / len(cnts)

# choose only rectangles that fulfill requirement:
# area > mean_area*0.6
for rect in rect_list:
    _, (width, height), _ = rect
    box = cv2.boxPoints(rect)
    box = np.int0(box * 5)
    area = width * height

    if(area > mean_area*0.6):
        # shrink the rectangles, since the shadows and reflections
        # make the resulting rectangle a bit bigger
        # the value was guessed - might need refinig
        rect = shrink_rect(rect, 0.8)
        box = cv2.boxPoints(rect)
        box = np.int0(box * resize_times)
        cv2.drawContours(imgc, [box], 0, (0,255,0),1)

# resize for visualizing purposes
imgc = cv2.resize(imgc, None, imgc, fx = 0.5, fy = 0.5)
cv2.imshow("imgc", imgc)
cv2.imwrite("result3.png", imgc)
cv2.waitKey(0)

В целом я думаю, что очень интересная проблема, немного слишком большая, чтобы на нее можно было ответить. Предложенный мной подход должен рассматриваться как дорожный знак, а не полное решение. Основная идея:

  • Адаптивная предварительная обработка.

  • Поиск контуров дважды: для фильтрации, а затем для фактической классификации.

  • Фильтрация блобов на основе их среднего размера.

Спасибо за удовольствие и удачу!

Ответ 2

Вот код, который я использовал:

import cv2
from sympy import Point, Ellipse
import numpy as np
x1='C:\\Users\\Desktop\\python\\stack_over_flow\\XsXs9.png'    
image = cv2.imread(x1,0)
image1 = cv2.imread(x1,1)
x,y=image.shape
median = cv2.GaussianBlur(image,(9,9),0)
median1 = cv2.GaussianBlur(image,(21,21),0)
a=median1-median
c=255-a
ret,thresh1 = cv2.threshold(c,12,255,cv2.THRESH_BINARY)
kernel=np.ones((5,5),np.uint8)
dilation = cv2.dilate(thresh1,kernel,iterations = 1)
kernel=np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(dilation, cv2.MORPH_OPEN, kernel)
cv2.imwrite('D:\\test12345.jpg',opening)
ret,contours,hierarchy =    cv2.findContours(opening,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
c=np.size(contours[:])
Blank_window=np.zeros([x,y,3])
Blank_window=np.uint8(Blank_window)
for u in range(0,c-1):
    if (np.size(contours[u])>200):
        ellipse = cv2.fitEllipse(contours[u])
        (center,axes,orientation) =ellipse
        majoraxis_length = max(axes)
        minoraxis_length = min(axes)
        eccentricity=(np.sqrt(1-(minoraxis_length/majoraxis_length)**2))
        if (eccentricity<0.8):
             cv2.drawContours(image1, contours, u, (255,1,255), 3)
cv2.imwrite('D:\\marked.jpg',image1)

это изображение вывода

Здесь проблема состоит в том, чтобы найти круглый объект рядом с. Это простое решение основано на нахождении эксцентриситета для каждого контура. Обнаружение таких объектов - капля воды.

Ответ 3

У меня есть частичное решение на месте.

ПЕРВОЕ

Я первоначально преобразовал изображение в цветовое пространство HSV и переработал канал значений. При этом я наткнулся на нечто уникальное. Почти у каждого изображения капли имеют крошечное отражение света. Это было четко выделено в канале значений.

После этого я смог получить следующее:

Пример 1:

введите описание изображения здесь

Пример 2:

введите описание изображения здесь

Пример 3:

введите описание изображения здесь

ВТОРОЕ

Теперь нам нужно извлечь местоположение этих точек. Для этого я выполнил обнаружение аномалии на полученном канале инвертированного значения. По аномалии я имею в виду черную точку, присутствующую в них.

Чтобы сделать это, я вычислил медианный канала инвертированного значения. Я выделил значение пикселя в пределах 70% выше и ниже медианы, которое должно рассматриваться как нормальные пиксели. Но каждое значение пикселя, лежащее за пределами этого диапазона, является аномальным. Черные точки прекрасно вписаны.

Пример 1:

введите описание изображения здесь

Пример 2:

введите описание изображения здесь

Пример 3:

введите описание изображения здесь

Для нескольких изображений не получилось.

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

ТРЕТИЙ

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

Отказ от ответственности: этот метод не работает для всех изображений. Вы можете добавить свои предложения к этому.