Редактировать: Краткое резюме на данный момент: я использую алгоритм водораздела, но у меня, вероятно, проблема с порогом. Яркие круги не были обнаружены.
Новое: метод быстрого преобразования радиальной симметрии, который не вполне сработал (Редактировать 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)
с такими же параметрами:
Изменено на строк / 8
Я также попробовал два других подхода этой темы: написание надежного (инвариантного цвета и размера) определения круга с помощью opencv (на основе преобразования Хафа или других функций)
Подход пожарного приводит к такому результату:
Подход Фракселя тоже не сработал.
Для первого подхода: это происходит со всеми различными размерами, а также с минимальным и максимальным радиусом.
Как я могу изменить код, чтобы размер монеты не имел значения или чтобы он сам находил параметры?
Заранее спасибо за любую помощь!
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:
Но он очень плохо работает на моих собственных изображениях:
Не могу придумать вескую причину, почему она не работает с моими изображениями?
Редактировать 2:
Как и предполагалось, я посмотрел на промежуточные изображения. thresh
выглядит не очень хорошо по моему мнению. Далее, нет никакой разницы между opening
и dist_transform
. Соответствующее sure_fg
показывает обнаруженные изображения.
молотить:
открытие:
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)
Я получаю только этот почти черный вывод (у него есть очень темно-серые тени). Я не знаю, что изменить, и буду благодарен за помощь!