|
import numpy as np |
|
import cv2 |
|
from typing import Tuple, List |
|
|
|
|
|
BONE_NAMES = [ |
|
"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", |
|
"J0", "J1", "J2", "J3", "J4", "J5", "J6", "J7", "J8", |
|
"B0", "C0", "D0", "E0", "F0", "G0", "H0", "I0", |
|
"B8", "C8", "D8", "E8", "F8", "G8", "H8", "I8", |
|
] |
|
|
|
def check_keypoints(keypoints: np.ndarray): |
|
""" |
|
检查关键点坐标是否正确 |
|
@param keypoints: 关键点坐标, shape 为 (34, 2) |
|
""" |
|
if keypoints.shape != (34, 2): |
|
raise Exception(f"keypoints shape error: {keypoints.shape}") |
|
|
|
|
|
def build_cells_xywh_by_cronners(corner_points: np.ndarray, padding: int = 3) -> np.ndarray: |
|
""" |
|
根据 棋盘的 corner 点坐标 计算 每个位置的 xywh |
|
@param corner_points: 棋盘的 corner 点坐标, shape 为 (4, 2) |
|
@param padding: 棋盘边框 padding |
|
|
|
@return: 棋盘的 xywh, shape 为 (10, 9, 4), 4 为 center_x, center_y, w, h |
|
""" |
|
|
|
if corner_points.shape != (4, 2): |
|
raise Exception(f"corner_points shape error: {corner_points.shape}") |
|
|
|
top_left_xy = corner_points[0] |
|
top_right_xy = corner_points[1] |
|
bottom_left_xy = corner_points[2] |
|
bottom_right_xy = corner_points[3] |
|
|
|
|
|
item_w = (top_right_xy[0] - top_left_xy[0]) / (9 - 1) |
|
item_h = (bottom_left_xy[1] - top_left_xy[1]) / (10 - 1) |
|
|
|
item_w = item_w |
|
item_h = item_h |
|
|
|
item_w_with_padding = item_w - padding * 2 |
|
item_h_with_padding = item_h - padding * 2 |
|
|
|
|
|
cells_xywh = np.zeros((10, 9, 4)) |
|
|
|
for i in range(10): |
|
for j in range(9): |
|
center_x = top_left_xy[0] + item_w * j |
|
center_y = top_left_xy[1] + item_h * i |
|
|
|
cells_xywh[i, j] = [center_x, center_y, item_w_with_padding, item_h_with_padding] |
|
|
|
return cells_xywh |
|
|
|
|
|
|
|
|
|
def build_cells_xywh(keypoints: np.ndarray, width: int = 450, height: int = 500, padding: int = 3) -> np.ndarray: |
|
""" |
|
@param keypoints: 关键点坐标, shape 为 (34, 2) |
|
@param width: 棋盘宽度 |
|
@param height: 棋盘高度 |
|
@param padding: 棋盘边框 padding |
|
@return: 棋盘的 xywh, shape 为 (10, 9, 4), 4 为 center_x, center_y, w, h |
|
""" |
|
check_keypoints(keypoints) |
|
|
|
|
|
|
|
cells_xywh = np.zeros((10, 9, 4), dtype=np.int16) |
|
|
|
|
|
for i in range(10): |
|
for j in range(9): |
|
|
|
row_name = chr(ord('A') + i) |
|
col_name = str(j) |
|
flag_name = f"{row_name}{col_name}" |
|
if flag_name in BONE_NAMES: |
|
|
|
cur_xy = keypoints[BONE_NAMES.index(flag_name)] |
|
cells_xywh[i, j] = [cur_xy[0], cur_xy[1], 0, 0] |
|
else: |
|
|
|
row_start_name = f"{row_name}0" |
|
row_end_name = f"{row_name}8" |
|
|
|
col_start_name = f"A{col_name}" |
|
col_end_name = f"J{col_name}" |
|
|
|
row_start_xy = keypoints[BONE_NAMES.index(row_start_name)] |
|
row_end_xy = keypoints[BONE_NAMES.index(row_end_name)] |
|
|
|
col_start_xy = keypoints[BONE_NAMES.index(col_start_name)] |
|
col_end_xy = keypoints[BONE_NAMES.index(col_end_name)] |
|
|
|
|
|
|
|
x1, y1 = row_start_xy |
|
x2, y2 = row_end_xy |
|
x3, y3 = col_start_xy |
|
x4, y4 = col_end_xy |
|
|
|
|
|
|
|
denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) |
|
|
|
|
|
x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denominator |
|
|
|
y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denominator |
|
|
|
cells_xywh[i, j] = [int(x), int(y), 0, 0] |
|
|
|
|
|
for i in range(10): |
|
for j in range(9): |
|
cur_xy = cells_xywh[i, j] |
|
|
|
if i == 0: |
|
|
|
up_xy = 2 * cur_xy - cells_xywh[i+1, j] |
|
else: |
|
up_xy = cells_xywh[i - 1, j] |
|
|
|
if i == 9: |
|
|
|
down_xy = 2 * cur_xy - cells_xywh[i-1, j] |
|
else: |
|
down_xy = cells_xywh[i+1, j] |
|
|
|
if j == 0: |
|
left_xy = 2 * cur_xy - cells_xywh[i, j+1] |
|
else: |
|
left_xy = cells_xywh[i, j-1] |
|
|
|
if j == 8: |
|
right_xy = 2 * cur_xy - cells_xywh[i, j-1] |
|
else: |
|
right_xy = cells_xywh[i, j+1] |
|
|
|
min_x = min(up_xy[0].tolist(), down_xy[0].tolist(), left_xy[0].tolist(), right_xy[0].tolist()) |
|
min_y = min(up_xy[1].tolist(), down_xy[1].tolist(), left_xy[1].tolist(), right_xy[1].tolist()) |
|
|
|
min_x += padding |
|
min_y += padding |
|
|
|
|
|
min_x = max(min_x, 1) |
|
min_y = max(min_y, 1) |
|
|
|
max_x = max(up_xy[0].tolist(), down_xy[0].tolist(), left_xy[0].tolist(), right_xy[0].tolist()) |
|
max_y = max(up_xy[1].tolist(), down_xy[1].tolist(), left_xy[1].tolist(), right_xy[1].tolist()) |
|
|
|
max_x -= padding |
|
max_y -= padding |
|
|
|
|
|
max_x = min(max_x, width - 1) |
|
max_y = min(max_y, height - 1) |
|
|
|
w = (max_x - min_x) / 2 |
|
h = (max_y - min_y) / 2 |
|
|
|
cells_xywh[i, j] = [int(cur_xy[0]), int(cur_xy[1]), int(w), int(h)] |
|
|
|
return cells_xywh |
|
|
|
|
|
def perspective_transform( |
|
image: cv2.UMat, |
|
src_points: np.ndarray, |
|
keypoints: np.ndarray, |
|
dst_size=(450, 500)) -> Tuple[cv2.UMat, np.ndarray, np.ndarray]: |
|
""" |
|
透视变换 |
|
@param image: 图片 |
|
@param src_points: 源点坐标 |
|
@param keypoints: 关键点坐标 |
|
@param dst_size: 目标尺寸 (width, height) 10 行 9 列 |
|
|
|
@return: |
|
result: 透视变换后的图片 |
|
transformed_keypoints: 透视变换后的关键点坐标 |
|
corner_points: 棋盘的 corner 点坐标, shape 为 (4, 2) A0, A8, J0, J8 |
|
""" |
|
|
|
check_keypoints(keypoints) |
|
|
|
|
|
|
|
src = np.float32(src_points) |
|
padding = 50 |
|
corner_points = np.float32([ |
|
|
|
[padding, padding], |
|
|
|
[dst_size[0]-padding, padding], |
|
|
|
[padding, dst_size[1]-padding], |
|
|
|
[dst_size[0]-padding, dst_size[1]-padding]]) |
|
|
|
|
|
matrix = cv2.getPerspectiveTransform(src, corner_points) |
|
|
|
|
|
result = cv2.warpPerspective(image, matrix, dst_size) |
|
|
|
|
|
keypoints_reshaped = keypoints.reshape(-1, 1, 2).astype(np.float32) |
|
transformed_keypoints = cv2.perspectiveTransform(keypoints_reshaped, matrix) |
|
|
|
transformed_keypoints = transformed_keypoints.reshape(-1, 2) |
|
|
|
return result, transformed_keypoints, corner_points |
|
|
|
|
|
|
|
def get_board_corner_points(keypoints: np.ndarray) -> np.ndarray: |
|
""" |
|
计算棋局四个边角的 points |
|
@param keypoints: 关键点坐标, shape 为 (34, 2) |
|
@return: 边角的坐标, shape 为 (4, 2) |
|
""" |
|
check_keypoints(keypoints) |
|
|
|
|
|
a0_index = BONE_NAMES.index("A0") |
|
a8_index = BONE_NAMES.index("A8") |
|
j0_index = BONE_NAMES.index("J0") |
|
j8_index = BONE_NAMES.index("J8") |
|
|
|
a0_xy = keypoints[a0_index] |
|
a8_xy = keypoints[a8_index] |
|
j0_xy = keypoints[j0_index] |
|
j8_xy = keypoints[j8_index] |
|
|
|
|
|
dst_points = np.array([ |
|
a0_xy, |
|
a8_xy, |
|
j0_xy, |
|
j8_xy |
|
], dtype=np.float32) |
|
|
|
return dst_points |
|
|
|
def extract_chessboard(img: cv2.UMat, keypoints: np.ndarray) -> Tuple[cv2.UMat, np.ndarray, np.ndarray]: |
|
""" |
|
提取棋盘信息 |
|
@param img: 图片 |
|
@param keypoints: 关键点坐标, shape 为 (34, 2) |
|
@return: |
|
transformed_image: 透视变换后的图片 |
|
transformed_keypoints: 透视变换后的关键点坐标 |
|
transformed_corner_points: 棋盘的 corner 点坐标, shape 为 (4, 2) A0, A8, J0, J8 |
|
""" |
|
|
|
check_keypoints(keypoints) |
|
|
|
source_corner_points = get_board_corner_points(keypoints) |
|
|
|
transformed_image, transformed_keypoints, transformed_corner_points = perspective_transform(img, source_corner_points, keypoints) |
|
|
|
return transformed_image, transformed_keypoints, transformed_corner_points |
|
|
|
|
|
def collect_cells_images(image: cv2.UMat, cells_xywh: np.ndarray) -> List[List[np.ndarray]]: |
|
""" |
|
收集 棋盘的 cells_xywh 对应的图片集合 |
|
""" |
|
width = image.shape[1] |
|
height = image.shape[0] |
|
crop_cells: List[List[np.ndarray]] = [] |
|
|
|
for i in range(10): |
|
row_cells = [] |
|
for j in range(9): |
|
x, y, w, h = cells_xywh[i, j] |
|
|
|
x_0 = max(int(x-w/2), 0) |
|
y_0 = max(int(y-h/2), 0) |
|
x_1 = min(int(x+w/2), width-1) |
|
y_1 = min(int(y+h/2), height-1) |
|
|
|
crop_img = image[y_0:y_1, x_0:x_1] |
|
row_cells.append(crop_img) |
|
crop_cells.append(row_cells) |
|
|
|
return crop_cells |
|
|
|
def draw_cells_box(image: cv2.UMat, cells_xywh: np.ndarray) -> cv2.UMat: |
|
""" |
|
绘制 棋盘的 cells_xywh 对应的 矩形框 |
|
""" |
|
width = image.shape[1] |
|
height = image.shape[0] |
|
for i in range(10): |
|
for j in range(9): |
|
x, y, w, h = cells_xywh[i, j] |
|
|
|
x_0 = max(int(x-w/2), 0) |
|
y_0 = max(int(y-h/2), 0) |
|
x_1 = min(int(x+w/2), width-1) |
|
y_1 = min(int(y+h/2), height-1) |
|
|
|
cv2.rectangle(image,(x_0, y_0), (x_1, y_1), (0, 0, 255), 1) |
|
|
|
return image |
|
|