探测游戏小地图上的小圈子 [英] Detectings small circles on game minimap

查看:193
本文介绍了探测游戏小地图上的小圈子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



质量不是很好,因为在1080p视频中,小地图小于300px / 300px



我想检测这张图片上的10个英雄圈子:



像这样:

< img src =https://i.stack.imgur.com/4l1Em.pngalt =预期结果>



对于背景删除,我可以使用这个:





英雄人像圆圈半径在8到12之间,因为英雄人像是21x21px。



使用此代码

  Mat minimapMat = mgcodecs.imread(minimap.png); 
Mat minimapCleanMat = Imgcodecs.imread(minimapClean.png);
Mat minimapDiffMat = new Mat();
Core.subtract(minimapMat,minimapCleanMat,minimapDiffMat);

我得到这个:



现在我在其上应用圆圈检测:

  findCircles(minimapDiffMat); 
public static void findCircles(Mat imgSrc){
Mat img = imgSrc.clone();

垫灰=新垫();
Imgproc.cvtColor(img,gray,Imgproc.COLOR_BGR2GRAY);

Imgproc.blur(灰色,灰色,新尺寸(3,3));

Mat边缘=新Mat();
int lowThreshold = 40;
int ratio = 3;
Imgproc.Canny(gray,edges,lowThreshold,lowThreshold * ratio);

Mat circles = new Mat();
Vector< Mat> circlesList = new Vector< Mat>();

Imgproc.HoughCircles(边缘,圆圈,Imgproc.CV_HOUGH_GRADIENT,1,10,5,20,7,15);

double x = 0.0;
double y = 0.0;
int r = 0; (int i = 0; i for(int k = 0; k


double [] data = circles.get(i,k);
for(int j = 0; j x = data [0];
y = data [1];
r =(int)data [2];
}
点中心=新点(x,y);
//圆心
Imgproc.circle(img,center,3,new Scalar(0,255,0),-1);
//圆形轮廓
Imgproc.circle(img,center,r,new Scalar(0,255,0),1);

}
}

HighGui.imshow(cirleIn,img);
}

结果不正常,只能检测到2个:





我也尝试过knn背景:



成功率较低。
任何提示?

解决方案

问题在于您的小地图包含突出显示的部分(可能在活动玩家周围)无法操作。为什么不从图像中选择突出显示的颜色?从我看到的只有其中几个。我不使用 OpenCV ,因此我在 C ++ 中给出了一个结果:

  int x,y; 
color c0,c1,c;
picture pic0,pic1,pic2;
// pic0 - 源背景
//图片1 - 源图
//图片2 - 输出
//确保所有图片大小相同
pic1.resize( pic0.xs,pic0.ys);
pic2.resize(pic0.xs,pic0.ys);
//为(x = 0; x {//处理所有像素
(y = 0; y ;
//得到两个颜色不带字母
c0.dd = pic0.p [y] [x] .dd& 0x00FFFFFF;
c1.dd = pic1.p [y] [x] .dd& 0x00FFFFFF; C = C1;如果(距离2(c1,颜色(0x00EEEEEE))<2000)c.dd = 0,则b $ b //阈值0xAARRGGBB距离^ 2
; (距离2(c1,颜色(0x00889971))<2000)c.dd = 0; //白色ish矩形
if (距离2(c1,颜色(0x005A6443))< 2000)c.dd = 0; //灰色ish路径
if (距离2(c1,颜色(0x0021A2C2))< 2000)c.dd = 0; //灰色ish路径
if (距离2(c1,颜色(0x002A6D70))<2000)c.dd = 0; //水a
if(distance2(c1,color(0x00439D96))<2000)c.dd = 0; //距离2(c1,c0)<2500)c.dd = 0; //接近背景
pic2.p [y] [x] = c;
}
pic2.save(out0.png);
pic2.pixel_format(_pf_u); //转换为灰度
pic2.smooth(); //模糊一点
pic2.save(out1.png);
pic2.threshold(0,80,765,0x00000000); //将黑色像素(<80)设置为黑色(0)并将其设置为白色(3 * 255)
pic2.pixel_format(_pf_rgba); //转换回RGB
pic2.save( out2.png);

所以你需要找到OpenCV计数器部分。阈值是颜色距离^ 2(所以我不需要sqrt)并且看起来像 50 ^ 2 对于 <0,255>是理想的每个频道 RGB 向量。

我使用自己的图片类来创建图片,
$ b


xs,ys 是以像素为单位的图像大小

p [y] [x] .dd 在32位整数类型$ b处位于(x,y)位置$ b
clear(color)使用 color

