Esempio n. 1
0
 def match(self, img_arr: np.ndarray) -> int:
     cursor = self.sqlite_connection.cursor()
     img_arr_resized = image_process.resize(img_arr,
                                            CV_SUPPORT_SERVANT_IMG_SIZE[1],
                                            CV_SUPPORT_SERVANT_IMG_SIZE[0])
     servant_part = img_arr_resized[:CV_SUPPORT_SERVANT_SPLIT_Y, :, :3]
     hsv_servant_part = image_process.rgb_to_hsv(servant_part)
     # querying servant icon database
     if self.cached_icon_meta is None:
         cursor.execute(
             "select id from servant_icon order by id desc limit 1")
         newest_svt_id = cursor.fetchone()[0]
         cursor.execute("select count(1) from servant_icon")
         entries = cursor.fetchone()[0]
         cursor.execute("select id, image_key from servant_icon")
         self.cached_icon_meta = cursor.fetchall()
         logger.info(
             'Finished querying support servant database, %d entries with newest servant id: %d'
             % (entries, newest_svt_id))
     # querying image data
     min_servant_id = 0
     min_abs_err = 0
     for servant_id, image_key in self.cached_icon_meta:
         if image_key not in self.cached_icons:
             cursor.execute(
                 "select image_data from image where image_key = ?",
                 (image_key, ))
             binary_data = cursor.fetchone()[0]
             # clipping alpha and opacity border
             np_image = image_process.imdecode(binary_data)[3:-3, 3:-3, :]
             if np_image.shape[:2] != CV_SUPPORT_SERVANT_IMG_SIZE:
                 if not self.__warn_size_mismatch:
                     self.__warn_size_mismatch = True
                     logger.warning(
                         'The configuration of image size for support servant matching is different from '
                         'database size, performance will decrease: servant id: %d, key: %s'
                         % (servant_id, image_key))
                 np_image = image_process.resize(
                     np_image, CV_SUPPORT_SERVANT_IMG_SIZE[1],
                     CV_SUPPORT_SERVANT_IMG_SIZE[0])
             np_image, alpha = image_process.split_rgb_alpha(np_image)
             hsv_image = image_process.rgb_to_hsv(np_image)
             self.cached_icons[image_key] = np.concatenate(
                 [hsv_image, np.expand_dims(alpha, 2)], axis=2)
         anchor_servant_part = self.cached_icons[
             image_key][:CV_SUPPORT_SERVANT_SPLIT_Y, ...]
         hsv_err = image_process.mean_hsv_diff_err(anchor_servant_part,
                                                   hsv_servant_part, 'hsv',
                                                   'hsv')
         if min_servant_id == 0 or hsv_err < min_abs_err:
             min_servant_id = servant_id
             min_abs_err = hsv_err
             logger.debug('svt_id = %d, key = %s, hsv_err = %f' %
                          (servant_id, image_key, hsv_err))
     cursor.close()
     return min_servant_id
Esempio n. 2
0
    def get_screenshot(self, width: Optional[int] = None, height: Optional[int] = None) -> np.ndarray:
        """
        Get the current screenshot of simulator.

        :param width: the width of reshaped screenshot (in pixels), default: window width
        :param height: the height of reshaped screenshot (in pixels), default: window height
        :return: returns a numpy array shapes [h, w, c] which c = 3 in RGB order
        """
        left, top, right, bottom = win32gui.GetWindowRect(self.handle())
        window_height = bottom - top
        window_width = right - left
        handle_dc = win32gui.GetWindowDC(self.handle())
        new_dc = win32ui.CreateDCFromHandle(handle_dc)
        compat_dc = new_dc.CreateCompatibleDC()

        new_bitmap = win32ui.CreateBitmap()
        new_bitmap.CreateCompatibleBitmap(new_dc, window_width, window_height)
        compat_dc.SelectObject(new_bitmap)
        compat_dc.BitBlt((0, 0), (window_width, window_height), new_dc, (0, 0), win32con.SRCCOPY)
        arr = new_bitmap.GetBitmapBits(True)
        arr = np.fromstring(arr, dtype='uint8').reshape([window_height, window_width, -1])
        new_dc.DeleteDC()
        compat_dc.DeleteDC()
        win32gui.ReleaseDC(self.handle(), handle_dc)
        win32gui.DeleteObject(new_bitmap.GetHandle())
        screenshot = np.flip(arr[..., :3], -1)
        if width is None:
            width = window_width
        if height is None:
            height = window_height
        if width != window_width or height != window_width:
            screenshot = image_process.resize(screenshot, width, height)
        return screenshot
