对于您的源图像
I would:
-
创建棕色东西的面具
只是门槛H,S
并忽略V
,你已经有了这个。我使用整数255
而不是颜色(Blue)用于后面的计算。
-
模糊面具
这将删除小簇错误选择的零件。之后,您应该再次对掩码进行阈值处理,因为掩码值会稍微小一点255
除非你完全选择了区域。面积越大,值越大(越接近255
)。我的阈值是>=150
通过水平线扫描掩模
-
对于每条线找到所有选定像素的重心
再次模糊和阈值处理后,使用过的蒙版位于Aqua。所以计算平均点x
每行中所有屏蔽像素的坐标。该点标记为White.
-
通过所有重心的回归线
我用我的近似搜索 https://stackoverflow.com/a/36368290/2521214为此,您可以使用任何您想要的回归。回归线标记为Red
我用的是直线方程x=x0+y*dx
where y=<0,pic1.ys>
。并按间隔搜索解:
x0=<-pic1.xs,+2*pic1.xs>
dx=<-10,+10>
Where pic1.xs,pic1.ys
是图像分辨率。正如您所看到的,我没有涵盖所有角度,但我认为这无论如何都不适用于这些边缘情况(接近水平方向)。对于这种情况,您应该在垂直线上执行此操作并使用x=y0+x*dy
反而。
Here C++我这样做的来源:
picture pic0,pic1;
// pic0 - source img
// pic1 - output img
int x,y,h,s,v,px,pn,*p;
color c;
// copy source image to output
pic1=pic0;
pic1.save("cornbot0.png");
// create brown stuff mask
for (y=0;y<pic1.ys;y++) // scan all H lines
for (x=0;x<pic1.xs;x++) // scan actual H line
{
c=pic1.p[y][x]; // get pixel color
rgb2hsv(c); // in HSV
h=WORD(c.db[picture::_h]);
s=WORD(c.db[picture::_s]);
v=WORD(c.db[picture::_v]);
// Treshold brownish stuff
if ((abs(h- 20)<10)&&(abs(s-200)<50)) c.dd=255; else c.dd=0;
pic1.p[y][x]=c;
}
pic1.save("cornbot1.png");
pic1.smooth(10); // blur a bit to remove small clusters as marked
pic1.save("cornbot2.png");
// compute centers of gravity
p=new int[pic1.ys]; // make space for points
for (y=0;y<pic1.ys;y++) // scan all H lines
{
px=0; pn=0; // init center of gravity (avg point) variables
for (x=0;x<pic1.xs;x++) // scan actual H line
if (pic1.p[y][x].dd>=150) // use marked points only
{
px+=x; pn++; // add it to avg point
pic1.p[y][x].dd=0x00004080; // mark used points (after smooth) with Aqua
}
if (pn) // finish avg point computation
{
px/=pn;
pic1.p[y][px].dd=0x00FFFFFF;// mark it by White
p[y]=px; // store result for line regression
} else p[y]=-1; // uncomputed value
}
// regress line
approx x0,dx;
double ee;
for (x0.init(-pic1.xs,pic1.xs<<1,100,3,&ee); !x0.done; x0.step()) // search x0
for (dx.init(-10.0 ,+10.0 ,1.0,3,&ee); !dx.done; dx.step()) // search dx
for (ee=0.0,y=0;y<pic1.ys;y++) // compute actua solution distance to dataset
if (p[y]!=-1) // ignore uncomputed values (no brown stuff)
ee+=fabs(double(p[y])-x0.a-(double(y)*dx.a));
// render regressed line with Red
for (y=0;y<pic1.ys;y++)
{
x=double(x0.aa+(double(y)*dx.aa));
if ((x>=0)&&(x<pic1.xs))
pic1.p[y][x].dd=0x00FF0000;
}
pic1.save("cornbot2.png");
delete[] p;
我用我自己的picture
图像类,因此一些成员是:
-
xs,ys
图像大小(以像素为单位)
-
p[y][x].dd
像素位于(x,y)
位置为 32 位整数类型
-
p[y][x].dw[2]
像素位于(x,y)
位置为 2x16 位整数类型2D fields
-
p[y][x].db[4]
像素位于(x,y)
位置为 4x8 位整数类型,方便通道访问
-
clear(color)
- 清除整个图像
-
resize(xs,ys)
- 将图像大小调整为新分辨率
-
bmp
- VCL封装的GDI具有 Canvas 访问权限的位图
-
smooth(n)
- 快速模糊图像n
times
您可以通过基于区域和位置的分割(删除小簇)来进一步改进这一点。您也可以忽略邻居之间平均点过大的峰值。您还可以检测天空并忽略天空存在的整个区域。
[编辑1] 平滑
这就是我的平滑的样子:
void picture::smooth(int n)
{
color *q0,*q1;
int x,y,i,c0[4],c1[4],c2[4];
bool _signed;
if ((xs<2)||(ys<2)) return;
for (;n>0;n--)
{
#define loop_beg for (y=0;y<ys-1;y++){ q0=p[y]; q1=p[y+1]; for (x=0;x<xs-1;x++) { dec_color(c0,q0[x],pf); dec_color(c1,q0[x+1],pf); dec_color(c2,q1[x],pf);
#define loop_end enc_color(c0,q0[x ],pf); }}
if (pf==_pf_rgba) loop_beg for (i=0;i<4;i++) { c0[i]=(c0[i]+c0[i]+c1[i]+c2[i])>>2; clamp_u8(c0[i]); } loop_end
if (pf==_pf_s ) loop_beg { c0[0]=(c0[0]+c0[0]+c1[0]+c2[0])/ 4; clamp_s32(c0[0]); } loop_end
if (pf==_pf_u ) loop_beg { c0[0]=(c0[0]+c0[0]+c1[0]+c2[0])>>2; clamp_u32(c0[0]); } loop_end
if (pf==_pf_ss ) loop_beg for (i=0;i<2;i++) { c0[i]=(c0[i]+c0[i]+c1[i]+c2[i])/ 4; clamp_s16(c0[i]); } loop_end
if (pf==_pf_uu ) loop_beg for (i=0;i<2;i++) { c0[i]=(c0[i]+c0[i]+c1[i]+c2[i])>>2; clamp_u16(c0[i]); } loop_end
#undef loop_beg
#define loop_beg for (y=ys-1;y>0;y--){ q0=p[y]; q1=p[y-1]; for (x=xs-1;x>0;x--) { dec_color(c0,q0[x],pf); dec_color(c1,q0[x-1],pf); dec_color(c2,q1[x],pf);
if (pf==_pf_rgba) loop_beg for (i=0;i<4;i++) { c0[i]=(c0[i]+c0[i]+c1[i]+c2[i])>>2; clamp_u8(c0[i]); } loop_end
if (pf==_pf_s ) loop_beg { c0[0]=(c0[0]+c0[0]+c1[0]+c2[0])/ 4; clamp_s32(c0[0]); } loop_end
if (pf==_pf_u ) loop_beg { c0[0]=(c0[0]+c0[0]+c1[0]+c2[0])>>2; clamp_u32(c0[0]); } loop_end
if (pf==_pf_ss ) loop_beg for (i=0;i<2;i++) { c0[i]=(c0[i]+c0[i]+c1[i]+c2[i])/ 4; clamp_s16(c0[i]); } loop_end
if (pf==_pf_uu ) loop_beg for (i=0;i<2;i++) { c0[i]=(c0[i]+c0[i]+c1[i]+c2[i])>>2; clamp_u16(c0[i]); } loop_end
#undef loop_beg
#undef loop_end
}
}
它只是对 3 个像素进行加权平均
(x,y)=(2*(x,y)+(x-1,y)+(x,y-1))/4
然后做同样的事情
(x,y)=(2*(x,y)+(x+1,y)+(x,y+1))/4
以避免图像移动。然后这整个事情就循环了n
次,仅此而已。在这种情况下,您可以忽略钳制和像素格式选项pf==_pf_rgba
但无论如何它只使用单个通道......dec_color,enc_color
只需将颜色通道解包、打包到变量数组中或从变量数组中打包,以避免 8 位通道上的截断和溢出问题,并更好地格式化/简化代码(针对不同的像素格式支持)
顺便说一句,平滑基与卷积相同
0.00 0.25 0.00
0.25 0.50 0.00
0.00 0.00 0.00
and
0.00 0.00 0.00
0.00 0.50 0.25
0.00 0.25 0.00