Compare commits

...

6 Commits

Author SHA1 Message Date
MistEO
b69e7c76e6 update readme 2021-07-14 21:08:52 +08:00
MistEO
e81421579d add cache function to ider 2021-07-14 20:59:36 +08:00
MistEO
1d3c3874ce add config file to vcxproj 2021-07-14 19:58:32 +08:00
MistEO
bfd21e4631 update debugtrace and config 2021-07-14 19:09:48 +08:00
MistEO
27dd02f4f4 update tasks and config 2021-07-14 17:26:36 +08:00
MistEO
30d714b814 update submodule meojson 2021-07-14 15:23:09 +08:00
12 changed files with 225 additions and 139 deletions

View File

@@ -31,6 +31,9 @@
<ClCompile Include="src\Identify.cpp" />
<ClCompile Include="src\WinMacro.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\resource\config.json" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>

View File

@@ -45,4 +45,9 @@
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\resource\config.json">
<Filter>资源文件</Filter>
</None>
</ItemGroup>
</Project>

View File

@@ -36,6 +36,7 @@ namespace asst {
std::condition_variable m_condvar;
bool m_thread_exit = false;
bool m_thread_running = false;
json::array m_next_tasks;
};
}

View File

@@ -12,9 +12,9 @@ namespace asst {
static bool reload();
static std::string getCurDir();
static std::string getResDir();
static json::object dataObj;
static json::object handleObj;
static json::object optionsObj;
static json::object tasksJson;
static json::object handleJson;
static json::object optionsJson;
private:
Configer() = default;

View File

@@ -16,15 +16,25 @@ namespace asst {
Identify() = default;
~Identify() = default;
void setUseCache(bool b) noexcept;
bool addImage(const std::string& name, const std::string& path);
double imgHistComp(const cv::Mat& lhs, const cv::Mat& rhs);
double imgHistComp(const cv::Mat& cur, const std::string& src, asst::Rect compRect);
std::pair<double, asst::Rect> findImage(const cv::Mat& image, const cv::Mat& templ);
std::pair<double, asst::Rect> findImage(const cv::Mat& cur, const std::string& templ);
std::pair<double, asst::Rect> findImageWithFile(const cv::Mat& cur, const std::string& filename);
// return pair< suitability, scaled asst::rect>
std::pair<double, asst::Rect> findImage(const cv::Mat& image, const std::string& templ, double threshold = 0.99);
private:
cv::Mat image2Hist(const cv::Mat& src);
double imageHistComp(const cv::Mat& src, const cv::MatND& hist);
asst::Rect cvRect2Rect(const cv::Rect& cvRect) {
return asst::Rect(cvRect.x, cvRect.y, cvRect.width, cvRect.height);
}
// return pair< suitability, raw opencv::point>
std::pair<double, cv::Point> findImage(const cv::Mat& cur, const cv::Mat& templ);
std::unordered_map<std::string, cv::Mat> m_matMap;
bool m_use_cache = true;
std::unordered_map<std::string, std::pair<cv::Rect, cv::Mat>> m_cacheMap;
};
}

View File

