在游戏小地图上检测小圆圈

时间:2018-03-12 08:11:49

标签: opencv geometry detection hough-transform

我坚持这个问题就像20小时一样。

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

我想在这张图片上检测到10个英雄圈:

Minimap

像这样:

Result Expected

要删除背景,我可以使用:

Clean Minimap

英雄肖像圆的半径在8到12之间,因为英雄肖像像21x21px。

使用此代码

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

我得到了这个:

Remove static background

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

    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);
    }

结果不正常,仅在10上检测到2:

Circles detections

我也尝试过knn背景:

KNN Substraction

成功率较低。 有小费吗 ?非常感谢。

3 个答案:

答案 0 :(得分:3)

问题是你的小地图包含突出显示的部分(可能在活动玩家周围),导致你的背景移除无法操作。为什么不从图像中突出显示突出显示的颜色?从我看到的只有少数几个。我不使用 OpenCV 所以我在 C ++ 中试了一下这里的结果是:

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");

所以你需要找到OpenCV计数器部件。阈值是颜色距离^ 2(因此我不需要sqrt),看起来50^2非常适合每个通道 RGB 矢量<0,255>

我将自己的图片类用于图片,因此有些成员是:


xs,ys是图像的大小(以像素为单位)
p[y][x].dd(x,y)位置的像素,为32位整数类型
clear(color)使用color清除整个图像
resize(xs,ys)将图片调整为新分辨率
bmp VCL 已封装 GDI 具有Canvas访问权限的位图
pf保存图像的实际像素格式:

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和像素编码如下:

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; };*/
    };

这些乐队是:

enum{
    _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:

enter image description here

<强> PIC1:

enter image description here

这里的(子)结果:

<强> out0.png:

enter image description here

<强> out1.png:

enter image description here

<强> out2.png:

enter image description here

现在只需消除噪音(通过模糊或侵蚀)并应用圆拟合或霍夫变换......

[Edit1]圆形探测器

我给了它一些教导并实现了简单的探测器。我只检查具有恒定半径(玩家圈子)的任何像素位置周围的圆周点,如果设定点的数量高于阈值,我发现了潜在的圆圈。它比使用整个光盘区域更好,因为一些播放器包含孔并且还有更多的像素可以测试...然后我将近圆平均放在一起并渲染输出......这里更新的代码:

    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");

正如您所看到的,代码的核心是相同的,我最后添加了探测器。

我也使用我的动态列表模板:


List<double> xxx;double xxx[];相同
xxx.add(5);5添加到列表的末尾
xxx[7]访问数组元素(安全)
xxx.dat[7]访问数组元素(不安全但快速直接访问)
xxx.num是数组的实际使用大小
xxx.reset()清除数组并设置xxx.num=0
xxx.allocate(100)100项目预分配空间

这里的最终结果是 out3.png

final output

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

我使用 VCL / GDI 进行圈子渲染,因此只需忽略/将pic2.bmp->Canvas->内容移植到您使用的内容中。

答案 1 :(得分:0)

由于填充的图像在英雄周围的蓝色区域较浅,因此您的背景减法几乎没用。

我尝试通过在减法之前对清晰图像应用3的增益来改进,这是结果。

背景已经消失,但英雄的轮廓严重受损。

我用其他方法看了你的情况,我认为这是一个非常困难的方法。

enter image description here

答案 2 :(得分:0)

当我想要进行图像处理时,我首先在绘画编辑器中打开图像(我使用Gimp)。然后我操纵图像,直到我最终定义了我想要检测的部分。 通常,RGB对于许多计算机视觉任务是不利的,并且使其成为灰度仅解决了问题的一部分。 一个好的开始是尝试将图像分解为HSL。 在第一张图片上这样做,只看Hue频道给我这个:

Hue component of minimap

有几个blob定义得很清楚。

使用Hue和Luminance图层的对比度和亮度进行一些比较并将它们相乘给我:

H * L

它增强了标记周围的环,这可能很有用。

这些方法在OpenCV中都具有相应的功能。

这是一项棘手的任务,您很可能需要几种不同的过滤器和技术才能成功。希望这个对你有帮助。祝你好运。