-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgrasping.py
executable file
·623 lines (473 loc) · 19.1 KB
/
grasping.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
#!/usr/bin/env python
"""
Starter code for EE106B grasp planning project.
Author: Amay Saxena, Tiffany Cappellari
Modified by: Kirthi Kumar
#!/usr/bin/env python -W ignore::DeprecationWarning
"""
# may need more imports
import numpy as np
import utils
import math
import trimesh
import vedo
import tqdm
MAX_GRIPPER_DIST = 0.075 + 1
MIN_GRIPPER_DIST = 0.03
GRIPPER_LENGTH = 0.105
import cvxpy as cvx # suggested, but you may change your solver to anything you'd like (ex. casadi)
def compute_force_closure(vertices, normals, num_facets, mu, gamma, object_mass):
"""
Compute the force closure of some object at contacts, with normal vectors
stored in normals. Since this is two contact grasp, we are using a basic algorithm
wherein a grasp is in force closure as long as the line connecting the two contact
points lies in both friction cones.
Parameters
----------
vertices (2x3 np.ndarray): obj mesh vertices on which the fingers will be placed
normals (2x3 np.ndarray): obj mesh normals at the contact points
num_facets (int): number of vectors to use to approximate the friction cone, vectors
will be along the friction cone boundary
mu (float): coefficient of friction
gamma (float): torsional friction coefficient
object_mass (float): mass of the object
Returns
-------
(float): quality of the grasp
"""
vertices = vertices.T
line = vertices[0] - vertices[1]
line /= np.linalg.norm(line)
normals = normals.T
normals[0] /= np.linalg.norm(normals[0])
normals[1] /= np.linalg.norm(normals[1])
if (-line) @ normals[0] > 0:
normals[0] = -normals[0]
if line @ normals[1] > 0:
normals[1] = -normals[1]
angle1 = np.arccos(normals[0].T @ line)
angle1 = min(angle1, np.pi - angle1)
angle2 = np.arccos(normals[1].T @ line)
angle2 = min(angle2, np.pi - angle2)
alpha = np.arctan(mu)
print(angle1, angle2)
return angle1 < alpha and angle2 < alpha and gamma > 0
def get_grasp_map(vertices, normals, num_facets, mu, gamma):
"""
Defined in the book on page 219. Compute the grasp map given the contact
points and their surface normals
Parameters
----------
vertices (2x3 np.ndarray): obj mesh vertices on which the fingers will be placed
normals (2x3 np.ndarray): obj mesh normals at the contact points
num_facets (int): number of vectors to use to approximate the friction cone, vectors
will be along the friction cone boundary
mu (float): coefficient of friction
gamma (float): torsional friction coefficient
Returns
-------
(np.ndarray): grasp map
"""
B = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 1]
])
normal1 = utils.normalize(-normals[0])
vertex1 = vertices[0]
g_oc1 = utils.look_at_general(vertex1, normal1)
adj_T_inv1 = utils.adj(np.linalg.inv(g_oc1)).T
G_1 = adj_T_inv1 @ B
normal2 = utils.normalize(-normals[1])
vertex2 = vertices[1]
g_oc2 = utils.look_at_general(vertex2, normal2)
adj_T_inv2 = utils.adj(np.linalg.inv(g_oc2)).T
G_2 = adj_T_inv2 @ B
return np.hstack((G_1, G_2))
def contact_forces_exist(vertices, normals, num_facets, mu, gamma, desired_wrench):
"""
Compute whether the given grasp (at contacts with surface normals) can produce
the desired_wrench. Will be used for gravity resistance.
Parameters
----------
vertices (2x3 np.ndarray): obj mesh vertices on which the fingers will be placed
normals (2x3 np.ndarray): obj mesh normals at the contact points
num_facets (int): number of vectors to use to approximate the friction cone, vectors
will be along the friction cone boundary
mu (float): coefficient of friction
gamma (float): torsional friction coefficient
desired_wrench (np.ndarray):potential wrench to be produced
Returns
-------
(bool): whether contact forces can produce the desired_wrench on the object
"""
G = get_grasp_map(vertices, normals, num_facets, mu, gamma)
fc = cvx.Variable(8)
# constraints = [np.sqrt(FC[0]**2 + FC[1]**2) <= mu * FC[2], FC[2] > 0, np.abs(FC[3] <= gamma*FC[2]),
# np.sqrt(FC[5]**2 + FC[6]**2) <= mu * FC[7], FC[7] > 0, np.abs(FC[8] <= gamma*FC[7])]
constraints = []
for fc_i in (fc[:4], fc[4:]):
constraints += [
# cvx.sqrt(fc_i[0]**2 + fc_i[1]**2) <= mu * fc_i[2],
fc_i[2] >= 0,
cvx.abs(fc_i[3]) <= gamma * fc_i[2],
]
# friction cone constraints
f_i_z = np.cos(np.arctan(mu)) / np.sqrt(1 - np.cos(np.arctan(mu))**2)
F = [[0, 0, 1]]
for i in range(num_facets):
angle = 2 * np.pi * i / num_facets
F.append([np.cos(angle), np.sin(angle), f_i_z])
F = np.array(F).T
alpha = cvx.Variable((2, num_facets+1))
for (i, fc_i) in enumerate((fc[:4], fc[4:])):
# print(F.shape, alpha[i].shape)
constraints.append(fc_i[:3] == F @ alpha[i])
constraints.append(alpha >= 0)
cost = cvx.sum_squares(desired_wrench - G @ fc)
prob = cvx.Problem(cvx.Minimize(cost), constraints)
try:
prob.solve()
except Exception as e:
print("problem solve error:", e)
return False, None
if prob.status != 'optimal':
print(prob.status)
# print(fc.value)
return prob.status == 'optimal', fc.value
def compute_gravity_resistance(vertices, normals, num_facets, mu, gamma, object_mass):
"""
Gravity produces some wrench on your object. Computes whether the grasp can
produce an equal and opposite wrench.
Parameters
----------
vertices (2x3 np.ndarray): obj mesh vertices on which the fingers will be placed
normals (2x3 np.ndarray): obj mesh normals at the contact points
num_facets (int): number of vectors to use to approximate the friction cone, vectors
will be along the friction cone boundary
mu (float): coefficient of friction
gamma (float): torsional friction coefficient
torsional friction coefficient
object_mass (float): mass of the object
Returns
-------
(float): quality of the grasp
"""
# YOUR CODE HERE
desired_wrench = np.array([0, 0, 9.8 * object_mass, 0, 0, 0]).T
statement, _ = contact_forces_exist(vertices, normals, num_facets, mu, gamma, desired_wrench)
return statement
"""
you're encouraged to implement a version of this method,
def sample_around_vertices(delta, vertices, object_mesh=None):
raise NotImplementedError
"""
def compute_robust_force_closure(vertices, normals, num_facets, mu, gamma, object_mass, object_mesh):
"""
Should return a score for the grasp according to the robust force closure metric.
Parameters
----------
vertices (2x3 np.ndarray): obj mesh vertices on which the fingers will be placed
normals (2x3 np.ndarray): obj mesh normals at the contact points
num_facets (int): number of vectors to use to approximate the friction cone, vectors
will be along the friction cone boundary
mu (float): coefficient of friction
gamma (float): torsional friction coefficient
torsional friction coefficient
object_mass (float): mass of the object
Returns
-------
(float): quality of the grasp
"""
desired_wrench = np.array([0, 0, 9.8 * object_mass, 0, 0, 0]).T
delta = 0.01
trials = 50
full_trails = 0
success = 0
while full_trails < trials:
p1 = np.random.uniform(vertices[0]-delta, vertices[0]+delta)
p2 = np.random.uniform(vertices[1]-delta, vertices[1]+delta)
ray_dir = p2 - p1
p1_locs_in, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p1],
ray_directions=[ray_dir],
multiple_hits=True
)
p1_locs_out, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p1],
ray_directions=[-ray_dir],
multiple_hits=True
)
if len(p1_locs_in) % 2 != 0 or len(p1_locs_in) == 0 or len(p1_locs_out) != 0:
print("p1 inside mesh")
continue
p2_locs_in, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p2],
ray_directions=[-ray_dir],
multiple_hits=True
)
p2_locs_out, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p2],
ray_directions=[ray_dir],
multiple_hits=True
)
if len(p2_locs_in) % 2 != 0 or len(p2_locs_in) == 0 or len(p2_locs_out) != 0:
print("p2 inside mesh")
continue
new_vertices, _ = utils.find_grasp_vertices(object_mesh, p1, p2)
v1, v2 = new_vertices
grip_dist = np.linalg.norm(v1 - v2)
if not (MIN_GRIPPER_DIST <= grip_dist <= MAX_GRIPPER_DIST):
print("ray outside of gripper bounds")
continue
# if v1[2] <= 0 or v2[2] <= 0:
# # print("point under object")
# continue
n1 = utils.normal_at_point(object_mesh, v1)
n2 = utils.normal_at_point(object_mesh, v2)
normals = np.array([n1, n2])
full_trails += 1
statement, _ = contact_forces_exist(new_vertices, normals, num_facets, mu, gamma, desired_wrench)
if statement:
# if compute_force_closure(new_vertices, normals, num_facets, mu, gamma, 0.25):
success += 1
print("RFC quality: " + str(success/full_trails))
return success/full_trails
def compute_ferrari_canny(vertices, normals, num_facets, mu, gamma, object_mass):
"""
Should return a score for the grasp according to the Ferrari Canny metric.
Use your favourite python convex optimization package. We suggest cvxpy.
Parameters
----------
vertices (2x3 np.ndarray): obj mesh vertices on which the fingers will be placed
normals (2x3 np.ndarray): obj mesh normals at the contact points
num_facets (int): number of vectors to use to approximate the friction cone, vectors
will be along the friction cone boundary
mu (float): coefficient of friction
gamma (float): torsional friction coefficient
torsional friction coefficient
object_mass (float): mass of the object
Returns
-------
(float): quality of the grasp
"""
N = 100
g = 9.8 * object_mass
other = .25*g
d = np.array([other,other,other,0,0,0])
LQs = []
for i in range(N):
w = np.random.uniform(-d, d) + np.array([0,0,g,0,0,0])
w = utils.normalize(w)
statement, fc = contact_forces_exist(vertices, normals, num_facets, mu, gamma, w)
if statement:
LQ = min(np.linalg.norm(fc[0:4]) ** 2, np.linalg.norm(fc[4:]) ** 2)
if LQ:
LQs.append(LQ)
if len(LQs) > 0:
return min(1/np.sqrt(LQs))
return 0
def sample_point_on_face(min_bound, max_bound, face):
"""
Args:
min_bound (3x1 np.ndarray): minimum coordinates for bound
max_bound (3x1 np.ndarray): maximum coordinates for bound
face (int): one of five faces
Returns:
(3x1 np.ndarray) numpy array sampled uniformly from the face
"""
point = np.random.uniform(min_bound, max_bound)
if face == 0:
point[0] = min_bound[0]
elif face == 1:
point[0] = max_bound[0]
elif face == 2:
point[1] = min_bound[1]
elif face == 3:
point[1] = max_bound[1]
else:
point[2] = max_bound[2]
# never choose bottom face
return point
def custom_grasp_planner(object_mesh):
# constants -- you may or may not want to use these variables below
num_facets = 64
mu = 0.5
gamma = 0.1
object_mass = 0.25
g = 9.8
desired_wrench = np.array([0, 0, g * object_mass, 0, 0, 0]).T
trials = 1000
delta = 0.04
ferrari_th = 0.31 #Nozzle 0.6, Pawn 0.3
RFC_bound = 0.67
min_bound, max_bound = object_mesh.bounds
min_bound = np.asarray(min_bound)
max_bound = np.asarray(max_bound)
for i in tqdm.trange(trials):
# ensures faces are not the same
face1 = np.random.randint(4)
face2 = np.random.randint(3)
if face1 == face2:
face2 += 1
p1 = sample_point_on_face(min_bound, max_bound, face1)
p2 = sample_point_on_face(min_bound, max_bound, face2)
ray_dir = p2 - p1
p1_locs_in, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p1],
ray_directions=[ray_dir],
multiple_hits=True
)
p1_locs_out, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p1],
ray_directions=[-ray_dir],
multiple_hits=True
)
if len(p1_locs_in) % 2 != 0 or len(p1_locs_in) == 0 or len(p1_locs_out) != 0:
# print("p1 not on mesh")
continue
p2_locs_in, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p2],
ray_directions=[-ray_dir],
multiple_hits=True
)
p2_locs_out, _, _ = object_mesh.ray.intersects_location(
ray_origins=[p2],
ray_directions=[ray_dir],
multiple_hits=True
)
if len(p2_locs_in) % 2 != 0 or len(p2_locs_in) == 0 or len(p2_locs_out) != 0:
# print("p2 not on mesh")
continue
vertices, _ = utils.find_grasp_vertices(object_mesh, p1, p2)
v1, v2 = vertices
grip_dist = np.linalg.norm(v1 - v2)
if not (MIN_GRIPPER_DIST <= grip_dist <= MAX_GRIPPER_DIST):
# print("ray outside of gripper bounds")
continue
# if v1[2] <= 0 or v2[2] <= 0:
# print("point under object")
# continue
n1 = utils.normal_at_point(object_mesh, v1)
n2 = utils.normal_at_point(object_mesh, v2)
normals = np.array([n1, n2])
if not compute_force_closure(vertices, normals, num_facets, mu, gamma, object_mass):
# print("grasp not force closure")
continue
# Gravity
# if not compute_gravity_resistance(vertices, normals, num_facets, mu, gamma, object_mass):
# print("grasp cannot get desired wrench for gravity")
# continue
# quality = 1
# RFC
quality = compute_robust_force_closure((p1, p2), normals, num_facets, mu, gamma, object_mass, object_mesh)
if quality < RFC_bound:
# print("Failed RFC {}".format(quality))
pose = get_gripper_pose(vertices, object_mesh)
visualize_grasp(object_mesh, vertices, pose)
continue
# #Ferrari
# quality = compute_ferrari_canny(vertices, normals, num_facets, mu, gamma, object_mass)
# if quality< ferrari_th:
# print("Failed Ferrari {}".format(quality))
# continue
# if not (compute_force_closure(vertices, normals, num_facets, mu, gamma, object_mass) \
# and compute_gravity_resistance(vertices, normals, num_facets, mu, gamma, object_mass)):
# continue
print("found good grasp!!!")
return vertices, get_gripper_pose(vertices, object_mesh)
print("no grasps found :(\n")
return None, None
def get_gripper_pose(vertices, object_mesh): # you may or may not need this method
"""
Creates a 3D Rotation Matrix at the origin such that the y axis is the same
as the direction specified. There are infinitely many of such matrices,
but we choose the one where the z axis is as vertical as possible.
z -> y
x -> x
y -> z
Parameters
----------
object_mesh (tnp.ndarrayrimesh.base.Trimesh): A triangular mesh of the object, as loaded in with trimesh.
vertices (2x3 ): obj mesh vertices on which the fingers will be placed
Returns
-------
4x4 :obj:`numpy.ndarray`
"""
origin = np.mean(vertices, axis=0)
direction = vertices[0] - vertices[1]
up = np.array([0, 0, 1])
y = utils.normalize(direction)
x = utils.normalize(np.cross(up, y))
z = np.cross(x, y)
gripper_top = origin + GRIPPER_LENGTH * z
gripper_double = origin + 2 * GRIPPER_LENGTH * z
if len(utils.find_intersections(object_mesh, gripper_top, gripper_double)[0]) > 0:
z = utils.normalize(np.cross(up, y))
x = np.cross(y, x)
result = np.eye(4)
result[0:3,0] = x
result[0:3,1] = y
result[0:3,2] = z
result[0:3,3] = origin + np.array([0,0,0.07]) #Pawn 0.065, Nozzle 0.07
return result
def visualize_grasp(mesh, vertices, pose):
"""Visualizes a grasp on an object. Object specified by a mesh, as
loaded by trimesh. vertices is a pair of (x, y, z) contact points.
pose is the pose of the gripper tip.
Parameters
----------
mesh (trimesh.base.Trimesh): mesh of the object
vertices (np.ndarray): 2x3 matrix, coordinates of the 2 contact points
pose (np.ndarray): 4x4 homogenous transform matrix
"""
# vertices = np.array([[0.5,0,-.03],[-0.5,0,-.03]])
p1, p2 = vertices
center = (p1 + p2) / 2
approach = pose[:3, 2]
tail = center - GRIPPER_LENGTH * approach
contact_points = []
for v in vertices:
contact_points.append(vedo.Point(pos=v, r=30))
vec = (p1 - p2) / np.linalg.norm(p1 - p2)
line = vedo.shapes.Tube([center + 0.5 * MAX_GRIPPER_DIST * vec,
center - 0.5 * MAX_GRIPPER_DIST * vec], r=0.001, c='g')
approach = vedo.shapes.Tube([center, tail], r=0.001, c='g')
vedo.show([mesh, line, approach] + contact_points, new=True)
def randomly_sample_from_mesh(mesh, n):
"""Example of sampling points from the surface of a mesh.
Returns n (x, y, z) points sampled from the surface of the input mesh
uniformly at random. Also returns the corresponding surface normals.
Parameters
----------
mesh (trimesh.base.Trimesh): mesh of the object
n (int): number of desired sample points
Returns
-------
vertices (np.ndarray): nx3 matrix, coordinates of the n surface points
normals (np.ndarray): nx3 matrix, normals of the n surface points
"""
vertices, face_ind = trimesh.sample.sample_surface(mesh, n) # you may want to check out the trimesh mehtods here:)
normals = mesh.face_normals[face_ind]
return vertices, normals
def load_mesh(file):
mesh = trimesh.load_mesh(file)
mesh.fix_normals()
mesh.invert()
point_cloud = trimesh.PointCloud(mesh.vertices)
vedo.show([point_cloud], new=True)
return mesh
def main():
mesh = load_mesh("pawn_scan2.obj")
vedo.show([mesh], new=True)
for _ in range(5):
while True:
v, p = custom_grasp_planner(mesh)
if p is not None:
visualize_grasp(mesh, v, p)
break
if __name__ == '__main__':
main()