Сложность в обнаружении внешнего круга с помощью cv2.HoughCircles - PullRequest
2 голосов
/ 05 февраля 2020

Я пытаюсь определить внешнюю границу круглого объекта на изображениях ниже:

image image image image

image image image image

Я пробовал OpenCV Hough Circle, но код работает не для каждого изображения. Я также попытался отрегулировать такие параметры, как minRadius и maxRadius в Hough Circle, но он не работает на каждом изображении.

Цель состоит в том, чтобы обнаружить объект на изображении и обрезать его.

Ожидаемый результат:

image

Исходный код:

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


image = cv2.imread("path to the image i have provided")
r = 600.0 / image.shape[1]
dim = (600, int(image.shape[0] * r))
resized = cv2.resize(image, dim, interpolation = cv2.INTER_AREA)
cv2.imwrite("path to were we want to save downscaled image", resized)


image = cv2.imread('path of downscaled image')
image1 = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image2 = cv2.GaussianBlur(image1, (5, 5), 0)
edged = cv2.Canny(image2, 30, 150)

img = cv2.medianBlur(image2,5)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)

circles = cv2.HoughCircles(edged,cv2.HOUGH_GRADIENT,1,20,
                            param1=50,param2=30,minRadius=200,maxRadius=280)

circles = np.uint16(np.around(circles))

max_circle = max(circles[0,:], key=lambda x:x[2])
# print(max_circle)

# # Create mask
height,width = image1.shape
mask = np.zeros((height,width), np.uint8)


for i in [max_circle]:
    cv2.circle(mask,(i[0],i[1]),i[2],(255,255,255),thickness=-1)  


masked_data = cv2.bitwise_and(image, image, mask=mask)

_,thresh = cv2.threshold(mask,1,255,cv2.THRESH_BINARY)

# Find Contour
contours = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[0]
x,y,w,h = cv2.boundingRect(contours[0])

# Crop masked_data
crop = masked_data[y:y+h,x:x+w]

#Code to close Window
cv2.imshow('OG',image)
cv2.imshow('Cropped ROI',crop)
cv2.imwrite("path to save roi image", crop)
cv2.waitKey(0)
cv2.destroyAllWindows()

Ответы [ 2 ]

2 голосов
/ 06 февраля 2020

Второй ответ : подход, основанный на сегментации цвета.

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

image image image image

image image image image

Этот более простой (но умный) подход предполагает, что барабан в значительной степени находится в одном месте и каждый раз имеет более или менее одинаковые размеры:

  • Чтобы определить приблизительный цвет катушки на изображении, определите список областей интереса (ROI) для выборки пикселей и определите min и max цвет этой области в цветовом пространстве HSV. Местоположение и размер ROI являются значениями, полученными из размера изображения. На изображениях ниже вы можете видеть ROI в виде прямоугольников blue-i sh:

image image image image

  • Как только найдены цвета min и max HSV, можно выполнить пороговую операцию с cv2.inRange() для сегментирования барабана:

image image image image

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

image image image image

  • На этом этапе также можно вычислить ограничивающую рамку для контура и извлечь его точное местоположение, чтобы позже можно было выполнить операцию обрезки и полностью изолировать катушку на изображении:

image image image image

Этот подход работает для КАЖДОГО изображения, предоставленного на вопрос.

Исходный код :

import cv2
import numpy as np
import sys


# initialize global H, S, V values
min_global_h = 179
min_global_s = 255
min_global_v = 255

max_global_h = 0
max_global_s = 0
max_global_v = 0

# load input image from the cmd-line
filename = sys.argv[1]
img = cv2.imread(sys.argv[1])
if (img is None):
    print('!!! Failed imread')
    sys.exit(-1)

# create an auxiliary image for debugging purposes
dbg_img = img.copy()

# initiailize a list of Regions of Interest that need to be scanned to identify good HSV values to threhsold by color
w = img.shape[1]
h = img.shape[0]
roi_w = int(w * 0.10)
roi_h = int(h * 0.10)
roi_list = []
roi_list.append( (int(w*0.25), int(h*0.15), roi_w, roi_h) )
roi_list.append( (int(w*0.25), int(h*0.60), roi_w, roi_h) )

# convert image to HSV color space
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# iterate through the ROIs to determine the min/max HSV color of the reel
for rect in roi_list:
    x, y, w, h = rect
    x2 = x + w
    y2 = y + h
    print('ROI rect=', rect)

    cropped_hsv_img = hsv_img[y:y+h, x:x+w]

    h, s, v = cv2.split(cropped_hsv_img)
    min_h  = np.min(h)
    min_s  = np.min(s)
    min_v  = np.min(v)

    if (min_h < min_global_h):
        min_global_h = min_h

    if (min_s < min_global_s):
        min_global_s = min_s

    if (min_v < min_global_v):
        min_global_v = min_v

    max_h  = np.max(h)
    max_s  = np.max(s)
    max_v  = np.max(v)

    if (max_h > max_global_h):
        max_global_h = max_h

    if (max_s > max_global_s):
        max_global_s = max_s

    if (max_v > max_global_v):
        max_global_v = max_v

    # debug: draw ROI in original image
    cv2.rectangle(dbg_img, (x, y), (x2, y2), (255,165,0), 4) # red


