opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配

上一篇我写了如何用dfs深度优先搜索算法来求解,入参是一个二维数组,这个二维数组是人为手动赋值的

这一篇我们来讲如何自动来完成这一过程.

也就是说 入参是一个 游戏的画面,出参是一个二维数组

如下图:

opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第1张图片

得到了二维数组 我们就可以用dfs算法求解了.


ok ,回到主题
怎么才能 根据图片得到 二维数组地图了?

那就是通过计算机视觉库,这里我们使用opencv

先说下总体的目标吧 ,要想自动的识别地图首先要确认地图最左上角的坐标,
如下图红点
opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第2张图片

先看下图像的坐标系

以左上角作为起点,越往右x值越大,越往下y值越大

游戏方块的大小我们是可以提前测量好的,只要我们知道了地图最左上角的坐标 , 以最上角的坐标为起点,进行for循环遍历,就可以得到每个方块中心的坐标,根据每个方块坐标的颜色,就可以确定这个方块是否可以走
如下图

只要是可以走的路我都画上了绿色的点,不可以走的路我都画上了红色的点.起点我画的是蓝色的点.

opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第3张图片
OK,下面来说一说 具体的步骤.

首先确定的是方块的颜色

rgb 的数值 大概是 205 205 205
opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第4张图片

再看一下游戏的背景图的颜色

rgb 大概是 39 42 60 左右
opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第5张图片

然后最重要的opencv的函数就出现了 matchTemplete,模板匹配

先大概看一下这个函数的参数吧

CV_EXPORTS_W void matchTemplate( InputArray image, InputArray templ,
                                 OutputArray result, int method, InputArray mask = noArray() );

返回值是void,
第一个入参image ,是要匹配的图像也就是我们的游戏画面截图
第二个入参templ,是模板图像,也就是游戏的方块的截图
第三个入参result,是匹配的结果,入参得到函数调用的返回结果,是不是很抓狂.哈哈
第四个入参method,是匹配的算法,这个有很多种的选择

enum TemplateMatchModes {
    TM_SQDIFF        = 0, //!< \f[R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2\f]
    TM_SQDIFF_NORMED = 1, //!< \f[R(x,y)= \frac{\sum_{x',y'} (T(x',y')-I(x+x',y+y'))^2}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}\f]
    TM_CCORR         = 2, //!< \f[R(x,y)= \sum _{x',y'} (T(x',y')  \cdot I(x+x',y+y'))\f]
    TM_CCORR_NORMED  = 3, //!< \f[R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y'))}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}\f]
    TM_CCOEFF        = 4, //!< \f[R(x,y)= \sum _{x',y'} (T'(x',y')  \cdot I'(x+x',y+y'))\f]
                          //!< where
                          //!< \f[\begin{array}{l} T'(x',y')=T(x',y') - 1/(w  \cdot h)  \cdot \sum _{x'',y''} T(x'',y'') \\ I'(x+x',y+y')=I(x+x',y+y') - 1/(w  \cdot h)  \cdot \sum _{x'',y''} I(x+x'',y+y'') \end{array}\f]
    TM_CCOEFF_NORMED = 5  //!< \f[R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x',y+y')) }{ \sqrt{\sum_{x',y'}T'(x',y')^2 \cdot \sum_{x',y'} I'(x+x',y+y')^2} }\f]
};

我们使用的是第5个 ,至于这些算法 具体的实现 ,大家可以查找算法仔细研究下.我们就直接使用现成的了.

第五个入参mask,是一个图像,默认是noAyyary(),我们就不管了.

ok 这里我们统一一下图像的名称

输入的游戏截图图像是image
匹配的模板图像也就是方块的图像是templ
匹配的结果图像是result

下面就用变量的名字来代替名称了

这里要讲一下第三个参数result,也就是我们的输出的结果,这个参数的大小有限制,

它的高度是 image的高度- templ的高度+1
它的宽度是 image的宽度-templ的宽度+1

为什么要有这个大小限制了?

这个其实要看matchTemplete匹配的过程,这个函数它大体的匹配过程就是把templ放到image上面 推测是从左往右,从上往下依次匹配的过程,具体的细节需要看算法实现.但是大体如此

