Skip to content

Commit d3435d2

Browse files
committed
Leaving only edge methods in here
1 parent 4ddc829 commit d3435d2

File tree

1 file changed

+10
-224
lines changed

1 file changed

+10
-224
lines changed

Image_based/fit_bezier_cyl_2d_edge.py

Lines changed: 10 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -16,133 +16,11 @@
1616
from BaseStatsImage import BaseStatsImage
1717
from HandleFileNames import HandleFileNames
1818

19-
class FitBezierCyl2D(BaseStatsImage):
19+
class FitBezierCyl2DEdge:
2020
def __init__(self, fname_mask_image, fname_calculated=None, fname_debug=None, b_recalc=False):
21-
""" Read in the mask image, use the stats to start the quad fit, then fit the quad
22-
@param fname_mask_image: name of mask file, rgb or gray scale image with white where the mask is
23-
@param fname_calculated: the file name for the saved .json file; should be image name w/o _stats.json
24-
@param fname_debug: the file name for a debug image showing the bounding box, etc
25-
@param b_recalc: Force recalculate the result, y/n"""
26-
27-
# Initializes the base stats image class, which does the image read and calculates the stats
28-
self.BaseStatsImage.__init__(fname_mask_image, fname_calculated, fname_debug, b_recalc)
29-
30-
# Fit a quad to the mask, using the end points of the base image as a starting point
31-
print("Fitting quads")
32-
self.fname_quad = None # The actual quadratic bezier
33-
self.fname_params = None # Parameters used to do the fit
34-
if fname_calculated:
35-
self.fname_quad = fname_calculated + "quad.json"
36-
self.fname_params = fname_calculated + "quad_params.json"
37-
38-
self.quad = None
39-
40-
# Current parameters for the vertical leader - will need to make this a parameter
41-
self.params = {"step_size": int(0.5 * self.stats_dict['width'] * 1.5), "width_mask": 1.4, "width": 0.25}
42-
43-
if b_recalc or not fname_calculated or not exists(fname_calculated):
44-
if exists(self.fname_quad) and not b_recalc:
45-
self.quad = BezierCyl2D.read_json(self.fname_quad)
46-
with open(self.fname_params, 'r') as f:
47-
self.params = json.load(f)
48-
else:
49-
self.quad = self.fit_quad_to_mask(self.mask_image, stats=self.stats_dict, params=self.params)
50-
self.quad.write_json(self.fname_quad)
51-
with open(self.fname_params, 'w') as f:
52-
json.dump(self.params, f)
53-
54-
if fname_debug:
55-
# Draw the edge and original image with the fitted quad and rects
56-
im_covert_back = cv2.cvtColor(self.mask_image, cv2.COLOR_GRAY2RGB)
57-
self.debug_image(im_covert_back) # The eigen vec
58-
self.debug_image_quad_fit(im_covert_back)
59-
cv2.imwrite(fname_debug, im_covert_back)
60-
61-
self.score = self.score_quad(self.quad)
62-
63-
def _setup_least_squares(self, ts, ):
64-
"""Setup the least squares approximation
65-
@param ts - t values to use
66-
@returns A, B for Ax = b """
67-
# Set up the matrix - include the 3 current points plus the centers of the mask
68-
a_constraints = np.zeros((len(ts) + 3, 3))
69-
ts_constraints = np.zeros((len(ts) + 3))
70-
b_rhs = np.zeros((len(ts) + 3, 2))
71-
ts_constraints[-3] = 0.0
72-
ts_constraints[-2] = 0.5
73-
ts_constraints[-1] = 1.0
74-
ts_constraints[:-3] = np.transpose(ts)
75-
a_constraints[:, -3] = (1-ts_constraints) * (1-ts_constraints)
76-
a_constraints[:, -2] = 2 * (1-ts_constraints) * ts_constraints
77-
a_constraints[:, -1] = ts_constraints * ts_constraints
78-
for i, t in enumerate(ts_constraints):
79-
b_rhs[i, :] = self.pt_axis(ts_constraints[i])
80-
return a_constraints, b_rhs
81-
82-
def _extract_least_squares(self, a_constraints, b_rhs):
83-
""" Do the actual Ax = b and keep horizontal/vertical end points
84-
@param a_constraints the A of Ax = b
85-
@param b_rhs the b of Ax = b
86-
@returns fit error L0 norm"""
87-
if a_constraints.shape[0] < 3:
88-
return 0.0
89-
90-
# a_at = a_constraints @ a_constraints.transpose()
91-
# rank = np.rank(a_at)
92-
# if rank < 3:
93-
# return 0.0
94-
95-
new_pts, residuals, rank, _ = np.linalg.lstsq(a_constraints, b_rhs, rcond=None)
96-
97-
print(f"Residuals {residuals}, rank {rank}")
98-
b_rhs[1, :] = self.p1
99-
pts_diffs = np.sum(np.abs(new_pts - b_rhs[0:3, :]))
100-
101-
if self.orientation is "vertical":
102-
new_pts[0, 1] = self.p0[1]
103-
new_pts[2, 1] = self.p2[1]
104-
else:
105-
new_pts[0, 0] = self.p0[0]
106-
new_pts[2, 0] = self.p2[0]
107-
self.p0 = new_pts[0, :]
108-
self.p1 = new_pts[1, :]
109-
self.p2 = new_pts[2, :]
110-
return pts_diffs
111-
112-
def adjust_quad_by_mask(self, im_mask, step_size=40, perc_width=1.2, axs=None):
113-
"""Replace the linear approximation with one based on following the mask
114-
@param im_mask - mask image
115-
@param step_size - how many pixels to step along
116-
@param perc_width - how much wider than the radius to look in the mask
117-
@param axs - optional axes to draw the cutout in
118-
@returns how much the points moved"""
119-
height = int(self.radius_2d)
120-
rects, ts = self.interior_rects(step_size=step_size, perc_width=perc_width)
121-
122-
# Set up the matrix - include the 3 current points plus the centers of the mask
123-
a_constraints, b_rhs = self._setup_least_squares(ts)
21+
# TODO: Make this look like fit_bezier_cyl_2d_mask, creating a FitBezierCyl2DMask then using the
22+
# output of that to do the fit to the edge process
12423

