Пример #1
0
def _detect_CO_balls(img, balls, cue, display_list):
    PARA1 = int(float(img.shape[1]) / 640 * 15 + 0.5)
    PARA2 = int(float(img.shape[1]) / 640 * 40 + 0.5)
    p_cue_top, p_cue_bottom, cue_length = cue

    ## determine which ball is cue ball
    min_dist2cue_top = img.shape[0] + 1
    cue_ball_idx = -1
    for idx, ball in enumerate(balls):
        center, radius = ball
        dist2cue = zc.calc_triangle_area(center, p_cue_top,
                                         p_cue_bottom) * 2 / cue_length
        if dist2cue < PARA1:
            dist2cue_top = zc.euc_dist(center, p_cue_top)
            if dist2cue_top < min_dist2cue_top:
                min_dist2cue_top = dist2cue_top
                cue_ball_idx = idx
    if cue_ball_idx == -1:
        rtn_msg = {'status': 'fail', 'message': 'Cannot find cue ball'}
        return (rtn_msg, None)
    cue_ball = balls[cue_ball_idx]

    ## determine which ball is object ball
    min_dist2cue_top = img.shape[0] + 1
    object_ball_idx = -1
    for idx, ball in enumerate(balls):
        if idx == cue_ball_idx:
            continue
        center, radius = ball
        dist2cue = zc.calc_triangle_area(center, p_cue_top,
                                         p_cue_bottom) * 2 / cue_length
        if dist2cue < PARA2:
            dist2cue_top = zc.euc_dist(center, p_cue_top)
            if dist2cue_top < min_dist2cue_top:
                min_dist2cue_top = dist2cue_top
                object_ball_idx = idx
    if object_ball_idx == -1:
        rtn_msg = {'status': 'fail', 'message': 'Cannot find object ball'}
        return (rtn_msg, None)
    object_ball = balls[object_ball_idx]

    if 'CO_balls' in display_list:
        img_balls = img.copy()
        cv2.circle(img_balls, (int(cue_ball[0][0]), int(cue_ball[0][1])),
                   int(cue_ball[1]), (255, 255, 255), -1)
        cv2.circle(img_balls, (int(object_ball[0][0]), int(object_ball[0][1])),
                   int(object_ball[1]), (0, 0, 255), -1)
        zc.display_image("CO_balls",
                         img_balls,
                         resize_max=config.DISPLAY_MAX_PIXEL,
                         wait_time=config.DISPLAY_WAIT_TIME)

    rtn_msg = {'status': 'success'}
    return (rtn_msg, (cue_ball, object_ball))
Пример #2
0
def _detect_aim_point(cue, CO_balls, pocket):
    def _angle2fraction(angle):
        '''
        Calculcate desired fraction of overlap from angle according to fraction aiming system
        Reference: http://billiards.colostate.edu/threads/aiming.html, https://youtu.be/2kuJTwQ1M9k
        '''
        if angle > 48.6:  # very thin
            return (90 - angle) / 41.4 * 0.25
        elif angle > 30:  # thin
            return (48.6 - angle) / 28.6 * 0.25 + 0.25
        elif angle > 14.5:  # thick
            return (30 - angle) / 15.5 * 0.25 + 0.5
        else:  # very thick
            return 1 - angle / 14.5 * 0.25
        return None

    p_cue_top, p_cue_bottom, cue_length = cue
    cue_ball, object_ball = CO_balls

    ## calculate where to aim based on fractional aiming technique
    p_aim = object_ball[0]  # set initial aim point to object ball center
    # because the angle should be angle between pocket-to-OB line and aim line, but not CTC (center to center) line,
    # we need to iterate several times to get the correct fraction
    for iteration in xrange(3):  # 3 is an arbitrary number here
        dist_pocket2aim_line = zc.calc_triangle_area(
            cue_ball[0], p_aim, pocket) * 2 / zc.euc_dist(cue_ball[0], p_aim)
        dist_pocket2aim_point = zc.euc_dist(p_aim, pocket)
        angle = np.arcsin(dist_pocket2aim_line / dist_pocket2aim_point)
        angle = angle / 1.8  # adjust angle because user is not looking straight down. This adjustment is very rough
        angle = angle / np.pi * 180  # convert to degrees

        fraction = _angle2fraction(angle)
        if pocket[0] < object_ball[0][0]:  # cut to left
            p_aim = (object_ball[0][0] + (1 - fraction) * object_ball[1] * 2,
                     object_ball[0][1])
        else:  # cut to right
            p_aim = (object_ball[0][0] - (1 - fraction) * object_ball[1] * 2,
                     object_ball[0][1])

    ## calculate where is aimed
    p_aimed = ((object_ball[0][1] - cue_ball[0][1]) /
               (cue_ball[0][1] - p_cue_bottom[1]) *
               (cue_ball[0][0] - p_cue_bottom[0]) + cue_ball[0][0],
               object_ball[0][1])

    rtn_msg = {'status': 'success'}
    return (rtn_msg, (p_aim, p_aimed))
