Skip to content

Commit 2ee72d8

Browse files
...
1 parent 916378d commit 2ee72d8

File tree

9 files changed

+597
-251
lines changed

9 files changed

+597
-251
lines changed

pose_object.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import numpy as np
2+
import cv2
3+
import pygame
4+
from pygame.locals import *
5+
from OpenGL.GL import *
6+
from OpenGL.GLU import *
7+
8+
class ObjLoader:
9+
def __init__(self, filename, swapyz=False):
10+
self.vertices = []
11+
self.normals = []
12+
self.texcoords = []
13+
self.faces = []
14+
self.gl_list = None
15+
16+
for line in open(filename, "r"):
17+
if line.startswith('#'): continue
18+
values = line.split()
19+
if not values: continue
20+
21+
if values[0] == 'v':
22+
v = list(map(float, values[1:4]))
23+
if swapyz:
24+
v = v[0], v[2], v[1]
25+
self.vertices.append(v)
26+
elif values[0] == 'vn':
27+
v = list(map(float, values[1:4]))
28+
if swapyz:
29+
v = v[0], v[2], v[1]
30+
self.normals.append(v)
31+
elif values[0] == 'vt':
32+
self.texcoords.append(list(map(float, values[1:3])))
33+
elif values[0] == 'f':
34+
face = []
35+
texcoords = []
36+
norms = []
37+
for v in values[1:]:
38+
w = v.split('/')
39+
face.append(int(w[0]))
40+
if len(w) >= 2 and len(w[1]) > 0:
41+
texcoords.append(int(w[1]))
42+
else:
43+
texcoords.append(0)
44+
if len(w) >= 3 and len(w[2]) > 0:
45+
norms.append(int(w[2]))
46+
else:
47+
norms.append(0)
48+
self.faces.append((face, norms, texcoords))
49+
50+
def create_gl_list(self):
51+
if self.gl_list is not None:
52+
return self.gl_list
53+
54+
self.gl_list = glGenLists(1)
55+
glNewList(self.gl_list, GL_COMPILE)
56+
glFrontFace(GL_CCW)
57+
for face in self.faces:
58+
vertices, normals, texture_coords = face
59+
glBegin(GL_POLYGON)
60+
for i in range(len(vertices)):
61+
if normals[i] > 0:
62+
glNormal3fv(self.normals[normals[i] - 1])
63+
glVertex3fv(self.vertices[vertices[i] - 1])
64+
glEnd()
65+
glEndList()
66+
return self.gl_list
67+
68+
def render(self):
69+
glCallList(self.create_gl_list())
70+
71+
def init_ar():
72+
pygame.init()
73+
display = (1280, 720)
74+
pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
75+
76+
glEnable(GL_DEPTH_TEST)
77+
glEnable(GL_LIGHTING)
78+
glLightfv(GL_LIGHT0, GL_POSITION, (0, 0, -2, 1))
79+
glLightfv(GL_LIGHT0, GL_AMBIENT, (0.2, 0.2, 0.2, 1))
80+
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.5, 0.5, 0.5, 1))
81+
glEnable(GL_LIGHT0)
82+
glEnable(GL_COLOR_MATERIAL)
83+
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
84+
85+
return display
86+
87+
def set_projection_from_camera(intrinsic):
88+
glMatrixMode(GL_PROJECTION)
89+
glLoadIdentity()
90+
91+
fx = intrinsic[0,0]
92+
fy = intrinsic[1,1]
93+
fovy = 2 * np.arctan(0.5*720 / fy) * 180 / np.pi
94+
aspect = (1280 * fy) / (720 * fx)
95+
96+
gluPerspective(fovy, aspect, 0.1, 100.0)
97+
glViewport(0, 0, 1280, 720)
98+
99+
def set_modelview_from_camera(rvec, tvec):
100+
glMatrixMode(GL_MODELVIEW)
101+
glLoadIdentity()
102+
103+
rotation = rvec[0][0]
104+
translation = tvec[0][0]
105+
106+
rmtx = cv2.Rodrigues(rotation)[0]
107+
108+
view_matrix = np.array([[rmtx[0,0], rmtx[0,1], rmtx[0,2], translation[0]],
109+
[rmtx[1,0], rmtx[1,1], rmtx[1,2], translation[1]],
110+
[rmtx[2,0], rmtx[2,1], rmtx[2,2], translation[2]],
111+
[0.0, 0.0, 0.0, 1.0]])
112+
113+
view_matrix = view_matrix * np.array([1, -1, -1, 1])
114+
115+
inverse_matrix = np.linalg.inv(view_matrix)
116+
glLoadMatrixf(inverse_matrix.T)
117+
118+
def draw_background(frame):
119+
glDisable(GL_DEPTH_TEST)
120+
glMatrixMode(GL_PROJECTION)
121+
glLoadIdentity()
122+
gluOrtho2D(0, 1280, 0, 720)
123+
glMatrixMode(GL_MODELVIEW)
124+
glLoadIdentity()
125+
126+
# Convert frame to OpenGL texture format
127+
bg_image = cv2.flip(frame, 0)
128+
bg_image = cv2.cvtColor(bg_image, cv2.COLOR_BGR2RGB)
129+
130+
glEnable(GL_TEXTURE_2D)
131+
texture_id = glGenTextures(1)
132+
glBindTexture(GL_TEXTURE_2D, texture_id)
133+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1280, 720, 0, GL_RGB, GL_UNSIGNED_BYTE, bg_image)
134+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
135+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
136+
137+
# Draw textured quad
138+
glBegin(GL_QUADS)
139+
glTexCoord2f(0.0, 1.0); glVertex2f(0, 0)
140+
glTexCoord2f(1.0, 1.0); glVertex2f(1280, 0)
141+
glTexCoord2f(1.0, 0.0); glVertex2f(1280, 720)
142+
glTexCoord2f(0.0, 0.0); glVertex2f(0, 720)
143+
glEnd()
144+
145+
glDeleteTextures([texture_id])
146+
glDisable(GL_TEXTURE_2D)
147+
glEnable(GL_DEPTH_TEST)
148+
149+
def main():
150+
cap = cv2.VideoCapture(0)
151+
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
152+
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
153+
154+
display = init_ar()
155+
156+
camera_matrix = np.array([[933.15867, 0, 657.59],
157+
[0, 933.1586, 400.36993],
158+
[0, 0, 1]])
159+
dist_coeffs = np.array([-0.43948, 0.18514, 0, 0])
160+
161+
aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_5X5_100)
162+
parameters = cv2.aruco.DetectorParameters_create()
163+
164+
obj = ObjLoader("objects/cube.obj", swapyz=True)
165+
166+
clock = pygame.time.Clock()
167+
168+
# Initialize smoothing variables
169+
smooth_rvec = None
170+
smooth_tvec = None
171+
smooth_factor = 0.8
172+
173+
while True:
174+
for event in pygame.event.get():
175+
if event.type == pygame.QUIT:
176+
pygame.quit()
177+
cap.release()
178+
return
179+
180+
ret, frame = cap.read()
181+
if not ret:
182+
break
183+
184+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
185+
186+
draw_background(frame)
187+
188+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
189+
corners, ids, _ = cv2.aruco.detectMarkers(gray, aruco_dict, parameters=parameters)
190+
191+
if ids is not None:
192+
set_projection_from_camera(camera_matrix)
193+
194+
for i in range(len(ids)):
195+
rvec, tvec, _ = cv2.aruco.estimatePoseSingleMarkers(corners[i], 0.02, camera_matrix, dist_coeffs)
196+
197+
# Apply smoothing
198+
if smooth_rvec is None:
199+
smooth_rvec, smooth_tvec = rvec, tvec
200+
else:
201+
smooth_rvec = smooth_factor * smooth_rvec + (1 - smooth_factor) * rvec
202+
smooth_tvec = smooth_factor * smooth_tvec + (1 - smooth_factor) * tvec
203+
204+
set_modelview_from_camera(smooth_rvec, smooth_tvec)
205+
206+
glColor3f(1.0, 1.0, 1.0) # Set color to white
207+
glPushMatrix()
208+
glScalef(0.005, 0.005, 0.005) # Scale down the cube
209+
obj.render()
210+
glPopMatrix()
211+
212+
pygame.display.flip()
213+
clock.tick(60)
214+
215+
if __name__ == "__main__":
216+
main()

