@TOC

1、实验内容

实验题目:Implement Panorama Stitching with Harris corner detector, RANSAC and HOG descriptor. 实验步骤:

  • 1.使用Harris焦点检测器寻找关键点。
  • 2.构建描述算子来描述图中的每个关键点,比较两幅图像的两组描述子,并进行匹配。
  • 3.根据一组匹配关键点,使用最小二乘法进行仿射变换矩阵的计算。
  • 4.使用RANSAC计算一个更加稳定的仿射变换的矩阵,然后将第二幅图变换过来并覆盖在第一幅图上,形成一个全景。
  • 5.实现不同的描述子,并得到不同的拼接结果。

2、实验设计(略)

3、实验环境及实验数据集

实验环境:

  • Windows10的操作系统、anaconda3、python3.6、jupyter notebook。

数据集:

  • 任务给出的原图片以及处理结果后的图片,如image1.jpg、image2.jpg、solution_harris.png等等。

四、实验过程及结果

4.1 Harris角点检测器寻找关键点

结果展示:

机器视觉基础实验-Panorama Stitching_仿射变换

实现代码:

1.def harris_corners(img, window_size=3, k=0.04):  
2. """ 
3. Compute Harris corner response map. Follow the math equation 
4. R=Det(M)-k(Trace(M)^2). 
5. 
6. Hint: 
7.     You may use the function scipy.ndimage.filters.convolve, 
8.     which is already imported above. 
9. 
10. Args: 
11.     img: Grayscale image of shape (H, W) 
12.     window_size: size of the window function 
13.     k: sensitivity parameter 
14. 
15. Returns: 
16.     response: Harris response image of shape (H, W) 
17. """  
18.  
19. H, W = img.shape  
20. window = np.ones((window_size, window_size))  
21.  
22. response = np.zeros((H, W))  
23.  
24. dx = filters.sobel_v(img)  
25. dy = filters.sobel_h(img)  
26.  
27. ### YOUR CODE HERE  
28. Ix2 = np.multiply(dx,dx)  
29. Iy2 = np.multiply(dy,dy)  
30. IxIy = np.multiply(dx,dy)  
31.   
32. #高斯加权  
33. A = convolve(Ix2,window)  
34. B = convolve(Iy2,window)  
35. C = convolve(IxIy,window)  
36.  
37. detM = np.multiply(A, B) - np.multiply(C, C)  
38. traceM = A + B  
39.  
40. response = detM - k * np.square(traceM)  
41. ### END YOUR CODE  
42.  
 return response

4.2 构建描述算子来描述图中的每个关键点,比较两幅图像的两组描述子,并进行匹配。

结果展示:

机器视觉基础实验-Panorama Stitching_仿射变换_02

机器视觉基础实验-Panorama Stitching_仿射变换_03

实现代码: 构建描述算子

1.def simple_descriptor(patch):  
2. """ 
3. Describe the patch by normalizing the image values into a standard 
4. normal distribution (having mean of 0 and standard deviation of 1) 
5. and then flattening into a 1D array. 
6. 
7. The normalization will make the descriptor more robust to change 
8. in lighting condition. 
9. 
10. Hint: 
11.     If a denominator is zero, divide by 1 instead. 
12. 
13. Args: 
14.     patch: grayscale image patch of shape (H, W) 
15. 
16. Returns: 
17.     feature: 1D array of shape (H * W) 
18. """  
19. feature = []  
20. ### YOUR CODE HERE  
21. mean = np.mean(patch)  
22. sigma = np.std(patch)  
23. if sigma == 0.0:  
24.     sigma = 1  
25. #均值0,标准差为1  
26. normalized = (patch - mean) / sigma  
27. #生成一维特征向量  
28. feature = normalized.flatten()  
29. ### END YOUR CODE  
30. return feature

根据描述子进行匹配

