单应性(Homographies)
学习使用仿射变换和透视变换对平面物体的图像进行配准。
\begin{bmatrix} \lambda u \ \lambda v \ \lambda \end{bmatrix} = PX_{world} = \begin{bmatrix} f/l_x & 0 & c_x \ 0 & f/l_y & c_y \ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} r_{00} & r_{01} & r_{02} & t_x \ r_{10} & r_{11} & r_{12} & t_y \ r_{20} & r_{21} & r_{22} & t_z \end{bmatrix} \begin{bmatrix} X \ Y \ Z \ 1 \end{bmatrix}_{world} \quad (1)
\begin{bmatrix} \lambda u \ \lambda v \ \lambda \end{bmatrix} = P \begin{bmatrix} X \ Y \ \alpha X + \beta Y + \gamma \ 1 \end{bmatrix} \qquad (2)
\begin{bmatrix} \lambda u \ \lambda v \ \lambda \end{bmatrix} = M \begin{bmatrix} X \ Y \ 1 \end{bmatrix} = \begin{bmatrix} m_{00} & m_{01} & m_{02} \ m_{10} & m_{11} & m_{12} \ m_{20} & m_{21} & m_{22} \end{bmatrix} \begin{bmatrix} X \ Y \ 1 \end{bmatrix} \qquad (3)
\begin{bmatrix} u \ v \ 1 \end{bmatrix} = A \begin{bmatrix} X \ Y \ 1 \end{bmatrix} = \begin{bmatrix} a_{00} & a_{01} & a_{02} \ a_{10} & a_{11} & a_{12} \ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} X \ Y \ 1 \end{bmatrix}
1import cv2 2import numpy as np 3import os 4import copy 5 6image_filenames = ['draw_book_perpendicular.jpg', 'draw_book_angle.jpg'] 7images_directory = "./images/paper" 8# 加载图像 9images = [cv2.imread(os.path.join(images_directory, filename)) for filename in image_filenames] 10 11# 将图像写入"./output"目录 12cv2.imwrite("./output/0_original.png", images[0]) 13cv2.imwrite("./output/1_original.png", images[1])
1def locate_corner(blurred_img, corner, template_sizeHW, blur_size): 2 template = np.zeros((template_sizeHW[0], template_sizeHW[1], 3), dtype=np.uint8) 3 template_center = (template_sizeHW[1]//2, template_sizeHW[0]//2) 4 if corner == 'NW': 5 cv2.rectangle(template, template_center, (template_sizeHW[1] - 1, template_sizeHW[0] - 1), 6 (255, 255, 255), thickness=-1) 7 elif corner == 'NE': 8 cv2.rectangle(template, template_center, (0, template_sizeHW[0] - 1), 9 (255, 255, 255), thickness=-1) 10 elif corner == 'SE': 11 cv2.rectangle(template, template_center, (0, 0), 12 (255, 255, 255), thickness=-1) 13 elif corner == 'SW': 14 cv2.rectangle(template, template_center, (template_sizeHW[1] - 1, 0), 15 (255, 255, 255), thickness=-1) 16 else: 17 raise NotImplementedError(f'locate_corner(): 未实现的角点类型 {corner}') 18 template = cv2.blur(template, blur_size) 19 20 # 模板匹配 21 match_img = cv2.matchTemplate(blurred_img, template, cv2.TM_CCOEFF_NORMED) 22 23 # 找到最高匹配位置 24 _, max_val, _, max_loc = cv2.minMaxLoc(match_img) 25 max_loc = (max_loc[0] + template_sizeHW[1] // 2, max_loc[1] + template_sizeHW[0] // 2) 26 return max_loc
1# ... 续 2blur_size = (5, 5) 3 4# 对图像进行模糊处理 5blurred_images = [] 6for img_ndx in range(len(images)): 7 blurred_images.append(cv2.blur(images[img_ndx], blur_size)) 8 9# 检测三个角点 10template_sizeHW = (17, 17) 11corners_NW_list = [locate_corner(img, 'NW', template_sizeHW, blur_size) for img in blurred_images] 12corners_NE_list = [locate_corner(img, 'NE', template_sizeHW, blur_size) for img in blurred_images] 13corners_SE_list = [locate_corner(img, 'SE', template_sizeHW, blur_size) for img in blurred_images] 14 15# 标注检测到的角点 16for img_ndx in range(len(images)): 17 annotated_img = copy.deepcopy(images[img_ndx]) 18 cv2.circle(annotated_img, corners_NW_list[img_ndx], 5, (255, 0, 0), 2) 19 cv2.circle(annotated_img, corners_NE_list[img_ndx], 5, (0, 255, 0), 2) 20 cv2.circle(annotated_img, corners_SE_list[img_ndx], 5, (0, 0, 255), 2) 21 cv2.imwrite(f"./output/{img_ndx}_corners.png", annotated_img)
1# ... 续 2# 仿射变换 3feature_points0 = np.array([[corners_NW_list[0][0], corners_NW_list[0][1]], 4 [corners_NE_list[0][0], corners_NE_list[0][1]], 5 [corners_SE_list[0][0], corners_SE_list[0][1]]], dtype=np.float32) 6feature_points1 = np.array([[corners_NW_list[1][0], corners_NW_list[1][1]], 7 [corners_NE_list[1][0], corners_NE_list[1][1]], 8 [corners_SE_list[1][0], corners_SE_list[1][1]]], dtype=np.float32) 9 10# 定义目标映射点(任意设定) 11warped_corners = np.array([[100, 100], [1100, 100], [1100, 1100]], dtype=np.float32) 12affine_mtx0 = cv2.getAffineTransform(feature_points0, warped_corners) 13affine_mtx1 = cv2.getAffineTransform(feature_points1, warped_corners) 14print(f"affine_mtx0 =\n{affine_mtx0}") 15print(f"affine_mtx1 =\n{affine_mtx1}") 16 17# 生成变换后的图像 18warped_image_size = (1200, 1200) 19warped_affine_img0 = cv2.warpAffine(images[0], affine_mtx0, dsize=warped_image_size) 20warped_affine_img1 = cv2.warpAffine(images[1], affine_mtx1, dsize=warped_image_size) 21cv2.imwrite("./output/0_warped_affine.png", warped_affine_img0) 22cv2.imwrite("./output/1_warped_affine.png", warped_affine_img1)
1# ... 续 2# 检测四个角点 3template_sizeHW = (17, 17) 4corners_NW_list = [locate_corner(img, 'NW', template_sizeHW, blur_size) for img in blurred_images] 5corners_NE_list = [locate_corner(img, 'NE', template_sizeHW, blur_size) for img in blurred_images] 6corners_SE_list = [locate_corner(img, 'SE', template_sizeHW, blur_size) for img in blurred_images] 7corners_SW_list = [locate_corner(img, 'SW', template_sizeHW, blur_size) for img in blurred_images] 8 9# 标注检测到的角点 10for img_ndx in range(len(images)): 11 annotated_img = copy.deepcopy(images[img_ndx]) 12 cv2.circle(annotated_img, corners_NW_list[img_ndx], 5, (255, 0, 0), 2) 13 cv2.circle(annotated_img, corners_NE_list[img_ndx], 5, (0, 255, 0), 2) 14 cv2.circle(annotated_img, corners_SE_list[img_ndx], 5, (0, 0, 255), 2) 15 cv2.circle(annotated_img, corners_SW_list[img_ndx], 5, (0, 255, 255), 2) 16 cv2.imwrite(f"./output/{img_ndx}_corners.png", annotated_img)
1# ... 续 2# 透视变换 3feature_points0 = np.array([[corners_NW_list[0][0], corners_NW_list[0][1]], 4 [corners_NE_list[0][0], corners_NE_list[0][1]], 5 [corners_SE_list[0][0], corners_SE_list[0][1]], 6 [corners_SW_list[0][0], corners_SW_list[0][1]]], dtype=np.float32) 7feature_points1 = np.array([[corners_NW_list[1][0], corners_NW_list[1][1]], 8 [corners_NE_list[1][0], corners_NE_list[1][1]], 9 [corners_SE_list[1][0], corners_SE_list[1][1]], 10 [corners_SW_list[1][0], corners_SW_list[1][1]]], dtype=np.float32) 11 12# 定义目标映射点(任意设定) 13warped_corners = np.array([[100, 100], [1100, 100], [1100, 1100], [100, 1100]], dtype=np.float32) 14perspective_mtx0 = cv2.getPerspectiveTransform(feature_points0, warped_corners) 15perspective_mtx1 = cv2.getPerspectiveTransform(feature_points1, warped_corners) 16print(f"perspective_mtx0 =\n{perspective_mtx0}") 17print(f"perspective_mtx1 =\n{perspective_mtx1}") 18 19# 生成透视变换后的图像 20warped_image_size = (1200, 1200) 21warped_perspective_img0 = cv2.warpPerspective(images[0], perspective_mtx0, dsize=warped_image_size) 22warped_perspective_img1 = cv2.warpPerspective(images[1], perspective_mtx1, dsize=warped_image_size) 23cv2.imwrite("./output/0_warped_perspective.png", warped_perspective_img0) 24cv2.imwrite("./output/1_warped_perspective.png", warped_perspective_img1)
1# ... 续 2 3title_rect = (124, 132, 508, 316) 4educational_series_rect = (458, 1000, 278, 126) 5 6annotated_img0 = copy.deepcopy(warped_perspective_img0) 7annotated_img1 = copy.deepcopy(warped_perspective_img1) 8img_ndx = 0 9for annotated_img in [annotated_img0, annotated_img1]: 10 cv2.rectangle(annotated_img, (title_rect[0], title_rect[1]), 11 (title_rect[0] + title_rect[2], title_rect[1] + title_rect[3]), 12 (255, 0, 0), thickness=3) 13 cv2.rectangle(annotated_img, (educational_series_rect[0], educational_series_rect[1]), 14 (educational_series_rect[0] + educational_series_rect[2], educational_series_rect[1] + educational_series_rect[3]), 15 (0, 0, 255), thickness=3) 16 cv2.imwrite(f"./output/{img_ndx}_annotated.png", annotated_img) 17 img_ndx += 1