cv2.imshow('ROIs', cv2.resize(dbg_img, dsize=(0, 0), fx=0.5, fy=0.5))
#cv2.waitKey(0)
cv2.imwrite(filename[:-4] + '_rois.png', dbg_img)

# define min/max color for threshold
low_hsv = np.array([min_h, min_s, min_v])
max_hsv = np.array([max_h, max_s, max_v])
#print('low_hsv=', low_hsv)
#print('max_hsv=', max_hsv)

# threshold image by color
img_bin = cv2.inRange(hsv_img, low_hsv, max_hsv)
cv2.imshow('binary', cv2.resize(img_bin, dsize=(0, 0), fx=0.5, fy=0.5))
cv2.imwrite(filename[:-4] + '_binary.png', img_bin)

#cv2.imshow('img_bin', cv2.resize(img_bin, dsize=(0, 0), fx=0.5, fy=0.5))
#cv2.waitKey(0)

# create a mask to store the contour of the reel (hopefully)
mask = np.zeros((img_bin.shape[0], img_bin.shape[1]), np.uint8)
crop_x, crop_y, crop_w, crop_h = (0, 0, 0, 0)

# iterate throw all the contours in the binary image:
#   assume that the first contour with an area larger than 100k belongs to the reel
contours, hierarchy = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
for contourIdx, cnt in enumerate(contours):
    area = cv2.contourArea(contours[contourIdx])
    print('contourIdx=', contourIdx, 'area=', area)

    # draw potential reel blob on the mask (in white)
    if (area > 100000):
        crop_x, crop_y, crop_w, crop_h = cv2.boundingRect(cnt)
        centers, radius = cv2.minEnclosingCircle(cnt)

        cv2.circle(mask, (int(centers[0]), int(centers[1])), int(radius), (255), -1) # fill with white
        break

cv2.imshow('mask', cv2.resize(mask, dsize=(0, 0), fx=0.5, fy=0.5))
cv2.imwrite(filename[:-4] + '_mask.png', mask)

# copy just the reel area into its own image
reel_img = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('reel_img', cv2.resize(reel_img, dsize=(0, 0), fx=0.5, fy=0.5))
cv2.imwrite(filename[:-4] + '_reel.png', reel_img)

# crop the reel to a smaller image
if (crop_w != 0 and crop_h != 0):
    cropped_reel_img = reel_img[crop_y:crop_y+crop_h, crop_x:crop_x+crop_w]
    cv2.imshow('cropped_reel_img', cv2.resize(cropped_reel_img, dsize=(0, 0), fx=0.5, fy=0.5))

    output_filename = filename[:-4] + '_crop.png'
    cv2.imwrite(output_filename, cropped_reel_img)

cv2.waitKey(0)
2 голосов
/ 05 февраля 2020

Первый ответ : подход, основанный на предварительной обработке изображения и выполнении операции adaptiveThreshold.

Могут быть и другие способы решения этой проблемы, которые не основаны на кругах Хафа. Вот результат подхода, который не является:

image image

  • Предварительная обработка изображения! Уменьшение размера изображения и выполнение размытия помогает в сегментации:

image

  • Метод сегментации использует cv2.adaptiveThreshold() для создания двоичного изображения, которое сохраняет наиболее важные объекты: центр катушки и внешний край катушки. Это важный шаг, поскольку нас интересует только то, что существует между этими двумя объектами. Тем не менее, жизнь не идеальна, как и эта сегментация. Тень барабана на столе стала частью обнаруженных бинарных объектов. Кроме того, внешний край не полностью соединен, как вы можете видеть на получающемся изображении справа (посмотрите на верхний левый угол окружности):

image image

  • Чтобы соединить сломанные сегменты, можно выполнить морфологическую операцию:

image

  • Наконец, вся область барабана может быть открыта путем итерации по контуры изображения выше и отбрасывают те, чья площадь больше, чем ожидается для барабана. Полученное двоичное изображение (слева) можно затем использовать в качестве маски для определения местоположения барабана на исходном изображении:

image image

Имейте в виду, что я не пытаюсь найти универсальное решение для вашей проблемы. Я просто показываю, что могут быть другие решения, которые не зависят от Hough Circles.

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

Исходный код:

import cv2
import numpy as np
import sys


img = cv2.imread("test_images/reel.jpg")
if (img is None):
    print('!!! Failed imread')
    sys.exit(-1)