125-
x_grid, y_grid = np.meshgrid(range(0, step_size), range(0, height))
126-
if axs is not None:
127-
axs.imshow(im_mask, origin='lower')
128-
for i, r in enumerate(rects):
129-
b_rect_inside = BezierCyl2D._rect_in_image(im_mask, r, pad=2)
130-
131-
im_warp, tform_inv = self._image_cutout(im_mask, r, step_size=step_size, height=height)
132-
if b_rect_inside and np.sum(im_warp > 0) > 0:
133-
x_mean = np.mean(x_grid[im_warp > 0])
134-
y_mean = np.mean(y_grid[im_warp > 0])
135-
pt_warp_back = tform_inv @ np.transpose(np.array([x_mean, y_mean, 1]))
136-
print(f"{self.pt_axis(ts[i])} ({x_mean}, {y_mean}), {pt_warp_back}")
137-
b_rhs[i, :] = pt_warp_back[0:2]
138-
else:
139-
print(f"Empty slice {r}")
140-
141-
if axs is not None:
142-
axs.clear()
143-
axs.imshow(im_warp, origin='lower')
144-
145-
return self._extract_least_squares(a_constraints, b_rhs)
14624

14725
def _hough_edge_to_middle(self, p1, p2):
14826
""" Convert the two end points to an estimate of the mid-point and the pt on the spine
@@ -157,6 +35,10 @@ def _hough_edge_to_middle(self, p1, p2):
15735
return mid_pt, pt_middle
15836

15937
def adjust_quad_by_hough_edges(self, im_edge, step_size=40, perc_width=0.3, axs=None):
38+
# TODO Make this look like fit_bezier_crv_to_mask in FitBezierCyl2DMask
39+
# TODO rename quad to Beier
40+
# TODO Like adjust, create a FitBezierCrv2D from an input Bezier crv (the setup_least_squares etc will work)
41+
# TODO change self.ls etc to fit_bezier_crv
16042
"""Replace the linear approximation with one based on following the mask
16143
@param im_mask - mask image
16244
@param step_size - how many pixels to step along
@@ -241,7 +123,8 @@ def adjust_quad_by_hough_edges(self, im_edge, step_size=40, perc_width=0.3, axs=
241123
return self._extract_least_squares(a_constraints, b_rhs)
242124

243125
def set_end_pts(self, pt0, pt2):
244-
""" Set the end point to the new end point while trying to keep the curve the same
126+
""" TODO: pass in the curve and set the crv's end points
127+
Set the end point to the new end point while trying to keep the curve the same
245128
@param pt0 new p0
246129
@param pt2 new p2"""
247130
l0 = LineSeg2D(self.p0, self.p1)
@@ -261,39 +144,6 @@ def set_end_pts(self, pt0, pt2):
261144

262145
return self._extract_least_squares(a_constraints, b_rhs)
263146

264-
@staticmethod
265-
def fit_quad_to_mask(self, im_mask, stats, params):
266-
""" Fit a quad to the mask, edge image
267-
@param im_mask - the image mask
268-
@param stats - the stats from BaseStatsImage
269-
@param params - the parameters to use in the fit
270-
@returns fitted quad and stats used in the fit"""
271-
272-
# Fit a quad to the trunk
273-
pt_lower_left = stats['center']
274-
vec_len = stats["Length"] * 0.4
275-
while pt_lower_left[0] > 2 + stats['x_min'] and pt_lower_left[1] > 2 + stats['y_min']:
276-
pt_lower_left = stats["center"] - stats["EigenVector"] * vec_len
277-
vec_len = vec_len * 1.1
278-
279-
pt_upper_right = stats['center']
280-
vec_len = stats["Length"] * 0.4
281-
while pt_upper_right[0] < -2 + stats['x_max'] and pt_upper_right[1] < -2 + stats['y_max']:
282-
pt_upper_right = stats["center"] + stats["EigenVector"] * vec_len
283-
vec_len = vec_len * 1.1
284-
285-
quad = BezierCyl2D(pt_lower_left, pt_upper_right, 0.5 * stats['width'])
286-
287-
# Iteratively move the quad to the center of the mask
288-
print(f"Res: ", end="")
289-
for i in range(0, 5):
290-
res = quad.adjust_quad_by_mask(im_mask,
291-
step_size=params["step_size"], perc_width=params["width_mask"],
292-
axs=None)
293-
print(f"{res} ", end="")
294-
print("")
295-
return quad, params
296-
297147
def find_edges_hough_transform(self, im_edge, step_size=40, perc_width=0.3, axs=None):
298148
"""Find the hough transform of the images in the boxes; save the line orientations
299149
@param im_edge - edge image
@@ -424,71 +274,7 @@ def adjust_quad_by_edge_image(im_edge, quad, params):
424274

425275
return quad
426276

427-
def check_interior_depth(self, im_depth, step_size=40, perc_width=0.3):
428-
""" Find which pixels are valid depth and fit average depth
429-
@param im_depth - depth image
430-
@param step_size how many pixels to move along the boundary
431-
@param perc_width How much of the radius to move in/out of the edge
432-
"""
433-
height = int(self.radius_2d)
434-
rects, ts = self.interior_rects(step_size=step_size, perc_width=perc_width)
435-
436-
stats = []
437-
perc_consistant = 0.0
438-
for i, r in enumerate(rects):
439-
b_rect_inside = BezierCyl2D._rect_in_image(im_depth, r, pad=2)
440-
441-
im_warp, tform_inv = self._image_cutout(im_depth, r, step_size=step_size, height=height)
442-
443-
stats_slice = {"Min": np.min(im_warp),
444-
"Max": np.max(im_warp),
445-
"Median": np.median(im_warp)}
446-
stats_slice["Perc_in_range"] = np.count_nonzero(np.abs(im_warp - stats_slice["Median"]) < 10) / (im_warp.size)
447-
perc_consistant += stats_slice["Perc_in_range"]
448-
stats.append(stats_slice)
449-
perc_consistant /= len(rects)
450-
return perc_consistant, stats
451-
452-
@staticmethod
453-
def adjust_quad_by_flow_image(im_flow, quad, params):
454-
""" Not really useful now - fixes the mask by calculating the average depth in the flow mask under the bezier
455-
the trimming off mask pixels that don't belong
456-
@param im_flow: Flow or depth image (gray scale)
457-
@param quad: The quad to adjust
458-
@param params: Parameters to use for adjust
459-
@return the adjusted quad"""
460-
461-
print("Quad adjust res: ", end="")
462-
for i in range(0, 5):
463-
res = quad.adjust_quad_by_mask(im_flow,
464-
step_size=params["step_size"], perc_width=params["width_mask"],
465-
axs=None)
466-
print(f"{res} ")
467-
print(" done")
468-
469-
return quad
470-
471-
def score_quad(self, im_flow, quad):
472-
""" See if the quad makes sense over the optical flow image
473-
@quad - the quad
474-
"""
475-
476-
# Two checks: one, are the depth/optical fow values largely consistent under the quad center
477-
# Are there boundaries in the optical flow image where the edge of the quad is?
478-
im_flow_mask = cv2.cvtColor(im_flow, cv2.COLOR_BGR2GRAY)
479-
perc_consistant, stats_slice = quad.check_interior_depth(im_flow_mask)
480-
481-
diff = 0
482-
for i in range(1, len(stats_slice)):
483-
diff_slices = np.abs(stats_slice[i]["Median"] - stats_slice[i-1]["Median"])
484-
if diff_slices > 20:
485-
print(f"Warning: Depth values not consistant from slice {self.fname_quad} {i} {stats_slice}")
486-
diff += diff_slices
487-
if perc_consistant < 0.9:
488-
print(f"Warning: not consistant {self.fname_quad} {stats_slice}")
489-
return perc_consistant, diff / (len(stats_slice) - 1)
490-
491-
def debug_image_quad_fit(self, image_debug):
277+
def debug_image_edge_fit(self, image_debug):
492278
""" Draw the fitted quad on the image
493279
@param image_debug - rgb image"""
494280
# Draw the original, the edges, and the depth mask with the fitted quad

0 commit comments

Comments
 (0)