初学者见
如果您使用图像上的标准化目标(顺便说一句。我的弓也使用这些目标:))然后不要切断颜色。您可以选择蓝色红色和黄色像素区域以简化检测。见:
从那里你需要适应圆圈。但是当你得到透视时,物体不是圆形也不是椭圆形。你有两个选择:
-
透视校正
使用右下角的表格矩形区域作为标记(或整个目标)。它是具有已知纵横比的矩形。所以在图像上测量它并构建将改变图像的变换,使其再次变为矩形。这方面有很多东西:3D 场景重建所以谷歌/阅读/实施。基本只基于 De-skew + scaling。
-
椭圆近似圆(不是轴对齐!)
所以将椭圆拟合到找到的边缘而不是圆形。这不会那么精确,但仍然足够接近。见:
[Edit1] 很抱歉有一段时间没有时间/心情
由于您无法自行调整我的方法,因此:
-
去除噪音
您需要重新着色您的图像以消除噪音以减轻其余部分...我将其转换为 HSV 并通过简单的阈值检测您的 4 种颜色(圆圈+纸)并将图像重新着色为4 种颜色(圆圈、纸张、背景)回到 RGB 空间。
-
填补空白
在一些临时图像中,我填补了由箭头和其他东西创建的圆圈中的空白。这很简单,只需从图像的相对两侧(每行/行)扫描像素,如果击中选定的圆圈颜色,则停止(您需要从外圈转到内圈,而不是覆盖以前的圆圈......)。现在只需用您选择的圆圈颜色填充这两个点之间的空间。 (我从纸开始,然后是蓝色、红色和黄色):
-
现在您可以使用链接方法
所以找到每种颜色的平均点,即大约圆心。然后做一个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;