Соответствие шаблона: эффективный способ создания маски для minMaxLoc? - PullRequest
0 голосов
/ 31 мая 2018

Соответствие шаблонов в OpenCV великолепно.И вы можете передать маску в cv2.minMaxLoc, чтобы вы искали (как бы) только часть изображения для нужного вам шаблона.Вы также можете использовать маску в операции matchTemplate, но это только маскирует шаблон.

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

Вычисление маски для minMaxLoc кажется довольно сложным.То есть, вычисление точной маски кажется тяжелым.Если вы вычисляете маску простым способом, она игнорирует размер шаблона.

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

clock1 clock1

clock2 clock2

template template

В clock1 моноблок находится внутри круглого циферблатаи это "ПРОЙДЕТ".Но в clock2, моноблок только частично внутри лица, и я хочу, чтобы это было «FAIL».Вот пример кода для того, чтобы сделать это простым способом.Я использую cv.HoughCircles, чтобы найти циферблат часов.

import numpy as np
import cv2

img = cv2.imread('clock1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

template = cv2.imread('template.png')
t_h, t_w = template.shape[0:2]  # template height and width

# find circle in gray image using Hough transform
circles = cv2.HoughCircles(gray, method = cv2.HOUGH_GRADIENT, dp = 1, 
                           minDist  = 150, param1 = 50, param2 = 70,
                           minRadius = 131, maxRadius = 200)
i = circles[0,0]
x0 = i[0]
y0 = i[1]
r  = i[2] 

# display circle on color image
cv2.circle(img,(x0, y0), r,(0,255,0),2)

# do the template match
result = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)

# finally, here is the part that gets tricky. we want to find highest
# rated match inside circle and we'd like to use minMaxLoc

# make mask by drawing circle on zero array
mask = np.zeros(result.shape, dtype = np.uint8)  # minMaxLoc will throw
                                                 # error w/o np.uint8
cv2.circle(mask, (x0, y0), r, color = 1, thickness = -1)

# call minMaxLoc
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result, mask = mask)

# draw found rectangle on img
if max_val > 0.4:  # use 0.4 as threshold for finding candy bar
    cv2.rectangle(img, max_loc, (max_loc[0]+t_w, max_loc[1]+t_h), (0,255,0), 4)

cv2.imwrite('output.jpg', img)

вывод с использованием clock1
output from clock1

вывод с использованием clock2 находит моноблок, хотя частьэто вне круга output from clock2

Поэтому, чтобы правильно сделать маску, я использую кучу операций NumPy.Я делаю четыре отдельные маски (по одной для каждого угла ограничивающей рамки шаблона), а затем И их вместе.Я не знаю каких-либо удобных функций в OpenCV, которые бы сделали маску для меня.Я немного нервничаю, что все операции с массивами будут дорогими.Есть ли лучший способ сделать это?

h, w = result.shape[0:2]

# make arrays that hold x,y coords 
grid = np.indices((h, w))
x = grid[1]
y = grid[0]

top_left_mask  = np.hypot(x - x0, y - y0) - r < 0
top_right_mask = np.hypot(x + t_w - x0, y - y0) - r < 0
bot_left_mask  = np.hypot(x - x0, y + t_h - y0) - r < 0
bot_right_mask = np.hypot(x + t_w - x0, y + t_h - y0) - r < 0

mask = np.logical_and.reduce((top_left_mask, top_right_mask, 
                              bot_left_mask, bot_right_mask))
mask = mask.astype(np.uint8)
cv2.imwrite('mask.png', mask*255)

Вот как выглядит «причудливая» маска:
enter image description here

Кажется, правильно.Он не может быть круглым из-за формы шаблона.Если я запускаю clock2.jpg с этой маской, я получаю: output with fancy mask on clock2.jpg

Это работает.Конфеты не определены.Но я бы хотел сделать это с меньшим количеством строк кода ...

РЕДАКТИРОВАТЬ : Я провел некоторое профилирование.Я выполнил 100 циклов «простого» и «точного» и рассчитал количество кадров в секунду (к / с):

  • , простой способ: 12,7 к / с
  • , точный способ: 7,8 к / с

, поэтому нужно заплатить определенную цену за создание маски с помощью NumPy.Эти тесты были проведены на относительно мощной рабочей станции.На более скромном оборудовании это может быть ужаснее ...

1 Ответ

0 голосов
/ 31 мая 2018

Метод 1: изображение 'mask' перед cv2.matchTemplate

Я просто пытался создать собственную маску изображения, которое я передаю cv2.matchTemplate , чтобы увидеть, чтовид производительности, которого я могу достичь.Чтобы было ясно, это не правильная маска - я установил все пиксели, чтобы игнорировать один цвет (черный или белый).Это делается для того, чтобы обойти тот факт, что только TM_SQDIFF и TM_CORR_NORMED поддерживают правильную маску.

