Написание надежного (размерно инвариантного) круга обнаружения (Watershed) - PullRequest
0 голосов
/ 11 сентября 2018

Редактировать: Краткое резюме на данный момент: я использую алгоритм водораздела, но у меня, вероятно, проблема с порогом. Яркие круги не были обнаружены.

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


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

Для этого я использовал Hough Circle Transform 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) enter image description here

с такими же параметрами: enter image description here

Изменено на строк / 8 enter image description here

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

Подход пожарного приводит к такому результату: enter image description here

Подход Фракселя тоже не сработал.

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

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

Edit:

Я попробовал алгоритм водораздела 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:

enter image description here

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

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

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

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

молотить: thresh открытие: opening dist_transform: dist_transform sure_bg: sure_bg sure_fg: sure_fg

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

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

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

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

enter image description here enter image description here

В уроке это выглядит так: enter image description here

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

Редактировать 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)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...