Ejemplo n.º 1
0
def laps(img, lines):
    """
    Lattice points search in the given image.

    :param img: Image to search.
    :param lines: Lines detected by slid.
    :return: Points detected to be part of the chessboard grid.
    """
    intersection_points = __find_intersections(lines)

    debug.DebugImage(img) \
        .lines(lines, color=(0, 0, 255)) \
        .points(intersection_points, color=(255, 0, 0), size=2) \
        .save("laps_in_queue")

    points = []
    for pt in intersection_points:
        # Pixels are in integers
        pt = (int(pt[0]), int(pt[1]))

        if pt[0] < 0 or pt[1] < 0:
            continue

        # Size of our analysis area
        lx1 = max(0, int(pt[0] - __ANALYSIS_RADIUS - 1))
        lx2 = max(0, int(pt[0] + __ANALYSIS_RADIUS))
        ly1 = max(0, int(pt[1] - __ANALYSIS_RADIUS))
        ly2 = max(0, int(pt[1] + __ANALYSIS_RADIUS + 1))

        # Cropping for detector
        dimg = img[ly1:ly2, lx1:lx2]
        dimg_shape = np.shape(dimg)

        # Not valid
        if dimg_shape[0] <= 0 or dimg_shape[1] <= 0:
            continue

        # Detect if it is a lattice point
        if not __is_lattice_point(dimg):
            continue

        points.append(pt)

    if points:
        points = __cluster_points(points)

    debug.DebugImage(img) \
        .points(intersection_points, color=(0, 0, 255), size=3) \
        .points(points, color=(0, 255, 0)) \
        .save("laps_good_points")

    return points
Ejemplo n.º 2
0
 def simplify_image(img, limit, grid, iters):
     """Simplify image using CLAHE algorithm (adaptive histogram
     equalization)."""
     img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
     for _ in range(iters):
         img = cv2.createCLAHE(clipLimit=limit,
                               tileGridSize=grid).apply(img)
     debug.DebugImage(img).save("slid_clahe_@1")
     if limit != 0:
         kernel = np.ones((10, 10), np.uint8)
         img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
         debug.DebugImage(img).save("slid_clahe_@2")
     return img
Ejemplo n.º 3
0
def compute_corners(image_object):
    """
    Compute the coordinates of the board on the original image from the
    ImageObject obtained in the detection.

    :param image_object: ImageObject obtained in the detect method.
    :return: The coordinates in the original image of the chessboard
        corners and the coordinates of each of the corners of the
        chessboard squares as a pair of board_corners and
        square_corners.
    """
    board_corners, square_corners = __original_points_coords(
        image_object.get_points())

    debug.DebugImage(image_object.get_images()[0]['orig']) \
        .points(square_corners, size=50, color=(0, 0, 255)) \
        .points(board_corners, size=50, color=(0, 255, 0)) \
        .save("corner_points")

    return board_corners, square_corners
Ejemplo n.º 4
0
def __padcrop(img, four_points):
    """
    Apply a border to the inner four points of the chessboard in order
    to obtain a frame that contains the full board.
    """
    pco = pyclipper.PyclipperOffset()
    pco.AddPath(four_points, pyclipper.JT_MITER, pyclipper.ET_CLOSEDPOLYGON)

    padded = pco.Execute(60)[0]

    debug.DebugImage(img) \
        .points(four_points, color=(0, 0, 255)) \
        .points(padded, color=(0, 255, 0)) \
        .lines(
        [[four_points[0], four_points[1]], [four_points[1], four_points[2]],
         [four_points[2], four_points[3]], [four_points[3], four_points[0]]],
        color=(255, 255, 255)) \
        .lines([[padded[0], padded[1]], [padded[1], padded[2]],
                [padded[2], padded[3]], [padded[3], padded[0]]],
               color=(255, 255, 255)) \
        .save("cps_final_pad")

    return __order_points(padded)
Ejemplo n.º 5
0
def detect(input_image, output_board, board_corners=None):
    """
    Detects the board position in input_image and stores the cropped
    detected board in output_board.

    :param input_image: Input chessboard image.
    :param output_board: Path (including name and extension) where to
        store the image with the detected chessboard.
    :param board_corners: A list of the coordinates of the four board
        corners. If it is not None, first check if the board is in the
        position given by these corners. If not, runs the full
        detection.
    :return: Final ImageObject with which to compute the corners if
        necessary.
    """
    # Check if we can skip full board detection (if board position is
    # already known)
    if board_corners is not None:
        found, cropped_img = check_board_position(input_image, board_corners)
        if found:
            cv2.imwrite(output_board, cropped_img)
            image = ImageObject(input_image)
            # For corners calculation
            image.add_points([[0, 0], [1200, 0], [1200, 1200], [0, 1200]])
            image.add_points(board_corners)
            return image

    # Read the input image and store the cropped detected board
    n_layers = 3
    image = ImageObject(input_image)
    for i in range(n_layers):
        __layer(image)
        debug.DebugImage(image['orig']).save(f"end_iteration{i}")
    cv2.imwrite(output_board, image['orig'])

    return image
