Неадекватное преобразование, вызванное хорошим совпадением гомографических признаков

Я пытаюсь определить метод поворота и преобразования отсканированного 2D-изображения, чтобы он соответствовал почти идентичному цифровому шаблону более низкого качества. После запуска кода я хочу, чтобы изображения при наложении были очень точно выровнены, чтобы к скану можно было применить эффекты постобработки на основе знания макета шаблона.

Я испробовал несколько различных подходов, основанных на определении определенных функций на отсканированном изображении, которые можно последовательно сопоставить с шаблоном (не очень повезло с подходом, основанным на findContour), но в конечном итоге решил, что наиболее эффективным подходом является выполнение гомографическое совпадение с использованием openCV, затем примените преобразование (либо с помощьюspectiveTransform, либо warpPerspective).

Матч, который я получаю, феноменально хорош. Даже когда я делаю порог расстояния для совпадений чрезвычайно строгим, я получаю десятки совпадений по очкам. Я немного изменил порог и findHomography RANSAC. Но в конечном итоге преобразование, которое я получаю от findHomography, недостаточно хорошо для моих нужд; Я не уверен, есть ли ручки, которые я не изучаю должным образом, или несоответствие в качестве изображения достаточно, чтобы это было невыполнимо.

Вот код, который я использую:

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


def feature_match(scanned_image, template_image, MIN_MATCH_COUNT=10, dist_thresh=0.2, RANSAC=10.0):
    # Initiate SIFT detector
    sift = cv.SIFT_create()

    # find the keypoints and descriptors with SIFT
    kp1, des1 = sift.detectAndCompute(scanned_image, None)
    kp2, des2 = sift.detectAndCompute(template_image, None)
    FLANN_INDEX_KDTREE = 1
    index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    search_params = dict(checks=50)
    flann = cv.FlannBasedMatcher(index_params, search_params)
    matches = flann.knnMatch(des1, des2, k=2)

    # store all the good matches as per Lowe's ratio test.
    good = []
    for m, n in matches:
        if m.distance < dist_thresh * n.distance:
            good.append(m)

    # Do we have enough?
    if len(good) > MIN_MATCH_COUNT:
        print("%s good matches using distance threshold of %s" % (len(good), dist_thresh))
        src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
        dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
        M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, RANSAC)
        matchesMask = mask.ravel().tolist()

        # Apply warp perspective based on homography matrix
        warped_image = cv.warpPerspective(scanned_image, M, (scanned_image.shape[1], scanned_image.shape[0]))
        plt.imshow(warped_image, 'gray'), plt.show()

    else:
        print("Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT))
        matchesMask = None

    # Show quality of matches
    draw_params = dict(matchColor=(0, 255, 0),  # draw matches in green color
                       singlePointColor=None,
                       matchesMask=matchesMask,  # draw only inliers
                       flags=2)
    match_quality = cv.drawMatches(scanned_image, kp1, template_image, kp2, good, None, **draw_params)
    plt.imshow(match_quality, 'gray'), plt.show()

    cv.imwrite(r"img1.png", cv.cvtColor(img1, cv.COLOR_GRAY2RGB))
    cv.imwrite(r"img2.png", cv.cvtColor(img2, cv.COLOR_GRAY2RGB))
    cv.imwrite(r"warped_image.png", cv.cvtColor(warped_image, cv.COLOR_GRAY2RGB))

# Load images
img1_path = r"scanned_image.png"
img2_path = r"template_image.png"

img1 = cv.imread(img1_path)
img1 = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)

img2 = cv.imread(img2_path)
img2 = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
# upscaling img2 to the final scale I'm ultimately after; saves an upscale
img2 = cv.resize(img2, (img2.shape[1] * 2, img2.shape[0] * 2), cv.IMREAD_UNCHANGED)

feature_match(scanned_image=img1, template_image=img2, MIN_MATCH_COUNT=10, dist_thresh=0.2)

Вот изображения, которые я использую:

Отсканированное изображение

Изображение шаблона

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

совпадения между отсканированным и шаблонным изображением

Используя порог расстояния 0,2, я получаю 100 совпадений. Установив около 0,8, я получаю более 2400

отсканированное изображение преобразовано в шаблон с использованием возвращенной гомографической матрицы

Искаженный скан, наложенный поверх шаблона

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

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

Вы можете попробовать сопоставление ECC. См. docs.opencv.org/4.1.1/dc/d6b/… . Также Learnopencv.com/image-alignment-ecc-in-opencv-c-python и amroamroamro.github.io/mexopencv/opencv/… и stackoverflow.com/questions/45997891/…. Найдите в Google дополнительные примеры. Вы также можете использовать SIFT warp в качестве первого этапа, чтобы приблизиться, а затем ECC для уточнения.

fmw42 20.12.2022 20:10

Включает ли sift.detectAndCompute интерполяцию положения ключевой точки субпикселя? Если нет, есть ли параметр для его активации? Сколько зарегистрированных возрастов? Можете ли вы вручную определить лучшую трансформацию, затрачивая много ручного времени? Если нет, может ли быть какое-то искажение объектива на любом из изображений? Например, какова точность используемого сканера?

Micka 20.12.2022 20:22

@Micka Полученные точки имеют субпиксельную точность. Это точки, которые вводятся в findHomography. Окончательное зарегистрированное изображение в этом случае отличается примерно на 10 пикселей и кажется недостаточно «растянутым», чтобы его можно было выровнять. Я мог бы сделать преобразование вручную в PS, но это то, что я пытаюсь сделать здесь; вместо этого автоматизируйте подход. Я не думаю, что искажение объектива является проблемой, и даже если это было искажение, оно должно исправить это, подумал я.

ConcernedAboutOwls 20.12.2022 21:56

@ fmw42 Это кажется очень многообещающим подходом. Я попробую. Спасибо!

ConcernedAboutOwls 20.12.2022 21:58
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
4
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы ответить на мой собственный вопрос на случай, если у кого-то таинственным образом возникнет аналогичная проблема в будущем: важно убедиться, что когда вы применяете свою матрицу гомографии, ваш целевой размер соответствует шаблону, который вы пытаетесь сопоставить, если вы ищете получить «точное» совпадение с указанным шаблоном.

В моем оригинале у меня было:

warped_image = cv.warpPerspective(scanned_image, M, (scanned_image.shape[1], scanned_image.shape[0]))

Должно было быть так:

warped_image = cv.warpPerspective(scanned_image, M, (template_image.shape[1], template_image.shape[0]))

Между размерами scanned_image и template_image есть небольшие различия в масштабе; хотя они близки, этих незначительных различий достаточно, да, проецирование на неправильный размер исказит выравнивание при их непосредственном сравнении.

Эта обновленная версия не идеальна, но, вероятно, достаточно близка к моим потребностям. Я подозреваю, что описанный @ fmw42 подход к сопоставлению ECC / методы лечения второго этапа были бы хорошим вторым проходом. Я рассмотрю это и отредактирую этот ответ, если они достаточно существенны, чтобы их стоило изучить, если кто-то будет иметь дело с подобными вещами в будущем.

edit: честно говоря, метод ECC примерно эквивалентен. Не обязательно хуже или лучше. Однако определенно медленнее, так что, вероятно, хуже в этом отношении.

Я сомневаюсь, что ECC будет лучше, чем то, что у вас есть сейчас.

fmw42 21.12.2022 00:00

Другие вопросы по теме