Проблема с маской при выравнивании изображений Python OpenCV ORB

Я пытаюсь реализовать выравнивание изображений ORB Python (3.7) OpenCV (3.4.3). Обычно я выполняю большую часть обработки с помощью ImageMagick. Но мне нужно выполнить выравнивание изображений, и я пытаюсь использовать Python OpenCV ORB. Мой сценарий основан на одном из учебника Сатьи Маллик Learn OpenCV по адресу https://www.learnopencv.com/image-alignment-feature-based-using-opencv-c-python/.

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

Подход с использованием маски был взят из кода выравнивания FLANN в последнем примере по адресу https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_matcher/py_matcher.html.

Мой скрипт работает нормально, если я удалю маску совпадений, которая должна обеспечивать фильтрацию точек. (У меня есть еще два рабочих сценария. Один похож, но просто фильтрует точки и игнорирует маску. Другой основан на алгоритме ECC.)

Однако я хотел бы понять, почему мой приведенный ниже код не работает.

Возможно, структура моей маски неверна в текущих версиях Python Opencv?

Я получаю следующую ошибку:

Traceback (most recent call last):
  File "warp_orb_rigid2_filter.py", line 92, in <module>
    imReg, m = alignImages(im, imReference)
  File "warp_orb_rigid2_filter.py", line 62, in alignImages
    imMatches = cv2.drawMatches(im1, keypoints1, im2, keypoints2, matches, None, **draw_params)
SystemError: <built-in function drawMatches> returned NULL without setting an error


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

#!/bin/python3.7

import cv2
import numpy as np


MAX_FEATURES = 500
GOOD_MATCH_PERCENT = 0.15


def alignImages(im1, im2):

  # Convert images to grayscale
  im1Gray = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY)
  im2Gray = cv2.cvtColor(im2, cv2.COLOR_BGR2GRAY)

  # Detect ORB features and compute descriptors.
  orb = cv2.ORB_create(MAX_FEATURES)
  keypoints1, descriptors1 = orb.detectAndCompute(im1Gray, None)
  keypoints2, descriptors2 = orb.detectAndCompute(im2Gray, None)

  # Match features.
  matcher = cv2.DescriptorMatcher_create(cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING)
  matches = matcher.match(descriptors1, descriptors2, None)

  # Sort matches by score
  matches.sort(key=lambda x: x.distance, reverse=False)

  # Remove not so good matches
  numGoodMatches = int(len(matches) * GOOD_MATCH_PERCENT)
  matches = matches[:numGoodMatches]

  # Extract location of good matches and filter by diffy
  points1 = np.zeros((len(matches), 2), dtype=np.float32)
  points2 = np.zeros((len(matches), 2), dtype=np.float32)

  for i, match in enumerate(matches):
    points1[i, :] = keypoints1[match.queryIdx].pt
    points2[i, :] = keypoints2[match.trainIdx].pt

  # initialize empty arrays for newpoints1 and newpoints2 and mask
  newpoints1 = np.empty(shape=[0, 2])
  newpoints2 = np.empty(shape=[0, 2])
  matches_Mask = [0] * len(matches)

  # filter points by using mask    
  for i in range(len(matches)):
      pt1 = points1[i]
      pt2 = points2[i]
      pt1x, pt1y = zip(*[pt1])
      pt2x, pt2y = zip(*[pt2])
      diffy = np.float32( np.float32(pt2y) - np.float32(pt1y) )
      print(diffy)
      if abs(diffy) < 10.0:
        newpoints1 = np.append(newpoints1, [pt1], axis=0)
        newpoints2 = np.append(newpoints2, [pt2], axis=0)
        matches_Mask[i]=[1,0]  #<--- mask created
  print(matches_Mask)

  draw_params = dict(matchColor = (255,0,),
    singlePointColor = (255,255,0),
    matchesMask = matches_Mask, #<---- remove mask here
    flags = 0)

  # Draw top matches
  imMatches = cv2.drawMatches(im1, keypoints1, im2, keypoints2, matches, None, **draw_params)
  cv2.imwrite("/Users/fred/desktop/lena_matches.png", imMatches)


  # Find Affine Transformation
  # true means full affine, false means rigid (SRT)
  m = cv2.estimateRigidTransform(newpoints1,newpoints2,False)

  # Use affine transform to warp im1 to match im2
  height, width, channels = im2.shape
  im1Reg = cv2.warpAffine(im1, m, (width, height))

  return im1Reg, m