<$ c清除整个图像$ c> resize(xs,ys)
将图像调整为新分辨率

bmp VCL 使用 Canvas封装 GDI 位图访问

pf 图片的像素格式:

  enum _pixel_format_enum 
{
_pf_none = 0,// undefined
_pf_rgba,// 32位RGBA
_pf_s,// 32位有符号整型
_pf_u,// 32位无符号整型
_pf_ss ,// 2x16 bit signed int
_pf_uu,// 2x16 bit unsigned int
_pixel_format_enum_end
};


color 和像素编码像这样:

 联合颜色
{
DWORD dd; WORD dw [2];字节数据库[4];
int i; short int ii [2];
color(){};颜色(color& a){* this = a; }; 〜颜色(){}; color * operator =(const color * a){dd = a-> dd;返回这个; }; / * color * operator =(const color& a){... copy ... return this; }; * /
};


乐队包括:


枚举{
_x = 0,// dw
_y = 1,

_b = 0,// db
_g = 1,
_r = 2,
_a = 3,

_v = 0,// db
_s = 1,
_h = 2 ,
};

这里也是用于阈值处理的颜色之间的距离^ 2:

  DWORD distance2(color& a,color& b)
{
DWORD d,dd;
d = DWORD(a.db [0]) - DWORD(b.db [0]); dd = d * d;
d = DWORD(a.db [1]) - DWORD(b.db [1]); DD + = d * d;
d = DWORD(a.db [2]) - DWORD(b.db [2]); DD + = d * d;
d = DWORD(a.db [3]) - DWORD(b.db [3]); DD + = d * d;
return dd;
}

作为输入我使用了您的图片:



pic0:



pic1:





这里的(子)结果:

out0.png:



out1.png:





out2.png:
$ b



正如你所看到的,当球员非常接近(由于圈子平均)并进行了一些调整,您可能会获得更好的结果。但在第二次教导,这可能是由于附近的一个小红圈......



我使用 VCL / GDI 忽略/移植 pic2.bmp-> Canvas-> 这些东西以供您使用。


i am stuck on this problem for like 20h.

The quality is not every good because on 1080p video, the minimap is less than 300px / 300px

I want to detect the 10 heros circles on this images:

Like this:

For background removal, i can use this:

The heroes portrait circle radius are between 8 to 12 because a hero portrait is like 21x21px.

With this code

Mat minimapMat = mgcodecs.imread("minimap.png");
Mat minimapCleanMat = Imgcodecs.imread("minimapClean.png");
Mat minimapDiffMat = new Mat();
Core.subtract(minimapMat, minimapCleanMat, minimapDiffMat);

I obtain this:

Now i apply circles detection on it:

    findCircles(minimapDiffMat);
    public static void findCircles(Mat imgSrc) {
        Mat img = imgSrc.clone();

        Mat gray = new Mat();
        Imgproc.cvtColor(img, gray, Imgproc.COLOR_BGR2GRAY);

        Imgproc.blur(gray, gray, new Size(3, 3));

        Mat edges = new Mat();
        int lowThreshold = 40;
        int ratio = 3;
        Imgproc.Canny(gray, edges, lowThreshold, lowThreshold * ratio);

        Mat circles = new Mat();
        Vector<Mat> circlesList = new Vector<Mat>();

        Imgproc.HoughCircles(edges, circles, Imgproc.CV_HOUGH_GRADIENT, 1, 10, 5, 20, 7, 15);

        double x = 0.0;
        double y = 0.0;
        int r = 0;

        for (int i = 0; i < circles.rows(); i++) {
            for (int k = 0; k < circles.cols(); k++) {

                double[] data = circles.get(i, k);
                for (int j = 0; j < data.length; j++) {
                    x = data[0];
                    y = data[1];
                    r = (int) data[2];
                }
                Point center = new Point(x, y);
                // circle center
                Imgproc.circle(img, center, 3, new Scalar(0, 255, 0), -1);
                // circle outline
                Imgproc.circle(img, center, r, new Scalar(0, 255, 0), 1);

            }
        }

        HighGui.imshow("cirleIn", img);
    }

Results is not ok, detecting only 2 on 10:

I have tried with knn background too:

With less success. Any tips ? Thanks a lot in advance.

解决方案

The problem is that your minimap contains highlighted parts (possibly around active players) rendering your background removal inoperable. Why not threshold the highlighted color out from the image? From what I see there are just few of them. I do not use OpenCV so I gave it a shot in C++ here is the result:

