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

#python #opencv #computer-vision #template-matching #orb

#python #opencv #компьютерное зрение #соответствие шаблону #сфера

Вопрос:

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

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

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

Мой скрипт работает нормально, если я удалю matchesMask, который должен обеспечивать фильтрацию точек. (У меня есть два других рабочих скрипта. Один похож, но просто фильтрует точки и игнорирует маску. Другая основана на алгоритме 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 градус. Обратите внимание, что это не мои реальные изображения. У этих изображений нет различий в значениях > 10, но у моих реальных изображений есть.

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

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

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

Ответ №1:

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

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

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

С помощью этого:

 matches_Mask = [0] * len(matches)
  

… итак:

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

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

   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]]
  

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

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

Комментарии:

1. @rayryeng Большое спасибо. Но, пожалуйста, объясните, почему маска не [1,0], как в другой ссылке. Связано ли это с разной структурой маски между моим описанием matcher_create () и другим ссылочным использованием flann.knnMatch()? Есть ли какая-нибудь ссылка, объясняющая структуру маски для разных сопоставителей? Также в приведенном выше примере я использовал два простых изображения вместо моих реальных изображений, которые я не могу показать здесь, но для которых у него были разные значения> 10. Я отредактирую свой пост, чтобы лучше объяснить это.

2. Да, это именно так. Перебор и kNN имеют разные структуры. При использовании kNN потребуется использовать вашу оригинальную реализацию. У меня также нет ссылки на структуру маски…. Это приходит из опыта.

3. Я также не знал, что вы используете FLANN. В вашем коде используется сопоставление методом грубой силы, поэтому маска представляет собой просто один список. Для КНН соответствуя, если вы используете k=2 , то это будет вложенный список списков, где каждый элемент списка, k долго. Например, если у вас было k=5 и три совпадения, это будет список длиной в три элемента, причем каждый элемент представляет собой список из 5 элементов. Каждый элемент во вложенном списке определяет, какое соответствие вы хотите использовать для рисования.

4. @rayryeng . На самом деле я не использовал FLANN, а просто попытался эмулировать структуру маскировки, выполненную в примере FLANN. Таким образом, структура маски действительно меняется при сопоставлении (потенциально, и, по крайней мере, FLANN отличается). Спасибо за это разъяснение. Я изменил свой скрипт, как вы написали, и теперь он работает должным образом даже для моих реальных изображений. из-за фильтрации diffy выбрасывается 13 баллов, и маска теперь хорошо с этим справляется. Начинающий пользователь Python / OpenCV высоко ценит вашу помощь и опыт.

5. @fmw42 Я понял. Хорошо, итак, если вы решите использовать FLANNВ следующий раз, вы знаете, как структурировать маску! Рад, что помог, и, пожалуйста, дайте мне знать, если у вас возникнут еще какие-либо вопросы или вам понадобится дополнительная помощь!