Difference between revisions of "Script: homography estimation.py"

From RidgeRun Developer Connection
Jump to: navigation, search
Line 1: Line 1:
 
<syntaxhighlight lang='python'>
 
<syntaxhighlight lang='python'>
 +
#!/usr/bin/env python3
 +
"""  Copyright (C) 2020 RidgeRun, LLC (http://www.ridgerun.com)
 +
All Rights Reserved.
 +
 +
The contents of this software are proprietary and confidential to RidgeRun,
 +
LLC.  No part of this program may be photocopied, reproduced or translated
 +
into another programming language without prior written consent of
 +
RidgeRun, LLC.  The user is free to modify the source code after obtaining
 +
a software license from RidgeRun.  All source code changes must be provided
 +
back to RidgeRun without any encumbrance. """
 +
 
""" Tool for estimate the homography matrix """
 
""" Tool for estimate the homography matrix """
  
import sys
 
 
import argparse
 
import argparse
 +
import cv2
 +
import json
 
import numpy as np
 
import numpy as np
import cv2
+
import sys
  
#----------------------------------------------------------------------------
+
HOMOGRAPHY_DIMENSION = 3
 +
MIN_MATCHES = 4
  
 
def drawMatches(imageA, imageB, kpsA, kpsB, matches, status):
 
def drawMatches(imageA, imageB, kpsA, kpsB, matches, status):
# initialize the output visualization image
+
    '''
(hA, wA) = imageA.shape[:2]
+
  Returns an image with the matches found.
(hB, wB) = imageB.shape[:2]
+
 
vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
+
          Parameters:
vis[0:hA, 0:wA] = imageA
+
                  imageA (np.array): Image A
vis[0:hB, wA:] = imageB
+
                  imageB (np.arry): Image B
 +
                  kpsA (np.arry): Keypoints for image A
 +
                  kpsB (np.arry): Keypoints for image B
 +
                  matches (np.array): List of found matches between image A and image B
 +
                  status (int): status of homography estimation
 +
 
 +
          Returns:
 +
                  Image
 +
  '''
  
# loop over the matches
+
    # initialize the output visualization image
for ((trainIdx, queryIdx), s) in zip(matches, status):
+
    (hA, wA) = imageA.shape[:2]
# only process the match if the keypoint was successfully
+
    (hB, wB) = imageB.shape[:2]
# matched
+
    vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
if s == 1:
+
    vis[0:hA, 0:wA] = imageA
# draw the match
+
    vis[0:hB, wA:] = imageB
ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
+
 
ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
+
    for ((trainIdx, queryIdx), s) in zip(matches, status):
cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
+
        # only process the match if the keypoint was successfully
 +
        # matched
 +
        if s == 1:
 +
            # draw the match
 +
            ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
 +
            ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
 +
            cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
 +
 
 +
    return vis
  
# return the visualization
 
return vis
 
  
 
def detectAndDescribe(gray, mask):
 
def detectAndDescribe(gray, mask):
# detect and extract features from the image
+
    '''
 +
    Returns the keyponts and the correspoinding descriptors for the
 +
    gray image.
 +
 
 +
            Parameters:
 +
                    gray (np.array): Input image in grayscale.
 +
                    mask (np.arry): Mask to apply before the feature extraction.
 +
 
 +
            Returns:
 +
                    Tuple with the keypoins and their descriptors.
 +
    '''
 +
 
 +
    # detect and extract features from the image
 
     detector = cv2.xfeatures2d.SIFT_create()
 
     detector = cv2.xfeatures2d.SIFT_create()
 
     (kps, features) = detector.detectAndCompute(gray, mask)
 
     (kps, features) = detector.detectAndCompute(gray, mask)
 
     kps = np.float32([kp.pt for kp in kps])
 
     kps = np.float32([kp.pt for kp in kps])
  
# return a tuple of keypoints and features
 
 
     return (kps, features)
 
     return (kps, features)
 +
  
 
def matchKeypoints(kpsA, kpsB, featuresA, featuresB,
 
def matchKeypoints(kpsA, kpsB, featuresA, featuresB,
ratio, reprojThresh):
+
                  ratio, reprojThresh):
# compute the raw matches and initialize the list of actual
+
    '''
# matches
+
    Function that search for the correspondiencies for the given descriptors (featuresA and featuresB) and
    #matcher = cv2.DescriptorMatcher_create("BruteForce-Hamming")
+
    with those correspondencies keypoints, the homography matrix is calculated.
 +
 
 +
            Parameters:
 +
                    kpsA (np.array): Keypoints of image A.
 +
                    kpsB (np.arry): Keypoints of image B.
 +
                    featuresA (np.arry): Descriptors of image A
 +
                    featuresB (np.arry): Descriptors of image B
 +
                    ratio (float): Max distance between a possible correspondence.
 +
                    reprojThresh (float): Reprojection error of the homography estimation
 +
 
 +
            Returns:
 +
                    Tuple with the resulting matches, the homography H and the estimation status.
 +
    '''
 +
 
 +
    # compute the raw matches and initialize the list of actual matches
 
     matcher = cv2.DescriptorMatcher_create("BruteForce")
 
     matcher = cv2.DescriptorMatcher_create("BruteForce")
 
     rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
 
     rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
 
     matches = []
 
     matches = []
  