如下图红框
其实这个红框所在的矩形范围就是模板图片templ左上角的那个顶点在image中划过的轨迹, 而这个矩形的高度和宽度恰好就是
image的高度- templ的高度+1
image的宽度-templ的宽度+1

所以这个也是为什么第三个入参的result的大小要有这个限制,为什么要说这个了? 下面要用到.

opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第6张图片

网上大部分的matchTemplete 函数 都是配合minMaxLoc函数使用 ,结果只能匹配到一个结果

但是我们的地图中有很多的方块啊,怎么可能值匹配一个了?这个怎么办,而且我们想象中的返回结果应该是一个数组 保存x坐标和y坐标.可是他的返回结果是一个Mat 怎么是一个图像?

看了下b站up主的代码 直接是一个numpy.where() 函数 就OK了. 又是一个python神仙语法,这谁顶得住啊.

opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第7张图片

没办法,继续研究下,再次回到上面那个输出结果图像的大小问题

我们把这个 结果图像显示出来看看不就行了

左边是 matchTemplete的返回结果的result图像 ,右边是游戏截图image图像

opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第8张图片

可以看到 result图像中 是有亮点的,而这些亮点 对比一下 就是对应的是image每个方块的左上角的位置.

越亮的地方 就代表在image图像中以这个点作为左上角和templ图像对齐 匹配度越高,他的数值也是越大,越接近1

而且result中的坐标 体系的值 和 原图 坐标体系的值 是一样的 想想上面的result大小的讨论 ,这个也就解释了 为什么matchTemplete 返回值的结果不是 x和y的数值 而是一个Mat图像. result图像在(x,y)处的值 就代表了,游戏图像image在(x,y)点和模板图像templ的匹配程度.

再次总结一下: 返回结果result的坐标体系和image是一样的,因为result图像本身也是一个二维数组,所以result(x,y)的值 就是代表游戏图像image以(x,y)为起点和模板图像左上角对齐 匹配模板图像的相似度,越靠近1 代表越像,在result图像中也就越亮.

这里大家可以打印出result的值看看,我们选择一个阈值进行过滤,就可以把比较像的点保存下来.这些点大概会有几百或者上千个,远远超过方块的数量,想一想这是为什么.

ok 到这里 我们看上去就获得了所有方块的左上角的坐标.

但是实际使用这个方法 有一定的误差,比较难过滤. 因为有的局部会被匹配成方块,pass ,我们需要换个思路

B站up主使用的方法是 使用minMaxLoc方法找到输入图像中和模板图像最相似的点.

然后以这个点作为扫描的起点,向四周进行扩散扫描,扫描的深度可以自定义 一般10-20差不多 因为二维地图的维度不会太大.

扩散扫描的时候查看扩散点的颜色.

如果颜色是方块的颜色 则把方块的颜色的坐标记录下来,这样就可以得到所有的方块的坐标. 按照这个来统计方块是比较准的,所有的方块都能被正确的识别.

到这里终于确定了所有方块的最上角的坐标

但是我们还没有获取游戏图像image地图的最上角的坐标,这个怎么办了?

首先是找到方块坐标的最大值和最小值
也就是
x坐标最大值 x_max
y坐标最大值 y_max
x最表最小值x_min
y坐标最小值y_min

(x_min,y_min)就是游戏地图最左上角的坐标
(x_max-x_min)/方块长度=二维数组的列数
(y_max-y_min)/方块长度=二维数组的行数

然后就是遍历地图 根据颜色确定二维数组的值了.

ok差不多就是这么多了 ,上代码吧.

#include
#include
#include
#include 

using namespace std;
using namespace cv;



void buildDfsMap(Mat srcImage, Mat templateImage, Mat result);



int dfsBeginX;
int dfsBeginY;
int blockCount = 0;
int gameMap[10][10];

int n; //x 的长度
int m; //y的长度


int blockSize = 155;


int main() {
    Mat templateImage = imread("/Users/saltedfish/Desktop/123.png");
    Mat srcImage = imread("/Users/saltedfish/Desktop/789.jpeg");
    Mat result;
    int result_cols = srcImage.cols - blockSize + 1;
    int result_rows = srcImage.rows - blockSize + 1;
    result.create(result_cols, result_rows, srcImage.type());
    matchTemplate(srcImage, templateImage, result, TM_CCOEFF_NORMED);
    buildDfsMap(srcImage, templateImage, result);

}