if __name__ == '__main__':

  # Read reference image
  refFilename = "/Users/fred/desktop/lena.png"
  print("Reading reference image : ", refFilename)
  imReference = cv2.imread(refFilename, cv2.IMREAD_COLOR)

  # Read image to be aligned
  imFilename = "/Users/fred/desktop/lena_r1.png"
  print("Reading image to align : ", imFilename);  
  im = cv2.imread(imFilename, cv2.IMREAD_COLOR)

  print("Aligning images ...")
  # Registered image will be stored in imReg. 
  # The estimated transform will be stored in m. 
  imReg, m = alignImages(im, imReference)

  # Write aligned image to disk. 
  outFilename = "/Users/fred/desktop/lena_r1_aligned.jpg"
  print("Saving aligned image : ", outFilename); 
  cv2.imwrite(outFilename, imReg)

  # Print estimated homography
  print("Estimated Affine Transform : \n",  m)


Вот два моих изображения: lena и lena, повернутые на 1 градус. Обратите внимание, что это не мои настоящие изображения. У этих изображений нет значений diffy> 10, но у моих реальных изображений они есть.

введите описание изображения здесь

введите описание изображения здесь

Я пытаюсь выровнять и деформировать повернутое изображение, чтобы оно соответствовало исходному изображению Лены.


person fmw42    schedule 05.03.2019    source источник


Ответы (1)


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

Поэтому замените эту строку:

matches_Mask = [[0,0] for i in range(len(matches))]

С этим:

matches_Mask = [0] * len(matches)

... so:

# matches_Mask = [[0,0] for i in range(len(matches))]
matches_Mask = [0] * len(matches)

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

  if abs(diffy) < 10.0:
    #matches_Mask[i]=[1,0]  #<--- mask created
    matches_Mask[i] = 1

Наконец-то я понял:

Estimated Affine Transform :
 [[ 1.00001187  0.01598318 -5.05963793]
  [-0.01598318  1.00001187 -0.86121051]]

Обратите внимание, что формат маски различается в зависимости от того, какой сопоставитель вы используете. В этом случае вы используете сопоставление методом грубой силы, поэтому маска должна быть в формате, который я только что описал.

Если вы использовали, например, knnMatch FLANN, то это будет вложенный список списков, каждый элемент которого будет иметь длину k. Например, если у вас было k=3 и пять ключевых точек, это будет список из пяти элементов, каждый из которых будет списком из трех элементов. Каждый элемент в подсписке определяет, какое совпадение вы хотите использовать для рисования.

person rayryeng    schedule 05.03.2019
comment
@rayryeng Большое спасибо. Но, пожалуйста, объясните, почему маска не [1,0], как в другой ссылке. Это связано с разной структурой маски между моим DescriptorMatcher_create () и другим ссылочным использованием flann.knnMatch ()? Есть ли ссылка, объясняющая структуру маски для разных сопоставителей? Также в приведенном выше примере я использовал два простых изображения, а не мои настоящие изображения, которые я не могу показать здесь, но для которых они имеют разные значения. ›10. Я отредактирую свой пост, чтобы лучше объяснить это. - person fmw42; 06.03.2019
comment
Да, именно так. Грубая сила против кНН имеют разные структуры. При использовании kNN потребуется использовать вашу исходную реализацию. У меня также нет упоминания о структуре маски ... Это из опыта. - person rayryeng; 06.03.2019
comment
Я также не знал, что вы используете FLANN. Код, который у вас есть, использует сопоставление методом перебора, поэтому маска представляет собой просто один список. Для сопоставления kNN, если вы используете k=2, это будет вложенный список списков, где каждый элемент будет списком длиной k. Например, если у вас было k=5 и три совпадения, это будет список из трех элементов, каждый из которых будет списком из 5 элементов. Каждый элемент в подсписке определяет, какое совпадение вы хотите использовать для рисования. - person rayryeng; 06.03.2019
comment
@rayryeng. На самом деле я не использовал FLANN, а просто пытался имитировать маскирующую структуру, выполненную в примере FLANN. Таким образом, структура маски меняется вместе с сопоставителем (потенциально, по крайней мере, FLANN отличается). Спасибо за это разъяснение. Я изменил свой сценарий, как вы написали, и теперь он работает правильно даже для моих настоящих изображений. 13 очков выброшены из-за диффузной фильтрации, и теперь маска справляется с этим хорошо. Мы очень ценим вашу помощь и опыт для этого начинающего пользователя Python / OpenCV. - person fmw42; 06.03.2019
comment
@ fmw42 Я понял. Итак, если вы решите использовать FLANN в следующий раз, вы знаете, как структурировать маску! Рад, что помог, и, пожалуйста, дайте мне знать, если у вас возникнут еще вопросы или вам понадобится помощь! - person rayryeng; 06.03.2019