Esempio n. 3
0
 def get_screenshot(self,
                    width: Optional[int] = None,
                    height: Optional[int] = None) -> np.ndarray:
     img = self._get_screenshot_internal()
     width = width or img.shape[1]
     height = height or img.shape[0]
     return image_process.resize(img, width, height)
Esempio n. 4
0
 def _craft_essence_empty_check(img1, img2):
     img1_h = int(img2.shape[1] / img1.shape[1] * img1.shape[0])
     img1 = image_process.resize(img1, img2.shape[1], img1_h)
     img1 = img1[-img2.shape[0]:, ...]
     v = mean_gray_diff_err(img1, img2)
     logger.debug('DEBUG value: empty support craft essence check: mean_gray_diff_err = %f' % v)
     return v < 10
Esempio n. 5
0
 def match(self, img_arr: np.ndarray) -> int:
     cursor = self.sqlite_connection.cursor()
     ratio_thresh = 0.7
     img_arr_resized = image_process.resize(img_arr,
                                            CV_SUPPORT_SERVANT_IMG_SIZE[1],
                                            CV_SUPPORT_SERVANT_IMG_SIZE[0])
     craft_essence_part = img_arr_resized[CV_SUPPORT_SERVANT_SPLIT_Y:-3,
                                          2:-2, :]
     # CACHE ACCESS
     cache_key = self.image_cacher.get(craft_essence_part, None)
     if cache_key is not None:
         return cache_key
     if craft_essence_part in self.image_cacher:
         return self.image_cacher[craft_essence_part]
     if self.cached_icon_meta is None:
         cursor.execute(
             "select id from craft_essence_icon order by id desc limit 1")
         newest_craft_essence_id = cursor.fetchone()[0]
         cursor.execute("select count(1) from craft_essence_icon")
         entries = cursor.fetchone()[0]
         cursor.execute("select id, image_key from craft_essence_icon")
         self.cached_icon_meta = cursor.fetchall()
         logger.info(
             'Finished querying craft essence database, %d entries with newest craft essence id: %d'
             % (entries, newest_craft_essence_id))
     _, target_descriptor = self.sift_detector.detectAndCompute(
         craft_essence_part, None)
     max_matches = 0
     max_craft_essence_id = 0
     for craft_essence_id, image_key in self.cached_icon_meta:
         if image_key not in self.cached_icons:
             cursor.execute(
                 "select descriptors from image_sift_descriptor where image_key = ?",
                 (image_key, ))
             descriptor_blob = cursor.fetchone()[0]
             # keypoint = [deserialize_cv2_keypoint(x) for x in pickle_loads(keypoint_blob)]
             descriptors = pickle_loads(descriptor_blob)
             self.cached_icons[
                 image_key] = descriptors  # {'key_point': keypoint, 'descriptor': descriptors}
         knn_matches = self.matcher.knnMatch(target_descriptor,
                                             self.cached_icons[image_key],
                                             2)
         # knn_matches = self.matcher.match(target_descriptor, self.cached_icons[image_key]['descriptor'])
         good_matches = []
         for m, n in knn_matches:
             if m.distance < ratio_thresh * n.distance:
                 good_matches.append(m)
         len_good_matches = len(good_matches)
         if len_good_matches > max_matches:
             max_matches = len_good_matches
             max_craft_essence_id = craft_essence_id
             logger.debug('craft_essence_id = %d, sift_matches = %d' %
                          (craft_essence_id, len_good_matches))
     cursor.close()
     self.image_cacher[craft_essence_part] = max_craft_essence_id
     return max_craft_essence_id