@@ -11,10 +11,11 @@ Assistance::Assistance()
Configer::reload();
m_Ider = std::make_shared<Identify>();
for (auto&& pair : Configer::dataObj)
for (auto&& pair : Configer::tasksJson)
{
m_Ider->addImage(pair.first, Configer::getResDir() + pair.second["filename"].as_string());
}
m_Ider->setUseCache(Configer::optionsJson["cache"].as_boolean());
m_working_thread = std::thread(working_proc, this);
@@ -50,7 +51,7 @@ std::optional<std::string> Assistance::setSimulator(const std::string& simulator
std::unique_lock<std::mutex> lock(m_mutex);
if (simulator_name.empty()) {
for (auto&& [name, value] : Configer::handleObj)
for (auto&& [name, value] : Configer::handleJson)
{
ret = create_handles(name);
if (ret) {
@@ -72,8 +73,13 @@ std::optional<std::string> Assistance::setSimulator(const std::string& simulator
void Assistance::start()
{
std::unique_lock<std::mutex> lock(m_mutex);
if (m_thread_running) {
return;
}
std::unique_lock<std::mutex> lock(m_mutex);
m_next_tasks.clear();
m_next_tasks.emplace_back("Begin");
m_thread_running = true;
m_condvar.notify_one();
}
@@ -83,6 +89,7 @@ void Assistance::stop()
std::unique_lock<std::mutex> lock(m_mutex);
m_thread_running = false;
m_next_tasks.clear();
}
void Assistance::working_proc(Assistance* pThis)
@@ -91,68 +98,49 @@ void Assistance::working_proc(Assistance* pThis)
std::unique_lock<std::mutex> lock(pThis->m_mutex);
if (pThis->m_thread_running) {
auto curImg = pThis->m_pView->getImage(pThis->m_pView->getWindowRect());
double max_value = -1;
Rect cor_rect;
std::pair<std::string, json::value> matched;
for (auto&& pair : Configer::dataObj) {
auto&& [value, rect] = pThis->m_Ider->findImage(curImg, pair.first);
DebugTrace("%-20s %f", pair.first.c_str(), value);
if (value >= pair.second["threshold"].as_double()) {
if (value >= max_value) {
max_value = value;
cor_rect = rect;
matched = pair;
}
std::string matched_task;
Rect matched_rect;
for (auto&& task_jstr : pThis->m_next_tasks) {
std::string task_name = task_jstr.as_string();
double threshold = Configer::tasksJson[task_name]["threshold"].as_double();
auto&& [value, rect] = pThis->m_Ider->findImage(curImg, task_name, threshold);
DebugTrace("%-20s %f", task_name.c_str(), value);
if (value >= threshold) {
matched_task = task_name;
matched_rect = rect;
break;
}
}
if (max_value >= 0) {
auto task = Configer::dataObj[matched.first].as_object();
if (!matched_task.empty()) {
auto task = Configer::tasksJson[matched_task].as_object();
std::string opType = task["type"].as_string();
DebugTraceInfo("Matched: %s, type: %s", matched.first.c_str(), opType.c_str());
bool next_loop = true;
while (next_loop) {
if (opType == "clickSelf") {
pThis->m_pCtrl->clickRange(cor_rect);
}
else if (opType == "clickRand") {
pThis->m_pCtrl->clickRange(pThis->m_pCtrl->getWindowRect());
}
else if (opType == "next") {
json::value nextObj = task["next"];
std::string name = nextObj["name"].as_string();
std::string filepath = Configer::getResDir() + nextObj["filename"].as_string();
auto&& [value, rect] = pThis->m_Ider->findImageWithFile(curImg, filepath);
DebugTrace("Next: %-20s %f", name.c_str(), value);
DebugTraceInfo("Matched: %s, type: %s", matched_task.c_str(), opType.c_str());
if (value >= nextObj["threshold"].as_double()) {
DebugTraceInfo("Next matched: %s", name.c_str());
task = nextObj.as_object();
opType = nextObj["type"].as_string();
cor_rect = rect;
max_value = value;
next_loop = true;
continue;
}
else {
DebugTraceInfo("Next not matched: %s", name.c_str());
}
}
else if (opType == "stop") {
DebugTrace("opType == stop");
pThis->m_thread_running = false;
break;
}
else if (opType == "doNothing") {
// do nothing
}
else {
DebugTraceError("Unknow option type: %s", opType.c_str());
}
next_loop = false;
if (opType == "clickSelf") {
pThis->m_pCtrl->clickRange(matched_rect);
}
else if (opType == "clickRand") {
pThis->m_pCtrl->clickRange(pThis->m_pCtrl->getWindowRect());
}
else if (opType == "doNothing") {
// do nothing
}
else if (opType == "stop") {
DebugTrace("opType == stop");
pThis->m_thread_running = false;
pThis->m_next_tasks.clear();
continue;
}
else {
DebugTraceError("Unknow option type: %s", opType.c_str());
}
pThis->m_next_tasks = Configer::tasksJson[matched_task]["next"].as_array();
DebugTrace("Next: %s", pThis->m_next_tasks.to_string().c_str());
}
pThis->m_condvar.wait_for(lock, std::chrono::milliseconds(Configer::optionsObj["delay"].as_integer()));
pThis->m_condvar.wait_for(lock, std::chrono::milliseconds(Configer::optionsJson["delay"]["fixedTime"].as_integer()));
}
else {
pThis->m_condvar.wait(lock);

View File

@@ -8,9 +8,9 @@
using namespace asst;
json::object Configer::dataObj;
json::object Configer::handleObj;
json::object Configer::optionsObj;
json::object Configer::tasksJson;
json::object Configer::handleJson;
json::object Configer::optionsJson;
std::string Configer::m_curDir;
@@ -32,9 +32,9 @@ bool Configer::reload()
}
auto root = std::move(ret).value();
dataObj = root["data"].as_object();
handleObj = root["handle"].as_object();
optionsObj = root["options"].as_object();
tasksJson = root["tasks"].as_object();
handleJson = root["handle"].as_object();
optionsJson = root["options"].as_object();
return true;

View File

@@ -1,6 +1,5 @@
#include "Identify.h"
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
@@ -17,12 +16,21 @@ bool Identify::addImage(const std::string& name, const std::string& path)
return true;
}
double Identify::imgHistComp(const cv::Mat& lhs, const cv::Mat& rhs)
void Identify::setUseCache(bool b) noexcept
{
cv::Mat lhs_hsv;
cv::Mat rhs_hsv;
cvtColor(lhs, lhs_hsv, COLOR_BGR2HSV);
cvtColor(rhs, rhs_hsv, COLOR_BGR2HSV);
if (b) {
m_use_cache = true;
}
else {
m_cacheMap.clear();
m_use_cache = false;
}
}
Mat Identify::image2Hist(const cv::Mat& src)
{
Mat src_hsv;
cvtColor(src, src_hsv, COLOR_BGR2HSV);
int histSize[] = { 50, 60 };
float h_ranges[] = { 0, 180 };
@@ -30,31 +38,23 @@ double Identify::imgHistComp(const cv::Mat& lhs, const cv::Mat& rhs)
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
MatND lhs_hist;
MatND rhs_hist;
MatND src_hist;
calcHist(&lhs_hsv, 1, channels, Mat(), lhs_hist, 2, histSize, ranges);
normalize(lhs_hist, lhs_hist, 0, 1, NORM_MINMAX);
calcHist(&src_hsv, 1, channels, Mat(), src_hist, 2, histSize, ranges);
normalize(src_hist, src_hist, 0, 1, NORM_MINMAX);
calcHist(&rhs_hsv, 1, channels, Mat(), rhs_hist, 2, histSize, ranges);
normalize(rhs_hist, rhs_hist, 0, 1, NORM_MINMAX);
return compareHist(lhs_hist, rhs_hist, CV_COMP_CORREL);
return src_hist;
}
double Identify::imgHistComp(const cv::Mat& cur, const std::string& src, asst::Rect compRect)
double Identify::imageHistComp(const cv::Mat& src, const cv::MatND& hist)
{
if (m_matMap.find(src) == m_matMap.end()) {
return 0;
}
cv::Rect cvRect(compRect.x, compRect.y, compRect.width, compRect.height);
return imgHistComp(cur(cvRect), m_matMap.at(src)(cvRect));
return compareHist(image2Hist(src), hist, CV_COMP_CORREL);
}
std::pair<double, asst::Rect> Identify::findImage(const cv::Mat& image, const cv::Mat& templ)
std::pair<double, cv::Point> Identify::findImage(const cv::Mat& image, const cv::Mat& templ)
{
cv::Mat image_hsv;
cv::Mat templ_hsv;
Mat image_hsv;
Mat templ_hsv;
cvtColor(image, image_hsv, COLOR_BGR2HSV);
cvtColor(templ, templ_hsv, COLOR_BGR2HSV);
@@ -64,18 +64,36 @@ std::pair<double, asst::Rect> Identify::findImage(const cv::Mat& image, const cv
double minVal = 0, maxVal = 0;
cv::Point minLoc, maxLoc;
minMaxLoc(matched, &minVal, &maxVal, &minLoc, &maxLoc);
return { maxVal, asst::Rect(maxLoc.x, maxLoc.y, templ.cols, templ.rows).center_zoom(0.8) };
return { maxVal, maxLoc };
}
std::pair<double, asst::Rect> Identify::findImage(const cv::Mat& cur, const std::string& templ)
std::pair<double, asst::Rect> Identify::findImage(const Mat& cur, const std::string& templ, double threshold)
{
if (m_matMap.find(templ) == m_matMap.end()) {
if (m_matMap.find(templ) == m_matMap.cend()) {
return { 0, asst::Rect() };
}
return findImage(cur, m_matMap.at(templ));
if (m_use_cache && m_cacheMap.find(templ) != m_cacheMap.cend()) {
DebugTrace("Identify | %s get cache", templ.c_str());
auto&& [rect, hist] = m_cacheMap.at(templ);
double value = imageHistComp(cur(rect), hist);
return { value, cvRect2Rect(rect).center_zoom(0.8) };
}
else {
auto&& templ_mat = m_matMap.at(templ);
auto&& [value, point] = findImage(cur, templ_mat);
cv::Rect raw_rect(point.x, point.y, templ_mat.cols, templ_mat.rows);
if (m_use_cache && value >= threshold) {
DebugTrace("Identify | %s add to cache", templ.c_str());
m_cacheMap.emplace(templ, std::make_pair(raw_rect, image2Hist(cur(raw_rect))));
}
return { value, cvRect2Rect(raw_rect).center_zoom(0.8) };
}
}
/*
std::pair<double, asst::Rect> Identify::findImageWithFile(const cv::Mat& cur, const std::string& filename)
{
Mat mat = imread(filename);
@@ -84,3 +102,4 @@ std::pair<double, asst::Rect> Identify::findImageWithFile(const cv::Mat& cur, co
}
return findImage(cur, mat);
}
*/

View File

@@ -29,7 +29,7 @@ bool WinMacro::captured() const noexcept
bool WinMacro::findHandle()
{
json::array handle_arr;
json::value simulator_json = Configer::handleObj[m_simulator_name];
json::value simulator_json = Configer::handleJson[m_simulator_name];
switch (m_handle_type) {
case HandleType::Window:
m_width = simulator_json["Width"].as_integer();

View File

@@ -71,7 +71,10 @@ A game assistance for Arknights
- [ ] 当前公招可能干员一览
- [x] 算法
- [x] 更换算法为模板匹配找图,而不是当前的区域相似度对比
- [ ] 优化算法效率,首次采用模板匹配,后续使用相似度对比
- [x] 优化算法效率,添加缓存功能
- [x] 优化任务队列,减少不必要的计算
- [ ] 其他
- [ ] 尝试减小程序体积
## 致谢

Submodule meojson updated: 6b854ac42b...b530ddcffa

View File

@@ -1,7 +1,11 @@
{
"version": 0.1,
"version": 0.2,
"options": {
"delay": 2000
"delay": {
"type": "fixedTime",
"fixedTime": 2000
},
"cache": true
},
"handle": {
"BlueStacks": {
@@ -145,53 +149,106 @@
"yOffset": -42
}
},
"data": {
"Random": {
"tasks": {
"Begin": {
"filename": "",
"threshold": 0,
"type": "clickRand"
},
"UseMedicine": {
"filename": "UseMedicine.png",
"threshold": 0.99,
"type": "next",
"next": {
"name": "MedicineConfirm",
"filename": "MedicineConfirm.png",
"threshold": 0.98,
"type": "clickSelf"
}
},
"PRTS": {
"filename": "PRTS.png",
"threshold": 0.98,
"type": "doNothing"
},
"UseStone": {
"filename": "UseStone.png",
"threshold": 0.99,
"type": "stop"
"type": "doNothing",
"next": [
"StartButton1",
"StartButton2",
"PRTS",
"UseMedicine",
"UseStone",
"PrtsErrorConfirm",
"Random"
]
},
"StartButton1": {
"filename": "StartButton1.png",
"threshold": 0.99,
"type": "clickSelf"
"type": "clickSelf",
"next": [
"StartButton2",
"UseMedicine",
"UseStone"
]
},
"StartButton2": {
"id": 2,
"filename": "StartButton2.png",
"threshold": 0.99,
"type": "clickSelf"
"type": "clickSelf",
"next": [
"PRTS"
]
},
"PRTS": {
"filename": "PRTS.png",
"threshold": 0.98,
"type": "doNothing",
"next": [
"PRTS",
"PrtsErrorConfirm",
"Random"
]
},
"Random": {
"filename": "",
"threshold": 0,
"type": "clickRand",
"next": [
"StartButton1",
"Random"
]
},
"UseMedicine": {
"filename": "UseMedicine.png",
"threshold": 0.99,
"type": "doNothing",
"next": [
"MedicineConfirm"
]
},
"MedicineConfirm": {
"filename": "MedicineConfirm.png",
"threshold": 0.98,
"type": "clickSelf",
"next": [
"StartButton1"
]
},
"UseStone": {
"filename": "UseStone.png",
"threshold": 0.99,
"type": "stop",
"next": [
"StoneConfirm"
]
},
"StoneConfirm": {
"filename": "MedicineConfirm.png",
"threshold": 0.98,
"type": "clickSelf",
"next": [
"StartButton1"
]
},
"PrtsErrorConfirm": {
"filename": "PrtsErrorConfirm.png",
"threshold": 0.98,
"type": "next",
"next": {
"name": "AbandonAction",
"filename": "AbandonAction.png",
"threshold": 0.98,
"type": "clickSelf"
}
"type": "doNothing",
"next": [
"AbandonAction"
]
},
"AbandonAction": {
"filename": "AbandonAction.png",
"threshold": 0.98,
"type": "clickSelf",
"next": [
"Random"
]
}
}
}