66
77#include " Common/Cpp/Exceptions.h"
88#include " CommonFramework/Exceptions/FatalProgramException.h"
9+ #include " CommonFramework/Globals.h"
10+ #include " CommonFramework/GlobalSettingsPanel.h"
911#include " CommonFramework/ImageTools/ImageStats.h"
12+ #include " CommonFramework/ImageTypes/ImageRGB32.h"
13+ #include " CommonFramework/ImageTypes/ImageHSV32.h"
1014#include " CommonFramework/VideoPipeline/VideoOverlay.h"
1115#include " CommonFramework/VideoPipeline/VideoOverlayScopes.h"
1216#include " CommonFramework/Tools/ErrorDumper.h"
1317#include " CommonFramework/Tools/VideoStream.h"
1418#include " CommonFramework/VideoPipeline/VideoFeed.h"
1519#include " CommonTools/Images/SolidColorTest.h"
1620#include " CommonTools/ImageMatch/SubObjectTemplateMatcher.h"
21+ #include " CommonTools/ImageMatch/WaterfillTemplateMatcher.h"
1722#include " CommonTools/Images/BinaryImage_FilterRgb32.h"
23+ #include " CommonTools/Images/WaterfillUtilities.h"
1824#include " Kernels/Waterfill/Kernels_Waterfill.h"
1925#include " Kernels/Waterfill/Kernels_Waterfill_Session.h"
2026#include " NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h"
@@ -36,6 +42,7 @@ namespace{
3642 bool debug_switch = false ;
3743}
3844
45+
3946// match the downward arrow (green border, white interior) in the box system view
4047class BoxCellSelectionArrowMatcher : public ImageMatch ::SubObjectTemplateMatcher{
4148public:
@@ -58,19 +65,101 @@ class BoxCellSelectionArrowMatcher : public ImageMatch::SubObjectTemplateMatcher
5865 m_path
5966 );
6067 }
61-
68+
6269 set_subobject (objects[0 ]);
6370
71+ // Analyze template HSV values to determine green hue range
72+ // analyze_template_hsv();
73+
6474// extract_box_reference(m_matcher.image_template(), objects[0]).save("template.png");
6575 }
6676
77+ // Optional custom image validation hook called before rmsd checks.
78+ // Check if the input image contains green pixels with hue close to 51
79+ virtual bool check_image (const ImageViewRGB32& input_image) const override {
80+ // Convert RGB to HSV
81+ ImageHSV32 input_hsv (input_image);
82+ ImageViewHSV32 hsv_view (input_hsv);
83+
84+ // Target hue for green arrow border
85+ const uint32_t target_hue = 50 ;
86+ const uint32_t hue_tolerance = 10 ;
87+ const uint32_t min_hue = target_hue - hue_tolerance;
88+ const uint32_t max_hue = target_hue + hue_tolerance;
89+
90+ // Minimum saturation and value to avoid detecting grayish/dark pixels as green
91+ const uint32_t min_saturation = 30 ; // At least 30/255 saturation
92+ const uint32_t min_value = 30 ; // At least 30/255 brightness
93+
94+ // Count pixels with green hue
95+ size_t green_pixel_count = 0 ;
96+ const size_t total_pixels = input_image.width () * input_image.height ();
97+
98+ for (size_t y = 0 ; y < hsv_view.height (); y++){
99+ for (size_t x = 0 ; x < hsv_view.width (); x++){
100+ uint32_t hsv_pixel = hsv_view.pixel (x, y);
101+ uint32_t hue = (hsv_pixel >> 16 ) & 0xFF ;
102+ uint32_t sat = (hsv_pixel >> 8 ) & 0xFF ;
103+ uint32_t val = hsv_pixel & 0xFF ;
104+
105+ // Check if pixel is green with target hue
106+ if (hue >= min_hue && hue <= max_hue &&
107+ sat >= min_saturation &&
108+ val >= min_value){
109+ green_pixel_count++;
110+ }
111+ }
112+ }
113+
114+ // Require at least 33% of pixels to be green
115+ const size_t min_required_green_pixels = total_pixels / 3 ;
116+ const bool has_green = green_pixel_count >= min_required_green_pixels;
117+
118+ if (debug_switch){
119+ static int counter = 0 ;
120+ cout << " check_image() input image: check_image_input_" << counter << " .png" << endl;
121+ input_image.save (" check_image_input_" + std::to_string (counter++) + " .png" );
122+ cout << " check_image() HSV validation:" << endl;
123+ cout << " Green pixels found: " << green_pixel_count << " / " << total_pixels
124+ << " (" << (100.0 * green_pixel_count / total_pixels) << " %)" << endl;
125+ cout << " Required minimum: " << min_required_green_pixels
126+ << " (" << (100.0 * min_required_green_pixels / total_pixels) << " %)" << endl;
127+ cout << " Hue range: [" << min_hue << " , " << max_hue << " ]" << endl;
128+ cout << " Result: " << (has_green ? " PASS" : " FAIL" ) << endl;
129+ }
130+
131+ return has_green;
132+ };
133+
67134 static const BoxCellSelectionArrowMatcher& matcher (){
68135 static BoxCellSelectionArrowMatcher matcher;
69136 return matcher;
70137 }
71138};
72139
73140
141+ class BoxCellSelectionArrowGreenPartMatcher : public ImageMatch ::WaterfillTemplateMatcher{
142+ public:
143+ BoxCellSelectionArrowGreenPartMatcher ();
144+
145+ static const BoxCellSelectionArrowGreenPartMatcher& instance ();
146+ };
147+
148+ BoxCellSelectionArrowGreenPartMatcher::BoxCellSelectionArrowGreenPartMatcher ()
149+ : WaterfillTemplateMatcher(
150+ " PokemonLZA/SelectionArrowDown.png" ,
151+ Color (150 , 200 , 20 ),
152+ Color(200 , 250 , 50 ),
153+ 100
154+ )
155+ {}
156+
157+ const BoxCellSelectionArrowGreenPartMatcher& BoxCellSelectionArrowGreenPartMatcher::instance (){
158+ static BoxCellSelectionArrowGreenPartMatcher matcher;
159+ return matcher;
160+ }
161+
162+
74163BoxDetector::BoxDetector (Color color, VideoOverlay* overlay)
75164 : m_color(color)
76165 , m_plus_button(color, ButtonType::ButtonPlus, {0.581 , 0.940 , 0.310 , 0.046 }, overlay)
@@ -80,6 +169,7 @@ BoxDetector::BoxDetector(Color color, VideoOverlay* overlay)
80169 for (size_t col = 0 ; col < 6 ; col++){
81170 double x = 0.058 + col * (0.386 - 0.059 )/5.0 ;
82171 m_arrow_boxes.emplace_back (x, y, 0.018 , 0.026 );
172+ m_lifted_arrow_boxes.emplace_back (x+0.011 , y-0.010 , 0.023 , 0.032 );
83173 }
84174 }
85175}
@@ -91,11 +181,46 @@ void BoxDetector::make_overlays(VideoOverlaySet& items) const{
91181 }
92182}
93183
184+
94185// detect arrow's white interior first
95186// then use subobject template matcher BoxCellSelectionArrowMatcher to detect the whole arrow
96- bool BoxDetector::detect_at_cell (const ImageViewRGB32& image_crop){
97- // the arrow's white interior has color between rgb [220, 220, 220] to [255, 255, 255]
98- // the arrow's green border has color between rgb [170, 230, 50] to [190, 255, 80]
187+ bool BoxDetector::detect_at_cell (const Resolution& screen_resolution, const ImageViewRGB32& image_crop){
188+ if (m_holding_pokemon){
189+ // If the box cursor is holding a pokemon, it will be a green downard arrow with white interior.
190+ // This green arrow may appear on top of the bottom part of a pokemon on the upper row of the box.
191+ // If the pokemon above has white bottom part, like a regular-color Spewpa, the arrrow white interior
192+ // blends in with the Spewpa white color and make it impossible to find the arrow interior using
193+ // waterfill. So we do a waterfill for the green part of the arrow against non-green background here.
194+ const std::vector<std::pair<uint32_t , uint32_t >> FILTERS = {
195+ {combine_rgb (170 , 230 , 50 ), combine_rgb (200 , 255 , 100 )},
196+ {combine_rgb (140 , 180 , 0 ), combine_rgb (200 , 255 , 100 )},
197+ };
198+ const double screen_rel_size = (screen_resolution.height / 1080.0 );
199+ const size_t min_area = static_cast <size_t >(250 * screen_rel_size * screen_rel_size);
200+ const double rmsd_threshold = 70.0 ;
201+ const bool detected = match_template_by_waterfill (
202+ screen_resolution,
203+ image_crop,
204+ BoxCellSelectionArrowGreenPartMatcher::instance (),
205+ FILTERS,
206+ {min_area, SIZE_MAX},
207+ rmsd_threshold,
208+ [&](Kernels::Waterfill::WaterfillObject& object) -> bool {
209+ return true ;
210+ }
211+ );
212+ if (detected){
213+ return true ;
214+ }
215+ }
216+
217+ // The box curosr either is not holding a pokemon, or it is holding a pokemon but the background of the cursor
218+ // happens to be green (bottom part of a green pokemon) failing the arrow green part waterfill detection.
219+ // In both cases, we try to use waterfill to find the white interior of the arrow and then match the full arrow
220+ // tempalte image around the found white interior:
221+
222+ // The arrow's white interior has color between rgb [220, 220, 220] to [255, 255, 255]
223+ // The arrow's green border has color between rgb [170, 230, 50] to [190, 255, 80]
99224 std::vector<std::pair<uint32_t , uint32_t >> filters = {
100225 {0xff808080 , 0xffffffff },
101226 {0xff909090 , 0xffffffff },
@@ -147,9 +272,9 @@ bool BoxDetector::detect_at_cell(const ImageViewRGB32& image_crop){
147272 }
148273
149274 if (debug_switch){
150- cout << " Find object: area " << object.area << " , save to found_object " << saved_object_id << " .png" << endl;
275+ cout << " Find object: area " << object.area << " , save to found_subobject " << saved_object_id << " .png" << endl;
151276 ImagePixelBox box (object);
152- extract_box_reference (image_crop, box).save (" found_object " + std::to_string (saved_object_id++) + " .png" );
277+ extract_box_reference (image_crop, box).save (" found_subobject " + std::to_string (saved_object_id++) + " .png" );
153278 }
154279 if (object.area > max_area){
155280 continue ;
@@ -186,6 +311,7 @@ bool BoxDetector::detect_at_cell(const ImageViewRGB32& image_crop){
186311 return detected;
187312}
188313
314+
189315bool BoxDetector::detect (const ImageViewRGB32& screen){
190316 if (!m_plus_button.detect (screen)){
191317 return false ;
@@ -201,16 +327,19 @@ bool BoxDetector::detect(const ImageViewRGB32& screen){
201327 }
202328 cout << "row = " << (int)row << ", col = " << (int)col << endl;
203329#endif
204- ImageViewRGB32 image_crop = extract_box_reference (screen, m_arrow_boxes[cell_idx]);
330+ const auto & box = (m_holding_pokemon ? m_lifted_arrow_boxes[cell_idx] : m_arrow_boxes[cell_idx]);
331+ ImageViewRGB32 image_crop = extract_box_reference (screen, box);
205332 // image_crop.save("cell_" + std::to_string(row) + "_" + std::to_string(col) + ".png");
206333 const uint8_t debug_cell_row = 255 , debug_cell_col = 255 ;
207334 if (row == debug_cell_row && col == debug_cell_col){
208335 debug_switch = true ;
209336 cout << " start debugging switch at " << int (row) << " , " << int (col) << endl;
337+ PreloadSettings::debug ().IMAGE_TEMPLATE_MATCHING = true ;
210338 }
211- bool detected = detect_at_cell (image_crop);
339+ const bool detected = detect_at_cell (screen. size (), image_crop);
212340 if (row == debug_cell_row && col == debug_cell_col){
213341 debug_switch = false ;
342+ PreloadSettings::debug ().IMAGE_TEMPLATE_MATCHING = false ;
214343 }
215344 if (detected){
216345 if (arrow_found && m_debug_mode){
@@ -244,12 +373,13 @@ BoxCursorCoordinates BoxDetector::detected_location() const{
244373
245374void BoxDetector::move_cursor (
246375 const ProgramInfo& info, VideoStream& stream, ProControllerContext& context,
247- uint8_t row, uint8_t col
376+ uint8_t row, uint8_t col, bool holding_pokemon
248377){
249378 if (row >= 6 || col >= 6 ){
250379 throw InternalProgramError (&stream.logger (), " BoxDetector::move_cursor" ,
251380 " row or col out of range: " + std::to_string (row) + " , " + std::to_string (col));
252381 }
382+ this ->holding_pokemon (holding_pokemon);
253383
254384 WallClock start = current_time ();
255385 while (true ){
0 commit comments