Пример #3
0
def find_pingpong(img, img_prev, mask_table, mask_ball_prev, rotation_matrix):
    def get_ball_stat(mask_ball):
        cnt_ball = zc.mask2cnt(mask_ball)
        area = cv2.contourArea(cnt_ball)
        center = cnt_ball.mean(axis = 0)[0]
        center_homo = np.hstack((center, 1)).reshape(3, 1)
        center_rotated = np.dot(rotation_matrix, center_homo)
        return (area, center_rotated)

    rtn_msg = {'status' : 'success'}

    mask_ball = zc.color_inrange(img, 'HSV', H_L = 165, H_U = 25, S_L = 60, V_L = 90, V_U = 240)
    mask_ball, _ = zc.get_small_blobs(mask_ball, max_area = 2300)
    mask_ball, _ = zc.get_big_blobs(mask_ball, min_area = 8)
    mask_ball, counter = zc.get_square_blobs(mask_ball, th_diff = 0.2, th_area = 0.2)

    if counter == 0:
        rtn_msg = {'status' : 'fail', 'message' : "No good color candidate"}
        return (rtn_msg, None)

    cnt_table = zc.mask2cnt(mask_table)
    loc_table_center = zc.get_contour_center(cnt_table)[::-1]
    mask_ball_ontable = np.bitwise_and(mask_ball, mask_table)
    mask_ball_ontable = zc.get_closest_blob(mask_ball_ontable, loc_table_center)

    if mask_ball_ontable is not None: # if any ball on the table, we don't have to rely on previous ball positions
        mask_ball = mask_ball_ontable
        return (rtn_msg, (mask_ball, get_ball_stat(mask_ball)))

    if mask_ball_prev is None: # mask_ball_ontable is already None
        rtn_msg = {'status' : 'fail', 'message' : "Cannot initialize a location of ball"}
        return (rtn_msg, None)

    cnt_ball_prev = zc.mask2cnt(mask_ball_prev)
    loc_ball_prev = zc.get_contour_center(cnt_ball_prev)[::-1]
    mask_ball = zc.get_closest_blob(mask_ball, loc_ball_prev)
    cnt_ball = zc.mask2cnt(mask_ball)
    loc_ball = zc.get_contour_center(cnt_ball)[::-1]
    ball_moved_dist = zc.euc_dist(loc_ball_prev, loc_ball)
    if ball_moved_dist > 110:
        rtn_msg = {'status' : 'fail', 'message' : "Lost track of ball: %d" % ball_moved_dist}
        return (rtn_msg, None)

    return (rtn_msg, (mask_ball, get_ball_stat(mask_ball)))
