diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp index 3f19c0301..65814e866 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp @@ -222,6 +222,51 @@ bool BattleOutOfPpDetector::detect(const ImageViewRGB32& screen){ return num_red_pixels > threshold; } +BattleLevelUpDetector::BattleLevelUpDetector(Color color, BattleLevelUpDialog dialog_type) + : dialog_type(dialog_type) + , m_border_top_box(0.619231, 0.374038, 0.362179, 0.001923) // gray (120, 115, 140) + , m_border_right_box(0.982692, 0.376923, 0.001923, 0.597115) + , m_dialog_top_box(0.626282, 0.393492, 0.341026, 0.006175) // white + , m_dialog_right_box(0.967949, 0.401923, 0.003846, 0.550962) + , m_plus_box(0.862663, 0.402852, 0.034267, 0.553560) +{} +void BattleLevelUpDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_border_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_border_right_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); +} +bool BattleLevelUpDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + //Border is teal + ImageViewRGB32 border_top_image = extract_box_reference(game_screen, m_border_top_box); + ImageViewRGB32 border_right_image = extract_box_reference(game_screen, m_border_right_box); + + //Menu is white + ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); + + //Plus box is not solid white on the first screen, but is white on the second one + ImageViewRGB32 plus_image = extract_box_reference(game_screen, m_plus_box); + bool good_plus_image = ( + dialog_type == BattleLevelUpDialog::either + || (dialog_type == BattleLevelUpDialog::plus && !is_white(plus_image)) + || (dialog_type == BattleLevelUpDialog::stats && is_white(plus_image)) + ); + + if (is_solid(border_top_image, { 0.320, 0.307, 0.373 }, 0.25, 20) + && is_solid(border_right_image, { 0.320, 0.307, 0.373 }, 0.25, 20) + && is_white(dialog_top_image) + && is_white(dialog_right_image) + && good_plus_image + ){ + return true; + } + return false; +} + } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h index f1610eedf..f314ed55e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h @@ -140,6 +140,35 @@ class BattleOutOfPpWatcher : public DetectorToFinder{ {} }; +enum class BattleLevelUpDialog{ + plus, + stats, + either +}; + +class BattleLevelUpDetector : public StaticScreenDetector{ +public: + BattleLevelUpDetector(Color color, BattleLevelUpDialog dialog_type); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + BattleLevelUpDialog dialog_type; + ImageFloatBox m_border_top_box; + ImageFloatBox m_border_right_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; + ImageFloatBox m_plus_box; +}; +class BattleLevelUpWatcher : public DetectorToFinder{ +public: + BattleLevelUpWatcher(Color color, BattleLevelUpDialog dialog_type) + : DetectorToFinder("BattleLevelUpWatcher", std::chrono::milliseconds(250), color, dialog_type) + {} +}; + + } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp new file mode 100644 index 000000000..891857223 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp @@ -0,0 +1,97 @@ +/* Battle Level Up Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "PokemonFRLG_BattleLevelUpReader.h" +#include "Common/Cpp/Color.h" +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/GlobalSettingsPanel.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/Images/ImageManip.h" +#include "CommonTools/OCR/OCR_NumberReader.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include "Pokemon/Inference/Pokemon_NameReader.h" +#include "Pokemon/Inference/Pokemon_NatureReader.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_DigitReader.h" +#include + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonFRLG { + + +BattleLevelUpReader::BattleLevelUpReader(Color color) + : m_color(color) + , m_box_hp(0.904069, 0.402852, 0.068535, 0.079387) + , m_box_attack(0.904069, 0.496052, 0.068535, 0.079387) + , m_box_defense(0.904069, 0.589252, 0.068535, 0.079387) + , m_box_sp_attack(0.904069, 0.682452, 0.068535, 0.079387) + , m_box_sp_defense(0.904069, 0.775652, 0.068535, 0.079387) + , m_box_speed(0.904069, 0.868852, 0.068535, 0.0793879) +{} + +void BattleLevelUpReader::make_overlays(VideoOverlaySet &items) const { + const BoxOption &GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(m_color, GAME_BOX.inner_to_outer(m_box_hp)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_attack)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_defense)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_sp_attack)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_sp_defense)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_speed)); +} + +PokemonFRLG_LevelUpStats BattleLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& frame){ + ImageViewRGB32 game_screen = + extract_box_reference(frame, GameSettings::instance().GAME_BOX); + + auto read_stat = [&](const ImageFloatBox &box, const std::string &name){ + ImageViewRGB32 stat_region = extract_box_reference(game_screen, box); + + if (!GlobalSettings::instance().USE_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 + ); + }; + + PokemonFRLG_LevelUpStats stats; + auto assign_stat = [](std::optional& field, int value){ + if (value != -1){ + field = static_cast(value); + } + }; + assign_stat(stats.hp, read_stat(m_box_hp, "hp")); + assign_stat(stats.attack, read_stat(m_box_attack, "attack")); + assign_stat(stats.defense, read_stat(m_box_defense, "defense")); + assign_stat(stats.sp_attack, read_stat(m_box_sp_attack, "spatk")); + assign_stat(stats.sp_defense, read_stat(m_box_sp_defense, "spdef")); + assign_stat(stats.speed, read_stat(m_box_speed, "speed")); + return stats; +} + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h new file mode 100644 index 000000000..6d95d32cd --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h @@ -0,0 +1,58 @@ +/* Battle Level Up Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_BattleLevelUpReader_H +#define PokemonAutomation_PokemonFRLG_BattleLevelUpReader_H + +#include +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Language.h" + + +namespace PokemonAutomation{ + +class Logger; +class ImageViewRGB32; +class VideoOverlaySet; + +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +struct PokemonFRLG_LevelUpStats{ + std::optional hp; + std::optional attack; + std::optional defense; + std::optional sp_attack; + std::optional sp_defense; + std::optional speed; +}; + +class BattleLevelUpReader { +public: + BattleLevelUpReader(Color color = COLOR_RED); + + void make_overlays(VideoOverlaySet &items) const; + + PokemonFRLG_LevelUpStats read_stats(Logger &logger, const ImageViewRGB32& frame); + +private: + Color m_color; + ImageFloatBox m_box_hp; + ImageFloatBox m_box_attack; + ImageFloatBox m_box_defense; + ImageFloatBox m_box_sp_attack; + ImageFloatBox m_box_sp_defense; + ImageFloatBox m_box_speed; + +}; + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif + diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp index c44d89e8c..f0681efa5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.cpp @@ -14,11 +14,15 @@ #include "Common/Cpp/Logging/AbstractLogger.h" #include "Kernels/Waterfill/Kernels_Waterfill_Session.h" #include "CommonFramework/Globals.h" +#include "CommonFramework/GlobalSettingsPanel.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonTools/ImageMatch/ExactImageMatcher.h" #include "CommonTools/Images/BinaryImage_FilterRgb32.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/Images/ImageManip.h" + #include "PokemonFRLG_DigitReader.h" //#include @@ -29,6 +33,108 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ +// Debug counter for unique filenames +static int debug_counter = 0; + +// Full OCR preprocessing pipeline for GBA pixel fonts. +// +// GBA fonts are seven-segment-like with 1-pixel gaps between segments. +// Pipeline: blur at native -> smooth upscale -> BW -> smooth BW -> re-BW -> pad +// +// The native blur connects gaps. Post-BW padding provides margins. +ImageRGB32 preprocess_for_ocr( + const ImageViewRGB32 &image, + const std::string &label, + int blur_kernel_size, int blur_passes, + bool in_range_black, uint32_t bw_min, + uint32_t bw_max +){ + const bool save_debug_images = GlobalSettings::instance().SAVE_DEBUG_IMAGES; + int id = debug_counter++; + std::string prefix = "DebugDumps/ocr_" + label + "_" + std::to_string(id); + + // Save raw input + if (save_debug_images){ + image.save(prefix + "_0_raw.png"); + } + + cv::Mat src = image.to_opencv_Mat(); + + // Step 1: Gaussian blur at NATIVE resolution 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); + if (blur_kernel_size > 0 && blur_passes > 0){ + for (int i = 0; i < blur_passes; i++){ + cv::GaussianBlur( + blurred_native, blurred_native, + cv::Size(blur_kernel_size, blur_kernel_size), 1.5 + ); + } + } + + // Save blurred at native res + ImageRGB32 blurred_native_img(blurred_native.cols, blurred_native.rows); + blurred_native.copyTo(blurred_native_img.to_opencv_Mat()); + if (save_debug_images){ + blurred_native_img.save(prefix + "_1_blurred_native.png"); + } + + // 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; + cv::Mat resized; + cv::resize( + blurred_native, resized, cv::Size(new_w, new_h), 0, 0, + cv::INTER_LINEAR + ); + + // Save upscaled + ImageRGB32 resized_img(resized.cols, resized.rows); + resized.copyTo(resized_img.to_opencv_Mat()); + if (save_debug_images){ + resized_img.save(prefix + "_2_upscaled.png"); + } + + // Step 3: BW threshold on the smooth upscaled image. + ImageRGB32 bw = + to_blackwhite_rgb32_range(resized_img, in_range_black, bw_min, bw_max); + if (save_debug_images){ + bw.save(prefix + "_3_bw.png"); + } + + // Step 4: Post-BW smoothing -> re-threshold. + // The BW image has angular seven-segment shapes. GaussianBlur on the + // binary image creates gray anti-aliased edges. Re-thresholding at 128 + // rounds the corners into natural smooth digit shapes that Tesseract + // recognizes much better. This is equivalent to morphological closing. + cv::Mat bw_mat = bw.to_opencv_Mat(); + cv::Mat smoothed; + cv::GaussianBlur(bw_mat, smoothed, cv::Size(7, 7), 2.0); + + // Re-threshold: convert smoothed back to ImageRGB32 and BW threshold. + // After blur on BW: text areas are dark gray (~0-64), bg areas are + // light gray (~192-255), edge zones are mid-gray (~64-192). + // Threshold at [0..128] captures text + expanded edges -> BLACK. + ImageRGB32 smoothed_img(smoothed.cols, smoothed.rows); + smoothed.copyTo(smoothed_img.to_opencv_Mat()); + ImageRGB32 smooth_bw = to_blackwhite_rgb32_range( + smoothed_img, true, combine_rgb(0, 0, 0), combine_rgb(128, 128, 128)); + if (save_debug_images){ + smooth_bw.save(prefix + "_4_smooth_bw.png"); + } + + // Step 5: Pad with white border (Tesseract needs margins). + ImageRGB32 padded = pad_image(smooth_bw, smooth_bw.height() / 2, 0xffffffff); + if (save_debug_images){ + padded.save(prefix + "_5_padded.png"); + } + + return padded; +} + // --------------------------------------------------------------------------- // Template store: loads 10 digit matchers from a resource sub-directory. // Results are cached in a static map keyed by template type. diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h index 447a2db23..15f6898bc 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h @@ -27,6 +27,20 @@ enum class DigitTemplateType{ LevelBox, // Lilac level box: PokemonFRLG/LevelDigits/ }; +// Full OCR preprocessing pipeline for GBA pixel fonts. +// +// GBA fonts are seven-segment-like with 1-pixel gaps between segments. +// Pipeline: blur at native -> smooth upscale -> BW -> smooth BW -> re-BW -> pad +// +// The native blur connects gaps. Post-BW padding provides margins. +ImageRGB32 preprocess_for_ocr( + const ImageViewRGB32 &image, + const std::string &label, + int blur_kernel_size, int blur_passes, + bool in_range_black, uint32_t bw_min, + uint32_t bw_max +); + // Read a string of decimal digits from `stat_region`. // // template_type Which template set to use (StatBox or LevelBox). diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index f29b6a2b2..71c569334 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -25,108 +25,6 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonFRLG { -// Debug counter for unique filenames -static int debug_counter = 0; - -// Full OCR preprocessing pipeline for GBA pixel fonts. -// -// GBA fonts are seven-segment-like with 1-pixel gaps between segments. -// Pipeline: blur at native -> smooth upscale -> BW -> smooth BW -> re-BW -> pad -// -// The native blur connects gaps. Post-BW padding provides margins. -static ImageRGB32 preprocess_for_ocr( - const ImageViewRGB32 &image, - const std::string &label, - int blur_kernel_size, int blur_passes, - bool in_range_black, uint32_t bw_min, - uint32_t bw_max -){ - const bool save_debug_images = GlobalSettings::instance().SAVE_DEBUG_IMAGES; - int id = debug_counter++; - std::string prefix = "DebugDumps/ocr_" + label + "_" + std::to_string(id); - - // Save raw input - if (save_debug_images){ - image.save(prefix + "_0_raw.png"); - } - - cv::Mat src = image.to_opencv_Mat(); - - // Step 1: Gaussian blur at NATIVE resolution 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); - if (blur_kernel_size > 0 && blur_passes > 0){ - for (int i = 0; i < blur_passes; i++){ - cv::GaussianBlur( - blurred_native, blurred_native, - cv::Size(blur_kernel_size, blur_kernel_size), 1.5 - ); - } - } - - // Save blurred at native res - ImageRGB32 blurred_native_img(blurred_native.cols, blurred_native.rows); - blurred_native.copyTo(blurred_native_img.to_opencv_Mat()); - if (save_debug_images){ - blurred_native_img.save(prefix + "_1_blurred_native.png"); - } - - // 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; - cv::Mat resized; - cv::resize( - blurred_native, resized, cv::Size(new_w, new_h), 0, 0, - cv::INTER_LINEAR - ); - - // Save upscaled - ImageRGB32 resized_img(resized.cols, resized.rows); - resized.copyTo(resized_img.to_opencv_Mat()); - if (save_debug_images){ - resized_img.save(prefix + "_2_upscaled.png"); - } - - // Step 3: BW threshold on the smooth upscaled image. - ImageRGB32 bw = - to_blackwhite_rgb32_range(resized_img, in_range_black, bw_min, bw_max); - if (save_debug_images){ - bw.save(prefix + "_3_bw.png"); - } - - // Step 4: Post-BW smoothing -> re-threshold. - // The BW image has angular seven-segment shapes. GaussianBlur on the - // binary image creates gray anti-aliased edges. Re-thresholding at 128 - // rounds the corners into natural smooth digit shapes that Tesseract - // recognizes much better. This is equivalent to morphological closing. - cv::Mat bw_mat = bw.to_opencv_Mat(); - cv::Mat smoothed; - cv::GaussianBlur(bw_mat, smoothed, cv::Size(7, 7), 2.0); - - // Re-threshold: convert smoothed back to ImageRGB32 and BW threshold. - // After blur on BW: text areas are dark gray (~0-64), bg areas are - // light gray (~192-255), edge zones are mid-gray (~64-192). - // Threshold at [0..128] captures text + expanded edges -> BLACK. - ImageRGB32 smoothed_img(smoothed.cols, smoothed.rows); - smoothed.copyTo(smoothed_img.to_opencv_Mat()); - ImageRGB32 smooth_bw = to_blackwhite_rgb32_range( - smoothed_img, true, combine_rgb(0, 0, 0), combine_rgb(128, 128, 128)); - if (save_debug_images){ - smooth_bw.save(prefix + "_4_smooth_bw.png"); - } - - // Step 5: Pad with white border (Tesseract needs margins). - ImageRGB32 padded = pad_image(smooth_bw, smooth_bw.height() / 2, 0xffffffff); - if (save_debug_images){ - padded.save(prefix + "_5_padded.png"); - } - - return padded; -} - StatsReader::StatsReader(Color color) : m_color(color), m_box_nature(0.028976, 0.729610, 0.502487, 0.065251), m_box_level(0.052000, 0.120140, 0.099000, 0.069416), diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp index 1458a9fde..b68c4563d 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp @@ -23,98 +23,6 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonFRLG { -// Debug counter for unique filenames -static int debug_counter = 0; - -// Full OCR preprocessing pipeline for GBA pixel fonts. -// -// GBA fonts are seven-segment-like with 1-pixel gaps between segments. -// Pipeline: blur at native -> smooth upscale -> BW -> smooth BW -> re-BW -> pad -// -// The native blur connects gaps. Post-BW padding provides margins. -static ImageRGB32 preprocess_for_ocr( - const ImageViewRGB32 &image, - int blur_kernel_size, int blur_passes, - bool in_range_black, uint32_t bw_min, - uint32_t bw_max -){ - const bool save_debug_images = GlobalSettings::instance().SAVE_DEBUG_IMAGES; - int id = debug_counter++; - std::string prefix = "DebugDumps/ocr_tid_" + std::to_string(id); - - // Save raw input - if (save_debug_images){ - image.save(prefix + "_0_raw.png"); - } - - cv::Mat src = image.to_opencv_Mat(); - - // Step 1: Gaussian blur at NATIVE resolution with 5x5 kernel. - cv::Mat blurred_native; - src.copyTo(blurred_native); - if (blur_kernel_size > 0 && blur_passes > 0){ - for (int i = 0; i < blur_passes; i++){ - cv::GaussianBlur( - blurred_native, blurred_native, - cv::Size(blur_kernel_size, blur_kernel_size), 1.5 - ); - } - } - - // Save blurred at native res - ImageRGB32 blurred_native_img(blurred_native.cols, blurred_native.rows); - blurred_native.copyTo(blurred_native_img.to_opencv_Mat()); - if (save_debug_images){ - blurred_native_img.save(prefix + "_1_blurred_native.png"); - } - - // 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; - cv::Mat resized; - cv::resize( - blurred_native, resized, cv::Size(new_w, new_h), 0, 0, - cv::INTER_LINEAR - ); - - // Save upscaled - ImageRGB32 resized_img(resized.cols, resized.rows); - resized.copyTo(resized_img.to_opencv_Mat()); - if (save_debug_images){ - resized_img.save(prefix + "_2_upscaled.png"); - } - - // Step 3: BW threshold on the smooth upscaled image. - ImageRGB32 bw = - to_blackwhite_rgb32_range(resized_img, in_range_black, bw_min, bw_max); - if (save_debug_images){ - bw.save(prefix + "_3_bw.png"); - } - - // Step 4: Post-BW smoothing -> re-threshold. - cv::Mat bw_mat = bw.to_opencv_Mat(); - cv::Mat smoothed; - cv::GaussianBlur(bw_mat, smoothed, cv::Size(7, 7), 2.0); - - // Re-threshold: convert smoothed back to ImageRGB32 and BW threshold. - ImageRGB32 smoothed_img(smoothed.cols, smoothed.rows); - smoothed.copyTo(smoothed_img.to_opencv_Mat()); - ImageRGB32 smooth_bw = to_blackwhite_rgb32_range( - smoothed_img, true, combine_rgb(0, 0, 0), combine_rgb(128, 128, 128)); - if (save_debug_images){ - smooth_bw.save(prefix + "_4_smooth_bw.png"); - } - - // Step 5: Pad with white border (Tesseract needs margins). - ImageRGB32 padded = pad_image(smooth_bw, smooth_bw.height() / 2, 0xffffffff); - if (save_debug_images){ - padded.save(prefix + "_5_padded.png"); - } - - return padded; -} - TrainerIdReader::TrainerIdReader(Color color) : m_color(color) , m_box_tid(0.742683, 0.117314, 0.129734, 0.076006) @@ -142,7 +50,7 @@ uint16_t TrainerIdReader::read_tid( // PaddleOCR path (original): preprocess then per-digit waterfill OCR. ImageRGB32 ocr_ready = preprocess_for_ocr( - tid_region, 7, 2, true, + tid_region, "TID", 7, 2, true, combine_rgb(0, 0, 0), combine_rgb(190, 190, 190) ); diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 36534212c..b1078b403 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -23,6 +23,7 @@ #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" +#include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" #include "Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h" #include "Programs/TestPrograms/PokemonFRLG_ReadEncounter.h" @@ -69,6 +70,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back("---- Developer Tools ----"); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp new file mode 100644 index 000000000..23f49356f --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp @@ -0,0 +1,96 @@ +/* Read Battle Level Up + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "Pokemon/Pokemon_Strings.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h" +#include "PokemonFRLG_ReadBattleLevelUp.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace std::chrono_literals; + + +ReadBattleLevelUp_Descriptor::ReadBattleLevelUp_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:ReadBattleLevelUp", + Pokemon::STRING_POKEMON + " FRLG", + "Read Battle Level-Up Stats", "", + "Read stats from the relevant popup after leveling up during a battle.", + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS +){} + +ReadBattleLevelUp::ReadBattleLevelUp() +{} + +void ReadBattleLevelUp::program( + SingleSwitchProgramEnvironment &env, + ProControllerContext &context +){ + env.log( + "Starting Read Battle Level Up program..." + ); + + BattleLevelUpDetector plus_detector(COLOR_RED, BattleLevelUpDialog::plus); + BattleLevelUpDetector stats_detector(COLOR_RED, BattleLevelUpDialog::stats); + + env.log("Detecting the relevant dialogue box..."); + VideoSnapshot screen = env.console.video().snapshot(); + bool plus_dialog = plus_detector.detect(screen); + bool stats_dialog = stats_detector.detect(screen); + + if (plus_dialog){ + env.log("Wrong dialogue box is visible. Stats are read from the next one."); + } + if (!stats_dialog){ + env.log("Stats dialogue box not detected."); + }else{ + env.log("Stats dialogue box detected!"); + } + + + BattleLevelUpReader reader(COLOR_RED); + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + + + env.log("Reading stats..."); + VideoSnapshot screen1 = env.console.video().snapshot(); + PokemonFRLG_LevelUpStats stats = reader.read_stats(env.logger(), screen1); + + env.log("Max HP: " + (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(); +} + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h new file mode 100644 index 000000000..2b61cc99e --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h @@ -0,0 +1,43 @@ +/* Read Battle Level Up + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_ReadBattleLevelUp_H +#define PokemonAutomation_PokemonFRLG_ReadBattleLevelUp_H + +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonTools/Options/LanguageOCROption.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class ReadBattleLevelUp_Descriptor : public SingleSwitchProgramDescriptor{ +public: + ReadBattleLevelUp_Descriptor(); +}; + +class ReadBattleLevelUp : public SingleSwitchProgramInstance{ +public: + ReadBattleLevelUp(); + virtual void program( + SingleSwitchProgramEnvironment &env, + ProControllerContext &context + ) override; + + virtual void start_program_border_check( + VideoStream &stream, FeedbackType feedback_type + ) override{} + +}; + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif + diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index bbb795791..16a4eb9a4 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1474,6 +1474,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h + Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp + Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp @@ -1522,6 +1524,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadTrainerId.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadEncounter.h + Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp + Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h Source/PokemonHome/Inference/PokemonHome_BallReader.cpp Source/PokemonHome/Inference/PokemonHome_BallReader.h Source/PokemonHome/Inference/PokemonHome_BoxGenderDetector.cpp