# create output image
output_img = img.copy()

# 1. Preprocess the image: downscale to speed up processing and execute a blur
SCALE_FACTOR = 0.5
smaller_img = cv2.resize(img, dsize=(0, 0), fx=SCALE_FACTOR, fy=SCALE_FACTOR)
blur_img = cv2.medianBlur(smaller_img, 9)
cv2.imwrite('reel1_blur_img.png', blur_img)


# 2. Segment the image to identify the 2 most important contours: the center of the reel and the outter edge
gray_img = cv2.cvtColor(blur_img, cv2.COLOR_BGR2GRAY)
img_bin = cv2.adaptiveThreshold(gray_img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 19, 4)
cv2.imwrite('reel2_img_bin.png', img_bin)

green_mask = np.zeros((img_bin.shape[0], img_bin.shape[1]), np.uint8)
#green_mask = cv2.cvtColor(img_bin, cv2.COLOR_GRAY2RGB) # debug

contours, hierarchy = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
for contourIdx, cnt in enumerate(contours):
    x, y, w, h = cv2.boundingRect(cnt)
    area = cv2.contourArea(contours[contourIdx])
    #print('contourIdx=', contourIdx, 'w=', w, 'h=', h, 'area=', area)

    # filter out tiny segments
    if (area < 5000):
        #cv2.fillPoly(green_mask, pts=[cnt], color=(0, 0, 255)) # red
        continue

    # draw green contour (filled)
    #cv2.fillPoly(green_mask, pts=[cnt], color=(0, 255, 0)) # green
    cv2.fillPoly(green_mask, pts=[cnt], color=(255)) # white

    # debug:
    #cv2.imshow('green_mask', green_mask)
    #cv2.waitKey(0)

cv2.imshow('green_mask', green_mask)
cv2.imwrite('reel2_green_mask.png', green_mask)


# 3. Fix mask: join segments nearby
kernel = np.ones((3,3), np.uint8)
img_dilation = cv2.dilate(green_mask, kernel, iterations=1)
green_mask = cv2.erode(img_dilation, kernel, iterations=1)

cv2.imshow('fixed green_mask', green_mask)
cv2.imwrite('reel3_img.png', green_mask)


# 4. Extract the reel area from the green mask
reel_mask = np.zeros((green_mask.shape[0], green_mask.shape[1]), np.uint8)
#reel_mask = cv2.cvtColor(green_mask, cv2.COLOR_GRAY2RGB) # debug

contours, hierarchy = cv2.findContours(green_mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
for contourIdx, cnt in enumerate(contours):
    x, y, w, h = cv2.boundingRect(cnt)
    area = cv2.contourArea(contours[contourIdx])
    print('contourIdx=', contourIdx, 'w=', w, 'h=', h, 'area=', area)

    # filter out smaller segments
    if (area > 110000):
        #cv2.fillPoly(reel_mask, pts=[cnt], color=(0, 0, 255)) # red
        continue

    # draw green contour (filled)
    #cv2.fillPoly(reel_mask, pts=[cnt], color=(0, 255, 0)) # green
    cv2.fillPoly(reel_mask, pts=[cnt], color=(255)) # white

    # debug:
    #cv2.imshow('reel_mask', reel_mask)
    #cv2.waitKey(0)

cv2.imshow('reel_mask', reel_mask)
cv2.imwrite('reel4_reel_mask.png', reel_mask)


# 5. Draw the reel area on the original image
contours, hierarchy = cv2.findContours(reel_mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
for contourIdx, cnt in enumerate(contours):
    centers, radius = cv2.minEnclosingCircle(cnt)

    # rescale these values back to the original image size
    centers_orig = (centers[0] // SCALE_FACTOR, centers[1] // SCALE_FACTOR)
    radius_orig = radius // SCALE_FACTOR

print('centers=', centers_orig, 'radius=', radius_orig)
cv2.circle(output_img, (int(centers_orig[0]), int(centers_orig[1])), int(radius_orig), (128,0,255), 5) # magenta

cv2.imshow('output_img', output_img)
cv2.imwrite('reel5_output.png', output_img)

# display just the pixels from the original image
larger_reel_mask = cv2.resize(reel_mask, (int(img.shape[1]), int(img.shape[0])))
output_reel_img = cv2.bitwise_and(img, img, mask=larger_reel_mask)

cv2.imshow('output_reel_img', output_reel_img)
cv2.imwrite('reel5_output_reel.png', output_reel_img)
cv2.waitKey(0)

На этом этапе можно использовать larger_reel_mask и вычислить минимальный вмещающий круг, нарисуйте его поверх этой маски, чтобы сделать ее немного больше округлим и позволим нам более точно получить площадь барабана:

image

Но 4 строки кода, которые достигают этого улучшения, я оставляю в качестве упражнения для читателя.

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