Пример #4
0
def find_table(img, display_list):
    ## find white border
    DoB = zc.get_DoB(img, 1, 31, method='Average')
    zc.check_and_display('DoB',
                         DoB,
                         display_list,
                         resize_max=config.DISPLAY_MAX_PIXEL,
                         wait_time=config.DISPLAY_WAIT_TIME)
    mask_white = zc.color_inrange(DoB, 'HSV', V_L=10)
    zc.check_and_display_mask('mask_white_raw',
                              img,
                              mask_white,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)

    ## find purple table (roughly)
    #mask_table = zc.color_inrange(img, 'HSV', H_L = 130, H_U = 160, S_L = 50, V_L = 50, V_U = 220)
    mask_ground = zc.color_inrange(img,
                                   'HSV',
                                   H_L=18,
                                   H_U=30,
                                   S_L=75,
                                   S_U=150,
                                   V_L=100,
                                   V_U=255)
    zc.check_and_display_mask('ground',
                              img,
                              mask_ground,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)

    # red car
    mask_red = zc.color_inrange(img, 'HSV', H_L=170, H_U=10, S_L=150)
    mask_ground = np.bitwise_or(mask_ground, mask_red)

    # ceiling
    mask_ceiling = np.zeros((360, 640), dtype=np.uint8)
    mask_ceiling[:40, :] = 255
    mask_ground = np.bitwise_or(mask_ground, mask_ceiling)

    # find the screen
    mask_screen1 = zc.color_inrange(img,
                                    'HSV',
                                    H_L=15,
                                    H_U=45,
                                    S_L=30,
                                    S_U=150,
                                    V_L=40,
                                    V_U=150)
    mask_screen2 = ((img[:, :, 2] - 5) > img[:, :, 0]).astype(np.uint8) * 255
    mask_screen = np.bitwise_or(mask_screen1, mask_screen2)
    mask_screen = np.bitwise_and(np.bitwise_not(mask_ground), mask_screen)
    mask_screen = zc.shrink(mask_screen, 5, iterations=3)
    zc.check_and_display_mask('screen_raw',
                              img,
                              mask_screen,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)

    bool_screen = zc.mask2bool([mask_screen])[0]
    c_pixels = img[bool_screen].astype(np.int8)
    d_pixels = c_pixels[:, 2] - c_pixels[:, 0]
    rb_diff = np.median(d_pixels)
    print rb_diff

    if rb_diff > 20:
        print "Case 1"
        mask_table1 = zc.color_inrange(img,
                                       'HSV',
                                       H_L=0,
                                       H_U=115,
                                       S_U=45,
                                       V_L=35,
                                       V_U=120)
        mask_table2 = zc.color_inrange(img,
                                       'HSV',
                                       H_L=72,
                                       H_U=120,
                                       S_L=20,
                                       S_U=60,
                                       V_L=35,
                                       V_U=150)
        mask_table = np.bitwise_or(mask_table1, mask_table2)
        mask_screen = zc.color_inrange(img,
                                       'HSV',
                                       H_L=15,
                                       H_U=45,
                                       S_L=60,
                                       S_U=150,
                                       V_L=40,
                                       V_U=150)
    elif rb_diff > 15:
        print "Case 2"
        mask_table1 = zc.color_inrange(img,
                                       'HSV',
                                       H_L=0,
                                       H_U=115,
                                       S_U=45,
                                       V_L=35,
                                       V_U=120)
        mask_table2 = zc.color_inrange(img,
                                       'HSV',
                                       H_L=72,
                                       H_U=120,
                                       S_L=20,
                                       S_U=60,
                                       V_L=35,
                                       V_U=150)
        mask_table = np.bitwise_or(mask_table1, mask_table2)
        mask_screen = zc.color_inrange(img,
                                       'HSV',
                                       H_L=15,
                                       H_U=45,
                                       S_L=35,
                                       S_U=150,
                                       V_L=40,
                                       V_U=150)
    else:
        print "Case 3"
        mask_table1 = zc.color_inrange(img,
                                       'HSV',
                                       H_L=0,
                                       H_U=115,
                                       S_U=20,
                                       V_L=35,
                                       V_U=115)
        mask_table2 = zc.color_inrange(img,
                                       'HSV',
                                       H_L=72,
                                       H_U=120,
                                       S_L=20,
                                       S_U=60,
                                       V_L=35,
                                       V_U=150)
        mask_table = np.bitwise_or(mask_table1, mask_table2)
        mask_screen1 = zc.color_inrange(img,
                                        'HSV',
                                        H_L=15,
                                        H_U=45,
                                        S_L=30,
                                        S_U=150,
                                        V_L=40,
                                        V_U=150)
        mask_screen2 = (
            (img[:, :, 2] - 1) > img[:, :, 0]).astype(np.uint8) * 255
        mask_screen = np.bitwise_or(mask_screen1, mask_screen2)

    mask_screen = np.bitwise_and(np.bitwise_not(mask_ground), mask_screen)
    zc.check_and_display_mask('screen',
                              img,
                              mask_screen,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)
    mask_table = np.bitwise_and(np.bitwise_not(mask_screen), mask_table)
    mask_table = np.bitwise_and(np.bitwise_not(zc.shrink(mask_ground, 3)),
                                mask_table)
    mask_table, _ = zc.get_big_blobs(mask_table, min_area=50)
    mask_table = cv2.morphologyEx(mask_table,
                                  cv2.MORPH_CLOSE,
                                  zc.generate_kernel(7, 'circular'),
                                  iterations=1)
    #mask_table, _ = zc.find_largest_CC(mask_table)
    zc.check_and_display_mask('table_purple_raw',
                              img,
                              mask_table,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)
    if mask_table is None:
        rtn_msg = {'status': 'fail', 'message': 'Cannot find table'}
        return (rtn_msg, None)
    #mask_table_convex, _ = zc.make_convex(mask_table.copy(), app_ratio = 0.005)
    #mask_table = np.bitwise_or(mask_table, mask_table_convex)
    mask_table_raw = mask_table.copy()
    zc.check_and_display_mask('table_purple',
                              img,
                              mask_table,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)
    mask_table_convex, _ = zc.make_convex(zc.shrink(mask_table,
                                                    5,
                                                    iterations=5),
                                          app_ratio=0.01)
    mask_table_shrunk = zc.shrink(mask_table_convex, 5, iterations=3)
    zc.check_and_display_mask('table_shrunk',
                              img,
                              mask_table_shrunk,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)

    ## fine tune the purple table based on white border
    mask_white = np.bitwise_and(np.bitwise_not(mask_table_shrunk), mask_white)
    if 'mask_white' in display_list:
        gray = np.float32(mask_white)
        dst = cv2.cornerHarris(gray, 10, 3, 0.04)
        dst = cv2.dilate(dst, None)
        img_white = img.copy()
        img_white[mask_white > 0, :] = [0, 255, 0]
        img_white[dst > 2.4e7] = [0, 0, 255]
        zc.check_and_display('mask_white',
                             img_white,
                             display_list,
                             resize_max=config.DISPLAY_MAX_PIXEL,
                             wait_time=config.DISPLAY_WAIT_TIME)
    #mask_table, _ = zc.make_convex(mask_table, app_ratio = 0.005)
    for i in xrange(15):
        mask_table = zc.expand(mask_table, 3)
        mask_table = np.bitwise_and(np.bitwise_not(mask_white), mask_table)
        mask_table, _ = zc.find_largest_CC(mask_table)
        if mask_table is None:
            rtn_msg = {
                'status': 'fail',
                'message': 'Cannot find table, case 2'
            }
            return (rtn_msg, None)
        if i % 4 == 3:
            mask_table, _ = zc.make_convex(mask_table, app_ratio=0.01)
            #img_display = img.copy()
            #img_display[mask_table > 0, :] = [0, 0, 255]
            #zc.display_image('table%d-b' % i, img_display, resize_max = config.DISPLAY_MAX_PIXEL, wait_time = config.DISPLAY_WAIT_TIME)
            #mask_white = np.bitwise_and(np.bitwise_not(mask_table), mask_white)
            mask_table = np.bitwise_and(np.bitwise_not(mask_white), mask_table)
    mask_table, _ = zc.find_largest_CC(mask_table)
    mask_table, hull_table = zc.make_convex(mask_table, app_ratio=0.01)
    zc.check_and_display_mask('table_purple_fixed',
                              img,
                              mask_table,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)

    ## check if table is big enough
    table_area = cv2.contourArea(hull_table)
    table_area_percentage = float(table_area) / img.shape[0] / img.shape[1]
    if table_area_percentage < 0.06:
        rtn_msg = {
            'status': 'fail',
            'message': "Detected table too small: %f" % table_area_percentage
        }
        return (rtn_msg, None)

    ## find top line of table
    hull_table = np.array(zc.sort_pts(hull_table[:, 0, :], order_first='y'))
    ul = hull_table[0]
    ur = hull_table[1]
    if ul[0] > ur[0]:
        t = ul
        ul = ur
        ur = t
    i = 2
    # the top two points in the hull are probably on the top line, but may not be the corners
    while i < hull_table.shape[0] and hull_table[i, 1] - hull_table[0, 1] < 80:
        pt_tmp = hull_table[i]
        if pt_tmp[0] < ul[0] or pt_tmp[0] > ur[0]:
            # computing the area of the part of triangle that lies inside the table
            triangle = np.vstack([pt_tmp, ul, ur]).astype(np.int32)
            mask_triangle = np.zeros_like(mask_table)
            cv2.drawContours(mask_triangle, [triangle], 0, 255, -1)
            pts = mask_table_raw[mask_triangle.astype(bool)]
            if np.sum(pts == 255) > 10:
                break
            if pt_tmp[0] < ul[0]:
                ul = pt_tmp
            else:
                ur = pt_tmp
            i += 1
        else:
            break
    ul = [int(x) for x in ul]
    ur = [int(x) for x in ur]

    if 'table' in display_list:
        img_table = img.copy()
        img_table[mask_table.astype(bool), :] = [255, 0, 255]
        #cv2.line(img_table, tuple(ul), tuple(ur), [0, 255, 0], 3)
        zc.check_and_display('table',
                             img_table,
                             display_list,
                             resize_max=config.DISPLAY_MAX_PIXEL,
                             wait_time=config.DISPLAY_WAIT_TIME)
    ## sanity checks about table top line detection
    if zc.euc_dist(ul, ur)**2 * 3.1 < table_area:
        rtn_msg = {
            'status':
            'fail',
            'message':
            "Table top line too short: %f, %f" %
            (zc.euc_dist(ul, ur)**2 * 3.1, table_area)
        }
        return (rtn_msg, None)
    if abs(zc.line_angle(ul, ur)) > 0.4:
        rtn_msg = {
            'status': 'fail',
            'message': "Table top line tilted too much"
        }
        return (rtn_msg, None)
    # check if two table sides form a reasonable angle
    mask_table_bottom = mask_table.copy()
    mask_table_bottom[:-30] = 0
    p_left_most = zc.get_edge_point(mask_table_bottom, (-1, 0))
    p_right_most = zc.get_edge_point(mask_table_bottom, (1, 0))
    if p_left_most is None or p_right_most is None:
        rtn_msg = {
            'status': 'fail',
            'message': "Table doesn't occupy bottom part of image"
        }
        return (rtn_msg, None)
    left_side_angle = zc.line_angle(ul, p_left_most)
    right_side_angle = zc.line_angle(ur, p_right_most)
    angle_diff = zc.angle_dist(left_side_angle,
                               right_side_angle,
                               angle_range=math.pi * 2)
    if abs(angle_diff) > 2.0:
        rtn_msg = {
            'status': 'fail',
            'message': "Angle between two side edge not right: %f" % angle_diff
        }
        return (rtn_msg, None)

    if 'table' in display_list:
        img_table = img.copy()
        img_table[mask_table.astype(bool), :] = [255, 0, 255]
        cv2.line(img_table, tuple(ul), tuple(ur), [0, 255, 0], 3)
        zc.check_and_display('table',
                             img_table,
                             display_list,
                             resize_max=config.DISPLAY_MAX_PIXEL,
                             wait_time=config.DISPLAY_WAIT_TIME)

    ## rotate to make opponent upright, use table edge as reference
    pts1 = np.float32(
        [ul, ur, [ul[0] + (ur[1] - ul[1]), ul[1] - (ur[0] - ul[0])]])
    pts2 = np.float32([[0, config.O_IMG_HEIGHT],
                       [config.O_IMG_WIDTH, config.O_IMG_HEIGHT], [0, 0]])
    M = cv2.getAffineTransform(pts1, pts2)
    img[np.bitwise_not(zc.get_mask(img, rtn_type="bool", th=3)), :] = [3, 3, 3]
    img_rotated = cv2.warpAffine(img, M,
                                 (config.O_IMG_WIDTH, config.O_IMG_HEIGHT))

    ## sanity checks about rotated opponent image
    bool_img_rotated_valid = zc.get_mask(img_rotated, rtn_type="bool")
    if float(bool_img_rotated_valid.sum()
             ) / config.O_IMG_WIDTH / config.O_IMG_HEIGHT < 0.6:
        rtn_msg = {
            'status':
            'fail',
            'message':
            "Valid area too small after rotation: %f" %
            (float(bool_img_rotated_valid.sum()) / config.O_IMG_WIDTH /
             config.O_IMG_HEIGHT)
        }
        return (rtn_msg, None)

    rtn_msg = {'status': 'success'}
    return (rtn_msg, (img_rotated, mask_table, M))
