Skip to content

Commit 9ce2ae1

Browse files
committed
plane detector is running but it is not perfect
1 parent 029eb6f commit 9ce2ae1

File tree

3 files changed

+175
-34
lines changed

3 files changed

+175
-34
lines changed

wrappers/python/applications/planes/src/plane_detector.py

Lines changed: 141 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import sys
2121
import numpy as np
2222
import cv2 as cv
23+
import random
2324

2425

2526
# importing common Use modules
@@ -46,7 +47,8 @@ def __init__(self, detect_type = 'p'):
4647
# detector type
4748
self.matrix_inv = None # holds inverse params of the
4849
self.matrix_dir = None # direct u,v,1
49-
self.matrix_xyz = None # direct u,v,1 multiplied by z
50+
self.matrix_xyz = None # direct u,v,1 multiplied by z
51+
self.matrix_index = None # index of the points in the original image
5052
self.plane_params = None # rvec not normalized
5153
self.plane_center = None # tvec
5254
#self.corner_ind = [0, 10, 40, 50] # corner of the rectnagle for the projection
@@ -56,6 +58,9 @@ def __init__(self, detect_type = 'p'):
5658
self.MIN_SPLIT_SIZE = 32
5759
self.MIN_STD_ERROR = 0.01
5860

61+
# color for the mask
62+
self.color_mask = (0,255,0) # green
63+
5964
# help variable
6065
self.ang_vec = np.zeros((3,1)) # help variable
6166

@@ -95,6 +100,10 @@ def preprocess(self, img = None):
95100

96101
if self.rect is None: # use entire image
97102
self.rect = self.init_roi(4)
103+
104+
# init params of the inverse
105+
if self.matrix_dir is None:
106+
self.fit_plane_init()
98107

99108
x0, y0, x1, y1 = self.rect
100109
if len(img.shape) > 2:
@@ -223,6 +232,38 @@ def fit_plane_init(self):
223232
self.cam_matrix = np.array([[650,0,self.frame_size[0]/2],[0,650,self.frame_size[1]/2],[0,0,1]], dtype = np.float32)
224233
self.cam_distort = np.array([0,0,0,0,0],dtype = np.float32)
225234

235+
x0,y0,x1,y1 = 0,0,self.frame_size[1],self.frame_size[0] #self.rect
236+
h,w = y1-y0, x1-x0
237+
x_grid = np.arange(x0, x1, 1)
238+
y_grid = np.arange(y0, y1, 1)
239+
x, y = np.meshgrid(x_grid, y_grid)
240+
241+
# remember corner indexes for reprojection [0 .... h*(w-1))
242+
# . .
243+
# h ......h*w-1]
244+
#self.corner_ind = [0, h, h*w-1, h*(w-1), 0]
245+
#h2,w2 = h>>1, w>>1
246+
#self.rect_3d = [[-w,-h,0],[w,-h,0],[w,h,0],[-w,h,0],[-w,-h,0]]
247+
248+
# camera coordinates
249+
xy = np.hstack((x.reshape(-1,1),y.reshape(-1,1)))
250+
xy = np.expand_dims(xy, axis=1).astype(np.float32)
251+
xy_undistorted = cv.undistortPoints(xy, self.cam_matrix, self.cam_distort)
252+
253+
u = xy_undistorted[:,0,0].reshape((h,w)).reshape(-1,1)
254+
v = xy_undistorted[:,0,1].reshape((h,w)).reshape(-1,1)
255+
256+
# check
257+
#u, v = u*self.cam_matrix[0,0], v*self.cam_matrix[1,1]
258+
259+
self.matrix_dir = np.hstack((u,v,u*0+1))
260+
#self.matrix_inv = np.linalg.pinv(self.matrix_dir)
261+
262+
def fit_plane_init_old(self):
263+
"prepares data for real time fit a*x+b*y+c = z"
264+
self.cam_matrix = np.array([[650,0,self.frame_size[0]/2],[0,650,self.frame_size[1]/2],[0,0,1]], dtype = np.float32)
265+
self.cam_distort = np.array([0,0,0,0,0],dtype = np.float32)
266+
226267
x0,y0,x1,y1 = self.rect
227268
h,w = y1-y0, x1-x0
228269
x_grid = np.arange(x0, x1, 1)
@@ -250,7 +291,50 @@ def fit_plane_init(self):
250291
self.matrix_dir = np.hstack((u,v,u*0+1))
251292
self.matrix_inv = np.linalg.pinv(self.matrix_dir)
252293

