From b0fd3f96f6b59c663d2230ecb7e78cdd79abfc80 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 17 Aug 2026 16:11:38 -0500 Subject: [PATCH] handle different resolutions, let test program run without controller --- .../PokemonFRLG_BattleLevelUpReader.cpp | 22 +---- .../Inference/PokemonFRLG_DigitReader.cpp | 23 +++-- .../PokemonFRLG_PartyLevelUpReader.cpp | 22 +---- .../Inference/PokemonFRLG_StatsReader.cpp | 4 +- .../TestPrograms/PokemonFRLG_ReadStats.cpp | 89 ++++++++++--------- .../TestPrograms/PokemonFRLG_ReadStats.h | 14 ++- 6 files changed, 79 insertions(+), 95 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp index 770ba43357..8e8094f17b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp @@ -54,27 +54,7 @@ StatReads BattleLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& auto read_stat = [&](const ImageFloatBox &box, const std::string &name){ ImageViewRGB32 stat_region = extract_box_reference(game_screen, box); - - if (GlobalSettings::instance().OCR_LIBRARY != OcrLibrary::PADDLE_OCR){ - // Tesseract-free path: waterfill segmentation + template matching - // against the PokemonFRLG/Digits/0-9.png templates. - return read_digits_waterfill_template(logger, stat_region); - } - - // PaddleOCR path (original): preprocess then per-digit waterfill OCR. - // Dark text [0..190] -> black. Threshold at 190 captures the - // blurred gap pixels between segments, making bridges thicker. - // Not higher than 190 to avoid capturing yellow bg edge noise. - ImageRGB32 ocr_ready = preprocess_for_ocr( - stat_region, name, 7, 2, true, - combine_rgb(0, 0, 0), combine_rgb(190, 190, 190) - ); - - // Waterfill isolates each digit -> per-char SINGLE_CHAR OCR. - return OCR::read_number_waterfill( - logger, ocr_ready, 0xff000000, - 0xff808080 - ); + return read_digits_waterfill_template(logger, stat_region); }; StatReads stats; diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp index 86089d29a1..91a8880e83 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp @@ -61,11 +61,22 @@ ImageRGB32 preprocess_for_ocr( cv::Mat src = to_OpenCV_ref(image); - // Step 1: Gaussian blur at NATIVE resolution with 5x5 kernel. + // Step 0: Rescale the image to match the expected height at 1080p + // so that the Gaussian blur size works across resolutions + double scale_factor = image.height() / 69; + int new_w = static_cast((image.width()) * scale_factor); + int new_h = static_cast((image.height()) * scale_factor); + cv::Mat rescaled; + cv::resize( + src, rescaled, cv::Size(new_w, new_h), 0, 0, + cv::INTER_LINEAR + ); + + // Step 1: Gaussian blur at with 5x5 kernel. // The 5x5 kernel reaches 2 pixels away (vs 1px for 3x3), bridging // wider gaps in the seven-segment font. Two passes for heavy smoothing. cv::Mat blurred_native; - src.copyTo(blurred_native); + rescaled.copyTo(blurred_native); if (blur_kernel_size > 0 && blur_passes > 0){ for (int i = 0; i < blur_passes; i++){ cv::GaussianBlur( @@ -75,7 +86,7 @@ ImageRGB32 preprocess_for_ocr( } } - // Save blurred at native res + // Save blurred at rescaled res ImageRGB32 blurred_native_img(blurred_native.cols, blurred_native.rows); blurred_native.copyTo(to_OpenCV_ref(blurred_native_img)); if (save_debug_images){ @@ -83,9 +94,9 @@ ImageRGB32 preprocess_for_ocr( } // Step 2: Smooth upscale 4x with bilinear interpolation. - int scale_factor = 4; - int new_w = static_cast(image.width()) * scale_factor; - int new_h = static_cast(image.height()) * scale_factor; + scale_factor = 4; + new_w = static_cast(image.width()) * scale_factor; + new_h = static_cast(image.height()) * scale_factor; cv::Mat resized; cv::resize( blurred_native, resized, cv::Size(new_w, new_h), 0, 0, diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp index bc4f5f0e46..4ad3fa9a21 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp @@ -54,27 +54,7 @@ StatReads PartyLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& f auto read_stat = [&](const ImageFloatBox &box, const std::string &name){ ImageViewRGB32 stat_region = extract_box_reference(game_screen, box); - - if (GlobalSettings::instance().OCR_LIBRARY != OcrLibrary::PADDLE_OCR){ - // Tesseract-free path: waterfill segmentation + template matching - // against the PokemonFRLG/Digits/0-9.png templates. - return read_digits_waterfill_template(logger, stat_region); - } - - // PaddleOCR path (original): preprocess then per-digit waterfill OCR. - // Dark text [0..190] -> black. Threshold at 190 captures the - // blurred gap pixels between segments, making bridges thicker. - // Not higher than 190 to avoid capturing yellow bg edge noise. - ImageRGB32 ocr_ready = preprocess_for_ocr( - stat_region, name, 7, 2, true, - combine_rgb(0, 0, 0), combine_rgb(190, 190, 190) - ); - - // Waterfill isolates each digit -> per-char SINGLE_CHAR OCR. - return OCR::read_number_waterfill( - logger, ocr_ready, 0xff000000, - 0xff808080 - ); + return read_digits_waterfill_template(logger, stat_region); }; StatReads stats; diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index 8c2aba5fed..b1c3a7b97e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -332,8 +332,8 @@ void StatsReader::read_page1( extract_box_reference(frame, GameSettings::instance().GAME_BOX); bool success = read_name(logger, language, game_screen, stats, subset, save_debug_images); - success = success && read_level(logger, language, game_screen, stats, subset, save_debug_images); - success = success && read_nature(logger, language, game_screen, stats, subset, save_debug_images); + success = read_level(logger, language, game_screen, stats, subset, save_debug_images) && success; + success = read_nature(logger, language, game_screen, stats, subset, save_debug_images) && success; read_gender(logger, language, game_screen, stats, subset); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp index 89f168aca7..ae93bbeeea 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp @@ -41,13 +41,23 @@ ReadStats::ReadStats() Pokemon::PokemonNameReader::instance().languages(), LockMode::LOCK_WHILE_RUNNING, true ) + , PAGE( + "Summary Page:", + { + { SummaryPage::first, "first", "first" }, + { SummaryPage::second, "second", "second" } + }, + LockMode::LOCK_WHILE_RUNNING, + SummaryPage::first + ) { PA_ADD_OPTION(LANGUAGE); + PA_ADD_OPTION(PAGE); } void ReadStats::program( SingleSwitchProgramEnvironment &env, - ProControllerContext &context + CancellableScope &scope ){ env.log( "Starting Read Stats program... Please ensure you are on Page 1 (POKEMON INFO)." @@ -59,49 +69,46 @@ void ReadStats::program( PokemonFRLG_Stats stats; - env.log("Reading Page 1 (Name, Level, Nature)..."); - VideoSnapshot screen1 = env.console.video().snapshot(); - reader.read_page1(env.logger(), LANGUAGE, screen1, stats); - - std::string gender_strings[] = {"Male", "Female", "Genderless"}; - - env.log("Name: " + stats.name); - env.log("Level: " + - (stats.level.has_value() ? std::to_string(*stats.level) : "???")); - env.log("Nature: " + stats.nature); - env.log("Gender: " + std::string( - stats.gender.has_value() ? ( - (stats.gender == SummaryGender::Male) ? "Male" : ( - (stats.gender == SummaryGender::Female) ? "Female" : "Genderless" - ) - ) : "???")); - - env.log("Navigating to Page 2 (POKEMON SKILLS)..."); - pbf_press_dpad(context, DPAD_RIGHT, 100ms, 100ms); - context.wait_for_all_requests(); - pbf_wait(context, 500ms); // Wait for transition - context.wait_for_all_requests(); - - env.log("Reading Page 2 (Stats)..."); - VideoSnapshot screen2 = env.console.video().snapshot(); - reader.read_page2(env.logger(), LANGUAGE, screen2, stats); - - env.log("HP (Total): " + (stats.hp.has_value() ? std::to_string(*stats.hp) : "???")); - env.log("Attack: " + - (stats.attack.has_value() ? std::to_string(*stats.attack) : "???")); - env.log("Defense: " + - (stats.defense.has_value() ? std::to_string(*stats.defense) : "???")); - env.log("Sp. Attack: " + - (stats.sp_attack.has_value() ? std::to_string(*stats.sp_attack) : "???")); - env.log("Sp. Defense: " + - (stats.sp_defense.has_value() ? std::to_string(*stats.sp_defense) : "???")); - env.log("Speed: " + - (stats.speed.has_value() ? std::to_string(*stats.speed) : "???")); + if (PAGE == SummaryPage::first){ + env.log("Reading Page 1 (Name, Level, Nature)..."); + VideoSnapshot screen1 = env.console.video().snapshot(); + reader.read_page1(env.logger(), LANGUAGE, screen1, stats); + + std::string gender_strings[] = {"Male", "Female", "Genderless"}; + + env.log("Name: " + stats.name); + env.log("Level: " + + (stats.level.has_value() ? std::to_string(*stats.level) : "???")); + env.log("Nature: " + stats.nature); + env.log("Gender: " + std::string( + stats.gender.has_value() ? ( + (stats.gender == SummaryGender::Male) ? "Male" : ( + (stats.gender == SummaryGender::Female) ? "Female" : "Genderless" + ) + ) : "???")); + } + + if (PAGE == SummaryPage::second){ + env.log("Reading Page 2 (Stats)..."); + VideoSnapshot screen2 = env.console.video().snapshot(); + reader.read_page2(env.logger(), LANGUAGE, screen2, stats); + + env.log("HP (Total): " + (stats.hp.has_value() ? std::to_string(*stats.hp) : "???")); + env.log("Attack: " + + (stats.attack.has_value() ? std::to_string(*stats.attack) : "???")); + env.log("Defense: " + + (stats.defense.has_value() ? std::to_string(*stats.defense) : "???")); + env.log("Sp. Attack: " + + (stats.sp_attack.has_value() ? std::to_string(*stats.sp_attack) : "???")); + env.log("Sp. Defense: " + + (stats.sp_defense.has_value() ? std::to_string(*stats.sp_defense) : "???")); + env.log("Speed: " + + (stats.speed.has_value() ? std::to_string(*stats.speed) : "???")); + } env.log("Finished Reading Stats. Verification boxes are on overlay.", COLOR_BLUE); - pbf_wait(context, 10s); - context.wait_for_all_requests(); + scope.wait_for(10s); } } // namespace PokemonFRLG diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.h b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.h index 4f628e3dd2..db824d8687 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.h @@ -24,17 +24,23 @@ class ReadStats_Descriptor : public SingleSwitchProgramDescriptor{ class ReadStats : public SingleSwitchProgramInstance{ public: ReadStats(); - virtual void program( - SingleSwitchProgramEnvironment &env, - ProControllerContext &context - ) override; + virtual void start_program_controller_check(ControllerSession& session) override{} virtual void start_program_border_check( VideoStream &stream, FeedbackType feedback_type ) override{} + virtual void program( + SingleSwitchProgramEnvironment &env, + CancellableScope &scope + ) override; private: + enum class SummaryPage{ + first, + second + }; OCR::LanguageOCROption LANGUAGE; + EnumDropdownOption PAGE; }; } // namespace PokemonFRLG