Пример #5
0
def find_pingpong(img, img_prev, mask_table, mask_ball_prev, rotation_matrix,
                  display_list):
    def get_ball_stat(mask_ball):
        cnt_ball = zc.mask2cnt(mask_ball)
        area = cv2.contourArea(cnt_ball)
        center = cnt_ball.mean(axis=0)[0]
        center_homo = np.hstack((center, 1)).reshape(3, 1)
        center_rotated = np.dot(rotation_matrix, center_homo)
        return (area, center_rotated)

    rtn_msg = {'status': 'success'}

    mask_ball = zc.color_inrange(img,
                                 'HSV',
                                 H_L=165,
                                 H_U=25,
                                 S_L=65,
                                 V_L=150,
                                 V_U=255)

    mask_screen = find_screen(img, display_list)
    mask_screen, _ = zc.find_largest_CC(mask_screen)
    mask_possible = np.bitwise_or(mask_screen, zc.expand(mask_table, 3))
    mask_ball = np.bitwise_and(mask_ball, mask_possible)

    mask_ball, _ = zc.get_small_blobs(mask_ball, max_area=2300)
    mask_ball, _ = zc.get_big_blobs(mask_ball, min_area=8)
    mask_ball, counter = zc.get_square_blobs(mask_ball,
                                             th_diff=0.2,
                                             th_area=0.2)
    zc.check_and_display_mask('ball_raw',
                              img,
                              mask_ball,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)

    if counter == 0:
        rtn_msg = {'status': 'fail', 'message': "No good color candidate"}
        return (rtn_msg, None)

    cnt_table = zc.mask2cnt(mask_table)
    loc_table_center = zc.get_contour_center(cnt_table)[::-1]
    mask_ball_ontable = np.bitwise_and(mask_ball, mask_table)
    mask_ball_ontable = zc.get_closest_blob(mask_ball_ontable,
                                            loc_table_center)

    if mask_ball_ontable is not None:  # if any ball on the table, we don't have to rely on previous ball positions
        mask_ball = mask_ball_ontable
        zc.check_and_display_mask('ball',
                                  img,
                                  mask_ball,
                                  display_list,
                                  resize_max=config.DISPLAY_MAX_PIXEL,
                                  wait_time=config.DISPLAY_WAIT_TIME)
        return (rtn_msg, (mask_ball, get_ball_stat(mask_ball)))

    if mask_ball_prev is None:  # mask_ball_ontable is already None
        rtn_msg = {
            'status': 'fail',
            'message': "Cannot initialize a location of ball"
        }
        return (rtn_msg, None)

    cnt_ball_prev = zc.mask2cnt(mask_ball_prev)
    loc_ball_prev = zc.get_contour_center(cnt_ball_prev)[::-1]
    mask_ball = zc.get_closest_blob(mask_ball, loc_ball_prev)
    zc.check_and_display_mask('ball',
                              img,
                              mask_ball,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)
    cnt_ball = zc.mask2cnt(mask_ball)
    loc_ball = zc.get_contour_center(cnt_ball)[::-1]
    ball_moved_dist = zc.euc_dist(loc_ball_prev, loc_ball)
    if ball_moved_dist > 110:
        rtn_msg = {
            'status': 'fail',
            'message': "Lost track of ball: %d" % ball_moved_dist
        }
        return (rtn_msg, None)

    return (rtn_msg, (mask_ball, get_ball_stat(mask_ball)))