# loop over the raw matches
+
    # loop over the raw matches
 
     for m in rawMatches:
 
     for m in rawMatches:
# ensure the distance is within a certain ratio of each
+
        # ensure the distance is within a certain ratio of each
# other (i.e. Lowe's ratio test)
+
        # other (i.e. Lowe's ratio test)
    if len(m) == 2 and m[0].distance < m[1].distance * ratio:
+
        if len(m) == 2 and m[0].distance < m[1].distance * ratio:
    matches.append((m[0].trainIdx, m[0].queryIdx))
+
            matches.append((m[0].trainIdx, m[0].queryIdx))
  
# computing a homography requires at least 4 matches
 
 
     print("matches: " + str(len(matches)))
 
     print("matches: " + str(len(matches)))
     if len(matches) >= 4:
+
     if len(matches) >= MIN_MATCHES:
# construct the two sets of points
+
        # construct the two sets of points
    ptsA = np.float32([kpsA[i] for (_, i) in matches])
+
        ptsA = np.float32([kpsA[i] for (_, i) in matches])
    ptsB = np.float32([kpsB[i] for (i, _) in matches])
+
        ptsB = np.float32([kpsB[i] for (i, _) in matches])
  
# compute the homography between the two sets of points
+
        # compute the homography between the two sets of points
    (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC,
+
        (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC,
    reprojThresh)
+
                                        reprojThresh)
  
# return the matches along with the homograpy matrix
+
        # return the matches along with the homograpy matrix
# and status of each matched point
+
        # and status of each matched point
    return (matches, H, status)
+
        return (matches, H, status)
  
# otherwise, no homograpy could be computed
+
    # otherwise, no homograpy could be computed
 
     return None
 
     return None
 +
  
 
def imageEnhancement(img, sigma):
 
def imageEnhancement(img, sigma):
 +
    '''
 +
    Rerturns an image with some enhancement applied. Remove the noise of the input image img
 +
    using a Gaussian filter and then convert it to grayscale.
 +
 +
            Parameters:
 +
                    img (np.array): Input image img
 +
                    sigma (float): Sigma value of the Gaussian filter.
 +
 +
            Returns:
 +
                    Image
 +
    '''
 +
 
     # Declare the variables we are going to use
 
     # Declare the variables we are going to use
 
     kernel_size = 5
 
     kernel_size = 5
  
 
     # Remove noise
 
     # Remove noise
     img = cv2.GaussianBlur(img,(kernel_size,kernel_size),sigma)
+
     img = cv2.GaussianBlur(img, (kernel_size, kernel_size), sigma)
  
 
     # Convert to grayscale
 
     # Convert to grayscale
Line 85: Line 150:
 
     return gray
 
     return gray
  
def undistort(img):
 
    # Define camera matrix K
 
    K = np.array([[2.8472876737532920e+03, 0., 9.7983673800322515e+02],
 
                [0., 2.8608529052506838e+03, 5.0423299551699932e+02],
 
                [0., 0., 1.]])
 
  
     # Define distortion coefficients d
+
def removeDistortion(img, K, d):
    d = np.array([-6.7260720359999060e-01, 2.5160831522455513e+00, 5.4007310542765141e-02, -1.1365265232659062e-02, -1.2760075297700798e+01])
+
     '''
 +
    Returns an image with the distortion provoked by the camera lens.
 +
 
 +
            Parameters:
 +
                    img (np.array): Image img.
 +
                    K (np.arry): Camera matrix with a dimension of (3x3).
 +
                    d (np.arry): Vector with the distortion coefficients.
 +
            Returns:
 +
                    Undistorted image.
 +
    '''
  
 
     # Read an example image and acquire its size
 
     # Read an example image and acquire its size
Line 98: Line 167:
  
 
     # Generate new camera matrix from parameters
 
     # Generate new camera matrix from parameters
     newcameramatrix, roi = cv2.getOptimalNewCameraMatrix(K, d, (w,h), 0)
+
     newcameramatrix, roi = cv2.getOptimalNewCameraMatrix(K, d, (w, h), 0)
  
 
     # Generate look-up tables for remapping the camera image
 
     # Generate look-up tables for remapping the camera image
     mapx, mapy = cv2.initUndistortRectifyMap(K, d, None, newcameramatrix, (w, h), 5)
+
     mapx, mapy = cv2.initUndistortRectifyMap(
 +
        K, d, None, newcameramatrix, (w, h), 5)
  
 
     # Remap the original image to a new image
 
     # Remap the original image to a new image
Line 107: Line 177:
 
     return newimg
 
     return newimg
  
#----------------------------------------------------------------------------
 
  
def left_fixed(targetImage, originalImage, overlap, sigma):
+
def parseJSON(filename):
     # Variables
