【问题标题】:find archery target in image of different perspectives在不同视角的图像中找到射箭目标
【发布时间】:2016-04-11 04:07:32
【问题描述】:

我正在尝试找到一种方法来识别可能由不同视角组成的照片上的射箭目标及其所有环:

我的目标是识别目标,然后在箭头击中目标的位置自动计算得分。假设如下:

  • 摄像头的位置不固定,可能会发生变化
  • 射箭目标也可能会轻微移动或旋转
  • 目标的大小可能不同,圆圈的数量也不同
  • 目标上可能有很多孔(有时是大划痕)

我已经尝试过 OpenCV 来查找轮廓,但即使进行了预处理(灰度 -> 模糊(-> 阈值)-> 边缘检测),我仍然发现了一些被箭头或其他障碍物(孔洞)分散注意力的轮廓) 在目标上,因此不可能找到一条漂亮的圆形线。使用 Hough 查找圆圈也不起作用,因为它会给我带来奇怪的结果,因为 Hough 只会找到完美的圆圈而不是椭圆。

通过预处理图像,这是迄今为止我最好的结果:

我在考虑椭圆和圆形拟合,但由于我不知道目标的半径、位置和姿势,这可能是一项非常消耗 CPU 的任务。另一个想法是使用模板识别,但目标的位置和旋转经常变化。

现在我的想法是沿着图像上的每一条线检查它是否是一条曲线,然后猜测哪些曲线属于一起形成一个圆/椭圆(椭圆是因为透视)。问题是这些线可能与短距离内的箭头或孔相交,因此线太短而无法检查它是否是曲线。目标上的小圆圈很有可能根本无法识别。另外,如您所见,圆圈 8、7 和 6 在左侧没有清晰的线。

我认为,只要我能清楚地识别目标中的所有环,我就不需要进行透视校正来完成这项任务。

我在谷歌上搜索了很长时间,发现了一些论文,这些论文都没有完全集中在这个特定的任务上,而且太数学了,我无法理解。

有没有可能完成这项任务?你能和我分享一个如何解决这个问题的想法吗?任何事情都非常感谢。

我在 Java 中执行此操作,但编程语言是次要的。如果您需要更多详细信息,请告诉我。

【问题讨论】:

  • 颜色是固定的(不匹配确切的色调)所以里面是黄色,中间是红色,外面是蓝色,或者这对于不同的目标可能会有所不同?
  • 嗨 Micka,有不同的目标纸,有些根本不使用颜色,只是圈线。
  • 非常有趣的任务,如果有更多不同目标和视角的示例图像会很好。我想如果没有箭头,假设每个圆都是单线轮廓是不合法的,因为某些目标可能像典型的目标十字一样具有这些垂直线?
  • google上有很多:google.de/search?q=archery+target 看到这些图片我才发现,10支箭都打在黄色圆圈里的箭数是不可能的。密度太高,无法区分它们。

标签: java opencv image-processing


【解决方案1】:

初学者见

如果您使用图像上的标准化目标(顺便说一句。我的弓也使用这些目标:))然后不要切断颜色。您可以选择蓝色红色和黄色像素区域以简化检测。见:

从那里你需要适应圆圈。但是当你得到透视时,物体不是圆形也不是椭圆形。你有两个选择:

  1. 透视校正

    使用右下角的表格矩形区域作为标记(或整个目标)。它是具有已知纵横比的矩形。所以在图像上测量它并构建将改变图像的变换,使其再次变为矩形。这方面有很多东西:3D 场景重建所以谷歌/阅读/实施。基本只基于 De-skew + scaling。

  2. 椭圆近似圆(不是轴对齐!)

    所以将椭圆拟合到找到的边缘而不是圆形。这不会那么精确,但仍然足够接近。见:

[Edit1] 很抱歉有一段时间没有时间/心情

由于您无法自行调整我的方法,因此:

  1. 去除噪音

    您需要重新着色您的图像以消除噪音以减轻其余部分...我将其转换为 HSV 并通过简单的阈值检测您的 4 种颜色(圆圈+纸)并将图像重新着色为4 种颜色(圆圈、纸张、背景)回到 RGB 空间。

  2. 填补空白

    在一些临时图像中,我填补了由箭头和其他东西创建的圆圈中的空白。这很简单,只需从图像的相对两侧(每行/行)扫描像素,如果击中选定的圆圈颜色,则停止(您需要从外圈转到内圈,而不是覆盖以前的圆圈......)。现在只需用您选择的圆圈颜色填充这两个点之间的空间。 (我从纸开始,然后是蓝色、红色和黄色):

  3. 现在您可以使用链接方法

    所以找到每种颜色的平均点,即大约圆心。然后做一个radius-es的直方图并选择最大的一个。从这里开始,只需将线条从圆圈中投射出来,然后找到圆圈真正停止的位置并从中计算椭圆半轴并更新中心(处理透视失真)。为了直观地检查,我将 #1 中的每个圆圈的十字和圆圈渲染到图像中:

    如您所见,它非常接近。如果您需要更好的匹配,请投射更多线(不仅仅是 90 度 H、V 线)以获得更多点并以代数方式计算椭圆或通过近似拟合(第二个链接)

C++ 代码(解释请看第一个链接):

picture pic0,pic1,pic2;
    // pic0 - source
    // pic1 - output
    // pic2 - temp
