Методы обнаружения известной фигуры / объекта на изображении с использованием OpenCV - PullRequest
7 голосов
/ 26 марта 2020

Моя задача - обнаружить объект на заданном изображении с помощью OpenCV (мне все равно, является ли это реализацией Python или C ++). Объект, показанный ниже в трех примерах, представляет собой черный прямоугольник с пятью белыми прямоугольниками внутри. Все размеры известны.

enter image description here

Тем не менее, поворот, масштаб, расстояние, перспектива, условия освещения, фокус камеры / объектив и фон изображения не известны. Край черного прямоугольника не гарантированно будет полностью видимым, однако перед пятью белыми прямоугольниками ничего не будет - они всегда будут полностью видны. Конечная цель состоит в том, чтобы иметь возможность обнаруживать присутствие этого объекта на изображении, а также поворачивать, масштабировать и обрезать, чтобы показать объект со снятой перспективой. Я довольно уверен, что могу настроить изображение, чтобы обрезать его только до объекта, учитывая его четыре угла. Однако я не настолько уверен, что смогу надежно найти эти четыре угла. В неоднозначных случаях отказ от поиска объекта предпочтительнее, чем неправильная идентификация какой-либо другой функции изображения как объекта.

Используя OpenCV, я нашел следующие методы, однако я чувствую, что могу упустить что-то очевидное. Есть ли еще какие-либо методы или один из них является оптимальным решением?

Контур на основе ребер

Первой идеей было поискать внешний край объекта.

Использование обнаружения края Канни (после масштабирования до известного размера, оттенков серого и размытия по Гауссу), поиск контура, который наилучшим образом соответствует внешней форме объекта. Это касается вопросов перспективы, цвета, размера, но не работает, например, при наличии сложного фона, или если есть что-то похожее на объект в другом месте изображения. Возможно, это можно было бы улучшить с помощью лучшего набора правил для нахождения правильного контура - возможно, включающего пять белых прямоугольников и внешний край.

enter image description here enter image description here

Обнаружение признаков

Следующей идеей было сопоставление с известным шаблоном с использованием обнаружения признаков.

Использование обнаружения признаков ORB, сопоставления дескрипторов и гомографии ( из этого урока ) не получается, я полагаю, потому что обнаруживаемые им функции очень похожи на другие функции в объекте (множество ядер, которые ровно на одну четверть белого и три четверти черного). Однако мне нравится идея сопоставления с известным шаблоном - эта идея имеет смысл для меня. Я полагаю, однако, что, поскольку объект является довольно базовым c геометрически, он может найти много ложных срабатываний на этапе сопоставления объектов.

enter image description here

Параллельные линии

Используя Houghlines или HoughLinesP, ищите равномерно расположенные параллельные линии. Только что начали этот путь, поэтому необходимо изучить лучшие методы для определения порога и т. Д. c. Хотя это выглядит грязно для изображений со сложным фоном, я думаю, что это может работать хорошо, поскольку я могу положиться на тот факт, что белые прямоугольники внутри черного объекта всегда должны быть высококонтрастными, что дает хорошее представление о том, где находятся линии.

enter image description here

'Сканирование штрих-кода'

Моя последняя идея - сканировать изображение построчно в поисках белого или черного рисунка.

Я не запускал этот метод, но идея состоит в том, чтобы взять полосу изображения (под некоторым углом), преобразовать в цветовое пространство HSV и искать регулярный черно-белый рисунок, появляющийся пять раз последовательно в Столбец значения. Эта идея звучит многообещающе для меня, так как я считаю, что она должна игнорировать многие неизвестные переменные.

Мысли

Я рассмотрел ряд учебных пособий по OpenCV, а также вопросы SO, такие как этот , однако, поскольку мой объект довольно геометрически прост, у меня возникают проблемы с реализацией представленных идей.

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

РЕДАКТИРОВАТЬ : Как только углы будут найдены с помощью одного из вышеуказанных методов (или другого метода), я подумываю использовать Hu Moments или функцию matchChapes () OpenCV для удаления любых ложных срабатываний .

EDIT2 : добавлено еще несколько примеров входных изображений в соответствии с просьбой @ Timo


Ответы [ 2 ]

3 голосов
/ 11 апреля 2020

Посмотрев на постановку задачи, я смог вычислить довольно неплохие результаты.

Здесь мы использовали функции KAZE, новый многомасштабный алгоритм обнаружения и описания объектов 2D в нелинейных масштабных пространствах. Предыдущие подходы обнаруживают и описывают объекты на разных уровнях масштаба путем построения или аппроксимации гауссова масштаба масштаба изображения.