run.ps1

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
$VENV_DIR = "psdenv"
2+
3+
# Check if the virtual environment directory exists
4+
if (-not (Test-Path $VENV_DIR)) {
5+
# Create the virtual environment
6+
python -m venv $VENV_DIR
7+
}
8+
9+
# Activate the virtual environment and run the Python script
10+
& "$VENV_DIR\Scripts\Activate.ps1"
11+
python main.py
12+
13+
# Pause for user input before closing (optional)
14+
Read-Host -Prompt "Press Enter to continue..."

scripts/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# shape constants
2+
SHAPE_CONE = "cone"
3+
SHAPE_SPHERE = "sphere"

scripts/glyphdatabase.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from constants import *
2+
3+
# glyph table
4+
GLYPH_TABLE = [[[[0, 1, 0, 1, 0, 0, 0, 1, 1],[0, 0, 1, 1, 0, 1, 0, 1, 0],[1, 1, 0, 0, 0, 1, 0, 1, 0],[0, 1, 0, 1, 0, 1, 1, 0, 0]], SHAPE_CONE],[[[1, 0, 0, 0, 1, 0, 1, 0, 1],[0, 0, 1, 0, 1, 0, 1, 0, 1],[1, 0, 1, 0, 1, 0, 0, 0, 1],[1, 0, 1, 0, 1, 0, 1, 0, 0]], SHAPE_SPHERE]]
5+
6+
# match glyph pattern to database record
7+
def match_glyph_pattern(glyph_pattern):
8+
glyph_found = False
9+
glyph_rotation = None
10+
glyph_name = None
11+
12+
for glyph_record in GLYPH_TABLE:
13+
for idx, val in enumerate(glyph_record[0]):
14+
if glyph_pattern == val:
15+
glyph_found = True
16+
glyph_rotation = idx
17+
glyph_name = glyph_record[1]
18+
break
19+
if glyph_found: break
20+
21+
return (glyph_found, glyph_rotation, glyph_name)

