def parse_map_info(self,
                    frame: Frame) -> Tuple[Optional[str], Optional[str]]:
     map_info_image = self.REGIONS["map_info"].extract_one(frame.image)
     yellow_text = cv2.inRange(
         cv2.cvtColor(map_info_image, cv2.COLOR_BGR2HSV_FULL),
         ((30 / 360) * 255, 0.5 * 255, 0.6 * 255),
         ((45 / 360) * 255, 1.0 * 255, 1.0 * 255),
     )
     yellow_text = cv2.filter2D(yellow_text, -1, np.ones((4, 2)) / (4 * 2))
     yellow_text_left = np.argmax(np.sum(yellow_text, axis=0) / 255 > 4)
     map_image, mode_image = (
         map_info_image[:, :yellow_text_left - 20],
         map_info_image[:, yellow_text_left - 5:],
     )
     map_text = imageops.tesser_ocr(np.min(map_image, axis=2),
                                    whitelist=string.ascii_uppercase + " ",
                                    scale=2,
                                    invert=True)
     mode_text = imageops.tesser_ocr(np.max(mode_image, axis=2),
                                     whitelist=string.ascii_uppercase + " ",
                                     scale=2,
                                     invert=True)
     if len(map_text) < 4 or len(mode_text) < 4:
         logger.warning(
             f"Unexpected map/mode text: {map_text} | {mode_text}")
         return None, None
     else:
         logger.debug(f"Got map={map_text}, mode={mode_text}")
         return map_text, mode_text
示例#2
0
 def _get_placed(self, frame: Frame) -> Optional[int]:
     placed_image = self.REGIONS["squad_placed"].extract_one(
         frame.image).copy()
     cv2.normalize(placed_image, placed_image, 0, 255, cv2.NORM_MINMAX)
     orange = cv2.inRange(
         placed_image,
         np.array(self.PLACED_COLOUR) - 40,
         np.array(self.PLACED_COLOUR) + 40,
     )
     text = imageops.tesser_ocr(orange, whitelist=string.digits + "#")
     if text and text[0] == "#":
         try:
             placed = int(text[1:])
         except ValueError:
             logger.warning(f"Could not parse {text!r} as number")
             return None
         else:
             logger.debug(f"Parsed {text!r} as {placed}")
             if 1 <= placed <= 30:
                 return placed
             else:
                 logger.warning(f"Rejected placed={placed}")
     else:
         logger.warning(f'Rejected placed text {text!r} - did not get "#"')
         return None
示例#3
0
    def _get_bearing(self, frame: Frame,
                     debug_image: Optional[np.ndarray]) -> Optional[int]:
        bearing_image = self.REGIONS["bearing"].extract_one(
            frame.image_yuv[:, :, 0])
        _, bearing_thresh = cv2.threshold(bearing_image, 190, 255,
                                          cv2.THRESH_BINARY)

        if debug_image is not None:
            debug_image[90:90 + bearing_image.shape[0],
                        1020:1020 + bearing_image.shape[1], ] = cv2.cvtColor(
                            bearing_image, cv2.COLOR_GRAY2BGR)
            debug_image[90:90 + bearing_image.shape[0],
                        1100:1100 + bearing_image.shape[1], ] = cv2.cvtColor(
                            bearing_thresh, cv2.COLOR_GRAY2BGR)

        bearing = imageops.tesser_ocr(
            bearing_thresh,
            expected_type=int,
            engine=ocr.tesseract_ttlakes_digits,
            warn_on_fail=False,
        )
        if bearing is None or not 0 <= bearing <= 360:
            logger.debug(f"Got invalid bearing: {bearing}")
            return None
        if bearing is not None:
            logger.debug(f"Got bearing={bearing}")
            return bearing
        else:
            return None
示例#4
0
    def _process_yellowtext(self, image: np.ndarray) -> Optional[int]:
        # mask out only yellow text (digits)
        yellow = cv2.inRange(image, (0, 40, 150), (90, 230, 255))
        yellow = cv2.dilate(yellow, None)
        yellowtext_image = cv2.bitwise_and(
            image, cv2.cvtColor(yellow, cv2.COLOR_GRAY2BGR))
        yellowtext_image_g = np.max(yellowtext_image, axis=2)
        yellowtext_image_g = cv2.erode(yellowtext_image_g, np.ones((2, 2)))

        text = imageops.tesser_ocr(
            yellowtext_image_g,
            engine=imageops.tesseract_lstm,
            scale=4,
            blur=4,
            invert=True,
        )
        otext = text
        text = text.upper()
        for s1, s2 in "|1", "I1", "L1", "O0", "S5", "B6":
            text = text.replace(s1, s2)
        for hashchar in "#H":
            text = text.replace(hashchar, "")
        logger.info(f"Got text={otext} -> {text}")

        try:
            return int(text)
        except ValueError:
            logger.warning(f"Could not parse {text!r} as int")
            return None
 def ocr_region(self, frame: Frame, target_region: str):
     region = self.REGIONS[target_region].extract_one(frame.image)
     gray = 255 - imageops.normalise(np.min(region, axis=2))
     text = imageops.tesser_ocr(
         gray,
         engine=imageops.tesseract_lstm,
     )
     return text