@ Александр Рейнольдс очень хорошо замечает в комментариях, что нужно соблюдать осторожность, если изображение шаблона (то, что мы пытаемся сделать)найти) имеет много черного или много белого.Для многих проблем мы будем знать a priori , как выглядит шаблон, и мы можем указать белый или черный фон.

Я использую cv2.multiply , чтокажется быстрее чем numpy.multiply .cv2.multiply имеет дополнительное преимущество, заключающееся в том, что он автоматически обрезает результаты в диапазоне от 0 до 255.

import numpy as np
import cv2
import time

img = cv2.imread('clock1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

template = cv2.imread('target.jpg')
t_h, t_w = template.shape[0:2]  # template height and width

mask_background = 'WHITE'

start_time = time.time()

for i in range(100):  # do 100 cycles for timing
    # find circle in gray image using Hough transform
    circles = cv2.HoughCircles(gray, method = cv2.HOUGH_GRADIENT, dp = 1, 
                               minDist  = 150, param1 = 50, param2 = 70,
                               minRadius = 131, maxRadius = 200)
    i = circles[0,0]
    x0 = i[0]
    y0 = i[1]
    r  = i[2] 

    # display circle on color image
    cv2.circle(img,(x0, y0), r,(0,255,0),2)

    if mask_background == 'BLACK':  # black = 0, white = 255 on grayscale
        mask = np.zeros(img.shape, dtype = np.uint8)

    elif mask_background == 'WHITE':
        mask = 255*np.ones(img.shape, dtype = np.uint8)

    cv2.circle(mask, (x0, y0), r, color = (1,1,1), thickness = -1)
    img2 = cv2.multiply(img, mask)  # element wise multiplication
                                    # values > 255 are truncated at 255
    # do the template match
    result = cv2.matchTemplate(img2, template, cv2.TM_CCOEFF_NORMED)

    # call minMaxLoc
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

    # draw found rectangle on img
    if max_val > 0.4:
        cv2.rectangle(img, max_loc, (max_loc[0]+t_w, max_loc[1]+t_h), (0,255,0), 4)

fps = 100/(time.time()-start_time)
print('fps ', fps)

cv2.imwrite('output.jpg', img)

Результаты профилирования:

  • ЧЕРНЫЙ фон 12,3 кадра в секунду
  • БЕЛЫЙ фон 12,1 кадра в секунду

При использовании этого метода очень незначительное снижение производительности по сравнению с 12,7 кадрами в секунду в исходном вопросе.Однако у него есть недостаток, заключающийся в том, что он по-прежнему будет находить шаблоны, которые все еще немного прилипают к краю.В зависимости от точного характера проблемы, это может быть приемлемо во многих приложениях.

Метод 2: используйте cv2.boxFilter , чтобы создать маску для minMaxLoc

В этом методеМы начинаем с круговой маски (как в OP), но затем модифицируем ее с помощью cv2.boxFilter.Мы изменяем anchor с центра ядра по умолчанию на верхний левый угол (0, 0)
import numpy as np
import cv2
import time

img = cv2.imread('clock1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

template = cv2.imread('target.jpg')
t_h, t_w = template.shape[0:2]  # template height and width
print('t_h, t_w ', t_h, ' ', t_w)

start_time = time.time()

for i in range(100):
    # find circle in gray image using Hough transform
    circles = cv2.HoughCircles(gray, method = cv2.HOUGH_GRADIENT, dp = 1, 
                               minDist  = 150, param1 = 50, param2 = 70,
                               minRadius = 131, maxRadius = 200)
    i = circles[0,0]
    x0 = i[0]
    y0 = i[1]
    r  = i[2] 

    # display circle on color image
    cv2.circle(img,(x0, y0), r,(0,255,0),2)

    # do the template match
    result = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)

    # finally, here is the part that gets tricky. we want to find highest
    # rated match inside circle and we'd like to use minMaxLoc

    # start to make mask by drawing circle on zero array
    mask = np.zeros(result.shape, dtype = np.float)  
    cv2.circle(mask, (x0, y0), r, color = 1, thickness = -1)

    mask = cv2.boxFilter(mask, 
                         ddepth = -1, 
                         ksize = (t_w, t_h), 
                         anchor = (0,0),
                         normalize = True,
                         borderType = cv2.BORDER_ISOLATED)
    # mask now contains values from zero to 1. we want to make anything
    # less than 1 equal to zero
    _, mask = cv2.threshold(mask, thresh = 0.9999, 
                        maxval = 1.0, type = cv2.THRESH_BINARY)
    mask = mask.astype(np.uint8)

    # call minMaxLoc
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result, mask = mask)

    # draw found rectangle on img
    if max_val > 0.4:
        cv2.rectangle(img, max_loc, (max_loc[0]+t_w, max_loc[1]+t_h), (0,255,0), 4)

fps = 100/(time.time()-start_time)
print('fps ', fps)

cv2.imwrite('output.jpg', img)

Этот код дает маску, идентичную OP, но со скоростью 11,89 кадров в секунду.Этот метод дает нам больше точности при чуть большем увеличении производительности, чем Метод 1 .

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