+
    '''
     ratio=0.75
+
    Returns the algorithm variables read from a JSON configuration file.
     reprojThresh=4.0
+
 
 +
            Parameters:
 +
                    filename (string): Path to the JSON configuration file.
 +
 
 +
            Returns:
 +
                    Tuple the the read variables: (K, D, reprojError, matchRatio, sigma, overlap, crop, fov, undistort)
 +
    '''
 +
 
 +
    with open(filename) as json_file:
 +
        data = json.load(json_file)
 +
 
 +
        # load config variables
 +
        reprojError = float(data['reprojError'])
 +
        matchRatio = float(data['matchRatio'])
 +
        sigma = float(data['sigma'])
 +
        fov = float(data['fov'])
 +
        overlap = float(data['overlap'])
 +
        crop = float(data['crop'])
 +
        undistort = bool(data['undistort'])
 +
 
 +
        # load camera matrix and distortion coeficients
 +
        K = None
 +
        D = None
 +
        if undistort:
 +
            K = np.zeros(HOMOGRAPHY_DIMENSION * HOMOGRAPHY_DIMENSION)
 +
            for i, k in enumerate(data['K']):
 +
                K[i] = k
 +
            K = K.reshape(HOMOGRAPHY_DIMENSION, HOMOGRAPHY_DIMENSION)
 +
 
 +
            D = np.zeros([1, len(data['d'])])
 +
            for i, d in enumerate(data['d']):
 +
                D[0, i] = d
 +
 
 +
        return (K, D, reprojError, matchRatio,
 +
                sigma, overlap, crop, fov, undistort)
 +
 
 +
def left_fixed(config, targetImage, originalImage, homographyScale):
 +
     '''
 +
    Performs the homography estimation betwen two images, leaving the left one fixed and
 +
     transforming the right one to align them.
 +
 
 +
            Parameters:
 +
                    config (string): Path to the JSON configuration file.
 +
                    targetImage (string): Path to the traget iamge.
 +
                    originalImage (string): Path to the original image.
 +
                    homographyScale (float): Scale factor for the generated homography.
 +
 
 +
            Returns:
 +
                    No return value
 +
     '''
 +
 
 +
    (K, d, reprojThresh, ratio, sigma, overlap,
 +
    crop, fov, undistort) = parseJSON(config)
  
 
     # Load images
 
     # Load images
 
     target = cv2.imread(targetImage)
 
     target = cv2.imread(targetImage)
 
     original = cv2.imread(originalImage)
 
     original = cv2.imread(originalImage)
 +
    height, width = target.shape[:2]
 +
 +
    # crop
 +
    cropImg = int((crop * target.shape[1]) / fov)
 +
    if cropImg > 0:
 +
        target = target[:, :-1 * cropImg]
 +
        target = cv2.resize(target,(width, height), interpolation = cv2.INTER_CUBIC)
 +
        original = original[:, cropImg:]
 +
        original = cv2.resize(original,(width, height), interpolation = cv2.INTER_CUBIC)
  
 
     # remove undistort
 
     # remove undistort
     target = undistort(target)
+
     if undistort:
    original = undistort(original)
+
        target = removeDistortion(target, K, d)
 +
        original = removeDistortion(original, K, d)
  
 
     # Enhance images
 
     # Enhance images
Line 127: Line 258:
  
 
     # Mask
 
     # Mask
 +
    overlapImg = int((overlap * target.shape[1]) / fov)
 
     targetMask = np.zeros(enhancedTarget.shape, dtype=np.uint8)
 
     targetMask = np.zeros(enhancedTarget.shape, dtype=np.uint8)
     targetMask[0:, (-1*overlap):] = 1
+
     targetMask[0:, (-1 * overlapImg):] = 1
 
     originalMask = np.zeros(enhancedOriginal.shape, dtype=np.uint8)
 
     originalMask = np.zeros(enhancedOriginal.shape, dtype=np.uint8)
     originalMask[0:, 0:overlap] = 1
+
     originalMask[0:, 0:overlapImg] = 1
  
 
     # Extract keypoints
 
     # Extract keypoints
Line 137: Line 269:
  
 
     # Match features
 
     # Match features
     M = matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)
+
     (matches, H, status) = matchKeypoints(
    (matches, H, status) = M
+
        kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)
  
     print("""RC_HOMOGRAPHY= \\"h00\\":{},\\"h01\\":{}, \\"h02\\":{}, \\"h10\\":{}, \\"h11\\":{}, \\"h12\\":{}, \\"h20\\":{}, \\"h21\\":{}, \\"h22\\":""".format(
+
    vis = drawMatches(original, target, kpsA, kpsB, matches, status)
 +
    cv2.imwrite('vis.jpg', vis)
 +
 
 +
    # Apply homography
 +
    result = cv2.warpPerspective(
 +
        original, H, (target.shape[1] + original.shape[1], original.shape[0]))
 +
    result[0:target.shape[0], 0:target.shape[1]] = target
 +
    cv2.imwrite('result.jpg', result)
 +
 
 +
    # Scale homography
 +
    if homographyScale > 0:
 +
        Hmap = cv2.reg_MapProjec(H)
 +
        Hmap.scale(homographyScale)
 +
        H = Hmap.getProjTr()
 +
 
 +
     print("""RC_HOMOGRAPHY=\"{{\\"h00\\":{},\\"h01\\":{}, \\"h02\\":{}, \\"h10\\":{}, \\"h11\\":{}, \\"h12\\":{}, \\"h20\\":{}, \\"h21\\":{}, \\"h22\\":{}}}\"""".format(
 
           H[0][0],
 
           H[0][0],
 
           H[0][1],
 
           H[0][1],
Line 148: Line 295:
 
           H[1][2],
 
           H[1][2],
 
           H[2][0],
 
           H[2][0],
           H[2][1])
+
           H[2][1],
    )
+
          H[2][2])
 +
          )
  
    # Dray matches
 
    vis = drawMatches(original, target, kpsA, kpsB, matches, status)
 
    cv2.imwrite('vis.jpg', vis)
 
    vis = cv2.resize(vis,(1920, 540), interpolation = cv2.INTER_CUBIC)
 
  
    # Apply homography