1.#注意,这里引入了heapq堆排列模块算法  
2.import heapq  
3.def match_descriptors(desc1, desc2, threshold=0.5):  
4.    """ 
5.    Match the feature descriptors by finding distances between them. A match is formed 
6.    when the distance to the closest vector is much smaller than the distance to the 
7.    second-closest, that is, the ratio of the distances should be smaller 
8.    than the threshold. Return the matches as pairs of vector indices. 
9. 
10.    Hint: 
11.        The Numpy functions np.sort, np.argmin, np.asarray might be useful 
12. 
13.    Args: 
14.        desc1: an array of shape (M, P) holding descriptors of size P about M keypoints 
15.        desc2: an array of shape (N, P) holding descriptors of size P about N keypoints 
16. 
17.    Returns: 
18.        matches: an array of shape (Q, 2) where each row holds the indices of one pair 
19.        of matching descriptors 
20.    """  
21.    matches = []  
22.  
23.    N = desc1.shape[0]  
24.    dists = cdist(desc1, desc2)  
25.  
26.    ### YOUR CODE HERE  
27.    for i in range(N):  
28.        #找到最近的两个值  
29.        min2 = heapq.nsmallest(2, dists[i,:])  
30.        #保证最小的和第二小的有一定的距离  
31.        if min2[0] / min2[1] < threshold:  
32.            matches.append([i, np.argmin(dists[i,:])])  
33.    matches = np.asarray(matches)  
34.    ### END YOUR CODE  
35.  
36.    return matches

4.3 根据一组匹配关键点,使用最小二乘法进行仿射变换矩阵的计算

实验结果:

机器视觉基础实验-Panorama Stitching_归一化_04

机器视觉基础实验-Panorama Stitching_仿射变换_05

实验代码:

1.def fit_affine_matrix(p1, p2):  
2. """ Fit affine matrix such that p2 * H = p1 
3. 
4. Hint: 
5.     You can use np.linalg.lstsq function to solve the problem. 
6. 
7. Args: 
8.     p1: an array of shape (M, P) 
9.     p2: an array of shape (M, P) 
10. 
11. Return: 
12.     H: a matrix of shape (P, P) that transform p2 to p1. 
13. """  
14.  
15. assert (p1.shape[0] == p2.shape[0]),\  
16.     'Different number of points in p1 and p2'  
17. p1 = pad(p1)  
18. p2 = pad(p2)  
19.  
20. ### YOUR CODE HERE  
21. #仿射变换矩阵  
22. H, residuals, rank, s = np.linalg.lstsq(p2, p1, rcond=None)  
23. ### END YOUR CODE  
24.  
25. # Sometimes numerical issues cause least-squares to produce the last  
26. # column which is not exactly [0, 0, 1]  
27. H[:,2] = np.array([0, 0, 1])  
28. return H

4.4 使用RANSAC计算一个更加稳定的仿射变换的矩阵,然后将第二幅图变换过来并覆盖在第一幅图上,形成一个全景

实验结果:

机器视觉基础实验-Panorama Stitching_仿射变换_06

机器视觉基础实验-Panorama Stitching_数据集_07

机器视觉基础实验-Panorama Stitching_数据集_08

实验代码:

1.def ransac(keypoints1, keypoints2, matches, n_iters=200, threshold=20):  
2. """ 
3. Use RANSAC to find a robust affine transformation 
4. 
5.     1. Select random set of matches 
6.     2. Compute affine transformation matrix 
7.     3. Compute inliers 
8.     4. Keep the largest set of inliers 
9.     5. Re-compute least-squares estimate on all of the inliers 
10. 
11. Args: 
12.     keypoints1: M1 x 2 matrix, each row is a point 
13.     keypoints2: M2 x 2 matrix, each row is a point 
14.     matches: N x 2 matrix, each row represents a match 
15.         [index of keypoint1, index of keypoint 2] 
16.     n_iters: the number of iterations RANSAC will run 
17.     threshold: the number of threshold to find inliers 
18. 
19. Returns: 
20.     H: a robust estimation of affine transformation from keypoints2 to 
21.     keypoints 1 
22. """  
23. # Copy matches array, to avoid overwriting it  
24. orig_matches = matches.copy()  
25. matches = matches.copy()  
26.  
27. N = matches.shape[0]  
28. print(N)  
29. n_samples = int(N * 0.2)  
30.  
31. matched1 = pad(keypoints1[matches[:,0]])  
32. matched2 = pad(keypoints2[matches[:,1]])  
33.  
34. max_inliers = np.zeros(N)  
35. n_inliers = 0  
36.  
37. # RANSAC iteration start  
38. ### YOUR CODE HERE  
39. for i in range(n_iters):  
40.     inliersArr = np.zeros(N)  
41.     idx = np.random.choice(N, n_samples, replace=False)  
42.     p1 = matched1[idx, :]  
43.     p2 = matched2[idx, :]  
44.  
45.     H, residuals, rank, s = np.linalg.lstsq(p2, p1, rcond=None)  
46.     H[:, 2] = np.array([0, 0, 1])  
47.  
48.     output = np.dot(matched2, H)  
49.     inliersArr = np.linalg.norm(output-matched1, axis=1)**2 < threshold  
50.     inliersCount = np.sum(inliersArr)  
51.  
52.     if inliersCount > n_inliers:  
53.         max_inliers = inliersArr.copy() #这里需要注意深拷贝和浅拷贝  
54.         n_inliers = inliersCount  
55.  
56. # 迭代完成,拿最大数目的匹配点对进行估计变换矩阵  
57. H, residuals, rank, s = np.linalg.lstsq(matched2[max_inliers], matched1[max_inliers], rcond=None)  
58. H[:, 2] = np.array([0, 0, 1])  
59. ### END YOUR CODE  
60. print(H)  
61. return H, orig_matches[max_inliers]