Ejemplo n.º 6
0
def __slid_segments(img):
    """
    Find all segments in the image using different settings.

    :param img: Image to search.
    :return: A list of all the segments found.
    """
    def detect_edges(img):
        """Apply Canny edge detector (automatic threshold)."""
        sigma = 0.25
        v = np.median(img)
        img = cv2.medianBlur(img, 5)
        img = cv2.GaussianBlur(img, (7, 7), 2)
        lower = int(max(0, (1. - sigma) * v))
        upper = int(min(255, (1. + sigma) * v))
        return cv2.Canny(img, lower, upper)

    def simplify_image(img, limit, grid, iters):
        """Simplify image using CLAHE algorithm (adaptive histogram
        equalization)."""
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        for _ in range(iters):
            img = cv2.createCLAHE(clipLimit=limit,
                                  tileGridSize=grid).apply(img)
        debug.DebugImage(img).save("slid_clahe_@1")
        if limit != 0:
            kernel = np.ones((10, 10), np.uint8)
            img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
            debug.DebugImage(img).save("slid_clahe_@2")
        return img

    def detect_lines(img):
        """Detect lines using the probabilistic Hough transform."""
        beta = 2
        lines = cv2.HoughLinesP(img,
                                rho=1,
                                theta=np.pi / 360 * beta,
                                threshold=40,
                                minLineLength=50,
                                maxLineGap=15)  # [40, 40, 10]
        if lines is None:
            return []

        __lines = []
        for line in np.reshape(lines, (-1, 4)):
            __lines.append([[int(line[0]), int(line[1])],
                            [int(line[2]), int(line[3])]])
        return __lines

    clahe_settings = [
        [3, (2, 6), 5],  # @1
        [3, (6, 2), 5],  # @2
        [5, (3, 3), 5],  # @3
        [0, (0, 0), 0]
    ]  # EE

    segments = []
    i = 0
    for key, arr in enumerate(clahe_settings):
        tmp = simplify_image(img, limit=arr[0], grid=arr[1], iters=arr[2])
        __segments = detect_lines(detect_edges(tmp))
        segments += __segments
        i += 1
        debug.DebugImage(detect_edges(tmp)).lines(__segments).save(
            "pslid_F%d" % i)
    return segments