scripts/glyphfunctions.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import cv2
2+
import numpy as np
3+
4+
def order_points(points):
5+
6+
s = points.sum(axis=1)
7+
diff = np.diff(points, axis=1)
8+
9+
ordered_points = np.zeros((4,2), dtype="float32")
10+
11+
ordered_points[0] = points[np.argmin(s)]
12+
ordered_points[2] = points[np.argmax(s)]
13+
ordered_points[1] = points[np.argmin(diff)]
14+
ordered_points[3] = points[np.argmax(diff)]
15+
16+
return ordered_points
17+
18+
def max_width_height(points):
19+
20+
(tl, tr, br, bl) = points
21+
22+
top_width = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
23+
bottom_width = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
24+
max_width = max(int(top_width), int(bottom_width))
25+
26+
left_height = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
27+
right_height = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
28+
max_height = max(int(left_height), int(right_height))
29+
30+
return (max_width,max_height)
31+
32+
def topdown_points(max_width, max_height):
33+
return np.array([
34+
[0, 0],
35+
[max_width-1, 0],
36+
[max_width-1, max_height-1],
37+
[0, max_height-1]], dtype="float32")
38+
39+
def get_topdown_quad(image, src):
40+
41+
# src and dst points
42+
src = order_points(src)
43+
44+
(max_width,max_height) = max_width_height(src)
45+
dst = topdown_points(max_width, max_height)
46+
47+
# warp perspective
48+
matrix = cv2.getPerspectiveTransform(src, dst)
49+
warped = cv2.warpPerspective(image, matrix, max_width_height(src))
50+
51+
# return top-down quad
52+
return warped
53+
54+
def get_glyph_pattern(image, black_threshold, white_threshold):
55+
56+
# collect pixel from each cell (left to right, top to bottom)
57+
cells = []
58+
59+
cell_half_width = int(round(image.shape[1] / 10.0))
60+
cell_half_height = int(round(image.shape[0] / 10.0))
61+
62+
row1 = cell_half_height*3
63+
row2 = cell_half_height*5
64+
row3 = cell_half_height*7
65+
col1 = cell_half_width*3
66+
col2 = cell_half_width*5
67+
col3 = cell_half_width*7
68+
69+
cells.append(image[row1, col1])
70+
cells.append(image[row1, col2])
71+
cells.append(image[row1, col3])
72+
cells.append(image[row2, col1])
73+
cells.append(image[row2, col2])
74+
cells.append(image[row2, col3])
75+
cells.append(image[row3, col1])
76+
cells.append(image[row3, col2])
77+
cells.append(image[row3, col3])
78+
79+
# threshold pixels to either black or white
80+
for idx, val in enumerate(cells):
81+
if val < black_threshold:
82+
cells[idx] = 0
83+
elif val > white_threshold:
84+
cells[idx] = 1
85+
else:
86+
return None
87+
88+
return cells
89+
90+
def get_vectors(image, points):
91+
92+
# order points
93+
points = order_points(points)
94+
95+
# load calibration data
96+
with np.load('webcam_calibration_ouput.npz') as X:
97+
mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]
98+
99+
# set up criteria, image, points and axis
100+
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
101+
102+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
103+
104+
imgp = np.array(points, dtype="float32")
105+
106+
objp = np.array([[0.,0.,0.],[1.,0.,0.],
107+
[1.,1.,0.],[0.,1.,0.]], dtype="float32")
108+
109+
# calculate rotation and translation vectors
110+
cv2.cornerSubPix(gray,imgp,(11,11),(-1,-1),criteria)
111+
rvecs, tvecs, _ = cv2.solvePnPRansac(objp, imgp, mtx, dist)
112+
113+
return rvecs, tvecs

0 commit comments

Comments
 (0)