253-
def convert_roi_to_points(self, img_roi, point_num = 30, step_size = 1):
294+
def convert_roi_to_points(self, img, point_num = 30, step_size = 1, roi_rect = None):
295+
"converting roi to pts in XYZ - Nx3 array. point_num - is the target point number"
296+
297+
# init params of the inverse
298+
if self.matrix_dir is None:
299+
self.fit_plane_init()
300+
301+
# deal iwth different rect options
302+
roi_rect = self.rect if roi_rect is None else roi_rect
303+
304+
# extract roi - must be compatible with image dimensions
305+
n,m = img.shape[:2]
306+
img_roi_mask = np.zeros((n,m), dtype = np.bool_)
307+
x0, y0, x1, y1 = roi_rect
308+
img_roi_mask[y0:y1,x0:x1] = 1
309+
valid_bool = img_roi_mask > 0
310+
valid_bool = valid_bool.flatten()
311+
#log.info(f'Timing : 1')
312+
313+
# rect to show
314+
h,w = y1-y0, x1-x0
315+
self.rect_3d = [[-w,-h,0],[w,-h,0],[w,h,0],[-w,h,0],[-w,-h,0]]
316+
317+
# all non valid
318+
ii = np.where(valid_bool)[0]
319+
valid_point_num = len(ii)
320+
if valid_point_num < 5:
321+
return None
322+
step_size = np.maximum(step_size, np.int32(valid_point_num/point_num))
323+
ii = ii[::step_size]
324+
325+
# plane params - using only valid
326+
z = img.flat[ii].reshape((-1,1))
327+
xyz_matrix = self.matrix_dir[ii,:]
328+
xyz_matrix[:,:3] = xyz_matrix[:,:3]*z # keep 1 intact
329+
330+
#self.plane_center = xyz_center.flatten()
331+
self.matrix_xyz = xyz_matrix
332+
self.matrix_index = ii
333+
334+
return xyz_matrix
335+
336+
337+
def convert_roi_to_points_old(self, img_roi, point_num = 30, step_size = 1):
254338
"converting roi to pts in XYZ - Nx3 array. point_num - is the target point number"
255339
# x1,y1 = self.img_xyz.shape[:2]
256340
# roi_area = x1*y1
@@ -267,8 +351,10 @@ def convert_roi_to_points(self, img_roi, point_num = 30, step_size = 1):
267351
#
268352

269353
# init params of the inverse
270-
if self.matrix_inv is None:
271-
self.fit_plane_init()
354+
if self.matrix_dir is None:
355+
self.fit_plane_init_old()
356+
357+
# extract roi
272358

273359
n,m = img_roi.shape[:2]
274360
img_roi = img_roi.reshape((-1,1))
@@ -291,7 +377,10 @@ def convert_roi_to_points(self, img_roi, point_num = 30, step_size = 1):
291377

292378
# update corners of the rect in 3d
293379
#self.rect_3d = self.matrix_dir[self.corner_ind,:]*img_roi[self.corner_ind]
294-
380+
# rect to show
381+
x0, y0, x1, y1 = self.rect
382+
h,w = y1-y0, x1-x0
383+
self.rect_3d = [[-w,-h,0],[w,-h,0],[w,h,0],[-w,h,0],[-w,-h,0]]
295384
# substract mean
296385
#xyz_center = xyz_matrix[:,:3].mean(axis=0)
297386
#xyz_matrix = xyz_matrix - xyz_center
@@ -332,7 +421,8 @@ def fit_plane_svd(self, img_roi):
332421
#self.rect_3d = self.matrix_dir[self.corner_ind,:]*img_roi[self.corner_ind]
333422

