【问题标题】:Detectings small circles on game minimap检测游戏小地图上的小圆圈
【发布时间】:2023-03-04 17:55:02
【问题描述】:

我在这个问题上卡住了 20 小时。

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

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

像这样:

为了去除背景,我可以使用这个:

英雄头像圆半径在 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();

        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个:

我也尝试过 knn 背景:

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

【问题讨论】:

  • 为什么要使用模糊?它摆脱了重要的边缘。如果您在精明的边缘检测器之后发布图像会很好,可能会显示那里有什么问题。由于水的颜色,对这张图片进行阈值处理是一个棘手的问题。
  • 字符图标是否一直看起来都一样(无/低压缩伪影)并且大小固定?然后可能使用模板匹配(可能使用掩码来加速)或关键点匹配(确保描述符窗口足够小)。

标签: opencv geometry detection hough-transform


【解决方案1】:

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

我尝试通过在减法之前对干净图像应用 3 的增益来进行改进,结果如下。

背景已经消失,但英雄的轮廓被严重破坏。

我用其他方法查看了您的案例,我认为这是一个非常困难的案例。

【讨论】:

  • 我只是想先检测红色圆圈。所以This code 可能需要一些调整,我有这个image。但是从中心开始的圆圈缺少很多点。如果我能有更好的圈子,也许这种方法会很好。然后我可以对蓝色的做同样的事情。
  • 我已经检查过了。恐怕你的图片颜色太丰富了,这种过滤效果不好。
  • 好的,我真的很接近这种方法的解决方案。使用 minimapDiffMat 和 knn 图像,我从两者中提取红色像素,然后对其求和。我添加了一些膨胀和一些高斯模糊。我得到this。它现在检测到4 circles!我想我现在需要一些关于 gaussianblur 和 dilate 方法的帮助(首先不知道我们是否真的需要它,它是否有利于圆形检测或如何正确设置参数)。这是code
【解决方案2】:

当我想进行图像处理时,我首先在绘图编辑器中打开图像(我使用 Gimp)。然后我操纵图像,直到我最终得到一些定义我想要检测的部分的东西。 一般来说,RGB 对很多计算机视觉任务都是不利的,将其设为灰度只能解决部分问题。 一个好的开始是尝试将图像分解为 HSL。 在第一张图片上这样做,并且只看色调通道给我这个:

有几个 blob 的定义非常明确。

稍微调整一下色相和亮度层的对比度和亮度,然后将它们相乘得到:

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

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

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

【讨论】:

    【解决方案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 向量的 &lt;0,255&gt;

    我使用自己的图片类来制作图片,所以一些成员是:


    xs,ys 是图像的大小(以像素为单位)
    p[y][x].dd(x,y) 位置的像素,为 32 位整数类型
    clear(color)color 清除整个图像
    resize(xs,ys) 将图像大小调整为新分辨率
    bmpVCL 封装的 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:

    pic1:

    这里是(子)结果:

    out0.png:

    out1.png:

    out2.png:

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

    [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&lt;double&gt; 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

    正如您所看到的,当玩家非常靠近时(由于圆形平均),通过一些调整您可能会得到更好的结果,这会有点混乱。但是第二次教它可能是由于附近的那个小红圈......

    我使用 VCL/GDI 来渲染圆圈,所以只需忽略/移植 pic2.bmp-&gt;Canvas-&gt; 的东西到你使用的任何东西。

    【讨论】:

    • 非常感谢!我已经实现了这个并得到与 OUT2 相同的结果。现在我正在研究侵蚀、模糊、扩张等……;)
    • @GuillaumeKabaLEttori 添加了带有非常简单/原始的圆圈检测器的edit1 ...整个代码在我的机器上运行约35ms,没有png保存...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-02
    • 2015-06-14
    • 2013-11-12
    • 2014-07-08
    • 1970-01-01
    相关资源
    最近更新 更多