从纸靶上检测圆形和射击点

7
我正在制作一个小项目,需要从给定的纸靶图像中检测得分点,类似于iPhone上的TargetScan应用程序。
我使用openCV处理图像,基本上有两个部分,一是从靶标上检测出圆形(使用Hough Circle Transform功能非常好),二是检测出击打点。我需要一些想法来从给定的图像中检测这些击打点。OpenCV中可以使用哪些算法来检测这些击打点?
这里是一个带有圆形检测的示例图像(绿线表示检测到的圆形,红点表示圆心)。下面是第二个示例图像:
1个回答

6

算法:

  1. 为图像创建/清除掩模
  2. 将图像二值化(通过某个强度阈值转为黑白)
  3. 处理所有像素
  4. 计算同一颜色像素在 x、y 方向上有多少个

    称之为 wx, wy

  5. 检测圆形、射击和中间部分

    圆形很细,因此 wxwy 应小于细阈值,另一个应较大。 射击很大,因此 wxwy 都必须在射击直径范围内。 中间部分为黑色,wx, wy 均超过阈值(可以在此处计算平均点)。将此信息存储在掩模中

  6. 使用掩模信息重新着色图像

  7. 从找到的点计算圆的中心和半径

    中心是中间部分区域的平均点,现在处理所有绿色点并为其计算半径。对于所有找到的半径进行直方图,按计数降序排序。如果计数与 2*PI*r 不一致,则忽略此类点。

  8. 将射击像素组合在一起

    因此,对于每个命中点进行分割或泛洪重新着色,以避免单个射击的多次计算

我用 C++ 乐在其中编写了 #1..#6 的代码,以下是代码:

    picture pic0,pic1,pic2;
        // pic0 - source
        // pic1 - output
        // pic2 - mask
    int x,y,i,n,wx,wy;
    int r0=3;           // thin curve wide treshod [pixels]
    int r1a=15;         // shot diameter min treshod [pixels]
    int r1b=30;         // shot diameter max treshod [pixels]
    int x0,y0;          // avg point == center
    // init output as source image but in grayscale intensity only
    pic1=pic0;
    pic1.rgb2i();
    // init mask (size of source image)
    pic2.resize(pic0.xs,pic0.ys);
    pic2.clear(0);
    // binarize image and convert back to RGB
    for (y=r0;y<pic1.ys-r0-1;y++)
     for (x=r0;x<pic1.xs-r0-1;x++)
      if (pic1.p[y][x].dd<=500) // Black/White treshold <0,765>
           pic1.p[y][x].dd=0x00000000; // Black in RGB
      else pic1.p[y][x].dd=0x00FFFFFF; // White in RGB
    // process pixels
    x0=0; y0=0; n=0;
    for (y=r1b;y<pic1.ys-r1b-1;y++)
     for (x=r1b;x<pic1.xs-r1b-1;x++)
        {
        wy=1;   // count the same color pixels in column
        for (i=1;i<=r1b;i++) if (pic1.p[y-i][x].dd==pic1.p[y][x].dd) wy++; else break;
        for (i=1;i<=r1b;i++) if (pic1.p[y+i][x].dd==pic1.p[y][x].dd) wy++; else break;
        wx=1;   // count the same color pixels in line
        for (i=1;i<=r1b;i++) if (pic1.p[y][x-i].dd==pic1.p[y][x].dd) wx++; else break;
        for (i=1;i<=r1b;i++) if (pic1.p[y][x+i].dd==pic1.p[y][x].dd) wx++; else break;
        if ((wx<r0)||(wy<r0))       // if thin
         if ((wx>=r0)||(wy>=r0))    // but still line
            {
            pic2.p[y][x].dd=1;      // thin line
            }
        if (pic1.p[y][x].dd==0)     // black
         if ((wx>=r0)&&(wy>=r0))    // and thick in both axises
            {
            pic2.p[y][x].dd=2;      // middle section
            x0+=x; y0+=y; n++;
            }
        if (pic1.p[y][x].dd)        // white (background color)
        if ((wx>r1a)&&(wy>r1a))     // size in range of shot
         if ((wx<r1b)&&(wy<r1b))
            {
            pic2.p[y][x].dd=3;      // shot
            }
        }
     if (n) { x0/=n; y0/=n; }

    // add mask data (recolor) to output image