void buildDfsMap(Mat srcImage, Mat templateImage, Mat result) {
    //开始构建地图
    //随便获取一个位置 ,这里以最匹配的为开始位置
    Point maxPoint;
    Point minPoint;

    minMaxLoc(result, NULL, NULL, &minPoint, &maxPoint);
    //以开始位置为起点,向四周扫描 确定地图的范围
    int scanBeginX = maxPoint.x;
    int scanBeginY = maxPoint.y;


    int mapBeginX = srcImage.cols;
    int mapBeginY = srcImage.rows;
    int mapEndX = 0;
    int mapEndY = 0;


    //灰色点三通道的值大概是204 204 204 左右


    for (int i = -10; i < 10; i++) {
        for (int j = -10; j < 10; j++) {
            int dstPointX = scanBeginX + i * blockSize + 0.5 * blockSize;
            int dstPointY = scanBeginY + j * blockSize + 0.5 * blockSize;
            //超出范围不计
            if (dstPointX <= srcImage.cols && dstPointY <= srcImage.rows && dstPointX >= 0 && dstPointY >= 0) {

                circle(srcImage, Point(dstPointX, dstPointY), 6, Scalar(0, 0, 255), 8);
                int b = srcImage.at(dstPointY, dstPointX)[0];
                int g = srcImage.at(dstPointY, dstPointX)[1];
                int r = srcImage.at(dstPointY, dstPointX)[2];
                if (abs(b - 204) < 5 && abs(g - 204) < 5 && abs(r - 204) < 5) {


                    circle(srcImage, Point(dstPointX, dstPointY), 6, Scalar(0, 255, 0), 8);

                    if (mapBeginX >= dstPointX) {
                        mapBeginX = dstPointX;
                    }
                    if (mapBeginY >= dstPointY) {
                        mapBeginY = dstPointY;
                    }
                    if (mapEndX <= dstPointX) {
                        mapEndX = dstPointX;
                    }
                    if (mapEndY <= dstPointY) {
                        mapEndY = dstPointY;
                    }
                }

            }
        }
    }


    m = (mapEndX - mapBeginX) / blockSize;
    n = (mapEndY - mapBeginY) / blockSize;

    //背景图片的 bgr  57  41  38
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            int dstPointX = mapBeginX + i * blockSize;
            int dstPointY = mapBeginY + j * blockSize;
            int b = srcImage.at(dstPointY, dstPointX)[0];
            int g = srcImage.at(dstPointY, dstPointX)[1];
            int r = srcImage.at(dstPointY, dstPointX)[2];
            if (abs(b - 57) < 10 && abs(g - 41) < 10 && abs(r - 38) < 10) {
                //背景图片
                gameMap[j][i] = 0;
                continue;
            }
            if (abs(b - 204) < 10 && abs(g - 204) < 10 && abs(r - 204) < 10) {

                gameMap[j][i] = 1;
                blockCount++;
            } else {
                //起点
                circle(srcImage, Point(dstPointX, dstPointY), 6, Scalar(255, 0, 0), 8);

                gameMap[j][i] = 2;
                dfsBeginX = j;
                dfsBeginY = i;
            }
        }
    }



    //print Map
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            cout << gameMap[i][j] << ",";
        }
        cout << endl;
    }
    imwrite("/Users/saltedfish/Desktop/gameMap.png", srcImage);


}

运行这个代码 需要本地配置好opencv的环境

ok,看一下运行的效果吧

哈哈哈 ,成功了
opencv制作微信小游戏 最强连一连 辅助(3)--opencv matchTemplete多目标匹配_第9张图片

得到了游戏地图的二维数组,又有了dfs算法求解,我们就可以求出解了.

但是还有一个问题 就是 虽然计算机虽然知道怎么走了,但是计算机不能去像人一样去触摸屏幕啊

没事,好在有解决方法.那就是通过 adb 模拟 触摸

ok,我们下一章再见.

你可能感兴趣的:(OpenCV最强连一连辅助设计)