Esempio n. 6
0
def main():

    # =================== 동영상 테스트 ================================
    fianl_result_array = []
    section_result_array = []

    video_path = 'C:\\Users\\Administrator\\Desktop\\2021_ICT\\library_video2.mp4'
    # video에서 프레임 추출
    frame_images = vp.extract_frame_from_video(video_path)
    # 추출된 프레임에서 글자 영역 찾기
    for i, frame in enumerate(frame_images):
        vp.save_image(
            ct.get_gray(frame),
            'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test\\frame_{}.jpg'.
            format(i))
        final_result = []
        copy = ct.resize(frame)
        cropped_images = ct.image_all_process(copy)[1]
        num = 0
        # 한 프레임의 글자 영역들의 텍스트 추출
        # 자막들
        for con in cropped_images["contours"]:
            # 프레임에서 추출한 영역들 저장
            ct.save_crooped_contours(
                con,
                'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test\\fcontours_{}'
                .format(num))
            # 영역들에서 글자 추출
            result = reco.extract_text(con)
            #         # 추출된 글자 전처리 후 임시 보관
            tmp = txt.text_pre_process(result)
            if (tmp is not None):
                final_result.append(tmp)
            # final_result.append(result)
            num += 1
        # 한 프레임 씩 배열에 저장
        fianl_result_array.append(final_result)

        # 섹션
        # 섹션 영역 저장
        ct.save_crooped_contours(
            cropped_images["section"],
            'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test\\fsection_{}'.
            format(i))
        # 섹션에서 글자 추출
        section = reco.extract_text(cropped_images["section"])
        # 추출된 글자 전처리 후 저장
        section_result_array.append(txt.text_pre_process(section))
        # section_result_array.append(section)

    # 모든 결과 값들 저장 하기
    txt.text_save(
        fianl_result_array, section_result_array,
        'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test\\fnew3_jjan.csv')