+
def right_fixed(config, targetImage, originalImage, homographyScale):
    result = cv2.warpPerspective(original, H, (target.shape[1] + original.shape[1], original.shape[0]))
+
    '''
     result[0:target.shape[0], 0:target.shape[1]] = target
+
     Performs the homography estimation betwen two images, leaving the right one fixed and
     cv2.imwrite('result.jpg', result)
+
     transforming the left one to align them.
    result = cv2.resize(result,(1920, 540), interpolation = cv2.INTER_CUBIC)
+
 
 +
            Parameters:
 +
                    config (string): Path to the JSON configuration file.
 +
                    targetImage (string): Path to the traget iamge.
 +
                    originalImage (string): Path to the original image.
 +
                    homographyScale (float): Scale factor for the generated homography.
  
    # Display images
+
            Returns:
    cv2.imshow("Target", enhancedTarget)
+
                    No return value
     cv2.imwrite('target.jpg', enhancedTarget)
+
     '''
    cv2.imshow("Original", enhancedOriginal)
 
    cv2.imwrite('original.jpg', enhancedOriginal)
 
    cv2.imshow("VIS", vis)
 
    cv2.imshow("Result", result)
 
    cv2.waitKey(0)
 
  
def right_fixed(targetImage, originalImage, overlap, sigma):
+
    (K, d, reprojThresh, ratio, sigma, overlap,
    # Variables
+
    crop, fov, undistort) = parseJSON(config)
    ratio=0.75
 
    reprojThresh=4.0
 
  
 
     # Load images
 
     # Load images
 
     target = cv2.imread(targetImage)
 
     target = cv2.imread(targetImage)
 
     original = cv2.imread(originalImage)
 
     original = cv2.imread(originalImage)
 +
    height, width = target.shape[:2]
 +
 +
    # crop
 +
    cropImg = int((crop * target.shape[1]) / fov)
 +
    if cropImg > 0:
 +
        target = target[:, cropImg:]
 +
        target = cv2.resize(target,(width, height), interpolation = cv2.INTER_CUBIC)
 +
        original = original[:, :-1 * cropImg]
 +
        original = cv2.resize(original,(width, height), interpolation = cv2.INTER_CUBIC)
  
 
     # remove undistort
 
     # remove undistort
     target = undistort(target)
+
     if undistort:
    original = undistort(original)
+
        target = removeDistortion(target, K, d)
 +
        original = removeDistortion(original, K, d)
  
 
     # Enhance images
 
     # Enhance images
Line 189: Line 341:
  
 
     # Mask
 
     # Mask
 +
    overlapImg = int((overlap * target.shape[1]) / fov)
 
     targetMask = np.zeros(enhancedTarget.shape, dtype=np.uint8)
 
     targetMask = np.zeros(enhancedTarget.shape, dtype=np.uint8)
     targetMask[0:, 0:overlap] = 1
+
     targetMask[0:, 0:overlapImg] = 1
 
     originalMask = np.zeros(enhancedOriginal.shape, dtype=np.uint8)
 
     originalMask = np.zeros(enhancedOriginal.shape, dtype=np.uint8)
     originalMask[0:, (-1*overlap):] = 1
+
     originalMask[0:, (-1 * overlapImg):] = 1
  
 
     # Extract keypoints
 
     # Extract keypoints
Line 199: Line 352:
  
 
     # Match features
 
     # Match features
     M = matchKeypoints(kpsB, kpsA, featuresB, featuresA, ratio, reprojThresh)
+
     (matches, H, status) = matchKeypoints(
    (matches, H, status) = M
+
        kpsB, kpsA, featuresB, featuresA, ratio, reprojThresh)
 
     H = np.linalg.inv(H)
 
     H = np.linalg.inv(H)
  
     print("""LC_HOMOGRAPHY=\\"h00\\":{},\\"h01\\":{}, \\"h02\\":{}, \\"h10\\":{}, \\"h11\\":{}, \\"h12\\":{}, \\"h20\\":{}, \\"h21\\":{}, \\"h22\\":1""".format(
+
    # X translation shift
 +
    H[0][-1] += original.shape[1]
 +
 
 +
    vis = drawMatches(target, original, kpsB, kpsA, matches, status)
 +
    cv2.imwrite('vis.jpg', vis)
 +
 
 +
    # Apply homography
 +
    result = cv2.warpPerspective(
 +
        original, H, (target.shape[1] + original.shape[1], original.shape[0]))
 +
    result[0:target.shape[0], target.shape[1]:] = target
 +
    cv2.imwrite('result.jpg', result)
 +
 
 +
    # Scale homography
 +
    if homographyScale > 0:
 +
        Hmap = cv2.reg_MapProjec(H)
 +
        H[0][-1] -= original.shape[1]
 +
        Hmap.scale(homographyScale)
 +
        H = Hmap.getProjTr()
 +
 
 +
     print("""LC_HOMOGRAPHY=\"{{\\"h00\\":{},\\"h01\\":{}, \\"h02\\":{}, \\"h10\\":{}, \\"h11\\":{}, \\"h12\\":{}, \\"h20\\":{}, \\"h21\\":{}, \\"h22\\":{}}}\"""".format(
 
           H[0][0],
 
           H[0][0],
 
           H[0][1],
 
           H[0][1],
Line 211: Line 383:
 
           H[1][2],
 
           H[1][2],
 
           H[2][0],
 
           H[2][0],
           H[2][1])
+
           H[2][1],
    )
+
          H[2][2])
 +
          )
  
     H[0, 2] = H[0, 2] + target.shape[1]