Ejemplo n.º 7
0
def slid(img):
    """
    Straight line detector in the given image from the segments.

    :param img: Image to search.
    :return: A list of the detected lines. Each line is a pair of
        points.
    """
    group = {}
    hashmap = {}
    ptp_cache = {}

    def ptp_distance(a, b):
        """
        Distance from point to point with a cache to avoid multiple
        calculations.
        """
        idx = hash("__dis" + str(a) + str(b))
        if idx in ptp_cache:
            return ptp_cache[idx]
        ptp_cache[idx] = math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
        return ptp_cache[idx]

    def ptl_distance(line, point, dx):
        """
        Distance from point to line.

        :param line: Line defined by two points.
        :param point: Point.
        :param dx: Distance between the points that define the line.
        :return: The distance from point to line.
        """
        return abs((line[1][0] - line[0][0]) * (line[0][1] - point[1]) -
                   (line[1][1] - line[0][1]) * (line[0][0] - point[0])) / dx

    def similar_lines(line1, line2):
        """Returns if line1 is similar to line2."""
        da = ptp_distance(line1[0], line1[1])
        db = ptp_distance(line2[0], line2[1])

        d1a = ptl_distance(line1, line2[0], da)
        d2a = ptl_distance(line1, line2[1], da)
        d1b = ptl_distance(line2, line1[0], db)
        d2b = ptl_distance(line2, line1[1], db)

        # Average deviation from the straight line
        avg_dev = 0.25 * (d1a + d1b + d2a + d2b) + 0.00001

        # Allowed matching error
        delta = 0.0625 * (da + db)

        return da / avg_dev > delta and db / avg_dev > delta

    X = {}

    def __fi(x):
        if x not in X:
            X[x] = 0
        if X[x] == x or X[x] == 0:
            X[x] = x
        else:
            X[x] = __fi(X[x])
        return X[x]

    def __un(a, b):
        """Union & find."""
        ia, ib = __fi(a), __fi(b)
        X[ia] = ib
        group[ib] |= group[ia]

    def generate_points(a, b, n):
        """
        Returns n equispaced points in the segment given by a and b.
        """
        points = []
        t = 1 / n
        for i in range(n):
            x = a[0] + (b[0] - a[0]) * (i * t)
            y = a[1] + (b[1] - a[1]) * (i * t)
            points.append((int(x), int(y)))
        return points

    def merge_group(group, all_points):
        """Merge the group into a single line."""
        points = []
        for idx in group:
            points += generate_points(*hashmap[idx], n=10)

        all_points += points
        na_points = np.array(points)

        _, radius = cv2.minEnclosingCircle(na_points)
        w = radius * (math.pi / 2)
        vx, vy, cx, cy = cv2.fitLine(na_points, cv2.DIST_L2, 0, 0.01, 0.01)

        return ((int(cx - vx * w), int(cy - vy * w)), (int(cx + vx * w),
                                                       int(cy + vy * w)))

    # Find all segments in image
    segments = __slid_segments(img)

    # Divide segments into vertical and horizontal
    vh_segments = [[], []]
    for l in segments:
        h = hash(str(l))
        hashmap[h] = l
        group[h] = {h}
        X[h] = h

        t1 = l[0][0] - l[1][0]
        t2 = l[0][1] - l[1][1]
        if abs(t1) < abs(t2):  # If l is a vertical segment
            vh_segments[0].append(l)
        else:
            vh_segments[1].append(l)

    debug.DebugImage(img.shape) \
        .lines(vh_segments[0], color=debug.rand_color()) \
        .lines(vh_segments[1], color=debug.rand_color()) \
        .save("slid_pre_groups")

    for lines in vh_segments:
        for i in range(len(lines)):
            l1 = lines[i]
            h1 = hash(str(l1))
            if X[h1] != h1:  # Line already grouped
                continue
            for j in range(i + 1, len(lines)):
                l2 = lines[j]
                h2 = hash(str(l2))
                if X[h2] != h2:
                    continue

                if similar_lines(l1, l2):
                    __un(h1, h2)

    if debug.DEBUG:
        __d = debug.DebugImage(img.shape)
        for i in group:
            if X[i] != i:
                continue
            ls = [hashmap[h] for h in group[i]]
            __d.lines(ls, color=debug.rand_color())
        __d.save("slid_all_groups")

    all_points = []
    raw_lines = []
    for i in group:
        if X[i] != i:
            continue
        raw_lines.append(merge_group(group[i], all_points))

    lines = __scale_lines(raw_lines)

    debug.DebugImage(img.shape) \
        .points(all_points, color=(0, 255, 0), size=2) \
        .lines(raw_lines).save("slid_raw_lines")

    debug.DebugImage(img).lines(lines).save("slid_final")

    return lines
