Написание надежного (неизменного по размеру) обнаружения круга (водораздел)

Изменить: Краткое резюме: я использую алгоритм водораздела, но у меня, вероятно, проблема с порогом. Он не обнаружил более ярких кругов.

Новое: метод быстрого преобразования радиальной симметрии, который тоже не совсем работал (Редактировать 6).


Я хочу обнаружить круги разных размеров. Вариант использования — обнаружение монет на изображении и извлечение только их. -> Получить отдельные монеты в виде отдельных файлов изображений.

Для этого я использовал преобразование Hough Circle для open-cv: ">https://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.html)

import sys
import cv2 as cv
import numpy as np


def main(argv):
    ## [load]
    default_file =  "data/newcommon_1euro.jpg"
    filename = argv[0] if len(argv) > 0 else default_file

    # Loads an image
    src = cv.imread(filename, cv.IMREAD_COLOR)

    # Check if image is loaded fine
    if src is None:
        print ('Error opening image!')
        print ('Usage: hough_circle.py [image_name -- default ' + default_file + '] \n')
        return -1
    ## [load]

    ## [convert_to_gray]
    # Convert it to gray
    gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
    ## [convert_to_gray]

    ## [reduce_noise]
    # Reduce the noise to avoid false circle detection
    gray = cv.medianBlur(gray, 5)
    ## [reduce_noise]

    ## [houghcircles]
    rows = gray.shape[0]
    circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, rows / 8,
                           param1=100, param2=30,
                           minRadius=0, maxRadius=120)
    ## [houghcircles]

    ## [draw]
    if circles is not None:
        circles = np.uint16(np.around(circles))
        for i in circles[0, :]:
            center = (i[0], i[1])
            # circle center
            cv.circle(src, center, 1, (0, 100, 100), 3)
            # circle outline
            radius = i[2]
            cv.circle(src, center, radius, (255, 0, 255), 3)
    ## [draw]

    ## [display]
    cv.imshow("detected circles", src)
    cv.waitKey(0)
    ## [display]

    return 0

if __name__ == "__main__":
    main(sys.argv[1:])

Я попробовал все параметры (строки, param1, param2, minRadius и maxRadius), чтобы оптимизировать результаты. Это работало очень хорошо для одного конкретного изображения, но другие изображения с монетами разного размера не работали.

Примеры: параметры circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, rows / 16, param1=100, param2=30, minRadius=0, maxRadius=120) введите здесь описание изображения

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

Изменено на rows/8 введите здесь описание изображения

Я также попробовал два других подхода к этой теме: ">написание надежного (без изменения цвета и размера) обнаружения кругов с помощью opencv (на основе преобразования Хафа или других функций)

Подход fireant приводит к такому результату: введите здесь описание изображения

Метод фракселя тоже не сработал.

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

Спасибо заранее за любую помощь!

Изменить:

Я попробовал алгоритм водораздела Open-cv, предложенный Александром Рейнольдсом: https://docs.opencv.org/3.4/d3/db4/tutorial_py_watershed.html

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('data/P1190263.jpg')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)

# noise removal
kernel = np.ones((3,3),np.uint8)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)

# sure background area
sure_bg = cv.dilate(opening,kernel,iterations=3)

# Finding sure foreground area
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)

# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg,sure_fg)

# Marker labelling
ret, markers = cv.connectedComponents(sure_fg)

# Add one to all labels so that sure background is not 0, but 1
markers = markers+1

# Now, mark the region of unknown with zero
markers[unknown==255] = 0

markers = cv.watershed(img,markers)
img[markers == -1] = [255,0,0]

#Display:
cv.imshow("detected circles", img)
cv.waitKey(0)

Он очень хорошо работает на тестовом изображении веб-сайта open-cv:

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

Но на моих собственных изображениях он работает очень плохо: введите здесь описание изображения

Я не могу придумать вескую причину, почему это не работает с моими изображениями?

Редактировать 2:

Как было предложено, я посмотрел на промежуточные изображения. На мой взгляд, thresh выглядит не очень хорошо. Далее, нет никакой разницы между opening и dist_transform. Соответствующий sure_fg показывает обнаруженные изображения.

thresh: thresh открытие: opening dist_transform: dist_transform sure_bg: sure_bgsure_fg: sure_fg

Редактировать 3:

