Image_Stitching / estimate_homography.py
basab1142's picture
added App
165f2ce verified
import cv2
import numpy as np
def calculate_homography(in_pts, out_pts):
"""
Calculates the homography matrix H such that in_pts = H * out_pts.
:param in_pts: Source points as a numpy array.
:param out_pts: Destination points as a numpy array.
:return: Homography matrix H.
"""
if isinstance(in_pts, list):
in_pts = np.array(in_pts)
if isinstance(out_pts, list):
out_pts = np.array(out_pts)
mat_A, mat_b = build_sys_equations(in_pts, out_pts)
H = np.matmul(np.linalg.pinv(mat_A), mat_b)
H = np.reshape(np.hstack((H, 1)), (3, 3))
return H
def build_sys_equations(in_pts, out_pts):
"""
Builds the system of equations for homography calculation.
:param in_pts: Array of input points.
:param out_pts: Array of output points.
:return: Matrix A and vector b.
"""
mat_A = np.zeros((np.size(in_pts), 8))
mat_b = in_pts.ravel()
i = 0
for x, y in out_pts:
# x row
mat_A[i][0:3] = [x, y, 1]
mat_A[i][-2:] = [-x * mat_b[i], -y * mat_b[i]]
# y row
mat_A[i + 1][-5:] = [x, y, 1, -x * mat_b[i + 1], -y * mat_b[i + 1]]
i += 2
return mat_A, mat_b
def fit_image_in_target_space(img_src, img_dst, mask, H, offset=np.array([0, 0, 0])):
"""
Warps img_src into img_dst using the homography matrix H.
:param img_src: Source image.
:param img_dst: Target image.
:param mask: Mask for the destination region.
:param H: Homography matrix.
:param offset: Offset correction array [x_offset, y_offset, 0].
:return: Transformed image.
"""
pts = get_pixel_coord(mask) # Get all pixel coordinates in the mask region.
pts = pts + offset # Apply offset correction.
out_src = np.matmul(H, pts.T)
out_src = out_src / out_src[-1, :] # Normalize to homogenous coordinates.
out_src = out_src[0:2, :].T # Extract x, y coordinates.
pts = pts[:, 0:2].astype(np.int64) # Target points in destination image.
h, w, _ = img_src.shape
img_dst = get_pixel_val(img_dst, img_src, pts, out_src, offset)
return img_dst
def get_pixel_coord(mask):
"""
Extracts x, y coordinates of white pixels in a binary mask.
:param mask: Binary mask.
:return: Homogenous coordinates of mask pixels.
"""
y, x = np.where(mask)
pts = np.concatenate((x[:, np.newaxis], y[:, np.newaxis], np.ones((x.size, 1))), axis=1)
return pts
def get_pixel_val(img_dst, img_src, pts, out_src, offset):
"""
Performs bilinear interpolation to fetch pixel values.
:param img_dst: Destination image.
:param img_src: Source image.
:param pts: Points in the destination image.
:param out_src: Corresponding points in the source image.
:param offset: Offset correction.
:return: Updated destination image.
"""
h, w, _ = img_src.shape
tl = np.floor(out_src[:, ::-1]).astype(np.int64)
br = np.ceil(out_src[:, ::-1]).astype(np.int64)
pts = pts - offset[:2]
valid_mask = ~np.logical_or.reduce(
(np.any(tl < 0, axis=1), np.any(br < 0, axis=1), tl[:, 0] >= h - 1, tl[:, 1] >= w - 1, br[:, 0] >= h - 1, br[:, 1] >= w - 1)
)
pts = pts[valid_mask]
out_src = out_src[valid_mask]
tl = tl[valid_mask]
br = br[valid_mask]
tr = np.concatenate((tl[:, 0:1], br[:, 1:2]), axis=1)
bl = np.concatenate((br[:, 0:1], tl[:, 1:2]), axis=1)
weight = np.zeros((out_src.shape[0], 4))
weight[:, 0] = np.linalg.norm(tl - out_src[:, ::-1], axis=1)
weight[:, 1] = np.linalg.norm(tr - out_src[:, ::-1], axis=1)
weight[:, 2] = np.linalg.norm(bl - out_src[:, ::-1], axis=1)
weight[:, 3] = np.linalg.norm(br - out_src[:, ::-1], axis=1)
weight[weight == 0] = 1
weight = 1 / weight
weight /= np.sum(weight, axis=1, keepdims=True)
img_dst[pts[:, 1], pts[:, 0], :] = (
img_src[tl[:, 0], tl[:, 1], :] * weight[:, 0:1]
+ img_src[tr[:, 0], tr[:, 1], :] * weight[:, 1:2]
+ img_src[bl[:, 0], bl[:, 1], :] * weight[:, 2:3]
+ img_src[br[:, 0], br[:, 1], :] * weight[:, 3:4]
)
return img_dst