DWORD c0;
int x,y,i,j,n,m,r,*hist;
int x0,y0,rx,ry;    // ellipse
const int colors[4]=// color sequence from center
    {
    0x00FFFF00, // RGB yelow
    0x00FF0000, // RGB red
    0x000080FF, // RGB blue
    0x00FFFFFF, // RGB White
    };

// init output as source image and resize temp to same size
pic1=pic0;
pic2=pic0; pic2.clear(0);

// recolor image (in HSV space -> RGB) to avoid noise and select target pixels
pic1.rgb2hsv();
for (y=0;y<pic1.ys;y++)
 for (x=0;x<pic1.xs;x++)
    {
    color c;
    int h,s,v;
    c=pic1.p[y][x];
    h=c.db[picture::_h];
    s=c.db[picture::_s];
    v=c.db[picture::_v];
    if (v>100)  // bright enough pixels?
        {
        i=25; // treshold
             if (abs(h- 40)+abs(s-225)<i) c.dd=colors[0]; // RGB yelow
        else if (abs(h-250)+abs(s-165)<i) c.dd=colors[1]; // RGB red
        else if (abs(h-145)+abs(s-215)<i) c.dd=colors[2]; // RGB blue
        else if (abs(h-145)+abs(s- 10)<i) c.dd=colors[3]; // RGB white
        else                              c.dd=0x00000000; // RGB black means unselected pixels
        } else                            c.dd=0x00000000; // RGB black
    pic1.p[y][x]=c;
    }
pic1.save("out0.png");
// fit ellipses:
pic1.bmp->Canvas->Pen->Width=3;
pic1.bmp->Canvas->Pen->Color=0x0000FF00;
pic1.bmp->Canvas->Brush->Style=bsClear;
m=(pic1.xs+pic1.ys)*2;
hist=new int[m]; if (hist==NULL) return;
for (j=3;j>=0;j--)
    {
    // select color per pass
    c0=colors[j];
    // fill the gaps with H,V lines into temp pic2
    for (y=0;y<pic1.ys;y++)
        {
        for (x=        0;(x<pic1.xs)&&(pic1.p[y][x].dd!=c0);x++); x0=x;
        for (x=pic1.xs-1;(x>     x0)&&(pic1.p[y][x].dd!=c0);x--);
        for (;x0<x;x0++) pic2.p[y][x0].dd=c0;
        }
    for (x=0;x<pic1.xs;x++)
        {
        for (y=        0;(y<pic1.ys)&&(pic1.p[y][x].dd!=c0);y++); y0=y;
        for (y=pic1.ys-1;(y>     y0)&&(pic1.p[y][x].dd!=c0);y--);
        for (;y0<y;y0++) pic2.p[y0][x].dd=c0;
        }
    if (j==3) continue; // do not continue for border
    // avg point (possible center)
    x0=0; y0=0; n=0;
    for (y=0;y<pic2.ys;y++)
     for (x=0;x<pic2.xs;x++)
      if (pic2.p[y][x].dd==c0)
       { x0+=x; y0+=y; n++; }
    if (!n) continue;               // no points found
    x0/=n; y0/=n;                   // center
    // histogram of radius
    for (i=0;i<m;i++) hist[i]=0;
    n=0;
    for (y=0;y<pic2.ys;y++)
     for (x=0;x<pic2.xs;x++)
      if (pic2.p[y][x].dd==c0)
        {
        r=sqrt(((x-x0)*(x-x0))+((y-y0)*(y-y0))); n++;
        hist[r]++;
        }
    // select most occurent radius (biggest)
    for (r=0,i=0;i<m;i++)
     if (hist[r]<hist[i])
      r=i;
    // cast lines from possible center to find edges (and recompute rx,ry)
    for (x=x0-r,y=y0;(x>=     0)&&(pic2.p[y][x].dd==c0);x--); rx=x; // scan left
    for (x=x0+r,y=y0;(x<pic2.xs)&&(pic2.p[y][x].dd==c0);x++);       // scan right
    x0=(rx+x)>>1; rx=(x-rx)>>1;
    for (x=x0,y=y0-r;(y>=     0)&&(pic2.p[y][x].dd==c0);y--); ry=y; // scan up
    for (x=x0,y=y0+r;(y<pic2.ys)&&(pic2.p[y][x].dd==c0);y++);       // scan down
    y0=(ry+y)>>1; ry=(y-ry)>>1;

    i=10;
    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);
    //rx=r; ry=r;
    pic1.bmp->Canvas->Ellipse(x0-rx,y0-ry,x0+rx,y0+ry);
    }
pic2.save("out1.png");
pic1.save("out2.png");
pic1.bmp->Canvas->Pen->Width=1;
pic1.bmp->Canvas->Brush->Style=bsSolid;
delete[] hist;

【讨论】:

  • 谢谢你的帖子,我试过了,但还没有成功。我在这里创建了一个后续问题:stackoverflow.com/questions/36691419/…
  • @Normalo 有一段时间没有时间做这个了,它很容易实现(很难说你哪里弄错了),所以我添加了 edit1 以防万一。
猜你喜欢
  • 2017-04-05
  • 1970-01-01
  • 2012-10-06
  • 2019-03-06
  • 1970-01-01
  • 2017-10-15
  • 2021-03-24
  • 2018-11-28
  • 1970-01-01
相关资源
最近更新 更多