perf: 优化信用商店复核逻辑 (#16932)

* perf: 优化信用商店复核逻辑

* chore: 增加对 OCR 结果为空的处理
This commit is contained in:
uye
2026-05-30 17:12:59 +08:00
committed by GitHub
parent 5f1869ab46
commit a10391e9d4
4 changed files with 88 additions and 26 deletions

View File

@@ -48,6 +48,24 @@ asst::CreditShoppingTask& asst::CreditShoppingTask::set_reserve_max_credit(bool
return *this;
}
bool asst::CreditShoppingTask::verify_commodity_name(const TextRect& commodity) const
{
if (commodity.text.empty()) {
return false;
}
const auto product_name_task_ptr = Task.get<OcrTaskInfo>("CreditShop-ProductName");
Rect name_roi = product_name_task_ptr->roi;
name_roi.x += commodity.rect.x;
name_roi.y += commodity.rect.y;
OCRer ocr_analyzer(ctrler()->get_image());
ocr_analyzer.set_roi(name_roi);
ocr_analyzer.set_replace(product_name_task_ptr->replace_map);
ocr_analyzer.set_required({ commodity.text });
return ocr_analyzer.analyze().has_value();
}
int asst::CreditShoppingTask::credit_ocr()
{
cv::Mat credit_image = ctrler()->get_image();
@@ -101,7 +119,7 @@ int asst::CreditShoppingTask::discount_ocr(const asst::Rect& commodity)
return utils::chars_to_number(discount, discount_number) ? discount_number : 0;
}
bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool credit_ocr_enabled)
bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool should_stop_on_credit)
{
const cv::Mat& image = ctrler()->get_image();
@@ -120,7 +138,7 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre
}
const auto& shopping_list = shop_analyzer.get_result();
for (const Rect& commodity : shopping_list) {
for (const TextRect& commodity : shopping_list) {
if (need_exit()) {
return false;
}
@@ -132,7 +150,7 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre
}
if (!m_is_white_list && m_only_buy_discount) {
int discount = discount_ocr(commodity);
int discount = discount_ocr(commodity.rect);
if (discount <= 0) {
int credit = credit_ocr();
if (credit > MaxCredit && m_info_credit_full) {
@@ -149,12 +167,21 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre
// 因动画过渡等原因ctrler()->click(commodity) 点击商品时可能失败,共可尝试 4 次
// 若点击商品成功,则 CreditShop-BuyIt 的 ProcessTask 应顺利执行并返回 true
bool bought = false;
for (int clickCount = 0; clickCount <= 3; ++clickCount) {
ctrler()->click(commodity);
// 点击前复核一次商品名,避免动画或列表偏移导致盲点到错误商品。
if (verify_commodity_name(commodity)) {
ctrler()->click(commodity.rect);
}
if (ProcessTask(*this, { "CreditShop-BuyIt" }).run()) {
bought = true;
break;
}
}
// 多次点击仍未进入购买流程,说明当前商品无法购买,跳过后续 NoMoney / 信用 OCR 检查
if (!bought) {
continue;
}
if (ProcessTask(*this, { "CreditShop-NoMoney" }).set_task_delay(0).set_retry_times(0).run()) {
break;
@@ -162,7 +189,7 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre
if (need_exit()) {
return false;
}
if (credit_ocr_enabled) {
if (should_stop_on_credit) {
int credit = credit_ocr();
if (credit <= MaxCredit) { // 信用值不再溢出,停止购物
break;

View File

@@ -30,12 +30,13 @@ protected:
bool m_only_buy_discount = false; // 设置只购买折扣信用商品(未打折的白名单物品仍会购买)
bool m_reserve_max_credit = false; // 设置消耗信用点至300以下时停止购买商品仍然会购买白名单物品
bool m_info_credit_full = false; // 设置是否在不购买黑名单物品阶段通知信用点溢出
bool verify_commodity_name(const TextRect& commodity) const;
int credit_ocr();
// 用于在信用商店界面识别商品信息左上角的折扣信息
int discount_ocr(const Rect& commodity);
bool credit_shopping(bool white_list_enabled, bool credit_ocr_enabled);
bool credit_shopping(bool white_list_enabled, bool should_stop_on_credit);
std::vector<std::string> m_shopping_list;
bool m_is_white_list = false;

View File

@@ -2,6 +2,7 @@
#include <ranges>
#include "Config/Miscellaneous/OcrConfig.h"
#include "MaaUtils/NoWarningCV.hpp"
#include "Config/TaskData.h"
@@ -10,6 +11,26 @@
#include "Vision/MultiMatcher.h"
#include "Vision/OCRer.h"
size_t asst::CreditShopImageAnalyzer::match_required_index(
const std::string& text,
const std::vector<std::string>& required)
{
if (text.empty()) {
return required.size();
}
auto& ocr_config = OcrConfig::get_instance();
const std::string equ_text = ocr_config.process_equivalence_class(text);
for (size_t index = 0; index != required.size(); ++index) {
if (equ_text.find(ocr_config.process_equivalence_class(required.at(index))) != std::string::npos) {
return index;
}
}
return required.size();
}
void asst::CreditShopImageAnalyzer::set_black_list(std::vector<std::string> black_list)
{
Log.info(__FUNCTION__, black_list);
@@ -82,30 +103,33 @@ bool asst::CreditShopImageAnalyzer::whether_to_buy_analyze()
OCRer ocr_analyzer(m_image);
ocr_analyzer.set_roi(name_roi);
ocr_analyzer.set_replace(product_name_task_ptr->replace_map);
ocr_analyzer.set_required(m_shopping_list);
if (ocr_analyzer.analyze()) {
// 黑名单模式,有识别结果说明这个商品不买,直接跳过
if (!m_is_white_list && !m_shopping_list.empty()) {
continue;
}
if (!ocr_analyzer.analyze()) {
continue;
}
// 白名单模式,没有识别结果说明这个商品不买,直接跳过
else if (m_is_white_list) {
const auto& ocr_result = ocr_analyzer.get_result();
if (ocr_result.empty()) {
continue;
}
const std::string& name = ocr_result.front().text;
const size_t match_index = match_required_index(name, m_shopping_list);
// 黑名单模式,命中黑名单商品则跳过;白名单模式,未命中白名单则跳过。
if ((!m_is_white_list && !m_shopping_list.empty() && match_index != m_shopping_list.size()) ||
(m_is_white_list && match_index == m_shopping_list.size())) {
continue;
}
#ifdef ASST_DEBUG
cv::rectangle(m_image_draw, make_rect<cv::Rect>(commodity), cv::Scalar(0, 0, 255), 2);
#endif
const std::string& name =
ocr_analyzer.get_result().empty() ? std::string() : ocr_analyzer.get_result().front().text;
Log.info("need to buy", name);
m_need_to_buy.emplace_back(commodity, name);
m_need_to_buy.emplace_back(commodity, 0.0, name);
}
if (m_is_white_list) {
std::ranges::sort(m_need_to_buy, std::less {}, [&](const auto& pair) {
return std::ranges::find(m_shopping_list, pair.second);
std::ranges::sort(m_need_to_buy, std::less {}, [&](const TextRect& commodity) {
return match_required_index(commodity.text, m_shopping_list);
});
}
@@ -118,12 +142,18 @@ bool asst::CreditShopImageAnalyzer::sold_out_analyze()
Matcher sold_out_analyzer(m_image);
sold_out_analyzer.set_task_info("CreditShop-SoldOut");
for (const auto& commodity : m_need_to_buy | std::views::keys) {
sold_out_analyzer.set_roi(commodity);
for (const TextRect& commodity : m_need_to_buy) {
sold_out_analyzer.set_roi(commodity.rect);
if (sold_out_analyzer.analyze()) {
#ifdef ASST_DEBUG
cv::rectangle(m_image_draw, make_rect<cv::Rect>(commodity), cv::Scalar(0, 0, 255));
cv::putText(m_image_draw, "Sold Out", cv::Point(commodity.x, commodity.y), 1, 2, cv::Scalar(255, 0, 0));
cv::rectangle(m_image_draw, make_rect<cv::Rect>(commodity.rect), cv::Scalar(0, 0, 255));
cv::putText(
m_image_draw,
"Sold Out",
cv::Point(commodity.rect.x, commodity.rect.y),
1,
2,
cv::Scalar(255, 0, 0));
#endif // ASST_DEBUG
// 如果识别到了售罄,那这个商品就不用买了,跳过

View File

@@ -14,7 +14,7 @@ public:
void set_black_list(std::vector<std::string> black_list);
void set_white_list(std::vector<std::string> white_list);
const std::vector<Rect>& get_result() const noexcept { return m_result; }
const std::vector<TextRect>& get_result() const noexcept { return m_result; }
private:
// 该分析器不支持外部设置ROI
@@ -23,9 +23,13 @@ private:
bool whether_to_buy_analyze();
bool sold_out_analyze();
static size_t match_required_index(
const std::string& text,
const std::vector<std::string>& required);
std::vector<Rect> m_commodities;
std::vector<std::pair<Rect, std::string>> m_need_to_buy;
std::vector<Rect> m_result;
std::vector<TextRect> m_need_to_buy;
std::vector<TextRect> m_result;
std::vector<std::string> m_shopping_list;
bool m_is_white_list = false;