+
def cmdline(argv):
 +
     '''
 +
    Function taht parse the command line optins before execute the algorithm
  
    # Dray matches
+
            Parameters:
    vis = drawMatches(original, target, kpsA, kpsB, matches, status)
+
                    config (list): List of command line arguments.
    cv2.imwrite('vis.jpg', vis)
 
    vis = cv2.resize(vis,(1920, 540), interpolation = cv2.INTER_CUBIC)
 
  
    # Apply homography
+
            Returns:
    result = cv2.warpPerspective(original, H, (target.shape[1] + original.shape[1], original.shape[0]))
+
                    No return value
    result[0:target.shape[0], target.shape[1]:] = target
+
     '''
    cv2.imwrite('result.jpg', result)
 
    result = cv2.resize(result,(1920, 540), interpolation = cv2.INTER_CUBIC)
 
 
 
     # Display images
 
    cv2.imshow("Target", enhancedTarget)
 
    cv2.imwrite('target.jpg', enhancedTarget)
 
    cv2.imshow("Original", enhancedOriginal)
 
    cv2.imwrite('original.jpg', enhancedOriginal)
 
    cv2.imshow("VIS", vis)
 
    cv2.imshow("Result", result)
 
    cv2.waitKey(0)
 
 
 
#----------------------------------------------------------------------------
 
  
def cmdline(argv):
 
 
     prog = argv[0]
 
     prog = argv[0]
 
     parser = argparse.ArgumentParser(
 
     parser = argparse.ArgumentParser(
         prog       = prog,
+
         prog=prog,
         description = 'Tool for use the prediction capabilities of the models in the Adversarial Anomaly Detector.',
+
         description='Tool for use the prediction capabilities of the models in the Adversarial Anomaly Detector.',
         epilog     = 'Type "%s <command> -h" for more information.' % prog)
+
         epilog='Type "%s <command> -h" for more information.' % prog)
  
 
     subparsers = parser.add_subparsers(dest='command')
 
     subparsers = parser.add_subparsers(dest='command')
 
     subparsers.required = True
 
     subparsers.required = True
 +
 
     def add_command(cmd, desc, example=None):
 
     def add_command(cmd, desc, example=None):
         epilog = 'Example: %s %s' % (prog, example) if example is not None else None
+
         epilog = 'Example: %s %s' % (
         return subparsers.add_parser(cmd, description=desc, help=desc, epilog=epilog)
+
            prog, example) if example is not None else None
 +
         return subparsers.add_parser(
 +
            cmd, description=desc, help=desc, epilog=epilog)
  
     p = add_command(   'left_fixed', 'Estimation of homography between two images')
+
     p = add_command('left_fixed', 'Estimation of homography between two images')
  
     p.add_argument(     '--targetImage',   help='Path of the target image', default='')
+
     p.add_argument('--config',         help='Path of configure file', default='')
     p.add_argument(     '--originalImage', help='Path of the original image', default='')
+
     p.add_argument('--targetImage',     help='Path of the target image', default='')
     p.add_argument(     '--overlap',       help='Overlap size', type=int, default=350)
+
     p.add_argument('--originalImage',   help='Path of the original image', default='')
     p.add_argument(     '--sigma',         help='Gaussian filter sigma', type=float, default=1.5)
+
     p.add_argument('--homographyScale', help='Scale factor of the homography. For example if you go from 1920x1080 in the estimation to 640x360 in the processing the scale factor should be 1/3', type=float, default=0)
  
     p = add_command(   'right_fixed', 'Estimation of homographies two images')
+
     p = add_command('right_fixed', 'Estimation of homographies two images')
  
     p.add_argument(     '--targetImage',   help='Path of the target image', default='')
+
     p.add_argument('--config',         help='Path of configure file', default='')
     p.add_argument(     '--originalImage', help='Path of the original image', default='')
+
     p.add_argument('--targetImage',     help='Path of the target image', default='')
     p.add_argument(     '--overlap',       help='Overlap size', type=int, default=350)
+
     p.add_argument('--originalImage',   help='Path of the original image', default='')
     p.add_argument(     '--sigma',         help='Gaussian filter sigma', type=float, default=1.5)