334423
# roi converted to points with step size on the grid
335-
xyz_matrix = self.convert_roi_to_points(img_roi, point_num = 1e4, step_size = 1)
424+
#xyz_matrix = self.convert_roi_to_points(img_roi, point_num = 1e4, step_size = 1)
425+
xyz_matrix = self.convert_roi_to_points_old(img_roi, point_num = 1e4, step_size = 1)
336426

337427
# substract mean
338428
xyz_center = xyz_matrix[:,:3].mean(axis=0)
@@ -374,7 +464,7 @@ def fit_plane_with_outliers(self, img_roi):
374464
"computes normal for the specifric roi and evaluates error. Do it twice to reject outliers"
375465

376466
# roi converted to points with step size on the grid
377-
xyz_matrix = self.convert_roi_to_points(img_roi, point_num = 250, step_size = 0)
467+
xyz_matrix = self.convert_roi_to_points_old(img_roi, point_num = 250, step_size = 0)
378468

379469
# substract mean
380470
xyz_center = xyz_matrix[:,:3].mean(axis=0)
@@ -439,12 +529,16 @@ def fit_plane_ransac(self, img_roi):
439529
"""
440530
#log.info('Fit ransac: ...')
441531
# roi converted to points with step size on the grid
532+
#xyz_matrix = self.convert_roi_to_points_old(img_roi, point_num = 250, step_size = 1)
442533
xyz_matrix = self.convert_roi_to_points(img_roi, point_num = 250, step_size = 1)
534+
if xyz_matrix is None:
535+
log.error('No points in the ROI')
536+
return 0, 0
443537

444-
thresh = 0.05
538+
thresh = 1.05
445539
maxIteration = 100
446540

447-
import random
541+
448542
n_points = xyz_matrix.shape[0]
449543
best_eq = []
450544
best_inliers = []
@@ -525,8 +619,12 @@ def fit_plane_ransac_and_grow(self, img_full):
525619
"""
526620
Find the best equation for a plane of the predefined ROI and then grow the ROI
527621
"""
528-
img_roi = self.preprocess(img_full)
529-
img_mean, img_std = self.fit_plane_ransac(img_roi)
622+
h,w = img_full.shape[:2]
623+
# start from the original ROI
624+
if self.img_mask is None:
625+
isOk = self.init_image(img_full)
626+
627+
img_mean, img_std = self.fit_plane_ransac(img_full)
530628

531629
# make sure that mask is not empty
532630
x0, y0, x1, y1 = self.rect
@@ -540,28 +638,44 @@ def fit_plane_ransac_and_grow(self, img_full):
540638
x_min, x_max = np.maximum(0,x_min-1), np.minimum(self.img_mask.shape[1],x_max+1)
541639

542640
# extract ROI
543-
img_roi = img_full[y_min:y_max,x_min:x_max].astype(np.float32)
544-
xyz_matrix = self.convert_roi_to_points(img_roi, point_num = 250, step_size = 1)
641+
roi_rect = [x_min, y_min, x_max, y_max]
642+
#img_roi = img_full[y_min:y_max,x_min:x_max].astype(np.float32)
643+
xyz_matrix = self.convert_roi_to_points(img_full, point_num = 5000, step_size = 1, roi_rect = roi_rect)
545644

546-
# check against the plane
645+
# check against the plane : do not substract plane.center from all the points
547646
vecC = self.plane_params[:3]
548-
dist_pt = np.dot(xyz_matrix, vecC) + self.plane_params[3]
647+
dist_offset = np.dot(self.plane_center, vecC)
648+
dist_pt = np.dot(xyz_matrix, vecC) - dist_offset
549649

550650
# Select indexes where distance is biggers than the threshold
551-
thresh = 0.05
651+
thresh = 1.5
552652
err = np.abs(dist_pt)
553-
yi,xi = np.where( err <= thresh)
554-
yi,xi = yi + y_min, xi + x_min
653+
i2 = np.where( err <= thresh)[0]
654+
655+
656+
# transfer xi,yi coordinates to the original image index
657+
ii = self.matrix_index[i2] # convert to 2D index
658+
659+
# # Convert 2D index to flat index:
660+
# row, col = 2, 1
661+
# flat_index = np.ravel_multi_index((row, col), arr.shape) # flat_index = 9
662+
663+
# # Convert flat index back to 2D index:
664+
# row, col = np.unravel_index(flat_index, arr.shape) # (2, 1)
665+
666+
self.img_mask.flat[ii] = self.img_mask.flat[ii] + 0.1*(1 - self.img_mask.flat[ii])
555667

556668

669+
# position in 2d array
670+
# unravel_index(a.argmax(), a.shape)
671+
557672
# output
558673
img_std = err.std()
559-
img_mean = xyz_matrix.mean(axis=0)[2]
674+
img_mean = xyz_matrix[i2].mean(axis=0)[2]
560675

561676

562677
return img_mean, img_std
563678

564-
565679
def fit_and_split_roi_recursively(self, roi, level = 0):
566680
# splits ROI on 4 regions and recursevly call
567681
x0,y0,x1,y1 = roi
@@ -615,11 +729,15 @@ def find_planes(self, img):
615729

616730
img_mean, img_std = 0,0
617731
if detect_type == 'P':
618-
img_mean, img_std = self.fit_plane_svd(img_roi)
732+
img_mean, img_std = self.fit_plane_svd(img)
619733
elif detect_type == 'O':
620-
img_mean, img_std = self.fit_plane_with_outliers(img_roi)
734+
img_mean, img_std = self.fit_plane_svd(img_roi)
735+
#img_mean, img_std = self.fit_plane_with_outliers(img_roi)
621736
elif detect_type == 'R':
622-
img_mean, img_std = self.fit_plane_ransac(img_roi)
737+
img_mean, img_std = self.fit_plane_ransac(img)
738+
elif detect_type == 'G':
739+
img_mean, img_std = self.fit_plane_ransac_and_grow(img)
740+
623741

624742
#log.debug(f'camera noise - roi mean : {img_mean}')
625743
self.img_mean = img_mean # final measurements per frame

wrappers/python/applications/planes/test/test_plane_detector.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def test_image(self):
236236
img = self.init_image(1)
237237
roi = self.init_roi(1)
238238

239-
#%% Main
239+
#%% Adds display functionality to the PlaneDetector
240240
class PlaneDetectorDisplay(PlaneDetector):
241241
def __init__(self, detect_type='p'):
242242
super().__init__(detect_type)
@@ -379,7 +379,7 @@ def show_text(self, vis):
379379
return vis
380380

381381
x0, y0, x1, y1 = self.rect
382-
txt = f'{self.detect_type}:{err_mean:.2f}-{err_std:.3f}'
382+
txt = f'{self.detect_type}:{err_mean:.2f}:{err_std:.3f}'
383383
if self.detect_type == 'F':
384384
txt = f'{self.detect_type}:{self.img_fill:.2f} %'
385385
vis = draw_str(vis,(x0,y0-10),txt)
@@ -420,6 +420,21 @@ def show_rect_and_axis_projected(self, vis):
420420

421421
return vis
422422

423+
def show_mask(self, img):
424+
"draw image mask"
425+
426+
# deal with black and white
427+
img_show = img #np.uint8(img) #.copy()
428+
if len(img.shape) < 3:
429+
img_show = cv.applyColorMap(img_show, cv.COLORMAP_JET)
430+
431+
if not np.all(self.img_mask.shape[:2] == img_show.shape[:2]):
432+
log.error('mask and image size are not equal')
433+
return img_show
434+
435+
img_show[self.img_mask == 1] = self.color_mask
436+
return img_show
437+
423438
def show_scene(self, vis):
424439
"draw ROI and Info"
425440

@@ -429,6 +444,8 @@ def show_scene(self, vis):
429444
vis = self.show_rect_and_axis_projected(vis)
430445
vis = self.show_text(vis)
431446

447+
vis = self.show_mask(vis)
448+
432449
return vis
433450

434451

@@ -570,7 +587,7 @@ class PlaneApp:
570587
def __init__(self):
571588
self.cap = RealSense() #
572589
self.cap.set_display_mode('d16')
573-
#self.cap.set_exposure(1000)
590+
self.cap.set_exposure(5000)
574591
self.frame = None
575592
self.rect = None
576593
self.paused = False
@@ -580,7 +597,7 @@ def __init__(self):
580597

581598
self.detect_type = 'P'
582599
self.show_type = 'depth' # left, depth
583-
self.win_name = 'Plane Detector (q-quit, a,p,o,r)'
600+
self.win_name = 'Plane Detector (q-quit, c-clear, a,r,p,o,g,s)'
584601

585602
cv.namedWindow(self.win_name )
586603
self.rect_sel = RectSelector(self.win_name , self.on_rect)
@@ -667,13 +684,19 @@ def run(self):
667684
self.paused = not self.paused
668685
elif ch == ord('a'):
669686
self.detect_type = 'A'
687+
log.info(f'Detect type : {self.detect_type}')
670688
elif ch == ord('r'):
671689
self.detect_type = 'R'
690+
log.info(f'Detect type : {self.detect_type}')
672691
elif ch == ord('p'):
673692
self.detect_type = 'P'
674693
log.info(f'Detect type : {self.detect_type}')
675694
elif ch == ord('o'):
676-
self.detect_type = 'O'
695+
self.detect_type = 'O'
696+
log.info(f'Detect type : {self.detect_type}')
697+
elif ch == ord('g'):
698+
self.detect_type = 'G'
699+
log.info(f'Detect type : {self.detect_type}')
677700
elif ch == ord('s'):
678701
self.show_type = 'left' if self.show_type == 'depth' else 'depth'
679702
log.info(f'Show type : {self.show_type}')

wrappers/python/applications/pose/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ The camera calibration parameters are known. If the camera position is also know
1717

1818
# 6 DoF Pose Estimation
1919

20-
- Display Modes - different image data that could be extracted from the camera.
20+
Examples of the pose estimation utilities.
2121

22-
Example Image | Mode | Explanations |
22+
Example Image | Type | Explanations |
2323
:------------: | :----------: | :----------: |
24-
![RGB](doc/chess.gif) | Chess Board | RGB based chess board pose estimation |
25-
![d16](doc/cam_d_3.jpg) | Aruco Marker | Aruco marker detection and pose estimation |
26-
![sc1](doc/pose6d-cup.gif) | Known Object | cup detection and pose estimation |
27-
![sc1](doc/pose6d-matchbox.gif) | Known Object | a match box detection and pose estimation |
24+
![detecting chess board](doc/chess.gif) | Chess Board | RGB based chess board pose estimation |
25+
![aruco martker pose](doc/cam_d_3.jpg) | Aruco Marker | Aruco marker detection and pose estimation |
26+
![a general object pose](doc/pose6d-cup.gif) | Known Object | cup detection and pose estimation |
27+
![a general object pose](doc/pose6d-matchbox.gif) | Known Object | a match box detection and pose estimation |
2828

2929
# Modules and License
3030

0 commit comments

Comments
 (0)