Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>((image.width()) * scale_factor);
int new_h = static_cast<int>((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(
Expand All @@ -75,17 +86,17 @@ 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){
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<int>(image.width()) * scale_factor;
int new_h = static_cast<int>(image.height()) * scale_factor;
scale_factor = 4;
new_w = static_cast<int>(image.width()) * scale_factor;
new_h = static_cast<int>(image.height()) * scale_factor;
cv::Mat resized;
cv::resize(
blurred_native, resized, cv::Size(new_w, new_h), 0, 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,23 @@ ReadStats::ReadStats()
Pokemon::PokemonNameReader::instance().languages(),
LockMode::LOCK_WHILE_RUNNING, true
)
, PAGE(
"<b>Summary Page:</b>",
{
{ 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)."
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SummaryPage> PAGE;
};

} // namespace PokemonFRLG
Expand Down
Loading