4.5 实现不同的描述子HOG,得到不同的拼接结果

实验结果:

机器视觉基础实验-Panorama Stitching_数据集_09

机器视觉基础实验-Panorama Stitching_数据集_10

机器视觉基础实验-Panorama Stitching_仿射变换_11

实验代码:

1.def hog_descriptor(patch, pixels_per_cell=(8,8)):  
2. """ 
3. Generating hog descriptor by the following steps: 
4. 
5. 1. Compute the gradient image in x and y directions (already done for you) 
6. 2. Compute gradient histograms for each cell 
7. 3. Flatten block of histograms into a 1D feature vector 
8.     Here, we treat the entire patch of histograms as our block 
9. 4. Normalize flattened block 
10.     Normalization makes the descriptor more robust to lighting variations 
11. 
12. Args: 
13.     patch: grayscale image patch of shape (H, W) 
14.     pixels_per_cell: size of a cell with shape (M, N) 
15. 
16. Returns: 
17.     block: 1D patch descriptor array of shape ((H*W*n_bins)/(M*N)) 
18. """  
19. assert (patch.shape[0] % pixels_per_cell[0] == 0),\  
20.             'Heights of patch and cell do not match'  
21. assert (patch.shape[1] % pixels_per_cell[1] == 0),\  
22.             'Widths of patch and cell do not match'  
23.  
24. n_bins = 9  
25. degrees_per_bin = 180 // n_bins  
26.  
27. Gx = filters.sobel_v(patch)  
28. Gy = filters.sobel_h(patch)  
29.  
30. # Unsigned gradients  
31. G = np.sqrt(Gx**2 + Gy**2)  
32. theta = (np.arctan2(Gy, Gx) * 180 / np.pi) % 180  
33.  
34. # Group entries of G and theta into cells of shape pixels_per_cell, (M, N)  
35. #   G_cells.shape = theta_cells.shape = (H//M, W//N)  
36. #   G_cells[0, 0].shape = theta_cells[0, 0].shape = (M, N)  
37. G_cells = view_as_blocks(G, block_shape=pixels_per_cell)  
38. theta_cells = view_as_blocks(theta, block_shape=pixels_per_cell)  
39. rows = G_cells.shape[0]  
40. cols = G_cells.shape[1]  
41.  
42. # For each cell, keep track of gradient histrogram of size n_bins  
43. cells = np.zeros((rows, cols, n_bins))  
44.  
45. # Compute histogram per cell  
46. ### YOUR CODE HERE  
47. # 首先需要遍历Cell  
48. for row in range(rows):  
49.     for col in range(cols):  
50.         # 遍历cell中的像素  
51.         for y in range(pixels_per_cell[0]):  
52.             for x in range(pixels_per_cell[1]):  
53.                 # 计算该像素的梯度方向(在n_bins个方向中属于第几个区间)  
54.                 angle = theta_cells[row,col,y,x]  
55.                 order = int(angle) // degrees_per_bin  
56.                 if order == 9:  
57.                     order = 8  
58.                 # 统计该cell中每个方向区间内的强度  
59.                 # 累加强度也可以  
60.                 cells[row,col,order] += G_cells[row,col,y,x]  
61.  
62. # 最后做归一化处理,直方图归一化  
63. cells = (cells - cells.mean()) / (cells.std())  
64. block = cells.reshape(-1) # 一维  
65. ### YOUR CODE HERE  
66.  
67. return block