Пример #6
0
def _detect_cue(img, mask_tables, mask_balls, display_list):
    CUE_MIN_LENGTH = int(float(img.shape[1]) / 640 * 40 + 0.5)
    PARA1 = int(float(img.shape[1]) / 640 * 2 + 0.5)
    mask_blue, mask_bluer, mask_table, mask_table_fat = mask_tables

    ### edges on the table
    #img_table = np.zeros(img.shape, dtype=np.uint8)
    #img_table = cv2.bitwise_and(img, img, dst = img_table, mask = mask_table_convex)
    #bw_table = cv2.cvtColor(img_table, cv2.COLOR_BGR2GRAY)
    #edge_table = cv2.Canny(bw_table, 80, 160)
    #edge_table = zc.expand(edge_table, 2)
    #zc.check_and_display("edge_table", edge_table, display_list, resize_max = config.DISPLAY_MAX_PIXEL, wait_time = config.DISPLAY_WAIT_TIME)

    ### detect cue
    #lines = cv2.HoughLinesP(edge_table, 1, np.pi/180, 30, minLineLength = 70, maxLineGap = 3)
    #if lines is None:
    #    rtn_msg = {'status': 'fail', 'message' : 'Cannot find cue'}
    #    return (rtn_msg, None)
    #lines = lines[0]
    #if 'cue_edge' in display_list:
    #    img_cue = img.copy()
    #    for line in lines:
    #        pt1 = (line[0], line[1])
    #        pt2 = (line[2], line[3])
    #        cv2.line(img_cue, pt1, pt2, (255, 0, 255), 2)
    #    zc.check_and_display("cue_edge", img_cue, display_list, resize_max = config.DISPLAY_MAX_PIXEL, wait_time = config.DISPLAY_WAIT_TIME)

    ## interesting parts on the table (pockets, cue, hand, etc.)
    mask_table_convex, _ = zc.make_convex(mask_table.copy(),
                                          use_approxPolyDp=False)
    zc.check_and_display_mask("table_convex",
                              img,
                              mask_table_convex,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)
    mask_interesting = cv2.subtract(
        cv2.subtract(mask_table_convex, mask_table), mask_bluer)
    mask_interesting = cv2.subtract(mask_interesting, mask_balls)
    mask_interesting = zc.shrink(mask_interesting, PARA1)
    zc.check_and_display_mask("interesting",
                              img,
                              mask_interesting,
                              display_list,
                              resize_max=config.DISPLAY_MAX_PIXEL,
                              wait_time=config.DISPLAY_WAIT_TIME)
    # find the blob with cue (and probably hand)
    # TODO: this may be more robust with find_largest_CC function, in the case of half ball close to the bottom
    mask_cue_hand = zc.get_closest_blob(
        mask_interesting.copy(), (img.shape[0], img.shape[1] / 2),
        min_length=CUE_MIN_LENGTH,
        hierarchy_req='outer')  # cue must be close to the bottom

    ## find cue top
    p_cue_top = zc.get_edge_point(mask_cue_hand, (0, -1))
    if p_cue_top is None:
        rtn_msg = {'status': 'fail', 'message': 'Cannot find cue top'}
        return (rtn_msg, None)

    ## find cue bottom
    # the cue detected initially may not have reached the bottom of the image
    for i in xrange(10):
        mask_cue_hand = zc.expand_with_bound(mask_cue_hand,
                                             cv2.bitwise_not(mask_bluer))
    mask_cue_bottom = mask_cue_hand.copy()
    mask_cue_bottom[:-2, :] = 0
    mask_cue_bottom[:, :p_cue_top[0] - 40] = 0
    mask_cue_bottom[:, p_cue_top[0] + 40:] = 0
    nonzero = np.nonzero(mask_cue_bottom)
    if len(nonzero) < 2 or len(nonzero[0]) == 0:
        rtn_msg = {'status': 'fail', 'message': 'Cannot find cue bottom'}
        return (rtn_msg, None)
    rows, cols = nonzero
    p_cue_bottom = ((np.min(cols) + np.max(cols)) / 2, img.shape[0])

    ## cue info
    cue_length = zc.euc_dist(p_cue_top, p_cue_bottom)
    if 'cue' in display_list:
        img_cue = img.copy()
        img_cue[mask_cue_hand > 0, :] = [0, 255, 255]
        cv2.circle(img_cue, p_cue_top, 3, (255, 0, 255), -1)
        cv2.line(img_cue, p_cue_top, p_cue_bottom, (255, 0, 255), 2)
        zc.display_image("cue",
                         img_cue,
                         resize_max=config.DISPLAY_MAX_PIXEL,
                         wait_time=config.DISPLAY_WAIT_TIME)

    ## skeletonize
    #skeleton_cue_hand = zc.skeletonize(mask_cue_hand)
    rtn_msg = {'status': 'success'}
    return (rtn_msg, (p_cue_top, p_cue_bottom, cue_length))
