357 lines
13 KiB
Python
357 lines
13 KiB
Python
import numpy as np
|
||
import open3d as o3d
|
||
import cv2
|
||
from pokemon import Pokemon
|
||
import random
|
||
|
||
|
||
PAPER_WCS_POINT = np.array([[0, 0, 0], [18.5, 0, 0], [0, 26, 0], [18.5, 26, 0]], dtype=np.float32)
|
||
CAMERA_MATRIX = np.load('camera_parameters.npy', allow_pickle=True).item()['K']
|
||
DISTORTION_MATRIX = np.load('camera_parameters.npy', allow_pickle=True).item()['dist']
|
||
types = ['fire', 'water', 'electric', 'rock', 'grass', 'ice', 'steel']
|
||
animals = ['cat', 'dog', 'rat', 'rabbit', 'dragon', 'duck', 'turtle', 'butterfly', 'monkey', 'bee', 'fox', 'flower', 'horse', 'jellyfish', 'snake', 'Tyrannosaurus', 'dinosaur', 'fish', 'whale', 'bat', 'bear', 'deer', 'pig', 'eagle', 'chicken']
|
||
|
||
def get_new_approx(approx, previous_approx):
|
||
def distance(point, pre_point):
|
||
point = point[0]
|
||
pre_point = pre_point[0]
|
||
d = ((point[0]-pre_point[0]) ** 2) + ((point[1]-pre_point[1]) ** 2)
|
||
|
||
return np.sqrt(d)
|
||
|
||
new_approx = []
|
||
for pre_point in previous_approx:
|
||
min_d, min_index = 1e9, -100
|
||
for index, point in enumerate(approx):
|
||
d = distance(point, pre_point)
|
||
if d < min_d and d<=500:
|
||
min_d = d
|
||
min_index = index
|
||
|
||
if min_index != -100:
|
||
np.delete(approx, min_index)
|
||
new_approx.append(approx[min_index])
|
||
|
||
if len(new_approx) == 4:
|
||
return new_approx
|
||
else:
|
||
return previous_approx
|
||
|
||
def initial_approx(approx):
|
||
distance = []
|
||
for point in approx:
|
||
x, y = point[0]
|
||
distance.append(x**2+y**2)
|
||
min_d, max_d = 1e9, -1
|
||
min_index, max_index = -100, -100
|
||
for index, d in enumerate(distance):
|
||
if d < min_d:
|
||
min_d = d
|
||
min_index = index
|
||
if d > max_d:
|
||
max_d = d
|
||
max_index = index
|
||
new_approx = []
|
||
|
||
remain_index = list(range(4))
|
||
remain_index.remove(max_index)
|
||
remain_index.remove(min_index)
|
||
|
||
if approx[remain_index[0]][0][0] > approx[remain_index[1]][0][0]: # remain 的第一個點的 x 大於第二個點的,代表他在右上角
|
||
second_index = remain_index[0]
|
||
third_index = remain_index[1]
|
||
else:
|
||
second_index = remain_index[1]
|
||
third_index = remain_index[0]
|
||
|
||
new_approx.append(approx[min_index])
|
||
new_approx.append(approx[second_index])
|
||
new_approx.append(approx[third_index])
|
||
new_approx.append(approx[max_index])
|
||
|
||
return new_approx
|
||
|
||
def new_pokemon():
|
||
new_type = types[ random.randint(0, len(types)-1) ]
|
||
new_animal = animals[ random.randint(0, len(animals)-1) ]
|
||
print(new_type, new_animal)
|
||
return Pokemon(new_type, new_animal)
|
||
|
||
def plot_corner_points(frame, approx):
|
||
y, x = approx[0][0]
|
||
cv2.circle(frame, (x, y), 15, (0, 0, 255), -1) # 在角點位置畫紅色圓圈
|
||
|
||
y, x = approx[1][0]
|
||
cv2.circle(frame, (x, y), 30, (0, 0, 255), -1) # 在角點位置畫紅色圓圈
|
||
|
||
y, x = approx[2][0]
|
||
cv2.circle(frame, (x, y), 15, (255, 0, 0), -1) # 在角點位置畫紅色圓圈
|
||
|
||
y, x = approx[3][0]
|
||
cv2.circle(frame, (x, y), 30, (255, 0, 0), -1) # 在角點位置畫紅色圓圈
|
||
|
||
|
||
def visualization(inverse_matrix_M, pokemon):
|
||
box_size = 0.3
|
||
|
||
# initialize visualizer
|
||
vis = o3d.visualization.Visualizer()
|
||
vis.create_window()
|
||
|
||
# 只畫紙張
|
||
paper_points = np.array(PAPER_WCS_POINT)
|
||
paper_lines = np.array([[0, 1], [0, 2], [1, 3], [2, 3]])
|
||
paper_line_colors = np.array([[255, 0, 0], [255, 0, 0], [255, 0, 0], [255, 0, 0]])
|
||
paper_triangles = np.array([[0, 1, 2], [1, 2, 3], [2, 1, 0], [3, 2, 1]])
|
||
# 畫紙張所有線段
|
||
paper_line_set = o3d.geometry.LineSet()
|
||
paper_line_set.points = o3d.utility.Vector3dVector(paper_points)
|
||
paper_line_set.lines = o3d.utility.Vector2iVector(paper_lines)
|
||
paper_line_set.colors = o3d.utility.Vector3dVector(paper_line_colors)
|
||
vis.add_geometry(paper_line_set)
|
||
|
||
coordinate_line_set = o3d.geometry.LineSet()
|
||
coordinate_line_set.points = o3d.utility.Vector3dVector(np.array([[0, 0, 0],[10, 0, 0], [0, 10, 0], [0, 0, 10]]))
|
||
coordinate_line_set.lines = o3d.utility.Vector2iVector(np.array([[0, 1], [0, 2], [0, 3]]))
|
||
coordinate_line_set.colors = o3d.utility.Vector3dVector(np.array([[255, 0, 0], [0, 255, 0],[ 0, 0, 255]]))
|
||
vis.add_geometry(coordinate_line_set)
|
||
|
||
# 化紙張三角形
|
||
paper_triangle_set = o3d.geometry.TriangleMesh()
|
||
paper_triangle_set.vertices = o3d.utility.Vector3dVector(paper_points)
|
||
paper_triangle_set.triangles = o3d.utility.Vector3iVector(paper_triangles)
|
||
vis.add_geometry(paper_triangle_set)
|
||
|
||
# Pokemon model
|
||
pcd = o3d.geometry.PointCloud()
|
||
pokemon_point = pokemon.get_position()[:, :3]
|
||
pokemon_point[:, 2] = -pokemon_point[:, 2]
|
||
pcd.points = o3d.utility.Vector3dVector(pokemon_point)
|
||
pcd.colors = o3d.utility.Vector3dVector(pokemon.get_colors())
|
||
vis.add_geometry(pcd)
|
||
|
||
|
||
|
||
# 下面是畫相機
|
||
# 找 金字塔端點
|
||
points = []
|
||
lines = []
|
||
line_colors = []
|
||
triangles = []
|
||
for m in inverse_matrix_M: # 不同位置的 M
|
||
start_index = len(points)
|
||
for corner in [
|
||
[0, 0, 0, 1],
|
||
[-box_size, -box_size, 1, 1],
|
||
[-box_size, box_size, 1, 1],
|
||
[box_size, box_size, 1, 1],
|
||
[box_size, -box_size, 1, 1],
|
||
]:
|
||
location = m @ np.array(corner)
|
||
location[2] = -location[2]
|
||
points.append(location)
|
||
|
||
|
||
# 方框的線
|
||
lines.append(np.array([ start_index+1, start_index+2 ]))
|
||
lines.append(np.array([ start_index+2, start_index+3 ]))
|
||
lines.append(np.array([ start_index+3, start_index+4 ]))
|
||
lines.append(np.array([ start_index+4, start_index+1 ]))
|
||
|
||
# 相機點到方框四點的線
|
||
lines.append(np.array([ start_index, start_index+1 ]))
|
||
lines.append(np.array([ start_index, start_index+2 ]))
|
||
lines.append(np.array([ start_index, start_index+3 ]))
|
||
lines.append(np.array([ start_index, start_index+4 ]))
|
||
|
||
# 八條線都是黑色
|
||
for i in range(8):
|
||
line_colors.append(np.array([0, 0, 0]))
|
||
|
||
# trajectory 紅線
|
||
if start_index != 0:
|
||
lines.append(np.array([ start_index, start_index-5 ]))
|
||
line_colors.append(np.array([1, 0, 0]))
|
||
|
||
# 兩塊三角形組成方框
|
||
triangles.append([ start_index+1, start_index+2, start_index+3 ])
|
||
triangles.append([ start_index+1, start_index+3, start_index+4 ])
|
||
triangles.append([ start_index+3, start_index+2, start_index+1 ])
|
||
triangles.append([ start_index+4, start_index+3, start_index+1 ])
|
||
|
||
points = np.array(points)[:, :-1]
|
||
lines = np.array(lines)
|
||
line_colors = np.array(line_colors)
|
||
triangles = np.array(triangles)
|
||
|
||
# 畫所有線段
|
||
line_set = o3d.geometry.LineSet()
|
||
line_set.points = o3d.utility.Vector3dVector(points)
|
||
line_set.lines = o3d.utility.Vector2iVector(lines)
|
||
line_set.colors = o3d.utility.Vector3dVector(line_colors)
|
||
vis.add_geometry(line_set)
|
||
|
||
# 化三角形
|
||
triangle_set = o3d.geometry.TriangleMesh()
|
||
triangle_set.vertices = o3d.utility.Vector3dVector(points)
|
||
triangle_set.triangles = o3d.utility.Vector3iVector(triangles)
|
||
vis.add_geometry(triangle_set)
|
||
|
||
vis.run()
|
||
|
||
def render_pokemon(frame, M, camera_position, pokemon):
|
||
pokemon_points = pokemon.get_position()
|
||
pokemon_colors = pokemon.get_colors()
|
||
distance = np.linalg.norm(pokemon_points - camera_position, axis=1)
|
||
|
||
sorted_indices = np.argsort(-distance)
|
||
pokemon_points = pokemon_points[sorted_indices]
|
||
pokemon_colors = pokemon_colors[sorted_indices]
|
||
|
||
M = CAMERA_MATRIX.dot(M)
|
||
pokemon_points = (M @ (pokemon_points.T)).T
|
||
for point , color in zip(pokemon_points, pokemon_colors):
|
||
color = [int(i*255) for i in color]
|
||
color = np.array(color, dtype=int)
|
||
color = color[0:3]
|
||
point2D = point[:2] / point[2]
|
||
point2D = np.int_(point2D[::-1])
|
||
if 0 <= point2D[0] and point2D[0] < frame.shape[1] and 0 <= point2D[1] and point2D[1] < frame.shape[0]:
|
||
frame = cv2.circle(frame, tuple(point2D), 10,color.astype(int).tolist(), thickness=-1)
|
||
return frame
|
||
|
||
|
||
|
||
if __name__ == '__main__':
|
||
|
||
# 設定連接到 Android 手機的相機
|
||
cap = cv2.VideoCapture(0) # 0 表示第一個相機(通常是後置相機),若是前置相機,可以使用 1
|
||
# cap = cv2.VideoCapture('demo1.mp4')
|
||
|
||
previous_approx = []
|
||
|
||
|
||
pokemon = new_pokemon()
|
||
|
||
rotation_vectors = []
|
||
translation_vectors = []
|
||
while True:
|
||
ret, frame = cap.read() # 讀取影片幀
|
||
|
||
if not ret:
|
||
break
|
||
|
||
# 預處理影像(例如:轉為灰度)
|
||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
||
|
||
# 偵測邊緣
|
||
edges = cv2.Canny(gray, 30, 90)
|
||
dilate_kernel = np.ones((5, 5), np.uint8)
|
||
dilate_edges = cv2.dilate(edges, dilate_kernel, iterations=1)
|
||
|
||
# 偵測輪廓
|
||
contours, _ = cv2.findContours(dilate_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||
|
||
if contours != []:
|
||
# 找到最大的輪廓
|
||
max_contour = max(contours, key=cv2.contourArea)
|
||
|
||
# 找到輪廓的近似多邊形
|
||
epsilon = 0.05 * cv2.arcLength(max_contour, True)
|
||
approx = cv2.approxPolyDP(max_contour, epsilon, True)
|
||
|
||
approx = approx[:, :, ::-1]
|
||
|
||
# 繪製多邊形
|
||
if len(approx) == 4: # 確保是四個角點
|
||
|
||
# 比對 previous_approx,確認現在找到的四個點是紙張上的哪一點
|
||
if previous_approx == []:
|
||
print("INITIAL")
|
||
previous_approx = initial_approx(approx)
|
||
print(previous_approx)
|
||
new_approx = get_new_approx(approx, previous_approx)
|
||
previous_approx = new_approx
|
||
|
||
paper_ccs_point = np.concatenate(new_approx, axis=0, dtype=np.float32)
|
||
|
||
# 畫邊緣 & 四點
|
||
plot_corner_points(frame, new_approx)
|
||
cv2.drawContours(frame, [approx[:, :, ::-1]], -1, (0, 255, 0), 2) # 繪製輪廓
|
||
|
||
# 算 rotaion & translation
|
||
success, rotation_vector, translation_vector = cv2.solvePnP(PAPER_WCS_POINT, paper_ccs_point, \
|
||
CAMERA_MATRIX, DISTORTION_MATRIX)
|
||
rotation_matrix, _ = cv2.Rodrigues(rotation_vector)
|
||
rotation_vectors.append(rotation_matrix)
|
||
translation_vectors.append(translation_vector)
|
||
|
||
'''
|
||
print("R:", rotation_matrix)
|
||
print("t:", translation_vector)
|
||
print()
|
||
'''
|
||
|
||
R = rotation_matrix
|
||
t = translation_vector
|
||
rt = np.concatenate((R, t), axis=1)
|
||
# print('rt: ', rt)
|
||
M = np.concatenate((rt, [[0, 0, 0, 1]]), axis=0)
|
||
M_inv = np.linalg.inv(M)
|
||
camera_position = M_inv @ np.array([0, 0, 0, 1])
|
||
# camera_position[2] = -camera_position[2]
|
||
# print("CAMERA: ", camera_position)
|
||
|
||
frame = render_pokemon(frame, rt, camera_position, pokemon)
|
||
|
||
|
||
# 顯示結果
|
||
cv2.namedWindow('Paper Detection(edge)', 0)
|
||
cv2.imshow('Paper Detection(edge)', edges)
|
||
cv2.namedWindow('Paper Detection(dilate edge)', 0)
|
||
cv2.imshow('Paper Detection(dilate edge)', dilate_edges)
|
||
cv2.namedWindow('Paper Detection', 0)
|
||
cv2.imshow('Paper Detection', frame)
|
||
|
||
# 按下 'q' 鍵退出迴圈
|
||
key = cv2.waitKey(33) & 0xFF
|
||
if key == ord('q'): # 等待 33ms (1秒 = 1000ms, 1秒顯示幀)
|
||
break
|
||
if key == ord(' '):
|
||
pokemon = new_pokemon()
|
||
previous_approx = []
|
||
if key == ord('w'):
|
||
pokemon.walk_backward()
|
||
if key == ord('s'):
|
||
pokemon.walk_forward()
|
||
if key == ord('a'):
|
||
pokemon.walk_left()
|
||
if key == ord('d'):
|
||
pokemon.walk_right()
|
||
if key == ord('o'):
|
||
pokemon.rotate_right()
|
||
if key == ord('p'):
|
||
pokemon.rotate_left()
|
||
|
||
|
||
cap.release()
|
||
cv2.destroyAllWindows()
|
||
|
||
|
||
# 畫 camera pose
|
||
M_inv = []
|
||
for index in range(len(rotation_vectors)):
|
||
R = rotation_vectors[index]
|
||
t = translation_vectors[index]
|
||
rt = np.concatenate((R, t), axis=1)
|
||
M = np.concatenate((rt, [[0, 0, 0, 1]]), axis=0)
|
||
M_inv.append(np.linalg.inv(M))
|
||
|
||
# load Pokemon points
|
||
visualization(M_inv, pokemon)
|
||
|
||
|
||
|
||
|