Я пытаюсь определить метод поворота и преобразования отсканированного 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
отсканированное изображение преобразовано в шаблон с использованием возвращенной гомографической матрицы
Искаженный скан, наложенный поверх шаблона
Я ожидал лучшего результата, чем этот, учитывая количество матчболов. Преобразованное изображение выглядит хорошо на первый взгляд (и, безусловно, лучше, чем оно было изначально), но ему не хватает возможности использовать знание макета шаблона для последующего изменения скана.
Есть ли альтернативный подход, который я должен использовать здесь? Параметры, которые я должен использовать вместо этого? Или это просто то, что здесь достижимо, учитывая качество шаблона или используемой методологии?
Включает ли sift.detectAndCompute интерполяцию положения ключевой точки субпикселя? Если нет, есть ли параметр для его активации? Сколько зарегистрированных возрастов? Можете ли вы вручную определить лучшую трансформацию, затрачивая много ручного времени? Если нет, может ли быть какое-то искажение объектива на любом из изображений? Например, какова точность используемого сканера?
@Micka Полученные точки имеют субпиксельную точность. Это точки, которые вводятся в findHomography. Окончательное зарегистрированное изображение в этом случае отличается примерно на 10 пикселей и кажется недостаточно «растянутым», чтобы его можно было выровнять. Я мог бы сделать преобразование вручную в PS, но это то, что я пытаюсь сделать здесь; вместо этого автоматизируйте подход. Я не думаю, что искажение объектива является проблемой, и даже если это было искажение, оно должно исправить это, подумал я.
@ fmw42 Это кажется очень многообещающим подходом. Я попробую. Спасибо!
Чтобы ответить на мой собственный вопрос на случай, если у кого-то таинственным образом возникнет аналогичная проблема в будущем: важно убедиться, что когда вы применяете свою матрицу гомографии, ваш целевой размер соответствует шаблону, который вы пытаетесь сопоставить, если вы ищете получить «точное» совпадение с указанным шаблоном.
В моем оригинале у меня было:
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 будет лучше, чем то, что у вас есть сейчас.
Вы можете попробовать сопоставление 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 для уточнения.