-2

I have the following Python / OpenCV code which is supposed to take a filled out document (new.png), line it up with the reference document (ref.png) and put the result in output.png. Unfortunately, sometimes when I run it I get a "Killed" message in bash. I guess Python does this when it runs out of some resource so maybe my code is just written inefficiently and could be improved upon?

Here's my code:

import sys
import cv2
import numpy as np

if len(sys.argv) != 4:
  print('USAGE')
  print('  python3 diff.py ref.png new.png output.png')
  sys.exit()

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.AKAZE_create()
  #orb = cv2.ORB_create(500)
  keypoints1, descriptors1 = orb.detectAndCompute(im1, None)
  keypoints2, descriptors2 = orb.detectAndCompute(im2, 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)
  print(matches[numGoodMatches].distance)
  matches = matches[:numGoodMatches]

  # Draw top matches
  imMatches = cv2.drawMatches(im1, keypoints1, im2, keypoints2, matches, None)
  cv2.imwrite("matches.jpg", imMatches)

  # Extract location of good matches
  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

  # Find homography
  h, mask = cv2.findHomography(points1, points2, cv2.RANSAC)

  # Use homography
  height, width = im2.shape
  im1Reg = cv2.warpPerspective(im1, h, (width, height))

  return im1Reg, h

def removeOverlap(refBW, newBW):
  # invert each
  refBW = 255 - refBW
  newBW = 255 - newBW

  # get absdiff
  xor = cv2.absdiff(refBW, newBW)

  result = cv2.bitwise_and(xor, newBW)

  # invert
  result = 255 - result

  return result

def offset(img, xOffset, yOffset):
  # The number of pixels
  num_rows, num_cols = img.shape[:2]

  # Creating a translation matrix
  translation_matrix = np.float32([ [1,0,xOffset], [0,1,yOffset] ])

  # Image translation
  img_translation = cv2.warpAffine(img, translation_matrix, (num_cols,num_rows), borderValue = (255,255,255))

  return img_translation


# the ink will often bleed out on printouts ever so slightly
# to eliminate that we'll apply a "jitter" of sorts

refFilename = sys.argv[1]
imFilename = sys.argv[2]
outFilename = sys.argv[3]

imRef = cv2.imread(refFilename, cv2.IMREAD_GRAYSCALE)
im = cv2.imread(imFilename, cv2.IMREAD_GRAYSCALE)

imNew, h = alignImages(im, imRef)

refBW = cv2.threshold(imRef, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
newBW = cv2.threshold(imNew, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]

for x in range (-2, 2):
  for y in range (-2, 2):
    newBW = removeOverlap(offset(refBW, x, y), newBW)

cv2.imwrite(outFilename, newBW)

Is there anything obvious that I could be doing to improve the speed of my program?

I'm running 3.5.3 and OpenCV 4.4.0. uname -r return 4.14.301-224.520.amzn2.x86_64 (Linux)

Any ideas?

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
neubert
  • 15,947
  • 24
  • 120
  • 212

1 Answers1

1

As you didn't provide any test images so I can't test the performance, but using FLANN for NN-search and ORB for matching should be faster.

 def align_images(im1, im2):
  # Convert images to grayscale
  im1_gray = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY)
  im2_gray = cv2.cvtColor(im2, cv2.COLOR_BGR2GRAY)

  orb = cv2.ORB_create(500)
  keypoints1, descriptors1 = orb.detectAndCompute(im1_gray, None)
  keypoints2, descriptors2 = orb.detectAndCompute(im2_gray, None)

  # Use FLANN since it's probably will be faster.
  index_params = dict(algorithm = 6, table_number = 6, key_size = 12, multi_probe_level = 1)
  search_params = dict(checks = 500)
  flann = cv2.FlannBasedMatcher(index_params, search_params)
  matches = flann.knnMatch(descriptors1, descriptors2, k = 2)

  good_matches = []
  for m, n in matches:
    if m.distance < GOOD_MATCH_PERCENT * n.distance:
      good_matches.append(m)
  good_matches.sort(key = lambda x: x.distance, reverse = False)
# rest of the function