Spaces:
Sleeping
Sleeping
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 | |