Я перепробовал все типы расстояний и размеры масок, которые смог найти, но результаты были совершенно одинаковыми (https://www.tutorialspoint.com/opencv/opencv_distance_transformation.htm)

Редактировать 4:

Кроме того, я попытался изменить (первую) пороговую функцию. Я использовал другие пороговые значения вместо функции OTSU. Лучше всего было с 160, но это было далеко не хорошо:

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

В учебнике это выглядит так: введите здесь описание изображения

Похоже, что монеты какие-то слишком яркие, чтобы их обнаруживал этот алгоритм, но я не знаю, как его улучшить?

Редактировать 5:

Изменение общей контрастности и яркости изображения (с помощью cv.convertScaleAbs) не улучшило результаты. Однако увеличение контраста должно увеличить «разницу» между передним планом и фоном, по крайней мере, на обычном изображении. Но стало даже хуже. Соответствующее пороговое изображение не улучшилось (не стало больше белых пикселей).

Изменить 6: я попробовал другой подход, быстрое преобразование радиальной симметрии (отсюда https://github.com/ceilab/frst_python)

import cv2
import numpy as np


def gradx(img):
    img = img.astype('int')
    rows, cols = img.shape
    # Use hstack to add back in the columns that were dropped as zeros
    return np.hstack((np.zeros((rows, 1)), (img[:, 2:] - img[:, :-2]) /     2.0, np.zeros((rows, 1))))


def grady(img):
    img = img.astype('int')
    rows, cols = img.shape
    # Use vstack to add back the rows that were dropped as zeros
    return np.vstack((np.zeros((1, cols)), (img[2:, :] - img[:-2, :]) / 2.0, np.zeros((1, cols))))


# Performs fast radial symmetry transform
# img: input image, grayscale
# radii: integer value for radius size in pixels (n in the original     paper); also used to size gaussian kernel
# alpha: Strictness of symmetry transform (higher=more strict; 2 is good place to start)
# beta: gradient threshold parameter, float in [0,1]
# stdFactor: Standard deviation factor for gaussian kernel
# mode: BRIGHT, DARK, or BOTH
def frst(img, radii, alpha, beta, stdFactor, mode='BOTH'):
    mode = mode.upper()
    assert mode in ['BRIGHT', 'DARK', 'BOTH']
    dark = (mode == 'DARK' or mode == 'BOTH')
    bright = (mode == 'BRIGHT' or mode == 'BOTH')

    workingDims = tuple((e + 2 * radii) for e in img.shape)

    # Set up output and M and O working matrices
    output = np.zeros(img.shape, np.uint8)
    O_n = np.zeros(workingDims, np.int16)
    M_n = np.zeros(workingDims, np.int16)

    # Calculate gradients
    gx = gradx(img)
    gy = grady(img)

    # Find gradient vector magnitude
    gnorms = np.sqrt(np.add(np.multiply(gx, gx), np.multiply(gy, gy)))

    # Use beta to set threshold - speeds up transform significantly
    gthresh = np.amax(gnorms) * beta

    # Find x/y distance to affected pixels
    gpx = np.multiply(np.divide(gx, gnorms, out=np.zeros(gx.shape), where=gnorms != 0),         
    radii).round().astype(int);
    gpy = np.multiply(np.divide(gy, gnorms, out=np.zeros(gy.shape), where=gnorms != 0),     
    radii).round().astype(int);

    # Iterate over all pixels (w/ gradient above threshold)
    for coords, gnorm in np.ndenumerate(gnorms):
        if gnorm > gthresh:
            i, j = coords
            # Positively affected pixel
            if bright:
                ppve = (i + gpx[i, j], j + gpy[i, j])
                O_n[ppve] += 1
                M_n[ppve] += gnorm
            # Negatively affected pixel
            if dark:
                pnve = (i - gpx[i, j], j - gpy[i, j])
                O_n[pnve] -= 1
                M_n[pnve] -= gnorm

    # Abs and normalize O matrix
    O_n = np.abs(O_n)
    O_n = O_n / float(np.amax(O_n))

    # Normalize M matrix
    M_max = float(np.amax(np.abs(M_n)))
    M_n = M_n / M_max

    # Elementwise multiplication
    F_n = np.multiply(np.power(O_n, alpha), M_n)

    # Gaussian blur
    kSize = int(np.ceil(radii / 2))
    kSize = kSize + 1 if kSize % 2 == 0 else kSize

    S = cv2.GaussianBlur(F_n, (kSize, kSize), int(radii * stdFactor))

    return S


img = cv2.imread('data/P1190263.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

result = frst(gray, 60, 2, 0, 1, mode='BOTH')

cv2.imshow("detected circles", result)
cv2.waitKey(0)

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


person chris    schedule 11.09.2018    source источник
comment
Не рекомендовал бы обнаружение круга Хафа, поскольку он не особенно надежен и имеет слишком много гиперпараметров. Учебный пример алгоритма водораздела OpenCV показывает точно такую ​​же задачу с гораздо лучшими результатами (и в целом это гораздо более надежный алгоритм): docs.opencv.org/3.4/d3/db4/tutorial_py_watershed.html   -  person alkasm    schedule 11.09.2018
comment
Кроме того, если все ваши изображения такие, вы можете буквально просто сделать это с приличным порогом или, возможно, с некоторыми floodFill()ing с отличными результатами.   -  person alkasm    schedule 11.09.2018
comment
Большое спасибо за ваш ответ! Я попробовал алгоритм водораздела Open-cv, но он не очень хорошо работает с моими собственными изображениями (как вы можете видеть в вопросе). У вас есть идеи, почему?   -  person chris    schedule 11.09.2018
comment
Вы смотрели на изображение thresh и другие промежуточные изображения? thresh должно быть бинарным изображением, на котором видны все монеты. Я думаю, что это может быть не так для вашего изображения, так как вы находите только более темные монеты.   -  person Cris Luengo    schedule 11.09.2018
comment
Теперь я посмотрел на промежуточные изображения и добавил их в вопрос. В частности, 'dist_transform' выглядит не так, как я думаю.   -  person chris    schedule 11.09.2018
comment
Когда я пытался оптимизировать функции, действительно похоже, что пороговая функция не работает должным образом. Но у меня нет идеи, чтобы улучшить его? Я уже пробовал другие пороговые значения и функции. Может быть, если я увеличу контрастность изображений (так что-то в этом роде) перед тем, как запустить его через пороговую функцию? Поскольку монеты кажутся слишком похожими на фон.   -  person chris    schedule 12.09.2018
comment
Однажды я пытался использовать opencv для обнаружения клеток на микроскопических изображениях. В итоге я использовал его для создания помеченных наборов данных (путем ручной настройки параметров примерно для 100 изображений) и использовал эти изображения для обучения коннета. Потребовалось некоторое время и GPU. В итоге хорошо поработали. Основное преимущество заключалось в том, что не нужно было заниматься проектированием функций.   -  person Moritz    schedule 19.09.2018
comment
Я примерно так же пытаюсь. Как вы уже видели, я хочу обнаружить отдельные монеты на изображении с несколькими монетами. После этого их следует разлучить. Я хочу поместить каждую из отдельных монет в Convnet. Было бы здорово, если бы вы могли поделиться своим опытом разделения ячеек @Moritz. Возможно, это работает и для монет.   -  person chris    schedule 20.09.2018
comment
Это была утомительная процедура и требовала много ручной работы. Я использовал ImageJ и поиграл с порогом для каждого изображения, чтобы не было слишком много ложных срабатываний. После этого я очистил результаты вручную и использовал их в качестве тренировочного набора. По крайней мере для меня (не эксперта) это был самый эффективный способ. Я не знаю, хорошая ли это идея, но в вашем случае вы можете попробовать сопоставление шаблонов вместе с циклом: scikit-image.org/docs/dev/auto_examples/features_detection/ и пороговое значение   -  person Moritz    schedule 20.09.2018
comment
Жаль, что я не видел обновлений для этого, очень хорошая попытка попробовать все эти разные подходы. У меня есть некоторые идеи, но вы не предоставили исходные изображения. Если вы все еще работаете над этим, не могли бы вы прокомментировать несколько изображений для начала? Ваше исходное изображение thresh из подхода водораздела на самом деле выглядит довольно хорошо, если не считать нескольких маленьких дырок. Заполните их! Сначала сделайте морфологическое закрытие, которое должно дать вам (в основном) все белые круги. Или используйте floodFill(), чтобы закрыть отверстия. См. здесь.   -  person alkasm    schedule 01.10.2018