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

From RidgeRun Developer Connection
Jump to: navigation, search
(Replaced content with "<syntaxhighlight lang='python'> // The script is located in the Scrip's directory of the cudastitcher project. </syntaxhighlight>")
(Tag: Replaced)
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
<syntaxhighlight lang='python'>
 
<syntaxhighlight lang='python'>
#!/usr/bin/env python3
+
// The script is located in the Scrip's directory of the cudastitcher project.
"""  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)
 
 
 
# ----------------------------------------------------------------------------
 
  
 
</syntaxhighlight>
 
</syntaxhighlight>

Latest revision as of 22:24, 30 July 2020

// The script is located in the Scrip's directory of the cudastitcher project.