+
     p.add_argument('--homographyScale', help='Scale factor of the homography. For example if you go from 1920x1080 in the estimation to 640x360 in the processing the scale factor should be 1/3', type=float, default=0)
  
 
     args = parser.parse_args(argv[1:] if len(argv) > 1 else ['-h'])
 
     args = parser.parse_args(argv[1:] if len(argv) > 1 else ['-h'])
Line 270: Line 432:
 
     func(**vars(args))
 
     func(**vars(args))
  
#----------------------------------------------------------------------------
+
# ----------------------------------------------------------------------------
 +
 
  
 
if __name__ == "__main__":
 
if __name__ == "__main__":
 
     cmdline(sys.argv)
 
     cmdline(sys.argv)
  
#----------------------------------------------------------------------------
+
# ----------------------------------------------------------------------------
  
 
</syntaxhighlight>
 
</syntaxhighlight>

Revision as of 08:56, 29 July 2020

#!/usr/bin/env python3
"""  Copyright (C) 2020 RidgeRun, LLC (http://www.ridgerun.com)
 All Rights Reserved.

 The contents of this software are proprietary and confidential to RidgeRun,
 LLC.  No part of this program may be photocopied, reproduced or translated
 into another programming language without prior written consent of
 RidgeRun, LLC.  The user is free to modify the source code after obtaining
 a software license from RidgeRun.  All source code changes must be provided
 back to RidgeRun without any encumbrance. """

""" Tool for estimate the homography matrix """

import argparse
import cv2
import json
import numpy as np
import sys

HOMOGRAPHY_DIMENSION = 3
MIN_MATCHES = 4

def drawMatches(imageA, imageB, kpsA, kpsB, matches, status):
    '''
   Returns an image with the matches found.

           Parameters:
                   imageA (np.array): Image A
                   imageB (np.arry): Image B
                   kpsA (np.arry): Keypoints for image A
                   kpsB (np.arry): Keypoints for image B
                   matches (np.array): List of found matches between image A and image B
                   status (int): status of homography estimation

           Returns:
                   Image
   '''

    # initialize the output visualization image
    (hA, wA) = imageA.shape[:2]
    (hB, wB) = imageB.shape[:2]
    vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
    vis[0:hA, 0:wA] = imageA
    vis[0:hB, wA:] = imageB

    for ((trainIdx, queryIdx), s) in zip(matches, status):
        # only process the match if the keypoint was successfully
        # matched
        if s == 1:
            # draw the match
            ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
            ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
            cv2.line(vis, ptA, ptB, (0, 255, 0), 1)

    return vis


def detectAndDescribe(gray, mask):
    '''
    Returns the keyponts and the correspoinding descriptors for the
    gray image.

            Parameters:
                    gray (np.array): Input image in grayscale.
                    mask (np.arry): Mask to apply before the feature extraction.

            Returns:
                    Tuple with the keypoins and their descriptors.
    '''

    # detect and extract features from the image
    detector = cv2.xfeatures2d.SIFT_create()
    (kps, features) = detector.detectAndCompute(gray, mask)
    kps = np.float32([kp.pt for kp in kps])

    return (kps, features)


def matchKeypoints(kpsA, kpsB, featuresA, featuresB,
                   ratio, reprojThresh):
    '''
    Function that search for the correspondiencies for the given descriptors (featuresA and featuresB) and
    with those correspondencies keypoints, the homography matrix is calculated.

            Parameters:
                    kpsA (np.array): Keypoints of image A.
                    kpsB (np.arry): Keypoints of image B.
                    featuresA (np.arry): Descriptors of image A
                    featuresB (np.arry): Descriptors of image B
                    ratio (float): Max distance between a possible correspondence.
                    reprojThresh (float): Reprojection error of the homography estimation

            Returns:
                    Tuple with the resulting matches, the homography H and the estimation status.
    '''

    # compute the raw matches and initialize the list of actual matches
    matcher = cv2.DescriptorMatcher_create("BruteForce")
    rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
    matches = []

    # loop over the raw matches
    for m in rawMatches:
        # ensure the distance is within a certain ratio of each
        # other (i.e. Lowe's ratio test)
        if len(m) == 2 and m[0].distance < m[1].distance * ratio:
            matches.append((m[0].trainIdx, m[0].queryIdx))

    print("matches: " + str(len(matches)))
    if len(matches) >= MIN_MATCHES:
        # construct the two sets of points
        ptsA = np.float32([kpsA[i] for (_, i) in matches])
        ptsB = np.float32([kpsB[i] for (i, _) in matches])

        # compute the homography between the two sets of points
        (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC,
                                         reprojThresh)

        # return the matches along with the homograpy matrix
        # and status of each matched point
        return (matches, H, status)

    # otherwise, no homograpy could be computed
    return None


def imageEnhancement(img, sigma):
    '''
    Rerturns an image with some enhancement applied. Remove the noise of the input image img
    using a Gaussian filter and then convert it to grayscale.

            Parameters:
                    img (np.array): Input image img
                    sigma (float): Sigma value of the Gaussian filter.

            Returns:
                    Image
    '''

    # Declare the variables we are going to use
    kernel_size = 5

    # Remove noise
    img = cv2.GaussianBlur(img, (kernel_size, kernel_size), sigma)

    # Convert to grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    return gray