Esempio n. 7
0
    def __getitem__(self, idex):
        batch_size = self.batch_size
        image_size = self.image_size
        images_num = self.images_num

        inputs = np.zeros((batch_size, image_size, image_size, 3),
                          dtype=np.uint8)
        if self.inplace:
            image_size *= 4
        outputs = np.zeros((batch_size, image_size, image_size, 3),
                           dtype=np.uint8)

        path_index = idex * batch_size
        num = 0
        while True:
            if path_index >= images_num:
                path_index = path_index % images_num
            path = str(self.image_paths[path_index])
            img = imread(path)

            h, w, _ = img.shape
            mini = min(h, w)
            if mini < image_size:
                img = resize(img, image_size // mini + 1)
                h, w, _ = img.shape

            x = random.randint(0, w - image_size)
            y = random.randint(0, h - image_size)
            img = img[y:y + image_size, x:x + image_size]

            img_LR = LR_image(img, self.de_num, self.inplace)

            inputs[num] = img_LR
            outputs[num] = img

            num += 1
            path_index += 1

            if num >= batch_size:
                break
        return inputs, outputs
Esempio n. 8
0
def _imread_to_screen_size(path):
    return image_process.resize(image_process.imread(path),
                                CV_SCREENSHOT_RESOLUTION_X,
                                CV_SCREENSHOT_RESOLUTION_Y)
Esempio n. 9
0
def main():

    # =================== 동영상 테스트 ================================
    fianl_result_array = []
    section_result_array = []

    video_path = 'C:\\Users\\Administrator\\Desktop\\2021_ICT\\library_video2.mp4'
    # video에서 프레임 추출
    frame_images = vp.extract_frame_from_video(video_path)
    f_len = len(frame_images)
    # 추출된 프레임에서 글자 영역 찾기
    num = 0
    num2 = 0
    for i, frame in enumerate(frame_images):
        vp.save_image(
            frame,
            'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\frame_{}.jpg'.
            format(i))
        final_result = []
        copy = ct.resize(frame)
        cropped_images = ct.image_all_process(copy)
        # 섹션 추출
        ct.save_crooped_contours(
            cropped_images[0]["section"],
            'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\section_{}'.
            format(i))

        for con in cropped_images[0]["contours"]:
            # 프레임에서 추출한 영역들 저장 (origin 로)
            ct.save_crooped_contours(
                con,
                'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\contours_{}_frame_{}_'
                .format(num2, i))
            num2 += 1

        # 한 프레임의 글자 영역들에서 텍스트 추출 과정 시작
        for con in cropped_images[1]["contours"]:
            # 프레임에서 추출한 영역들 저장 (gray-scale로)
            ct.save_crooped_contours(
                ct.get_gradient(con),
                'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\contours_{}_frame_{}_'
                .format(num, i))
            num += 1

    judge_result = judge.get_text_image(num, 'final_new/')

    path_dir = 'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\contours'

    num_text = 0
    num_not_text = 0
    tmp_result = {}
    section = {}

    flie_list = os.listdir(path_dir)
    flie_list.sort()

    for j in range(1, num):
        print(flie_list[j])
        second = flie_list[j].split('_')[-2]

        # 섹션 처리하기
        if (second in section.keys()):
            pass
        else:
            section[second] = vision.image_ocr(
                'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\section_{}.jpg'
                .format(second))

        # 텍스트인 contour 찾아내기
        if judge_result[j] == 'text':
            val = vision.image_ocr(
                'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\contours/'
                + flie_list[j])
            if second in tmp_result:
                tmp_result[second] = [tmp_result[second], val]
            else:
                tmp_result[second] = val
            # print('num: %d, text: %s' %(num_text, val))
            num_text += 1

# 모든 결과 값들 저장 하기
    txt.directory_save(
        tmp_result, section,
        'C:\\Users\\Administrator\\Desktop\\2021_ICT\\test2\\jjan.csv')
Esempio n. 10
0
 def match(self, img_arr: np.ndarray) -> int:
     blur_radius = 2
     cursor = self.sqlite_connection.cursor()
     target_size = CV_COMMAND_CARD_IMG_SIZE
     # import matplotlib.pyplot as plt
     # plt.figure()
     # plt.imshow(img_arr)
     # plt.show()
     img_arr_resized = image_process.resize(img_arr, target_size[1],
                                            target_size[0])
     img_arr_resized, img_alpha = image_process.split_rgb_alpha(
         img_arr_resized)
     # use blur to remove high frequency noise introduced by interpolation, but blurring with alpha channel will
     # produce some weird artifacts on the edge of alpha, same ops to db images
     img_arr_resized = image_process.gauss_blur(img_arr_resized,
                                                blur_radius)
     hsv_img = np.concatenate([
         image_process.rgb_to_hsv(img_arr_resized),
         np.expand_dims(img_alpha, 2)
     ], 2)
     # querying servant icon database
     if self.cached_icon_meta is None:
         cursor.execute(
             "select id from servant_command_card_icon order by id desc limit 1"
         )
         newest_svt_id = cursor.fetchone()[0]
         cursor.execute("select count(1) from servant_command_card_icon")
         entries = cursor.fetchone()[0]
         cursor.execute(
             "select id, image_key from servant_command_card_icon")
         self.cached_icon_meta = cursor.fetchall()
         logger.info(
             'Finished querying servant command card database, %d entries with newest servant id: %d'
             % (entries, newest_svt_id))
     # querying image data
     min_servant_id = 0
     min_err = 0
     for servant_id, image_key in self.cached_icon_meta:
         if image_key not in self.cached_icons:
             cursor.execute(
                 "select image_data, name from image where image_key = ?",
                 (image_key, ))
             binary_data, name = cursor.fetchone()
             # All icon are PNG file with extra alpha channel
             np_image = image_process.imdecode(binary_data)
             # split alpha channel
             assert np_image.shape[
                 -1] == 4, 'Servant Icon should be RGBA channel'
             if np_image.shape[:2] != target_size:
                 if not self.__warn_size_mismatch:
                     self.__warn_size_mismatch = True
                     logger.warning(
                         'The configuration of image size for command card matching is different from '
                         'database size, performance will decrease: servant id: %d, key: %s'
                         % (servant_id, image_key))
                 np_image = image_process.resize(np_image, target_size[1],
                                                 target_size[0])
             np_image = image_process.gauss_blur(np_image, blur_radius)
             np_image, alpha = image_process.split_rgb_alpha(np_image)
             hsv_image = image_process.rgb_to_hsv(np_image)
             # weighted by alpha channel size
             self.cached_icons[image_key] = np.concatenate(
                 [hsv_image, np.expand_dims(alpha, 2)], 2)
         anchor = self.cached_icons[image_key]
         # err_map = image_process.mean_hsv_diff_err_dbg(anchor, hsv_img, 'hsv', 'hsv')
         hsv_err = image_process.mean_hsv_diff_err(anchor, hsv_img, 'hsv',
                                                   'hsv')
         if min_servant_id == 0 or hsv_err < min_err:
             min_servant_id = servant_id
             min_err = hsv_err
             logger.debug('svt_id = %d, key = %s, hsv_err = %f' %
                          (servant_id, image_key, hsv_err))
     cursor.close()
     return min_servant_id
Esempio n. 11
0
def callback(ch, method, properties, body):
    job_id = str(body, 'utf-8')
    logger.info(" [x] Received %s" % job_id)
    resize(job_id + '.png')
    logger.info(" [x] Done")
    ch.basic_ack(delivery_tag=method.delivery_tag)
Esempio n. 12
0
 def _is_in_requesting_friend_ui(self) -> bool:
     img = self.attacher.get_screenshot(CV_SCREENSHOT_RESOLUTION_X, CV_SCREENSHOT_RESOLUTION_Y)
     val = mean_gray_diff_err(image_process.resize(img, self._support_anchor.shape[1],
                                                   self._support_anchor.shape[0]), self._support_anchor)
     logger.debug('DEBUG value friend_ui mean_gray_diff_err = %f' % val)
     return val < 10
    def detect_command_cards(img: np.ndarray) -> List[DispatchedCommandCard]:
        """
        Detect in-battle command card for current turn (attack button must be pressed before calling this method!)

        :param img: In-game screenshot, with shape (h, w, 3) in RGB format or (h, w, 4) in RGBA format (A channel will
         be ignored)
        :return: A list containing command card info
        """
        assert len(img.shape) == 3, 'Invalid image shape, expected RGB format'
        if img.shape[-1] == 4:
            img = img.shape[..., :3]
        ret_list = []

        # 从者头像与指令卡的padding: Top 25px, Left 42px, Right 42px, Bottom 12px
        t = time()
        for card_idx, (x1, x2) in enumerate(
                zip(CV_COMMAND_CARD_X1S, CV_COMMAND_CARD_X2S)):
            command_card = img[CV_COMMAND_CARD_Y:, x1:x2, :3].copy()
            card_type = 0
            target_err = float('inf')
            y_offset = 0
            for idx, (command_card_type, offset) in enumerate(
                    zip(CommandCardDetector._command_card_type_anchor,
                        CV_COMMAND_CARD_TYPE_OFFSET)):
                score = np.empty(CV_COMMAND_CARD_Y_DETECTION_LENGTH, np.float)
                h = command_card_type.shape[0]
                for i in range(CV_COMMAND_CARD_Y_DETECTION_LENGTH):
                    y = CV_COMMAND_CARD_Y_DETECTION_OFFSET + i
                    score[i] = image_process.mean_gray_diff_err(
                        command_card[y:y + h, ...], command_card_type)
                min_score = np.min(score)
                if min_score < target_err:
                    card_type = idx
                    target_err = min_score
                    y_offset = np.argmin(
                        score
                    ) + CV_COMMAND_CARD_Y_DETECTION_OFFSET - offset + CV_COMMAND_CARD_Y
            # Extend pixels
            command_card = img[y_offset - CV_COMMAND_CARD_EXTEND_TOP:y_offset +
                               CV_COMMAND_CARD_EXTEND_BOTTOM +
                               CV_COMMAND_CARD_HEIGHT,
                               x1 - CV_COMMAND_CARD_EXTEND_LEFT:x2 +
                               CV_COMMAND_CARD_EXTEND_RIGHT]
            # support detection
            support_part = command_card[
                CV_COMMAND_CARD_SUPPORT_Y1:CV_COMMAND_CARD_SUPPORT_Y2,
                CV_COMMAND_CARD_SUPPORT_X1:CV_COMMAND_CARD_SUPPORT_X2, :]
            err = image_process.mean_hsv_diff_err(
                support_part, CommandCardDetector._command_card_support_anchor)
            logger.debug(
                'DEBUG value: command card support detection, hsv_err = %f' %
                err)
            is_support = err < CV_COMMAND_CARD_SUPPORT_HSV_THRESHOLD
            # mask command card: concat RGB with extra alpha channel
            alpha = CommandCardDetector._command_card_rev_alpha[card_type]
            if command_card.shape[:2] != alpha.shape[:2]:
                logger.warning(
                    'Width or height of command card rect does not match the alpha mask, alpha mask must be'
                    ' updated to obtain best matching accuracy')
                alpha = image_process.resize(alpha, command_card.shape[1],
                                             command_card.shape[0])
                CommandCardDetector._command_card_rev_alpha[card_type] = alpha
            # composite support mask
            if is_support:
                alpha = alpha.copy()
                alpha[CV_COMMAND_CARD_SUPPORT_Y1:CV_COMMAND_CARD_SUPPORT_Y2,
                      CV_COMMAND_CARD_SUPPORT_X1:CV_COMMAND_CARD_SUPPORT_X2] = \
                    CommandCardDetector._command_card_support_rev_alpha
            command_card = np.concatenate(
                [command_card, np.expand_dims(alpha, 2)], 2)
            servant_id = CommandCardDetector._servant_matcher.match(
                command_card)
            ret_list.append(
                DispatchedCommandCard(servant_id,
                                      CommandCardType(card_type + 1), card_idx,
                                      is_support, 0))
        logger.info('Detected command card data: %s (used %f sec(s))' %
                    (str(ret_list), time() - t))
        return ret_list
Esempio n. 14
0
 def _servant_empty_check(img1, img2):
     v = mean_gray_diff_err(image_process.resize(img1, img2.shape[1], img2.shape[0]), img2)
     logger.debug('DEBUG value: empty support servant check: mean_gray_diff_err = %f' % v)
     return v < 10
Esempio n. 15
0
    def match_support_servant(self, img: np.ndarray, range_list: List[Tuple[int, int]]) -> List[SupportServant]:
        # match servant
        def _servant_empty_check(img1, img2):
            v = mean_gray_diff_err(image_process.resize(img1, img2.shape[1], img2.shape[0]), img2)
            logger.debug('DEBUG value: empty support servant check: mean_gray_diff_err = %f' % v)
            return v < 10
        svt_id, t = self._wrap_call_matcher(self.servant_matcher.match, _servant_empty_check, img,
                                            self._support_empty_img, range_list)
        logger.debug('Detected support servant ID: %s (used %f sec(s))' % (str(svt_id), t))

        # match craft essence
        def _craft_essence_empty_check(img1, img2):
            img1_h = int(img2.shape[1] / img1.shape[1] * img1.shape[0])
            img1 = image_process.resize(img1, img2.shape[1], img1_h)
            img1 = img1[-img2.shape[0]:, ...]
            v = mean_gray_diff_err(img1, img2)
            logger.debug('DEBUG value: empty support craft essence check: mean_gray_diff_err = %f' % v)
            return v < 10
        # todo fix bug when empty servant and non-empty craft essence
        ce_id, t = self._wrap_call_matcher(self.craft_essence_matcher.match, _craft_essence_empty_check, img,
                                           self._support_craft_essence_img, range_list)
        logger.debug('Detected support craft essence ID: %s (used %f sec(s))' % (str(ce_id), t))
        ret_list = [SupportServant(x, y) for x, y in zip(svt_id, ce_id)]
        for i, (y1, y2) in enumerate(range_list):
            # detect craft essence max break state
            if ce_id[i] == 0:
                ret_list[i].craft_essence_max_break = False
            else:
                icon = img[y1:y2, CV_SUPPORT_SERVANT_X1:CV_SUPPORT_SERVANT_X2, :]
                icon = icon[CV_SUPPORT_CRAFT_ESSENCE_MAX_BREAK_Y1:CV_SUPPORT_CRAFT_ESSENCE_MAX_BREAK_Y2,
                            CV_SUPPORT_CRAFT_ESSENCE_MAX_BREAK_X1:CV_SUPPORT_CRAFT_ESSENCE_MAX_BREAK_X2, :]
                if self._support_craft_essence_img_resized is None:
                    # reduce unnecessary resize ops
                    anchor = image_process.resize(self._support_max_break_img, icon.shape[1], icon.shape[0])
                    self._support_craft_essence_img_resized = anchor
                else:
                    anchor = self._support_craft_essence_img_resized
                # err = mean_gray_diff_err(icon, anchor)
                hsv_err = image_process.mean_hsv_diff_err(icon, anchor)
                # logger.debug('DEBUG value: support craft essence max break check: gray_diff_err = %f, hsv_err = %f' %
                #              (err, hsv_err))
                ret_list[i].craft_essence_max_break = hsv_err < CV_SUPPORT_CRAFT_ESSENCE_MAX_BREAK_THRESHOLD

            # skip when support servant is empty
            if svt_id[i] == 0:
                continue
            # detect friend state
            friend_img = img[y1+CV_SUPPORT_FRIEND_DETECT_Y1:y1+CV_SUPPORT_FRIEND_DETECT_Y2,
                             CV_SUPPORT_FRIEND_DETECT_X1:CV_SUPPORT_FRIEND_DETECT_X2, :]
            # omit B channel here
            friend_part_binary = np.greater_equal(np.mean(friend_img[..., :2], 2),
                                                  CV_SUPPORT_FRIEND_DISCRETE_THRESHOLD)
            is_friend = np.mean(friend_part_binary) > CV_SUPPORT_FRIEND_DETECT_THRESHOLD
            ret_list[i].is_friend = is_friend

            # skill level detection
            skill_img = img[y1+CV_SUPPORT_SKILL_BOX_OFFSET_Y:y1+CV_SUPPORT_SKILL_BOX_OFFSET_Y+CV_SUPPORT_SKILL_BOX_SIZE,
                            CV_SUPPORT_SKILL_BOX_OFFSET_X1:CV_SUPPORT_SKILL_BOX_OFFSET_X2, :3].copy()
            gray = np.mean(skill_img, -1)
            vertical_diff = np.zeros_like(gray, dtype=np.float32)
            step_size = CV_SUPPORT_SKILL_V_DIFF_STEP_SIZE  # pixel offset for computing abs difference
            vertical_diff[:-step_size, :] = np.abs(gray[:-step_size, :] - gray[step_size:, :])
            # just use the first several pixels and last several pixels to determine
            edge_size = CV_SUPPORT_SKILL_V_DIFF_EDGE_SIZE
            skills = []
            for j in range(3):
                begin_x = j * (CV_SUPPORT_SKILL_BOX_MARGIN_X + CV_SUPPORT_SKILL_BOX_SIZE)
                v_diff_current_skill = np.mean(vertical_diff[:, begin_x:begin_x+CV_SUPPORT_SKILL_BOX_SIZE], -1)
                max_v_diff = np.maximum(np.max(v_diff_current_skill[:edge_size]),
                                        np.max(v_diff_current_skill[-edge_size:]))
                if max_v_diff > CV_SUPPORT_SKILL_V_DIFF_THRESHOLD:
                    # digit recognition, using SSIM metric, split by S (-> 0) and V (-> 255)
                    current_skill_img = skill_img[:, begin_x:begin_x + CV_SUPPORT_SKILL_BOX_SIZE, :]
                    hsv = image_process.rgb_to_hsv(current_skill_img).astype(np.float32)
                    img_digit_part = (1. - hsv[..., 1] / 255.) * (hsv[..., 2] / 255.)
                    img_digit_part = img_digit_part[30:, 3:30]
                    bin_digits = np.greater_equal(img_digit_part, CV_SUPPORT_SKILL_BINARIZATION_THRESHOLD)
                    digit_segments = image_process.split_image(bin_digits)
                    digits = []
                    for segment in sorted(digit_segments, key=lambda x: (x.max_x + x.min_x)):
                        if 50 < segment.associated_pixels.shape[0] < 150 \
                                and abs(segment.min_y + segment.max_y - 26) <= 3 \
                                and segment.max_x - segment.min_x < 14 <= segment.max_y - segment.min_y:
                            digits.append(self._digit_recognizer.recognize(segment.get_image_segment()))
                    if len(digits) == 2:
                        skill_lvl = digits[0] * 10 + digits[1]
                        if skill_lvl != 10:
                            logger.warning(f'Invalid 2 digits skill level: expected 10, but got {skill_lvl}, set to 10')
                            skill_lvl = 10
                    else:
                        skill_lvl = digits[0]
                        if skill_lvl == 0:
                            logger.warning(f'Invalid 1 digit skill level: expected 1~9, but got {skill_lvl}')
                    skills.append(skill_lvl)
                else:
                    # skill unavailable
                    skills.append(None)
            ret_list[i].skill_level = skills
        logger.info('Detected support servant info: %s' % str(ret_list))
        return ret_list