int x,y;
color c0,c1,c;
picture pic0,pic1,pic2;
    // pic0 - source background
    // pic1 - source map
    // pic2 - output
// ensure all images are the same size
pic1.resize(pic0.xs,pic0.ys);
pic2.resize(pic0.xs,pic0.ys);
// process all pixels
for (y=0;y<pic2.ys;y++)
 for (x=0;x<pic2.xs;x++)
    {
    // get both colors without alpha
    c0.dd=pic0.p[y][x].dd&0x00FFFFFF;
    c1.dd=pic1.p[y][x].dd&0x00FFFFFF;         c=c1;
    // threshold           0xAARRGGBB   distance^2
    if (distance2(c1,color(0x00EEEEEE))<2000) c.dd=0;   // white-ish rectangle
    if (distance2(c1,color(0x00889971))<2000) c.dd=0;   // gray-ish path
    if (distance2(c1,color(0x005A6443))<2000) c.dd=0;   // gray-ish path
    if (distance2(c1,color(0x0021A2C2))<2000) c.dd=0;   // aqua water
    if (distance2(c1,color(0x002A6D70))<2000) c.dd=0;   // aqua water
    if (distance2(c1,color(0x00439D96))<2000) c.dd=0;   // aqua water
    if (distance2(c1,c0               )<2500) c.dd=0;   // close to background
    pic2.p[y][x]=c;
    }
pic2.save("out0.png");
pic2.pixel_format(_pf_u);   // convert to gray scale
pic2.smooth();              // blur a little
pic2.save("out1.png");
pic2.threshold(0,80,765,0x00000000);    // set dark pixels (<80) to black (0) and rest to white (3*255)
pic2.pixel_format(_pf_rgba);// convert back to RGB
pic2.save("out2.png");

So you need to find OpenCV counter parts to this. The thresholds are color distance^2 (so I do not need sqrt) and looks like 50^2 is ideal for <0,255> per channel RGB vector.

I use my own picture class for images so some members are:


xs,ys is size of image in pixels
p[y][x].dd is pixel at (x,y) position as 32 bit integer type
clear(color) clears entire image with color
resize(xs,ys) resizes image to new resolution
bmp is VCL encapsulated GDI Bitmap with Canvas access
pf holds actual pixel format of the image:

enum _pixel_format_enum
    {
    _pf_none=0, // undefined
    _pf_rgba,   // 32 bit RGBA
    _pf_s,      // 32 bit signed int
    _pf_u,      // 32 bit unsigned int
    _pf_ss,     // 2x16 bit signed int
    _pf_uu,     // 2x16 bit unsigned int
    _pixel_format_enum_end
    };


color and pixels are encoded like this:

union color
    {
    DWORD dd; WORD dw[2]; byte db[4];
    int i; short int ii[2];
    color(){}; color(color& a){ *this=a; }; ~color(){}; color* operator = (const color *a) { dd=a->dd; return this; }; /*color* operator = (const color &a) { ...copy... return this; };*/
    };


The bands are:

enum{
    _x=0,   // dw
    _y=1,

    _b=0,   // db
    _g=1,
    _r=2,
    _a=3,

    _v=0,   // db
    _s=1,
    _h=2,
    };

Here also the distance^2 between colors I used for thresholding:

DWORD distance2(color &a,color &b)
    {
    DWORD d,dd;
    d=DWORD(a.db[0])-DWORD(b.db[0]); dd =d*d;
    d=DWORD(a.db[1])-DWORD(b.db[1]); dd+=d*d;
    d=DWORD(a.db[2])-DWORD(b.db[2]); dd+=d*d;
    d=DWORD(a.db[3])-DWORD(b.db[3]); dd+=d*d;
    return dd;
    }

As input I used your images:

pic0:

pic1:

And here the (sub) results:

out0.png:

out1.png:

out2.png:

Now just remove noise (by blurring or by erosion) a bit and apply your circle fitting or hough transform...

[Edit1] circle detector