def removeDistortion(img, K, d):
    '''
    Returns an image with the distortion provoked by the camera lens.

            Parameters:
                    img (np.array): Image img.
                    K (np.arry): Camera matrix with a dimension of (3x3).
                    d (np.arry): Vector with the distortion coefficients.
            Returns:
                    Undistorted image.
    '''

    # Read an example image and acquire its size
    h, w = img.shape[:2]

    # Generate new camera matrix from parameters
    newcameramatrix, roi = cv2.getOptimalNewCameraMatrix(K, d, (w, h), 0)

    # Generate look-up tables for remapping the camera image
    mapx, mapy = cv2.initUndistortRectifyMap(
        K, d, None, newcameramatrix, (w, h), 5)

    # Remap the original image to a new image
    newimg = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
    return newimg


def parseJSON(filename):
    '''
    Returns the algorithm variables read from a JSON configuration file.

            Parameters:
                    filename (string): Path to the JSON configuration file.

            Returns:
                    Tuple the the read variables: (K, D, reprojError, matchRatio, sigma, overlap, crop, fov, undistort)
    '''

    with open(filename) as json_file:
        data = json.load(json_file)

        # load config variables
        reprojError = float(data['reprojError'])
        matchRatio = float(data['matchRatio'])
        sigma = float(data['sigma'])
        fov = float(data['fov'])
        overlap = float(data['overlap'])
        crop = float(data['crop'])
        undistort = bool(data['undistort'])

        # load camera matrix and distortion coeficients
        K = None
        D = None
        if undistort:
            K = np.zeros(HOMOGRAPHY_DIMENSION * HOMOGRAPHY_DIMENSION)
            for i, k in enumerate(data['K']):
                K[i] = k
            K = K.reshape(HOMOGRAPHY_DIMENSION, HOMOGRAPHY_DIMENSION)

            D = np.zeros([1, len(data['d'])])
            for i, d in enumerate(data['d']):
                D[0, i] = d

        return (K, D, reprojError, matchRatio,
                sigma, overlap, crop, fov, undistort)

def left_fixed(config, targetImage, originalImage, homographyScale):
    '''
    Performs the homography estimation betwen two images, leaving the left one fixed and
    transforming the right one to align them.

            Parameters:
                    config (string): Path to the JSON configuration file.
                    targetImage (string): Path to the traget iamge.
                    originalImage (string): Path to the original image.
                    homographyScale (float): Scale factor for the generated homography.

            Returns:
                    No return value
    '''

    (K, d, reprojThresh, ratio, sigma, overlap,
     crop, fov, undistort) = parseJSON(config)

    # Load images
    target = cv2.imread(targetImage)
    original = cv2.imread(originalImage)
    height, width = target.shape[:2]

    # crop
    cropImg = int((crop * target.shape[1]) / fov)
    if cropImg > 0:
        target = target[:, :-1 * cropImg]
        target = cv2.resize(target,(width, height), interpolation = cv2.INTER_CUBIC)
        original = original[:, cropImg:]
        original = cv2.resize(original,(width, height), interpolation = cv2.INTER_CUBIC)

    # remove undistort
    if undistort:
        target = removeDistortion(target, K, d)
        original = removeDistortion(original, K, d)

    # Enhance images
    enhancedTarget = imageEnhancement(target, sigma)
    enhancedOriginal = imageEnhancement(original, sigma)

    # Mask
    overlapImg = int((overlap * target.shape[1]) / fov)
    targetMask = np.zeros(enhancedTarget.shape, dtype=np.uint8)
    targetMask[0:, (-1 * overlapImg):] = 1
    originalMask = np.zeros(enhancedOriginal.shape, dtype=np.uint8)
    originalMask[0:, 0:overlapImg] = 1

    # Extract keypoints
    (kpsA, featuresA) = detectAndDescribe(enhancedOriginal, originalMask)
    (kpsB, featuresB) = detectAndDescribe(enhancedTarget, targetMask)

    # Match features
    (matches, H, status) = matchKeypoints(
        kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)

    vis = drawMatches(original, target, kpsA, kpsB, matches, status)
    cv2.imwrite('vis.jpg', vis)

    # Apply homography
    result = cv2.warpPerspective(
        original, H, (target.shape[1] + original.shape[1], original.shape[0]))
    result[0:target.shape[0], 0:target.shape[1]] = target
    cv2.imwrite('result.jpg', result)

    # Scale homography
    if homographyScale > 0:
        Hmap = cv2.reg_MapProjec(H)
        Hmap.scale(homographyScale)
        H = Hmap.getProjTr()

    print("""RC_HOMOGRAPHY=\"{{\\"h00\\":{},\\"h01\\":{}, \\"h02\\":{}, \\"h10\\":{}, \\"h11\\":{}, \\"h12\\":{}, \\"h20\\":{}, \\"h21\\":{}, \\"h22\\":{}}}\"""".format(
          H[0][0],
          H[0][1],
          H[0][2],
          H[1][0],
          H[1][1],
          H[1][2],
          H[2][0],
          H[2][1],
          H[2][2])
          )