示例#6
0
    def process(self, frame: Frame) -> bool:
        # timer_y = self.REGIONS['timer'].extract_one(frame.image_yuv[:, :, 0])
        # _, timer_y_thresh = cv2.threshold(timer_y, 230, 255, cv2.THRESH_BINARY)

        spike_planted_im = self.REGIONS["spike_planted"].extract_one(frame.image)
        spike_planted_thresh = cv2.inRange(
            spike_planted_im,
            (0, 0, 130),
            (10, 10, 250),
        )
        # cv2.imshow('spike_planted_im', spike_planted_im)
        # cv2.imshow('spike_planted_thresh', spike_planted_thresh)
        # cv2.imshow('SPIKE_PLANTED_TEMPLATE', self.SPIKE_PLANTED_TEMPLATE)
        spike_planted_match = np.max(
            cv2.matchTemplate(
                spike_planted_thresh,
                self.SPIKE_PLANTED_TEMPLATE,
                cv2.TM_CCORR_NORMED,
            )
        )
        logger.debug(f"Spike planted match: {spike_planted_match:.2f}")
        spike_planted = bool(spike_planted_match > self.SPIKE_PLANTED_REQUIRED_MATCH)

        if spike_planted:
            buy_phase = False
        else:
            buy_phase_gray = np.min(self.REGIONS["buy_phase"].extract_one(frame.image), axis=2)
            buy_phase_norm = imageops.normalise(buy_phase_gray, bottom=80)
            # cv2.imshow('buy_phase_norm', buy_phase_norm)
            buy_phase_match = np.max(
                cv2.matchTemplate(buy_phase_norm, self.BUY_PHASE_TEMPLATE, cv2.TM_CCORR_NORMED)
            )
            logger.debug(f"Buy phase match: {buy_phase_match}")
            buy_phase = buy_phase_match > 0.9

        countdown_text = None
        if not spike_planted:
            countdown_gray = np.min(self.REGIONS["timer"].extract_one(frame.image), axis=2)
            countdown_norm = 255 - imageops.normalise(countdown_gray, bottom=80)
            # debugops.test_tesser_engines(
            #     countdown_norm
            # )
            countdown_text = imageops.tesser_ocr(
                countdown_norm,
                # whitelist=string.digits + ':.',
                engine=imageops.tesseract_only,
            )

            if len(countdown_text) > 6:
                countdown_text = None

        frame.valorant.timer = Timer(
            spike_planted=spike_planted,
            buy_phase=buy_phase,
            countdown=countdown_text,
        )
        draw_timer(frame.debug_image, frame.valorant.timer)
        return frame.valorant.timer.valid
    def parse_result_and_map(
            self, frame: Frame) -> Tuple[Optional[str], Optional[str]]:
        result_im = self.REGIONS["result"].extract_one(frame.image)
        gray = np.max(result_im, axis=2)
        # mask out white/gray text (this is map and match time info)
        white_text = ((gray > 100) &
                      (np.ptp(result_im, axis=2) < 20)).astype(np.uint8) * 255
        white_text = cv2.erode(white_text, None)
        white_text = np.sum(white_text, axis=0) / 255
        right = np.argmax(white_text > 2)
        if right > 150:
            right -= 10
            logger.info(
                f"Trimming width of result image {gray.shape[1]} -> {right} to cut white text"
            )
            gray = gray[:, :right]
        else:
            right = gray.shape[1]
        result_text = imageops.tesser_ocr(gray,
                                          whitelist="".join(
                                              set("".join(self.RESULTS))),
                                          invert=True)
        result = textops.matches(result_text, self.RESULTS)
        if np.min(result) > 2:
            logger.warning(
                f"Could not identify result from {result_text!r} (match={np.min(result)})"
            )
            return None, None

        result = self.RESULTS[arrayops.argmin(result)]
        logger.debug(f"Got result {result} from {result_text!r}")
        # TODO: test this with "draw" result
        map_image = self.REGIONS["map_name"].extract_one(frame.image)[:,
                                                                      right:]
        gray = np.min(map_image, axis=2)
        map_text = textops.strip_string(
            imageops.tesser_ocr(gray,
                                whitelist=string.ascii_uppercase + " :'",
                                invert=True,
                                scale=2),
            string.ascii_uppercase + " ",
        )
        logger.debug(f"Parsed map as {map_text}")

        return result, map_text
    def _ocr_playername(self, player_name_image: np.ndarray) -> str:
        # crop out crown
        _, thresh = cv2.threshold(player_name_image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
        mnv, mxv, mnl, mxl = cv2.minMaxLoc(cv2.matchTemplate(thresh, self.CROWN, cv2.TM_CCORR_NORMED))
        if mxv > 0.99:
            player_name_image = player_name_image[:, mxl[0] + self.CROWN.shape[1] :]

        player_name = imageops.tesser_ocr(player_name_image, scale=4)
        return player_name
    def process(self, frame: Frame):
        if frame.apex.apex_play_menu_match:
            return frame.apex.apex_play_menu_match >= self.REQUIRED_MATCH

        y = frame.image_yuv[:, :, 0]

        ready_button = self.REGIONS["ready_button"].extract_one(y)
        t, thresh = cv2.threshold(ready_button, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

        ready_match = np.max(cv2.matchTemplate(thresh, self.READY, cv2.TM_CCORR_NORMED))
        if ready_match >= self.REQUIRED_MATCH:
            cancel_match = 0.0
        else:
            cancel_match = np.max(cv2.matchTemplate(thresh, self.CANCEL, cv2.TM_CCORR_NORMED))
        frame.apex.apex_play_menu_match = round(float(max(ready_match, cancel_match)), 5)
        _draw_buttons_match(frame.debug_image, ready_match, cancel_match, self.REQUIRED_MATCH)

        if ready_match >= self.REQUIRED_MATCH or cancel_match >= self.REQUIRED_MATCH:
            player_name_image = self.REGIONS["player_name"].extract_one(y)
            mate1, mate2 = self.REGIONS["squadmates"].extract(y)

            rank_text_region = self.REGIONS["rank_text"].extract_one(y)
            rank_text = imageops.tesser_ocr(rank_text_region, invert=True, engine=imageops.tesseract_lstm)

            rp_text_region = self.REGIONS["rp_text"].extract_one(y)
            rp_text = imageops.tesser_ocr(rp_text_region, invert=True, engine=imageops.tesseract_lstm)

            frame.apex.apex_play_menu = PlayMenu(
                player_name=self._ocr_playername(player_name_image),
                squadmates=(self._ocr_playername(mate1), self._ocr_playername(mate2)),
                ready=cancel_match >= self.REQUIRED_MATCH,
                rank_text=rank_text,
                rp_text=rp_text,
            )
            self.REGIONS.draw(frame.debug_image)
            _draw_play_menu(frame.debug_image, frame.apex.apex_play_menu)

            return True

        else:
            return False
    def process(self, frame: Frame) -> bool:
        y = frame.image_yuv[:, :, 0]
        tank_region = np.max(self.REGIONS["tank_region"].extract_one(frame.image), axis=2)

        _, thresh = cv2.threshold(tank_region, 100, 255, cv2.THRESH_BINARY)
        # cv2.imshow('thresh1', thresh)

        tank_match_sm = cv2.matchTemplate(thresh, self.TANK_TEMPLATE, cv2.TM_CCORR_NORMED)
        _, match_sm, _, mxloc_sm = cv2.minMaxLoc(tank_match_sm)

        tank_match_lg = cv2.matchTemplate(thresh, self.TANK_LARGE_TEMPLATE, cv2.TM_CCORR_NORMED)
        _, match_lg, _, mxloc_lg = cv2.minMaxLoc(tank_match_lg)

        lock_match = cv2.matchTemplate(thresh, self.LOCK_TEMPLATE, cv2.TM_CCORR_NORMED)
        _, match_lock, _, mxloc_lock = cv2.minMaxLoc(lock_match)

        matched_i = arrayops.argmax([match_sm, match_lg, match_lock])
        # print([match_sm, match_lg, match_lock])
        match = [match_sm, match_lg, match_lock][matched_i]
        matched = ["tank", "tank_lg", "lock"][matched_i]
        best_match_pos = [mxloc_sm, mxloc_lg, mxloc_lock][matched_i]
        match_x = best_match_pos[0]
        # print(matched, match_x)

        frame.overwatch.role_select_match = round(match, 2)

        if match > self.REQUIRED_MATCH:
            grouped = match_x < 150

            logger.debug(
                f"Found match for {matched!r} with match={match:0.3f} ({match_sm:.2f}, {match_lg:.2f}, {match_lock:.2f}), x={match_x} => grouped={grouped}"
            )

            suffix = "_group" if grouped else "_solo"
            frame.overwatch.role_select = RoleSelect(
                placement_text=imageops.tesser_ocr_all(
                    self.REGIONS["placements" + suffix].extract(y), whitelist=string.digits + "/-"
                ),
                sr_text=big_noodle.ocr_all(self.REGIONS["srs" + suffix].extract(y), height=23, invert=True),
                account_name=imageops.tesser_ocr(
                    self.REGIONS["account_name"].extract_one(y), engine=imageops.tesseract_lstm
                ),
                grouped=grouped,
                image=lazy_upload(
                    "role_select", self.REGIONS.blank_out(frame.image), frame.timestamp, selection="last"
                ),
            )
            if frame.debug_image is not None:
                self.REGIONS.draw(frame.debug_image)
            _draw_role_select(frame.debug_image, frame.overwatch.role_select)
            return True

        return False
 def _parse_squads_left_text(self, luma: np.ndarray,
                             has_badge: bool) -> str:
     prefix = "ranked_" if has_badge else ""
     region = self.REGIONS[prefix + "squads_left"].extract_one(luma)
     squads_left_text = imageops.tesser_ocr(region,
                                            engine=imageops.tesseract_lstm,
                                            scale=2,
                                            invert=True).upper()
     squads_left_text = ("".join(c for c in squads_left_text
                                 if c in string.ascii_uppercase +
                                 string.digits + " ").strip().replace(
                                     "B", "6"))
     return squads_left_text
示例#12
0
    def process(self, frame: Frame) -> bool:
        if frame.overwatch.main_menu or frame.overwatch.play_menu:
            return True

        self.REGIONS.draw(frame.debug_image)
        if self.detect_main_menu(frame):
            version_region = self.REGIONS["version"].extract_one(frame.image)
            thresh = imageops.otsu_thresh_lb_fraction(version_region, 0.75)
            version = imageops.tesser_ocr(thresh, whitelist=string.digits + ".-", invert=True, scale=4, blur=2)

            frame.overwatch.main_menu = MainMenu(version=version)

            _draw_main_menu(frame.debug_image, frame.overwatch.main_menu)

            return True

        elif self.detect_play_menu(frame):
            # placement_region = self.REGIONS['placement_matches'].extract_one(frame.image)
            # placement_region = cv2.cvtColor(placement_region, cv2.COLOR_BGR2GRAY)
            # _, thresh = cv2.threshold(placement_region, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
            # match = 1 - float(np.min(cv2.matchTemplate(thresh, self.PLACEMENT_MATCHES_TEMPLATE, cv2.TM_SQDIFF_NORMED)))
            # is_placements = match > self.PLACEMENT_MATCHES_TEMPLATE_THRESHOLD
            #
            # if not is_placements:
            #     group_sr_region = self.REGIONS['group_sr'].extract_one(frame.image)
            #     color_variance = np.mean(np.var(group_sr_region, axis=(0, 1)))
            #     if color_variance < 100:
            #         # only one color - maybe placement
            #         logger.warning(f'Got low color variance ({color_variance:.2f}) - ignoring parsed SR')
            #         sr = None
            #     else:
            #         sr = self.read_sr(frame)
            # else:
            #     sr = None
            #
            # frame.overwatch.play_menu = PlayMenu(
            #     placements=is_placements,
            #     sr=sr,
            #     image=lazy_upload(
            #         'sr_full',
            #         self.REGIONS['sr_full'].extract_one(frame.image),
            #         frame.timestamp
            #     )
            # )
            #
            # _draw_play_menu(frame.debug_image, frame.overwatch.play_menu)

            return True

        return False
 def _get_players_alive(self, luma: np.ndarray,
                        has_badge: bool) -> Optional[int]:
     prefix = "ranked_" if has_badge else ""
     region = self.REGIONS[prefix + "alive"].extract_one(luma)
     players_alive = imageops.tesser_ocr(
         region,
         engine=ocr.tesseract_ttlakes_digits,
         scale=4,
         expected_type=int)
     # shows a '?' if below 10
     if players_alive and 10 <= players_alive <= 60:
         return players_alive
     else:
         logger.warning(f"Rejecting players_alive={players_alive}")
         return None
 def _parse_killed_name(self, frame, row, killed_agent_x) -> Optional[str]:
     killed_name_gray = self._get_region(
         frame.image_yuv,
         row.center[1] - 10,
         row.center[1] + 10,
         row.center[0] + 10,
         killed_agent_x - 10,
         0,
         debug_name="killed_name",
         debug_image=frame.debug_image,
     )
     if killed_name_gray.shape[1] == 0:
         return None
     killed_name_norm = 255 - imageops.normalise(killed_name_gray, min=170)
     return textops.strip_string(
         imageops.tesser_ocr(killed_name_norm,
                             engine=imageops.tesseract_lstm).upper(),
         alphabet=string.ascii_uppercase + string.digits + "# ",
     )
    def _get_kills(self, luma: np.ndarray, mode: str) -> Optional[int]:
        prefix = (mode + "_") if mode else ""
        key = prefix + "kills"
        if key not in self.REGIONS.regions:
            key = "kills"
        region = self.REGIONS[key].extract_one(luma)
        _, kills_thresh = cv2.threshold(region, 0, 255,
                                        cv2.THRESH_BINARY | cv2.THRESH_OTSU)
        kills_thresh = cv2.copyMakeBorder(kills_thresh,
                                          5,
                                          5,
                                          0,
                                          5,
                                          cv2.BORDER_CONSTANT,
                                          value=0)
        match = cv2.matchTemplate(kills_thresh, self.SKULL_TEMPLATE,
                                  cv2.TM_CCORR_NORMED)
        mn, mx, mnloc, mxloc = cv2.minMaxLoc(match)
        if mx > 0.9:
            kills_image = region[:, mxloc[0] + self.SKULL_TEMPLATE.shape[1]:]
            # cv2.imshow('kills', cv2.resize(kills_image, (100, 100)))

            kills_text = (imageops.tesser_ocr(kills_image,
                                              engine=imageops.tesseract_lstm,
                                              scale=2,
                                              invert=True).upper().strip())
            for s1, s2 in self.SUBS:
                kills_text = kills_text.replace(s1, s2)
            try:
                kills = int(kills_text)
                if 0 < kills <= 50:
                    return kills
                else:
                    logger.warning(f"Rejecting kills={kills}")
                    return None
            except ValueError:
                logger.warning(f"Cannot parse kills={kills_text!r} as int")
                return None
        else:
            return None
示例#16
0
    def _parse_xp_breakdown(self, y: np.ndarray) -> XPStats:
        xp_breakdown_image = self.REGIONS["xp_fields"].extract_one(y)
        xp_breakdown_image = cv2.adaptiveThreshold(
            xp_breakdown_image,
            255,
            cv2.ADAPTIVE_THRESH_MEAN_C,
            cv2.THRESH_BINARY_INV,
            63,
            -30,
        )
        lines = imageops.tesser_ocr(
            xp_breakdown_image,
            whitelist=string.ascii_letters + string.digits + "() \n",
            engine=imageops.tesseract_lstm_multiline,
        )
        for s1, s2 in self.SUBS:
            lines = lines.replace(s1, s2)

        xp_stats = XPStats()
        for line in lines.splitlines():
            stat_name, stat_value = self._parse_stat(line)
            if stat_name == "Won Match":
                xp_stats.won = True
            elif stat_name == "Top 3 Finish":
                xp_stats.top3_finish = True
            elif stat_name and stat_value is not None:
                # require stat value parsed correctly
                if stat_name == "Time Survived":
                    xp_stats.time_survived = mmss_to_seconds(stat_value)
                elif stat_name == "Kills":
                    xp_stats.kills = stat_value
                elif stat_name == "Damage Done":
                    xp_stats.damage_done = stat_value
                elif stat_name == "Revive Ally":
                    xp_stats.revive_ally = stat_value
                elif stat_name == "Respawn Ally":
                    xp_stats.respawn_ally = stat_value
        return xp_stats
示例#17
0
    def _parse_score_report(self, y: np.ndarray) -> ScoreReport:
        rp_report_image = self.REGIONS["rp_fields"].extract_one(y)

        lines = []
        for line in range(3):
            line_im = rp_report_image[line * 40 + 5:(line + 1) * 40 - 7, 5:]
            lines.append(
                imageops.tesser_ocr(line_im,
                                    engine=imageops.tesseract_lstm,
                                    invert=True,
                                    scale=2))

        score_report = ScoreReport()
        for line in lines:
            valid = False
            if ":" in line:
                stat_name, stat_value = line.lower().replace(" ",
                                                             "").split(":", 1)
                if stat_name == "entrycost":
                    score_report.entry_rank = stat_value.lower()
                    valid = True
                elif stat_name == "kills":
                    try:
                        score_report.kills = int(stat_value.replace("o", "0"))
                    except ValueError:
                        logger.warning(
                            f'Could not parse Score Report > kills: {stat_value!r}" as int'
                        )
                    else:
                        valid = True
                elif stat_name == "matchplacement":
                    stat_value = stat_value.replace("#", "")
                    try:
                        score_report.placement = int(
                            stat_value.replace("o", "0").split("/", 1)[0])
                    except ValueError:
                        logger.warning(
                            f'Could not parse Score Report > placement: {stat_value!r}" as placement'
                        )
                    else:
                        valid = True
            if not valid:
                logger.warning(f"Unknown line in score report: {line!r}")

        score_adjustment_image = self.REGIONS["score_adjustment"].extract_one(
            y)
        score_adjustment_text = imageops.tesser_ocr(
            score_adjustment_image,
            engine=imageops.tesseract_lstm,
            invert=True,
            scale=1)
        score_adjustment_text_strip = (textops.strip_string(
            score_adjustment_text, alphabet=string.digits + "RP+-").replace(
                "RP", "").replace("+", "").replace("-", ""))
        try:
            score_report.rp_adjustment = int(score_adjustment_text_strip)
        except ValueError:
            logger.warning(
                f'Could not parse Score Report > score adjustment: {score_adjustment_text!r}" as valid adjustment'
            )

        current_rp_image = self.REGIONS["current_rp"].extract_one(y)
        current_rp_text = imageops.tesser_ocr(current_rp_image,
                                              engine=imageops.tesseract_lstm,
                                              invert=True,
                                              scale=1)
        current_rp_text_strip = textops.strip_string(current_rp_text,
                                                     alphabet=string.digits +
                                                     "RP").replace("RP", "")
        try:
            score_report.current_rp = int(current_rp_text_strip)
        except ValueError:
            logger.warning(
                f'Could not parse Score Report > current RP: {current_rp_text!r}" as valid RP'
            )

        return score_report
    def process(self, frame: Frame):
        y = cv2.cvtColor(frame.image, cv2.COLOR_BGR2YUV)[:, :, 0]

        # The text moves depending on normal or elite queue
        # Look for the "head" template showing players alive
        head_region = np.max(self.REGIONS["head_region"].extract_one(
            frame.image),
                             axis=2)
        _, head_thresh = cv2.threshold(head_region, 200, 255,
                                       cv2.THRESH_BINARY)
        head_match = cv2.matchTemplate(head_thresh, self.HEAD_TEMPLATE,
                                       cv2.TM_CCORR_NORMED)
        mnv, mxv, mnl, mxl = cv2.minMaxLoc(head_match)
        frame.apex.match_status_match = round(float(mxv), 2)
        if mxv < 0.9:
            return False

        badge_image = self.REGIONS["rank_badge"].extract_one(frame.image)
        # cv2.imshow('rank_badge_image', badge_image)
        # print(rank_badge_matches)

        # 90 for unranked, 15 for ranked
        has_badge = mxl[0] < 30

        mode = None
        if has_badge:
            mode_badge_matches = self._parse_badge(badge_image,
                                                   self.MODE_TEMPLATES)
            if mode_badge_matches[0] < 750:
                mode = "duos"

        squads_left_text = self._parse_squads_left_text(y, has_badge)
        squads_left = self._get_squads_left(squads_left_text, mode)
        if not squads_left:
            mode = "solos"
            solos_players_left = self._get_squads_left(squads_left_text, mode)
        else:
            solos_players_left = None

        if not mode and has_badge:
            mode = "ranked"

        if mode == "ranked":
            rank_badge_matches = self._parse_badge(badge_image,
                                                   self.RANK_TEMPLATES)
            rank_text_image = self.REGIONS["rank_text"].extract_one(
                frame.image_yuv[:, :, 0])
            rank_text = imageops.tesser_ocr(
                rank_text_image,
                whitelist="IV",
                scale=3,
                invert=True,
                engine=imageops.tesseract_only,
            )
            rp_text_image = self.REGIONS["ranked_rp"].extract_one(
                frame.image_yuv[:, :, 0])
            rp_text = imageops.tesser_ocr(
                rp_text_image,
                whitelist=string.digits + "+-RP",
                scale=3,
                invert=True,
                engine=imageops.tesseract_only,
            )
        else:
            rank_badge_matches = None
            rank_text = None
            rp_text = None

        frame.apex.match_status = MatchStatus(
            squads_left=squads_left,
            players_alive=self._get_players_alive(y, has_badge)
            if squads_left and squads_left > 4 else None,
            kills=self._get_kills(y, mode),
            ranked=mode == "ranked",
            rank_badge_matches=rank_badge_matches,
            rank_text=rank_text,
            rp_text=rp_text,
            solos_players_left=solos_players_left,
            mode=mode,
        )
        self.REGIONS.draw(frame.debug_image)
        _draw_status(frame.debug_image, frame.apex.match_status)
        return True
    def process(self, frame: Frame) -> bool:
        result_y = self.REGIONS["result"].extract_one(frame.image_yuv[:, :, 0])
        _, result_thresh = cv2.threshold(result_y, 220, 255, cv2.THRESH_BINARY)
        match, result = imageops.match_templates(
            result_thresh,
            self.RESULTS,
            cv2.TM_CCORR_NORMED,
            required_match=self.RESULT_TEMPLATE_REQUIRED_MATCH,
            previous_match_context=(self.__class__.__name__, "result"),
        )

        if match > self.RESULT_TEMPLATE_REQUIRED_MATCH:
            logger.debug(f"Round result is {result} with match={match}")

            score_ims = self.REGIONS["scores"].extract(frame.image)
            score_gray = [
                imageops.normalise(np.max(im, axis=2)) for im in score_ims
            ]
            scores = imageops.tesser_ocr_all(
                score_gray,
                expected_type=int,
                engine=din_next_regular_digits,
                invert=True,
            )
            logger.debug(f"Round score is {scores}")

            frame.valorant.postgame = Postgame(
                victory=result == "victory",
                score=(scores[0], scores[1]),
                map=imageops.ocr_region(frame, self.REGIONS, "map"),
                game_mode=imageops.ocr_region(frame, self.REGIONS,
                                              "game_mode"),
                image=lazy_upload("postgame",
                                  self.REGIONS.blank_out(frame.image),
                                  frame.timestamp),
            )
            draw_postgame(frame.debug_image, frame.valorant.postgame)

            sort_mode_gray = np.min(
                self.SCOREBOARD_REGIONS["scoreboard_sort_mode"].extract_one(
                    frame.image),
                axis=2)
            sort_mode_filt = 255 - imageops.normalise(sort_mode_gray,
                                                      bottom=75)
            # cv2.imshow('sort_mode_gray', sort_mode_gray)
            sort_mode = imageops.tesser_ocr(sort_mode_filt,
                                            engine=imageops.tesseract_lstm)

            sort_mode_match = max([
                levenshtein.ratio(
                    textops.strip_string(sort_mode).upper(), expected)
                for expected in self.SCOREBOARD_SORT_MODES
            ])
            logger.debug(
                f"Got scoreboard sort mode: {sort_mode!r} match={sort_mode_match:.2f}"
            )

            if sort_mode_match > 0.75:
                frame.valorant.scoreboard = self._parse_scoreboard(frame)
                draw_scoreboard(frame.debug_image, frame.valorant.scoreboard)

            return True

        return False
    def _parse_scoreboard(self, frame: Frame) -> Scoreboard:
        agent_images = self.SCOREBOARD_REGIONS["agents"].extract(frame.image)

        name_images = self.SCOREBOARD_REGIONS["names"].extract(frame.image)

        stat_images = self.SCOREBOARD_REGIONS["stats"].extract(frame.image)
        stat_images_filt = [
            self._filter_statrow_image(im) for im in stat_images
        ]
        stat_image_rows = [
            stat_images_filt[r * 8:(r + 1) * 8] for r in range(10)
        ]

        # cv2.imshow(
        #     'stats',
        #     np.vstack([
        #         np.hstack([self._filter_statrow_image(n)] + r)
        #         for n, r in zip(name_images, stat_image_rows)
        #     ])
        # )

        stats = []
        for i, (agent_im, name_im, stat_row) in enumerate(
                zip(agent_images, name_images, stat_image_rows)):
            agent_match, agent = imageops.match_templates(
                agent_im,
                self.AGENT_TEMPLATES,
                method=cv2.TM_SQDIFF,
                required_match=self.AGENT_TEMPLATE_REQUIRED_MATCH,
                use_masks=True,
                previous_match_context=(self.__class__.__name__, "scoreboard",
                                        "agent", i),
            )
            if agent_match > self.AGENT_TEMPLATE_REQUIRED_MATCH:
                agent = None

            row_bg = name_im[np.max(name_im, axis=2) < 200]
            row_color = np.median(row_bg, axis=0).astype(np.int)

            # cv2.imshow('name', self._filter_statrow_image(name_im))
            # cv2.waitKey(0)
            stat = PlayerStats(
                agent,
                imageops.tesser_ocr(
                    self._filter_statrow_image(name_im),
                    engine=imageops.tesseract_lstm,
                ),
                row_color[0] > row_color[2],
                *imageops.tesser_ocr_all(
                    stat_row,
                    expected_type=int,
                    engine=din_next_regular_digits,
                ),
            )
            stats.append(stat)
            logger.debug(
                f"Got player stats: {stat} - agent match={agent_match:.2f}, row colour={tuple(row_color)}"
            )

        return Scoreboard(
            stats,
            image=lazy_upload("scoreboard",
                              self.SCOREBOARD_REGIONS.blank_out(frame.image),
                              frame.timestamp),
        )
示例#21
0
    def _process_player_stats(self,
                              y: np.ndarray,
                              duos: bool = False,
                              shunt: int = 0) -> Tuple[PlayerStats, ...]:
        name_images = self.REGIONS["names"].shunt(x=shunt).extract(y)
        names = []
        for im in name_images:
            # self._mask_components_touching_edges(im)
            im = 255 - cv2.bitwise_and(
                im,
                cv2.dilate(
                    cv2.threshold(im, 0, 255,
                                  cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1],
                    None,
                ),
            )
            im = cv2.resize(im, (0, 0), fx=2, fy=2)
            im = cv2.GaussianBlur(im, (0, 0), 1)

            name = imageops.tesser_ocr(
                im,
                engine=imageops.tesseract_lstm,
            ).replace(" ", "")
            match = np.mean(imageops.tesseract_lstm.AllWordConfidences())
            logger.info(f"Got name {name!r} ~ {match:1.2f}")
            if match < 0.75:
                name = imageops.tesser_ocr(
                    im,
                    engine=imageops.tesseract_only,
                )
                logger.info(f"Using {name!r} instead")
            names.append(name)

        stat_images = self.REGIONS["stats"].shunt(x=shunt).extract(y)

        # for im in stat_images:
        #     self._mask_components_touching_edges(im)

        stats = imageops.tesser_ocr_all(
            stat_images,
            engine=ocr.tesseract_ttlakes_digits_specials,
        )

        for i in range(len(stats)):
            value = stats[i]
            logger.debug(f"Got stat {i}: {value!r}")
            if value:
                value = value.lower().replace(" ", "")
                for c1, c2 in "l1", "i1", "o0", (":", ""):
                    value = value.replace(c1, c2)
                value = textops.strip_string(value, string.digits + "/")
            if i < 3:
                try:
                    stats[i] = tuple([int(v) for v in value.split("/")])
                except ValueError as e:
                    logger.warning(f'Could not parse {value!r} as 3 ints" {e}')
                    stats[i] = None
            elif 6 <= i <= 8:
                # survival time
                if stats[i] is not None:
                    try:
                        seconds = int(value)
                    except ValueError as e:
                        logger.warning(
                            f'Could not parse "{stats[i]}" as int: {e}')
                        seconds = None
                    else:
                        seconds = mmss_to_seconds(seconds)
                        logger.info(f"MM:SS {stats[i]} -> {seconds}")
                    stats[i] = seconds
            else:
                try:
                    stats[i] = int(value)
                except ValueError as e:
                    logger.warning(f'Could not parse {value!r} as int" {e}')
                    stats[i] = None

        # typing: ignore
        # noinspection PyTypeChecker
        count = 3 if not duos else 2
        r = tuple([PlayerStats(names[i], *stats[i::3]) for i in range(count)])

        for s in r:
            if not s.kills:
                pass
            elif len(s.kills) == 3:
                s.assists = s.kills[1]
                s.knocks = s.kills[2]
                s.kills = s.kills[0]
            else:
                s.kills = s.kills[0]

        logger.info(f"Got {pprint.pformat(r)}")
        return r
示例#22
0
    def update(_: int) -> None:
        scale = max(1, cv2.getTrackbarPos("scale", "ocr"))
        if scale < 5:
            scale = 1 / (5 - scale)
        else:
            scale = scale - 3
        blur = max(0, cv2.getTrackbarPos("blur", "ocr") / 10)
        invert = cv2.getTrackbarPos("invert", "ocr")

        print(scale, blur, invert)
        table = []
        if "engine" not in kwargs:
            for name, engine in [
                ("tesseract_lstm", imageops.tesseract_lstm),
                ("tesseract_futura", imageops.tesseract_futura),
                ("tesseract_only", imageops.tesseract_only),
                ("tesseract_ttlakes_digits",
                 overtrack_cv.games.apex.ocr.tesseract_ttlakes_digits),
                ("tesseract_ttlakes",
                 overtrack_cv.games.apex.ocr.tesseract_ttlakes),
                ("tesseract_ttlakes_medium",
                 overtrack_cv.games.apex.ocr.tesseract_ttlakes_medium),
                ("tesseract_arame",
                 overtrack_cv.games.apex.ocr.tesseract_arame),
                ("tesseract_mensura",
                 overtrack_cv.games.apex.ocr.tesseract_mensura),
                (
                    "tesseract_ttlakes_digits_specials",
                    overtrack_cv.games.apex.ocr.
                    tesseract_ttlakes_digits_specials,
                ),
                (
                    "tesseract_ttlakes_bold_digits_specials",
                    overtrack_cv.games.apex.ocr.
                    tesseract_ttlakes_bold_digits_specials,
                ),
            ]:
                imageops.tesser_ocr(im,
                                    scale=scale,
                                    blur=blur,
                                    invert=bool(invert),
                                    engine=engine,
                                    **kwargs)
                table.append(
                    (name, engine.GetUTF8Text(), engine.AllWordConfidences()))
        else:
            engine = kwargs["engine"]
            imageops.tesser_ocr(
                im,
                scale=scale,
                blur=blur,
                invert=bool(invert),
                engine=engine,
                **{k: v
                   for (k, v) in kwargs.items() if k != "engine"},
            )
            table.append(
                ("", engine.GetUTF8Text(), engine.AllWordConfidences()))
        import tabulate

        print(tabulate.tabulate(table))
        print()
    def process(self, frame: Frame):
        y = frame.image_yuv[:, :, 0]

        your_squad_image = self.REGIONS["your_squad"].extract_one(y)
        t, thresh = cv2.threshold(your_squad_image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

        match, key = imageops.match_templates(thresh, self.TEMPLATES, cv2.TM_CCORR_NORMED, self.REQUIRED_MATCH)
        frame.apex.your_squad_match = round(match, 4)
        if match < self.REQUIRED_MATCH:
            return False

        name1_trios = np.min(self.REGIONS["names"].extract_one(frame.image), axis=2)
        name1_duos = np.min(self.REGIONS["names_duos"].extract_one(frame.image), axis=2)
        # name1_thresh_value = max(np.max(name1_duos), np.max(name1_trios)) * 0.95
        name1_thresh_value = 240
        # logger.debug(f"Name thresh: {name1_thresh_value}")

        name1_trios_score = int(np.sum(name1_trios > name1_thresh_value))
        name1_duos_score = int(np.sum(name1_duos > name1_thresh_value))
        logger.debug(f"Trios name score: {name1_trios_score} vs duos name score: {name1_duos_score}")

        # self.duos = name1_duos_score and name1_duos_score > name1_trios_score
        self.duos = name1_trios_score < 100
        logger.info(f"Using duos={self.duos}")

        if key == "your_squad":
            names_region_name = "names_duos" if self.duos else "names"
            names = imageops.tesser_ocr_all(
                self.REGIONS[names_region_name].extract(y),
                engine=imageops.tesseract_lstm,
                invert=True,
            )
            frame.apex.your_squad = YourSquad(
                tuple(self._to_name(n) for n in names),
                mode="duos" if self.duos else None,
                images=lazy_upload(
                    "your_squad",
                    np.hstack(self.REGIONS[names_region_name].extract(frame.image)),
                    frame.timestamp,
                ),
            )
            self.REGIONS.draw(frame.debug_image)
            _draw_squad(frame.debug_image, frame.apex.your_squad)
        elif key == "your_selection":
            frame.apex.your_selection = YourSelection(
                name=self._to_name(
                    imageops.tesser_ocr(
                        self.REGIONS["names"].extract(y)[1],
                        engine=imageops.tesseract_lstm,
                        invert=True,
                    )
                ),
                image=lazy_upload(
                    "your_selection",
                    self.REGIONS["names"].extract(frame.image)[1],
                    frame.timestamp,
                ),
            )
            self.REGIONS.draw(frame.debug_image)
            _draw_squad(frame.debug_image, frame.apex.your_selection)
        elif key == "champion_squad":
            names_region_name = "names_duos" if self.duos else "names"
            names = imageops.tesser_ocr_all(
                self.REGIONS[names_region_name].extract(y),
                engine=imageops.tesseract_lstm,
                invert=True,
            )
            frame.apex.champion_squad = ChampionSquad(
                tuple(self._to_name(n) for n in names),
                mode="duos" if self.duos else None,
                images=lazy_upload(
                    "champion_squad",
                    np.hstack(self.REGIONS[names_region_name].extract(frame.image)),
                    frame.timestamp,
                ),
            )
            self.REGIONS.draw(frame.debug_image)
            _draw_squad(frame.debug_image, frame.apex.champion_squad)

        return True