Изменить: Краткое резюме: я использую алгоритм водораздела, но у меня, вероятно, проблема с порогом. Он не обнаружил более ярких кругов.
Новое: метод быстрого преобразования радиальной симметрии, который тоже не совсем работал (Редактировать 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)
Я также попробовал два других подхода к этой теме: ">написание надежного (без изменения цвета и размера) обнаружения кругов с помощью 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: открытие:
dist_transform:
sure_bg:
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)
Я получаю только этот почти черный результат (у него есть очень темно-серые тени). Я не знаю, что изменить, и был бы благодарен за помощь!
floodFill()
ing с отличными результатами. - person alkasm   schedule 11.09.2018thresh
и другие промежуточные изображения?thresh
должно быть бинарным изображением, на котором видны все монеты. Я думаю, что это может быть не так для вашего изображения, так как вы находите только более темные монеты. - person Cris Luengo   schedule 11.09.2018thresh
из подхода водораздела на самом деле выглядит довольно хорошо, если не считать нескольких маленьких дырок. Заполните их! Сначала сделайте морфологическое закрытие, которое должно дать вам (в основном) все белые круги. Или используйтеfloodFill()
, чтобы закрыть отверстия. См. здесь. - person alkasm   schedule 01.10.2018