I gave it a bit of taught and implemented simple detector. I just check circumference points around any pixel position with constant radius (player circle) and if number of set point is above threshold I found potential circle. It is better than use whole disc area as some of the players contain holes and there are more pixels to test also ... Then I average close circles together and render the output ... Here updated code:

    int i,j,x,y,xx,yy,x0,y0,r=10,d;
    List<int> cxy;  // circle circumferece points
    List<int> plr;  // player { x,y } list
    color c0,c1,c;
    picture pic0,pic1,pic2;
        // pic0 - source background
        // pic1 - source map
        // pic2 - output
    // ensure all images are the same size
    pic1.resize(pic0.xs,pic0.ys);
    pic2.resize(pic0.xs,pic0.ys);
    // process all pixels
    for (y=0;y<pic2.ys;y++)
     for (x=0;x<pic2.xs;x++)
        {
        // get both colors without alpha
        c0.dd=pic0.p[y][x].dd&0x00FFFFFF;
        c1.dd=pic1.p[y][x].dd&0x00FFFFFF;         c=c1;
        // threshold           0xAARRGGBB   distance^2
        if (distance2(c1,color(0x00EEEEEE))<2000) c.dd=0;   // white-ish rectangle
        if (distance2(c1,color(0x00889971))<2000) c.dd=0;   // gray-ish path
        if (distance2(c1,color(0x005A6443))<2000) c.dd=0;   // gray-ish path
        if (distance2(c1,color(0x0021A2C2))<2000) c.dd=0;   // aqua water
        if (distance2(c1,color(0x002A6D70))<2000) c.dd=0;   // aqua water
        if (distance2(c1,color(0x00439D96))<2000) c.dd=0;   // aqua water
        if (distance2(c1,c0               )<2500) c.dd=0;   // close to background
        pic2.p[y][x]=c;
        }
//  pic2.save("out0.png");
    pic2.pixel_format(_pf_u);   // convert to gray scale
    pic2.smooth();              // blur a little
//  pic2.save("out1.png");
    pic2.threshold(0,80,765,0x00000000);    // set dark pixels (<80) to black (0) and rest to white (3*255)

    // compute player circle circumference points mask
    x0=r-1; y0=r; x0*=x0; y0*=y0;
    for (x=-r,xx=x*x;x<=r;x++,xx=x*x)
     for (y=-r,yy=y*y;y<=r;y++,yy=y*y)
        {
        d=xx+yy;
        if ((d>=x0)&&(d<=y0))
            {
            cxy.add(x);
            cxy.add(y);
            }
        }

    // get all potential player circles
    x0=(5*cxy.num)/20;
    for (y=r;y<pic2.ys-r;y+=2)  // no need to step by single pixel ...
     for (x=r;x<pic2.xs-r;x+=2)
        {
        for (d=0,i=0;i<cxy.num;)
            {
            xx=x+cxy.dat[i]; i++;
            yy=y+cxy.dat[i]; i++;
            if (pic2.p[yy][xx].dd>100) d++;
            }
        if (d>=x0) { plr.add(x); plr.add(y); }
        }

//  pic2.pixel_format(_pf_rgba);// convert back to RGB
//  pic2.save("out2.png");

    // average all circles too close together
    pic2=pic1;  // use original image again
    pic2.bmp->Canvas->Pen->Color=TColor(0x0000FF00);
    pic2.bmp->Canvas->Pen->Width=3;
    pic2.bmp->Canvas->Brush->Style=bsClear;
    for (i=0;i<plr.num;i+=2) if (plr.dat[i]>=0)
        {
        x0=plr.dat[i+0]; x=x0;
        y0=plr.dat[i+1]; y=y0; d=1;
        for (j=i+2;j<plr.num;j+=2) if (plr.dat[j]>=0)
            {
            xx=plr.dat[j+0];
            yy=plr.dat[j+1];
            if (((x0-xx)*(x0-xx))+((y0-yy)*(y0-yy))*10<=20*r*r) // if close
                {
                x+=xx; y+=yy; d++;  // add to average
                plr.dat[j+0]=-1;    // mark as deleted
                plr.dat[j+1]=-1;
                }
            }
        x/=d; y/=d;
        plr.dat[i+0]=x;
        plr.dat[i+1]=y;
        pic2.bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
        }
    pic2.bmp->Canvas->Pen->Width=1;
    pic2.bmp->Canvas->Brush->Style=bsSolid;
//  pic2.save("out3.png");

As you can see the core of code is the same I just added the detector in the end.

I also use mine dynamic list template so:


List<double> xxx; is the same as double xxx[];
xxx.add(5); adds 5 to end of the list
xxx[7] access array element (safe)
xxx.dat[7] access array element (unsafe but fast direct access)
xxx.num is the actual used size of the array
xxx.reset() clears the array and set xxx.num=0
xxx.allocate(100) preallocate space for 100 items

And here the final result out3.png:

As you can see it is a bit messed up when the players are very near (due to circle averaging) with some tweaking you might get better results. But on second taught it might be due to that small red circle nearby ...

I used VCL/GDI for the circles render so just ignore/port the pic2.bmp->Canvas-> stuff to what ever you use.

这篇关于探测游戏小地图上的小圈子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