File size: 4,224 Bytes
165f2ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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