Однако размытие по Гауссу не учитывает естественные границы объектов и в равной степени сглаживает как детали, так и шумы, снижая точность локализации и различимость. Напротив, мы обнаруживаем и описываем 2D-объекты в нелинейном масштабном пространстве с помощью нелинейной диффузионной фильтрации. Таким образом, мы можем сделать размытие локально адаптивным к данным изображения, уменьшая шум, но сохраняя границы объекта, получая превосходную точность локализации и различимость.

Нелинейное масштабное пространство построено с использованием эффективных методов аддитивного разделения операторов (AOS) и диффузии с переменной проводимостью. Мы представляем обширную оценку эталонных наборов данных и практическое применение сопоставления на деформируемых поверхностях. Несмотря на то, что наши функции вычисляются несколько дороже, чем SURF из-за конструкции пространства с нелинейным масштабом, но сопоставимы с SIFT, наши результаты показывают шаг вперед в производительности как по обнаружению, так и по сравнению с предыдущими современными методами. .

Вы можете найти больше ссылок в исследовательской работе здесь .


import os, cv2, random
import numpy as np
import matplotlib.pyplot as plt


#show image
def displayImage(input_img, display_title=None):
    im_shape = input_img.shape
    c = 3
    if len(im_shape) >= 3:
        c = im_shape[2]
    if len(im_shape) == 2:
        c = 1

    if c == 3:
        rgb_img = cv2.cvtColor(input_img.copy(), cv2.COLOR_BGR2RGB)
        plt.imshow(rgb_img)
    if c == 1:
        plt.imshow(input_img,cmap='gray')

    plt.axis('off')
    plt.grid(False)
    if not display_title is None:
        plt.title(display_title)
    plt.show()


def featureExtractor(image, fd):
    kpts, desc = fd.detectAndCompute(image, None)
    return kpts, desc


def featureMatching(kpts1, desc1, kpts2, desc2, fd='kaze'):

    if desc1 is None and desc_2 is None:
        print('Empty descriptor')
        return

    if fd == 'akaze':
        # create BFMatcher object
        bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
        # Match descriptors.
        matches = bf.match(desc1, desc2)
        # Sort them in the order of their distance.
        matches = sorted(matches, key = lambda x:x.distance)
        # good_matches = matches[:10]
        return matches
    else:
        # Matching descriptor vectors with a FLANN based matcher
        matcher = cv2.DescriptorMatcher_create(cv2.DescriptorMatcher_FLANNBASED)
        knn_matches = matcher.knnMatch(desc1, desc2, 2)
        # Filter matches using the Lowe's ratio test
        good_matches = []
        for m,n in knn_matches:
            ratio_thresh = 0.7
            if m.distance < ratio_thresh * n.distance:
                good_matches.append(m)
        return good_matches


def reprojectionError(matches, kpts1, kpts2, M):
    ptsA = np.float32([ kpts1[m.queryIdx].pt for m in good_matches ])
    ptsA_ = ptsA.reshape(-1,1,2)
    ptsB = np.float32([ kpts2[m.trainIdx].pt for m in good_matches ])

    ptsB_ = cv2.perspectiveTransform(ptsA_, M)
    ptsB_ = ptsB_.reshape(ptsB.shape)
    reproj_err = 0.

    for i in range(len(ptsB)):
        delx = ptsB[i][0] - ptsB_[i][0]
        delx *= delx

        dely = ptsB[i][1] - ptsB_[i][1]
        dely *= dely

        reproj_err += delx + dely
        reproj_err = np.sqrt(reproj_err)
        #   print 'reprojection error:', reproj_err
    reproj_err /= float(len(ptsB))
    return reproj_err


def drawMatches(img1, img2, good_matches, kpts1, desc1, kpts2, desc2):
    src_pts = np.float32([ kpts1[m.queryIdx].pt for m in good_matches ]).reshape(-1,1,2)
    dst_pts = np.float32([ kpts2[m.trainIdx].pt for m in good_matches ]).reshape(-1,1,2)

    M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
    if M is not None:
        matchesMask = mask.ravel().tolist()
        h,w = img1.shape[:2]
        pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)

        dst = cv2.perspectiveTransform(pts,M)
        dst += (w, 0)  # adding offset

        draw_params = dict(matchColor = (0,0,255), # draw matches in green color
                        singlePointColor = None,
                        matchesMask = matchesMask, # draw only inliers
                        flags = 2)

        result = cv2.drawMatches(img1, kpts1, img2, kpts2, good_matches, None,**draw_params)
        # Draw bounding box in Red
        cv2.polylines(result, [np.int32(dst)], True, (0,0,255),3, cv2.LINE_AA)
        displayImage(result, 'result')
        return M