Ejemplo n.º 8
0
def cps(img, points, lines):
    """
    Chessboard position search in the given image.

    :param img: Image to search.
    :param points: Points obtained in laps.
    :param lines: Lines detected by slid.
    :return: The four inner points of the detected chessboard.
    """
    ptp_cache = {}

    def ptp_distance(a, b):
        """
        Distance from point to point with a cache to avoid multiple
        calculations.
        """
        idx = hash("__dis" + str(a) + str(b))
        if idx in ptp_cache:
            return ptp_cache[idx]
        ptp_cache[idx] = math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
        return ptp_cache[idx]

    points = __check_correctness(__normalize(points), img.shape)

    # Clustering
    __points = {}
    points = __sort_points(points)
    __max = 0
    __points_max = []
    alfa = math.sqrt(cv2.contourArea(np.array(points)) / 49)
    X = DBSCAN(eps=alfa * 4).fit(points)
    for i in range(len(points)):
        __points[i] = []
    for i in range(len(points)):
        if X.labels_[i] != -1:
            __points[X.labels_[i]].append(points[i])
    for i in range(len(points)):
        if len(__points[i]) > __max:
            __max = len(__points[i])
            __points_max = __points[i]

    if len(__points) > 0 and len(points) > 49 / 2:
        points = __points_max

    n = len(points)
    beta = n * (5 / 100)  # beta = n * (100 - (CPS efectiveness))
    alfa = math.sqrt(cv2.contourArea(np.array(points)) / 49)

    # We are looking for the focal point of the cluster
    x = [p[0] for p in points]
    y = [p[1] for p in points]
    centroid = (sum(x) / len(points), sum(y) / len(points))

    def __v(l):
        y_0, x_0 = l[0][0], l[0][1]
        y_1, x_1 = l[1][0], l[1][1]

        x_2 = 0
        t = (x_0 - x_2) / (x_0 - x_1 + 0.0001)
        a = [int((1 - t) * x_0 + t * x_1), int((1 - t) * y_0 + t * y_1)][::-1]

        x_2 = img.shape[0]
        t = (x_0 - x_2) / (x_0 - x_1 + 0.0001)
        b = [int((1 - t) * x_0 + t * x_1), int((1 - t) * y_0 + t * y_1)][::-1]

        poly1 = __sort_points([[0, 0], [0, img.shape[0]], a, b])
        s1 = __polyscore(np.array(poly1), points, centroid, alfa / 2, beta)
        poly2 = __sort_points(
            [a, b, [img.shape[1], 0], [img.shape[1], img.shape[0]]])
        s2 = __polyscore(np.array(poly2), points, centroid, alfa / 2, beta)

        return [a, b], s1, s2

    def __h(l):
        x_0, y_0 = l[0][0], l[0][1]
        x_1, y_1 = l[1][0], l[1][1]

        x_2 = 0
        t = (x_0 - x_2) / (x_0 - x_1 + 0.0001)
        a = [int((1 - t) * x_0 + t * x_1), int((1 - t) * y_0 + t * y_1)]

        x_2 = img.shape[1]
        t = (x_0 - x_2) / (x_0 - x_1 + 0.0001)
        b = [int((1 - t) * x_0 + t * x_1), int((1 - t) * y_0 + t * y_1)]

        poly1 = __sort_points([[0, 0], [img.shape[1], 0], a, b])
        s1 = __polyscore(np.array(poly1), points, centroid, alfa / 2, beta)
        poly2 = __sort_points(
            [a, b, [0, img.shape[0]], [img.shape[1], img.shape[0]]])
        s2 = __polyscore(np.array(poly2), points, centroid, alfa / 2, beta)

        return [a, b], s1, s2

    pregroup = [[], []]  # Division into 2 groups (for the frame)
    for l in lines:  # We will review all of the lines
        # We reject lines that pass through the center of the cluster
        if __ptl_distance(l, centroid, ptp_distance(*l)) > alfa * 2.5:
            for p in points:
                # We check that the line passes near a good point
                if __ptl_distance(l, p, ptp_distance(*l)) < alfa:
                    # The line belongs to the ring
                    tx, ty = l[0][0] - l[1][0], l[0][1] - l[1][1]
                    if abs(tx) < abs(ty):
                        ll, s1, s2 = __v(l)
                        orientation = 0
                    else:
                        ll, s1, s2 = __h(l)
                        orientation = 1
                    if s1 == 0 and s2 == 0:
                        continue
                    pregroup[orientation].append(ll)

    pregroup[0] = __remove_duplicates(pregroup[0])
    pregroup[1] = __remove_duplicates(pregroup[1])

    if debug.DEBUG:
        # We create an outer ring
        def convex_approx(points, alfa=0.01):
            points = np.array(points)
            hull = ConvexHull(points).vertices
            cnt = points[hull]
            approx = cv2.approxPolyDP(cnt, alfa * cv2.arcLength(cnt, True),
                                      True)
            return __normalize(itertools.chain(*approx))

        ring = convex_approx(__sort_points(points))

        debug.DebugImage(img) \
            .lines(lines, color=(0, 0, 255)) \
            .points(points, color=(0, 0, 255)) \
            .points(ring, color=(0, 255, 0)) \
            .points([centroid], color=(255, 0, 0)) \
            .save("cps_debug")

        debug.DebugImage(img) \
            .lines(pregroup[0], color=(0, 0, 255)) \
            .lines(pregroup[1], color=(255, 0, 0)) \
            .save("cps_pregroups")

    score = {}  # Frame ranking with the result
    for v in itertools.combinations(pregroup[0], 2):  # Horizontal
        for h in itertools.combinations(pregroup[1], 2):  # Vertical
            poly = [
                __intersection(v[0], v[1]),
                __intersection(v[0], h[0]),
                __intersection(v[0], h[1]),
                __intersection(v[1], h[0]),
                __intersection(v[1], h[1]),
                __intersection(h[0], h[1])
            ]
            poly = __check_correctness(poly, img.shape)
            if len(poly) != 4:
                continue
            poly = np.array(__sort_points(__normalize(poly)))
            if not cv2.isContourConvex(poly):
                continue
            score[-__polyscore(poly, points, centroid, alfa / 2, beta)] = poly

    score = collections.OrderedDict(sorted(score.items()))
    K = next(iter(score))

    inner_points = __normalize(score[K])
    inner_points = __order_points(inner_points)

    debug.DebugImage(img) \
        .points(points, color=(0, 255, 0)) \
        .points(inner_points, color=(0, 0, 255)) \
        .points([centroid], color=(255, 0, 0)) \
        .lines([[inner_points[0], inner_points[1]],
                [inner_points[1], inner_points[2]],
                [inner_points[2], inner_points[3]],
                [inner_points[3], inner_points[0]]],
               color=(255, 255, 255)) \
        .save("cps_debug_2")

    return __padcrop(img, inner_points)