Пример #7
0
def find_table(img, o_img_height, o_img_width):
    ## find white border
    DoB = zc.get_DoB(img, 1, 31, method = 'Average')
    mask_white = zc.color_inrange(DoB, 'HSV', V_L = 10)

    ## find purple table (roughly)
    mask_table = zc.color_inrange(img, 'HSV', H_L = 130, H_U = 160, S_L = 50, V_L = 50, V_U = 220)
    mask_table, _ = zc.get_big_blobs(mask_table, min_area = 50)
    mask_table = cv2.morphologyEx(mask_table, cv2.MORPH_CLOSE, zc.generate_kernel(7, 'circular'), iterations = 1)
    mask_table, _ = zc.find_largest_CC(mask_table)
    if mask_table is None:
        rtn_msg = {'status': 'fail', 'message' : 'Cannot find table'}
        return (rtn_msg, None)
    mask_table_convex, _ = zc.make_convex(mask_table.copy(), app_ratio = 0.005)
    mask_table = np.bitwise_or(mask_table, mask_table_convex)
    mask_table_raw = mask_table.copy()

    ## fine tune the purple table based on white border
    mask_white = np.bitwise_and(np.bitwise_not(mask_table), mask_white)
    for i in range(15):
        mask_table = zc.expand(mask_table, 3)
        mask_table = np.bitwise_and(np.bitwise_not(mask_white), mask_table)
        if i % 4 == 3:
            mask_table, _ = zc.make_convex(mask_table, app_ratio = 0.01)
            mask_table = np.bitwise_and(np.bitwise_not(mask_white), mask_table)
    mask_table, _ = zc.find_largest_CC(mask_table)
    mask_table, hull_table = zc.make_convex(mask_table, app_ratio = 0.01)

    ## check if table is big enough
    table_area = cv2.contourArea(hull_table)
    table_area_percentage = float(table_area) / img.shape[0] / img.shape[1]
    if table_area_percentage < 0.06:
        rtn_msg = {'status' : 'fail', 'message' : "Detected table too small: %f" % table_area_percentage}
        return (rtn_msg, None)

    ## find top line of table
    hull_table = np.array(zc.sort_pts(hull_table[:,0,:], order_first = 'y'))
    ul = hull_table[0]
    ur = hull_table[1]
    if ul[0] > ur[0]:
        t = ul; ul = ur; ur = t
    i = 2
    # the top two points in the hull are probably on the top line, but may not be the corners
    while i < hull_table.shape[0] and hull_table[i, 1] - hull_table[0, 1] < 80:
        pt_tmp = hull_table[i]
        if pt_tmp[0] < ul[0] or pt_tmp[0] > ur[0]:
            # computing the area of the part of triangle that lies inside the table
            triangle = np.vstack([pt_tmp, ul, ur]).astype(np.int32)
            mask_triangle = np.zeros_like(mask_table)
            cv2.drawContours(mask_triangle, [triangle], 0, 255, -1)
            pts = mask_table_raw[mask_triangle.astype(bool)]
            if np.sum(pts == 255) > 10:
                break
            if pt_tmp[0] < ul[0]:
                ul = pt_tmp
            else:
                ur = pt_tmp
            i += 1
        else:
            break
    ul = [int(x) for x in ul]
    ur = [int(x) for x in ur]

    ## sanity checks about table top line detection
    if zc.euc_dist(ul, ur) ** 2 * 2.5 < table_area:
        rtn_msg = {'status' : 'fail', 'message' : "Table top line too short"}
        return (rtn_msg, None)
    if abs(zc.line_angle(ul, ur)) > 0.4:
        rtn_msg = {'status' : 'fail', 'message' : "Table top line tilted too much"}
        return (rtn_msg, None)
    # check if two table sides form a reasonable angle
    mask_table_bottom = mask_table.copy()
    mask_table_bottom[:-30] = 0
    p_left_most = zc.get_edge_point(mask_table_bottom, (-1, 0))
    p_right_most = zc.get_edge_point(mask_table_bottom, (1, 0))
    if p_left_most is None or p_right_most is None:
        rtn_msg = {'status' : 'fail', 'message' : "Table doesn't occupy bottom part of image"}
        return (rtn_msg, None)
    left_side_angle = zc.line_angle(ul, p_left_most)
    right_side_angle = zc.line_angle(ur, p_right_most)
    angle_diff = zc.angle_dist(left_side_angle, right_side_angle, angle_range = math.pi * 2)
    if abs(angle_diff) > 1.8:
        rtn_msg = {'status' : 'fail', 'message' : "Angle between two side edge not right"}
        return (rtn_msg, None)

    ## rotate to make opponent upright, use table edge as reference
    pts1 = np.float32([ul,ur,[ul[0] + (ur[1] - ul[1]), ul[1] - (ur[0] - ul[0])]])
    pts2 = np.float32([[0, o_img_height], [o_img_width, o_img_height], [0, 0]])
    M = cv2.getAffineTransform(pts1, pts2)
    img[np.bitwise_not(zc.get_mask(img, rtn_type = "bool", th = 3)), :] = [3,3,3]
    img_rotated = cv2.warpAffine(img, M, (o_img_width, o_img_height))

    ## sanity checks about rotated opponent image
    bool_img_rotated_valid = zc.get_mask(img_rotated, rtn_type = "bool")
    if float(bool_img_rotated_valid.sum()) / o_img_width / o_img_height < 0.7:
        rtn_msg = {'status' : 'fail', 'message' : "Valid area too small after rotation"}
        return (rtn_msg, None)

    rtn_msg = {'status' : 'success'}
    return (rtn_msg, (img_rotated, mask_table, M))