def right_fixed(config, targetImage, originalImage, homographyScale):
    '''
    Performs the homography estimation betwen two images, leaving the right one fixed and
    transforming the left one to align them.

            Parameters:
                    config (string): Path to the JSON configuration file.
                    targetImage (string): Path to the traget iamge.
                    originalImage (string): Path to the original image.
                    homographyScale (float): Scale factor for the generated homography.

            Returns:
                    No return value
    '''

    (K, d, reprojThresh, ratio, sigma, overlap,
     crop, fov, undistort) = parseJSON(config)

    # Load images
    target = cv2.imread(targetImage)
    original = cv2.imread(originalImage)
    height, width = target.shape[:2]

    # crop
    cropImg = int((crop * target.shape[1]) / fov)
    if cropImg > 0:
        target = target[:, cropImg:]
        target = cv2.resize(target,(width, height), interpolation = cv2.INTER_CUBIC)
        original = original[:, :-1 * cropImg]
        original = cv2.resize(original,(width, height), interpolation = cv2.INTER_CUBIC)

    # remove undistort
    if undistort:
        target = removeDistortion(target, K, d)
        original = removeDistortion(original, K, d)

    # Enhance images
    enhancedTarget = imageEnhancement(target, sigma)
    enhancedOriginal = imageEnhancement(original, sigma)

    # Mask
    overlapImg = int((overlap * target.shape[1]) / fov)
    targetMask = np.zeros(enhancedTarget.shape, dtype=np.uint8)
    targetMask[0:, 0:overlapImg] = 1
    originalMask = np.zeros(enhancedOriginal.shape, dtype=np.uint8)
    originalMask[0:, (-1 * overlapImg):] = 1

    # Extract keypoints
    (kpsA, featuresA) = detectAndDescribe(enhancedOriginal, originalMask)
    (kpsB, featuresB) = detectAndDescribe(enhancedTarget, targetMask)

    # Match features
    (matches, H, status) = matchKeypoints(
        kpsB, kpsA, featuresB, featuresA, ratio, reprojThresh)
    H = np.linalg.inv(H)

    # X translation shift
    H[0][-1] += original.shape[1]

    vis = drawMatches(target, original, kpsB, kpsA, matches, status)
    cv2.imwrite('vis.jpg', vis)

    # Apply homography
    result = cv2.warpPerspective(
        original, H, (target.shape[1] + original.shape[1], original.shape[0]))
    result[0:target.shape[0], target.shape[1]:] = target
    cv2.imwrite('result.jpg', result)

    # Scale homography
    if homographyScale > 0:
        Hmap = cv2.reg_MapProjec(H)
        H[0][-1] -= original.shape[1]
        Hmap.scale(homographyScale)
        H = Hmap.getProjTr()

    print("""LC_HOMOGRAPHY=\"{{\\"h00\\":{},\\"h01\\":{}, \\"h02\\":{}, \\"h10\\":{}, \\"h11\\":{}, \\"h12\\":{}, \\"h20\\":{}, \\"h21\\":{}, \\"h22\\":{}}}\"""".format(
          H[0][0],
          H[0][1],
          H[0][2],
          H[1][0],
          H[1][1],
          H[1][2],
          H[2][0],
          H[2][1],
          H[2][2])
          )

def cmdline(argv):
    '''
    Function taht parse the command line optins before execute the algorithm

            Parameters:
                    config (list): List of command line arguments.

            Returns:
                    No return value
    '''

    prog = argv[0]
    parser = argparse.ArgumentParser(
        prog=prog,
        description='Tool for use the prediction capabilities of the models in the Adversarial Anomaly Detector.',
        epilog='Type "%s <command> -h" for more information.' % prog)

    subparsers = parser.add_subparsers(dest='command')
    subparsers.required = True

    def add_command(cmd, desc, example=None):
        epilog = 'Example: %s %s' % (
            prog, example) if example is not None else None
        return subparsers.add_parser(
            cmd, description=desc, help=desc, epilog=epilog)

    p = add_command('left_fixed', 'Estimation of homography between two images')

    p.add_argument('--config',          help='Path of configure file', default='')
    p.add_argument('--targetImage',     help='Path of the target image', default='')
    p.add_argument('--originalImage',   help='Path of the original image', default='')
    p.add_argument('--homographyScale', help='Scale factor of the homography. For example if you go from 1920x1080 in the estimation to 640x360 in the processing the scale factor should be 1/3', type=float, default=0)

    p = add_command('right_fixed', 'Estimation of homographies two images')

    p.add_argument('--config',          help='Path of configure file', default='')
    p.add_argument('--targetImage',     help='Path of the target image', default='')
    p.add_argument('--originalImage',   help='Path of the original image', default='')
    p.add_argument('--homographyScale', help='Scale factor of the homography. For example if you go from 1920x1080 in the estimation to 640x360 in the processing the scale factor should be 1/3', type=float, default=0)

    args = parser.parse_args(argv[1:] if len(argv) > 1 else ['-h'])
    func = globals()[args.command]
    del args.command
    func(**vars(args))

# ----------------------------------------------------------------------------


if __name__ == "__main__":
    cmdline(sys.argv)

# ----------------------------------------------------------------------------