fd = {
    'kaze': cv2.KAZE_create(),
    'akaze': cv2.AKAZE_create()
}
key = 'akaze'

detect = 'path/to/cropped/template/of/the/object/to/be/detected'
target = 'path/to/target/image/where/the/object/to/be/detected'

template = cv2.imread(detect)
scene = cv2.imread(target)

# extract features form the template image
kpts1, desc1  = featureExtractor(template, fd[key])
# extract features form the scene image
kpts2, desc2 = featureExtractor(scene, fd[key])

good_matches = featureMatching(kpts1, desc1, kpts2, desc2, key)

if good_matches is not None:
    M = drawMatches(scene, template, good_matches, kpts1, desc1, kpts2, desc2)
    reprojection_error = reprojectionError(good_matches, kpts1, kpts2, M)
    print(f'Reprojection error: {reprojection_error}')

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

Результаты:

enter image description here

2 голосов
/ 27 марта 2020

Я некоторое время изучал проблему и сделал небольшой python скрипт. Я обнаруживаю белые прямоугольники внутри твоей фигуры. Вставьте код в файл .py и скопируйте все входные изображения в подпапку input . Конечный результат изображения - просто фиктивный атм, а сценарий еще не завершен. Я постараюсь продолжить это в ближайшие пару дней. Сценарий создаст подпапку debug , в которой будут сохранены некоторые изображения, отображающие текущее состояние обнаружения.

import numpy as np
import cv2
import os

INPUT_DIR = 'input'
DEBUG_DIR = 'debug'
OUTPUT_DIR = 'output'
IMG_TARGET_SIZE = 1000

# each algorithm must return a rotated rect and a confidence value [0..1]: (((x, y), (w, h), angle), confidence)

def main():
    # a list of all used algorithms
    algorithms = [rectangle_detection] 

    # load and prepare images
    files = list(os.listdir(INPUT_DIR))
    images = [cv2.imread(os.path.join(INPUT_DIR, f), cv2.IMREAD_GRAYSCALE) for f in files]
    images = [scale_image(img) for img in images]

    for img, filename in zip(images, files):
        results = [alg(img, filename) for alg in algorithms]
        roi, confidence = merge_results(results)

        display = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
        display = cv2.drawContours(display, [cv2.boxPoints(roi).astype('int32')], -1, (0, 230, 0))            
        cv2.imshow('img', display)
        cv2.waitKey()


def merge_results(results):
    '''Merges all results into a single result.'''
    return max(results, key=lambda x: x[1]) 

def scale_image(img):    
    '''Scales the image so that the biggest side is IMG_TARGET_SIZE.'''
    scale = IMG_TARGET_SIZE / np.max(img.shape)
    return cv2.resize(img, (0,0), fx=scale, fy=scale)     


def rectangle_detection(img, filename):    
    debug_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    _, binarized = cv2.threshold(img, 50, 255, cv2.THRESH_BINARY)    
    contours, _ = cv2.findContours(binarized, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

    # detect all rectangles
    rois = []
    for contour in contours:
        if len(contour) < 4:
            continue
        cont_area = cv2.contourArea(contour)
        if not 1000 < cont_area < 15000: # roughly filter by the volume of the detected rectangles
            continue
        cont_perimeter = cv2.arcLength(contour, True)
        (x, y), (w, h), angle = rect = cv2.minAreaRect(contour)
        rect_area = w * h
        if cont_area / rect_area < 0.8: # check the 'rectangularity'
            continue        
        rois.append(rect)

    # save intermediate results in the debug folder
    rois_img = cv2.drawContours(debug_img, contours, -1, (0, 0, 230))
    rois_img = cv2.drawContours(rois_img, [cv2.boxPoints(rect).astype('int32') for rect in rois], -1, (0, 230, 0))
    save_dbg_img(rois_img, 'rectangle_detection', filename, 1)

    # todo: detect pattern

    return rois[0], 1.0 # dummy values


def save_dbg_img(img, folder, filename, index=0):
    '''Writes the given image to DEBUG_DIR/folder/filename_index.png.'''
    folder = os.path.join(DEBUG_DIR, folder)
    if not os.path.exists(folder):
        os.makedirs(folder)
    cv2.imwrite(os.path.join(folder, '{}_{:02}.png'.format(os.path.splitext(filename)[0], index)), img)


if __name__ == "__main__":
    main()

Вот пример изображения текущего WIP

1

Следующим шагом является выявление закономерности / отношения между прямоугольниками с множеством элементов. Я обновлю этот ответ, когда добьюсь прогресса.

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