Example #1
0
def _find_initial_grid_points_contours(preproc,
                                       transform,
                                       pattern_specs,
                                       det_params,
                                       vis=None):
    print('WARNING - FINDING INITIAL GRID POINTS BY CONTOURS IS DEPRECATED')
    pyutils.tic('initial grid estimate - contour')  #TODO remove
    ctpl = pattern_specs.calibration_template  # Alias
    coords_dst = points2numpy(ctpl.refpts_full_marker)
    coords_src = points2numpy(transform.marker_corners)
    H = cv2.getPerspectiveTransform(coords_src, coords_dst)
    if H is None:
        return None, vis
    h, w = ctpl.tpl_full.shape[:2]
    # OpenCV doc: finding contours is finding white objects from black background!
    warped_img = cv2.warpPerspective(preproc.wb, H, (w, h), cv2.INTER_CUBIC)
    warped_mask = cv2.warpPerspective(
        np.ones(preproc.wb.shape[:2], dtype=np.uint8), H, (w, h),
        cv2.INTER_NEAREST)
    cnts = cv2.findContours(warped_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    vis_alt = imutils.ensure_c3(warped_img.copy())
    idx = 0
    expected_circle_area = (pattern_specs.calibration_template.dia_circle_px /
                            2)**2 * np.pi
    exp_circ_area_lower = 0.5 * expected_circle_area
    exp_circ_area_upper = 2 * expected_circle_area
    for shape in cnts:
        area = cv2.contourArea(shape)
        if area < exp_circ_area_lower or area > exp_circ_area_upper:
            color = (255, 0, 0)
        else:
            color = (0, 0, 255)
            # continue
        # Centroid
        M = cv2.moments(shape)
        try:
            cx = np.round(M['m10'] / M['m00'])
            cy = np.round(M['m01'] / M['m00'])
        except ZeroDivisionError:
            continue

        idx += 1
        if det_params.debug:
            cv2.drawContours(vis_alt, [shape], 0, color, -1)
            cv2.circle(vis_alt, (int(cx), int(cy)), 1, (255, 255, 0), -1)
            if idx % 10 == 0:
                imvis.imshow(vis_alt, 'Points by contour', wait_ms=10)
    if det_params.debug:
        imvis.imshow(vis_alt, 'Points by contour', wait_ms=10)

    initial_estimates = list()
    #TODO match the points
    #TODO draw debug on vis
    pyutils.toc('initial grid estimate - contour')  #TODO remove
    return initial_estimates, vis
Example #2
0
def test_ensure_c3():
    # Invalid inputs
    assert ensure_c3(None) is None
    for invalid in [np.zeros(17), np.ones((4, 3, 2)), np.zeros((2, 2, 5))]:
        with pytest.raises(ValueError):
            ensure_c3(invalid)
    # Grayscale image (2-dim)
    x = np.random.randint(0, 255, (20, 30))
    c3 = ensure_c3(x)
    assert c3.ndim == 3 and c3.shape[2] == 3
    for c in range(3):
        assert np.array_equal(x, c3[:, :, c])
    # Grayscale image (3-dim, 1-channel)
    x = np.random.randint(0, 255, (10, 5, 1))
    c3 = ensure_c3(x)
    assert c3.ndim == 3 and c3.shape[2] == 3
    for c in range(3):
        assert np.array_equal(x[:, :, 0], c3[:, :, c])
    # RGB(A) inputs
    for x in [
            np.random.randint(0, 255, (12, 23, 3)),
            np.random.randint(0, 255, (12, 23, 4))
    ]:
        c3 = ensure_c3(x)
        assert c3.ndim == 3 and c3.shape[2] == 3
        assert np.array_equal(x[:, :, :3], c3)
Example #3
0
def contour(img):
    g = imutils.grayscale(img)
    _, g = cv2.threshold(g, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    # g = imutils.gaussian_blur(g, 1)
    # g = cv2.blur(g, (3,3))
    vis = imutils.ensure_c3(g)
    vis = img.copy()
    edges = cv2.Canny(g, 50, 200, apertureSize=3)
    kernel = np.ones((3, 3), np.uint8)
    edges = cv2.dilate(edges, kernel, iterations=1)
    cnts = cv2.findContours(edges, cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_NONE)  #cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    #https://docs.opencv.org/3.4/d4/d73/tutorial_py_contours_begin.html
    # White on black!

    approximated_polygons = list()
    for cnt in cnts:
        # get simplified convex hull
        epsilon = 0.05 * cv2.arcLength(cnt, True)
        approx = cv2.approxPolyDP(cnt, epsilon, True)
        hull = cv2.convexHull(approx)

        # cv2.drawContours(vis, [approx], 0, (0,255,0), 3)
        approximated_polygons.append({
            'hull': hull,
            'approx': approx,
            'hull_area': cv2.contourArea(hull),
            'corners': len(hull)
        })

    def _key(a):
        return a['hull_area']

    approximated_polygons.sort(key=_key, reverse=True)
    i = 0
    for approx in approximated_polygons:
        #cv2.drawContours(vis, [approx['cnt']], 0, (0,255,0) if i < 10 else (255,0,0), 3)
        cv2.drawContours(vis, [approx['hull']], 0,
                         (0, 255, 0) if 3 < approx['corners'] < 6 else
                         (255, 0, 0), 3)
        i += 1
        # if i < 15:
        # print('Largest', i, approx['approx'], approx['area'])
        # imvis.imshow(vis, title='contours', wait_ms=-1)
    imvis.imshow(vis, title='contours', wait_ms=-1)
Example #4
0
def find_target(img, pattern_specs, det_params=ContourDetectionParams()):
    # pyutils.tic('img-preprocessing')#TODO remove
    preprocessed = _md_preprocess_img(img, det_params)
    if det_params.debug:
        ### The pattern specification (+ rendered templates) takes ~1MB
        # # https://stackoverflow.com/questions/449560/how-do-i-determine-the-size-of-an-object-in-python
        # print(f"""Object sizes:
        # pattern_spec: {sizeof_fmt(sys.getsizeof(pattern_specs))}
        # det_params:   {sizeof_fmt(sys.getsizeof(det_params))}
        # preprocessed: {sizeof_fmt(sys.getsizeof(preprocessed))}
        # """)
        # print('REQUIRES pympler!!')
        # from pympler import asizeof
        # print(f"""Sizes with pympler:
        # pattern_spec: {sizeof_fmt(asizeof.asizeof(pattern_specs))}
        # det_params:   {sizeof_fmt(asizeof.asizeof(det_params))}
        # preprocessed: {sizeof_fmt(asizeof.asizeof(preprocessed))}
        # """)
        from vito import imvis
        vis = imutils.ensure_c3(preprocessed.gray)
    else:
        vis = None
    # pyutils.toc('img-preprocessing')#TODO remove - 20-30ms
    # pyutils.tic('center-candidates-contours')#TODO remove
    candidate_shapes, vis = _md_find_center_marker_candidates(
        det_params, preprocessed, vis)
    # pyutils.toc('center-candidates-contours')#TODO remove 1-2ms
    # pyutils.tic('center-verification-projective')#TODO remove 1-2ms
    # Find best fitting candidate (if any)
    transforms = list()
    for shape in candidate_shapes:
        # Compute homography between marker template and detected candidate
        transform = _find_center_marker_transform(
            preprocessed, shape, det_params,
            pattern_specs.calibration_template)
        if transform is not None:
            transforms.append(transform)
    transforms.sort(key=lambda t: t.similarity, reverse=True)
    # pyutils.toc('center-verification-projective')#TODO remove
    if len(transforms) > 0:
        _find_grid(preprocessed,
                   transforms[0],
                   pattern_specs,
                   det_params,
                   vis=vis)
Example #5
0
def _find_initial_grid_points_correlation(preproc,
                                          transform,
                                          pattern_specs,
                                          det_params,
                                          vis=None):
    pyutils.tic('initial grid estimate - correlation')  #TODO remove
    ctpl = pattern_specs.calibration_template  # Alias
    coords_dst = points2numpy(ctpl.refpts_full_marker)
    coords_src = points2numpy(transform.marker_corners)
    H = cv2.getPerspectiveTransform(coords_src, coords_dst)
    if H is None:
        return None, vis
    h, w = ctpl.tpl_full.shape[:2]
    warped_img = cv2.warpPerspective(preproc.bw, H, (w, h), cv2.INTER_CUBIC)
    warped_mask = cv2.warpPerspective(
        np.ones(preproc.bw.shape[:2], dtype=np.uint8), H, (w, h),
        cv2.INTER_NEAREST)

    ncc = cv2.matchTemplate(
        warped_img, ctpl.tpl_cropped_circle,
        cv2.TM_CCOEFF_NORMED)  # mask must be template size??
    ncc[ncc < det_params.grid_ccoeff_thresh_initial] = 0

    if det_params.debug:
        overlay = imutils.ensure_c3(
            imvis.overlay(ctpl.tpl_full, 0.3, warped_img, warped_mask))
        warped_img_corners = pru.apply_projection(
            H, points2numpy(image_corners(preproc.bw), Nx2=False))
        for i in range(4):
            pt1 = numpy2cvpt(warped_img_corners[:, i])
            pt2 = numpy2cvpt(warped_img_corners[:, (i + 1) % 4])
            cv2.line(overlay, pt1, pt2, color=(0, 0, 255), thickness=3)

    #FIXME FIXME FIXME
    # Idea: detect blobs in thresholded NCC
    # this could replace the greedy nms below
    # barycenter/centroid of each blob gives the top-left corner (then compute the relative offset to get the initial corner guess)
    initial_estimates = list()
    tpl = ctpl.tpl_cropped_circle
    while True:
        y, x = np.unravel_index(ncc.argmax(), ncc.shape)
        # print('Next', y, x, ncc[y, x], det_params.grid_ccoeff_thresh_initial, ncc.shape)
        if ncc[y, x] < det_params.grid_ccoeff_thresh_initial:
            break
        initial_estimates.append(
            CalibrationGridPoint(x=x, y=y, score=ncc[y, x]))
        # Clear the NCC peak around the detected template
        left = x - tpl.shape[1] // 2
        top = y - tpl.shape[0] // 2
        left, top, nms_w, nms_h = imutils.clip_rect_to_image(
            (left, top, tpl.shape[1], tpl.shape[0]), ncc.shape[1],
            ncc.shape[0])
        right = left + nms_w
        bottom = top + nms_h
        ncc[top:bottom, left:right] = 0
        if det_params.debug:
            cv2.rectangle(overlay, (x, y),
                          (x + ctpl.tpl_cropped_circle.shape[1],
                           y + ctpl.tpl_cropped_circle.shape[0]),
                          (255, 0, 255))
            if len(initial_estimates) % 20 == 0:
                # imvis.imshow(imvis.pseudocolor(ncc, [-1, 1]), 'NCC Result', wait_ms=10)
                imvis.imshow(overlay, 'Points by correlation', wait_ms=10)

    if vis is not None:
        cv2.drawContours(vis, [transform.shape['hull']], 0, (200, 0, 200), 3)
    if det_params.debug:
        print('Check "Points by correlation". Press key to continue')
        imvis.imshow(overlay, 'Points by correlation', wait_ms=-1)
    pyutils.toc('initial grid estimate - correlation')  #TODO remove
    return initial_estimates, vis
Example #6
0
def _md_find_center_marker_candidates(det_params, preprocessed, vis_img=None):
    """Locate candidate regions which could contain the marker."""
    debug_shape_extraction = det_params.debug and True
    # We don't want hierarchies of contours here, just the largest (i.e. the
    # root) contour of each hierarchy is fine:
    cnts = cv2.findContours(preprocessed.wb, cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    # Collect the convex hulls of all detected contours
    shapes = list()
    if debug_shape_extraction:
        tmp_vis = imutils.ensure_c3(preprocessed.wb)
        tmp_drawn = 0
    for cnt in cnts:
        # Compute a simplified convex hull
        epsilon = det_params.simplification_factor * cv2.arcLength(cnt, True)
        approx = cv2.approxPolyDP(cnt, epsilon, True)
        # Important:
        # Simplification with too large epsilons could lead to intersecting
        # polygons. These would cause incorrect area computation, and more
        # "fun" behavior. Thus, we work with the shape's convex hull from
        # now on.
        hull = cv2.convexHull(approx)
        area = cv2.contourArea(hull)
        if debug_shape_extraction:
            cv2.drawContours(tmp_vis, [cnt], 0, (255, 0, 0), 7)
            cv2.drawContours(tmp_vis, [hull], 0, (255, 0, 255), 7)
            tmp_drawn += 1
            if tmp_drawn % 10 == 0:
                imvis.imshow(tmp_vis, 'Shape candidates', wait_ms=10)
        if det_params.min_marker_area_px is None or\
                area >= det_params.min_marker_area_px:
            shapes.append({
                'hull': hull,
                'approx': approx,
                'cnt': cnt,
                'hull_area': area,
                'num_corners': len(hull)
            })
    if debug_shape_extraction:
        print('Check "shape candidates". Press key to continue')
        imvis.imshow(tmp_vis, 'Shape candidates', wait_ms=-1)
    # Sort candidate shapes by area (descending)
    shapes.sort(key=lambda s: s['hull_area'], reverse=True)
    # Collect valid convex hulls, i.e. having 4-6 corners which could
    # correspond to a rectangular region.
    candidate_shapes = list()
    for shape in shapes:
        is_candidate = False
        if 3 < shape['num_corners'] <= 8:
            candidate = _ensure_quadrilateral(
                shape
            )  #TODO pass image for debug visualizations, preprocessed.original)
            if candidate is not None:
                is_candidate = True
                candidate_shapes.append(candidate)
        if vis_img is not None:
            cv2.drawContours(vis_img, [shape['hull']], 0,
                             (0, 255, 0) if is_candidate else (255, 0, 0), 7)
        if det_params.max_candidates_per_image > 0 and det_params.max_candidates_per_image <= len(
                candidate_shapes):
            logging.info(
                f'Reached maximum amount of {det_params.max_candidates_per_image} candidate shapes.'
            )
            break
    return candidate_shapes, vis_img
Example #7
0
    def _compute_reference_grid(self):
        #TODO doc
        debug = True
        num_refpts_per_row = self.circles_per_row - 1
        num_refpts_per_col = self.circles_per_col - 1

        if debug:
            from vito import imvis, imutils
            import cv2
            vis = imutils.ensure_c3(self.calibration_template.tpl_full.copy())

        visited = np.zeros((num_refpts_per_col, num_refpts_per_row),
                           dtype=np.bool)
        nodes_to_visit = deque()
        nodes_to_visit.append(self._make_reference_point(0, 0))
        nnr = 0
        visible_count = 0
        reference_points = list()
        while nodes_to_visit:
            n = nodes_to_visit.popleft()
            vidx = self._refpt2posgrid(n.col, n.row)
            if vidx is None or visited[vidx.row, vidx.col]:
                continue
            nnr += 1
            visited[vidx.row, vidx.col] = True
            # # #circle test 31.03. bad idea
            # # if n.neighbor_dir is None or n.neighbor_dir == 0:
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col,   row=n.row-1, neighbor_dir=0))
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row-1, neighbor_dir=0))
            # # if n.neighbor_dir is None or n.neighbor_dir == 1:
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row,   neighbor_dir=1))
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row+1, neighbor_dir=1))
            # # if n.neighbor_dir is None or n.neighbor_dir == 2:
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col,   row=n.row+1, neighbor_dir=2))
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row+1, neighbor_dir=2))
            # # if n.neighbor_dir is None or n.neighbor_dir == 3:
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row,   neighbor_dir=3))
            # #     nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row-1, neighbor_dir=3))
            ## 8-neighborhood (works okayish 31.03)
            nodes_to_visit.append(
                self._make_reference_point(col=n.col, row=n.row - 1))
            nodes_to_visit.append(
                self._make_reference_point(col=n.col - 1, row=n.row - 1))
            nodes_to_visit.append(
                self._make_reference_point(col=n.col - 1, row=n.row))
            nodes_to_visit.append(
                self._make_reference_point(col=n.col - 1, row=n.row + 1))
            nodes_to_visit.append(
                self._make_reference_point(col=n.col, row=n.row + 1))
            nodes_to_visit.append(
                self._make_reference_point(col=n.col + 1, row=n.row + 1))
            nodes_to_visit.append(
                self._make_reference_point(col=n.col + 1, row=n.row))
            nodes_to_visit.append(
                self._make_reference_point(col=n.col + 1, row=n.row - 1))
            # ## 4-neighborhood
            # # nodes_to_visit.append(self._make_reference_point(col=n.col,   row=n.row-1))
            # # nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row))
            # # nodes_to_visit.append(self._make_reference_point(col=n.col,   row=n.row+1))
            # # nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row))

            if n.surrounding_circles is not None:
                reference_points.append(n)
                if debug:
                    pt = self._mm2px(n.pos_mm_tl)
                    # cv2.putText(vis, f'{nnr:d}', pt.int_repr(), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), 1)
                    cv2.putText(vis, f'{len(reference_points)-1:d}',
                                pt.int_repr(), cv2.FONT_HERSHEY_PLAIN, 1,
                                (0, 0, 255), 1)
                    cv2.circle(vis, pt.int_repr(), 3, (255, 0, 0), -1)
                    imvis.imshow(vis, "Reference Grid", wait_ms=1)
        object.__setattr__(self, 'reference_points', reference_points)
        if debug:
            print('Check "reference grid". Press key to continue.')
            imvis.imshow(vis, "Reference Grid", wait_ms=-1)