//  if (0)
    for (y=0;y<pic1.ys;y++)
     for (x=0;x<pic1.xs;x++)
        {
        if (pic2.p[y][x].dd==1) pic1.p[y][x].dd=0x0000FF00; // green thin line
        if (pic2.p[y][x].dd==2) pic1.p[y][x].dd=0x000000FF; // blue midle section
        if (pic2.p[y][x].dd==3) pic1.p[y][x].dd=0x00FF0000; // red shots
        }

    // Center cross
    i=25;
    pic1.bmp->Canvas->Pen->Color=0x0000FF;
    pic1.bmp->Canvas->MoveTo(x0-i,y0);
    pic1.bmp->Canvas->LineTo(x0+i,y0);
    pic1.bmp->Canvas->MoveTo(x0,y0-i);
    pic1.bmp->Canvas->LineTo(x0,y0+i);

我使用自己的图片类处理图像,其中一些成员如下:


xs,ys 图像大小(以像素为单位)
p[y][x].dd 是位于(x,y)位置的像素,类型为32位整数
clear(color) - 清除整个图像
resize(xs,ys) - 将图像调整为新分辨率

这是重新上色的结果

example

  • 绿色 - 细圆圈
  • 蓝色 - 中间部分
  • 红色 - 十字架(圆圈中心)
  • 红色 - 子弹

正如您所看到的,它需要从#7、#8子弹进一步处理,而且您的图像在中间部分之外没有射击,因此可能需要对中间部分之外的射击检测进行一些微调。

[编辑1] 半径

// create & clear radius histogram
n=xs; if (n<ys) n=ys;
int *hist=new int[n];
for (i=0;i<n;i++) hist[i]=0;
// compute histogram
for (y=0;y<pic2.ys;y++)
 for (x=0;x<pic2.xs;x++)
  if (pic2.p[y][x].dd==1)   // thin pixels
    {
    i=sqrt(((x-x0)*(x-x0))+((y-y0)*(y-y0)));
    hist[i]++;
    }
// merge neigbour radiuses
for (i=0;i<n;i++)
 if (hist[i])
    {
    for (x=i;x<n;x++) if (!hist[x]) break;
    for (wx=0,y=i;y<x;y++) { wx+=hist[y]; hist[y]=0; }
    hist[(i+x-1)>>1]=wx; i=x-1;
    }
// draw the valid circles
pic1.bmp->Canvas->Pen->Color=0xFF00FF;  // magenta
pic1.bmp->Canvas->Pen->Width=r0;
pic1.bmp->Canvas->Brush->Style=bsClear;
for (i=0;i<n;i++)
 if (hist[i])
    {
    float a=float(hist[i])/(2.0*M_PI*float(i));
    if ((a>=0.3)&&(a<=2.1))
     pic1.bmp->Canvas->Ellipse(x0-i,y0-i,x0+i,y0+i);
    }
pic1.bmp->Canvas->Brush->Style=bsSolid;
pic1.bmp->Canvas->Pen->Width=1;
delete[] hist;

半径圆

检测到的圆是品红色的...我认为这很不错。中间部分有些问题。您可以计算平均半径步长并插值缺失的圆...


你能分享一下你的图片类吗?我想使用它。 - Aman
@Aman 不过,这可能对你没有好处,因为它是VCL绑定的,并且使用了一些公司代码(这就是我不能分享的原因)。C++类本身是纯源代码,大约120 KByte。但是,像带有描述的方法这样的特定部分是可以共享的,因为我在SO/SE上经常这样做...如果您只想要快速简单的像素访问,请参见此Delphi/C++ Builder Windows 10 1709位图操作极其缓慢,因为我的图片是基于相同的东西构建的。 - Spektre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接