如何从Image中检测机器人方向?

时间:2016-05-11 07:14:53

标签: java image-processing computer-vision boofcv

我正在开发可以在玉米植物中运行并由罗盘传感器引导的机器人,但我想将相机用作机器人的眼睛,并使用图像处理来检测运动的误差角度。

这是图片示例。

已处理的图片 processed image 原始图片 raw image 分段图片 segmented image

我使用以下步骤

  1. 第1步:我使用的当前技术是将颜色值转换为从this code修改的HSV

  2. 步骤2:所以它将检测选定的颜色,即棕色或污垢颜色,然后我收集最左右两个棕色或两个阵列中每个图像行的选定颜色(红点)。

  3. 步骤3:我将2线性回归线绘制为蓝点,并将交点计算为粉红点
  4. 步骤4:绘制绿线以将粉红点与其他图像进行比较。我不知道如何处理这条绿线
  5. 问题是玉米叶子之间是否存在污垢或棕色,然后我让我的代码错过计算
  6. 问题是如何滤除玉米叶片之间的棕色像素或不在玉米路径中的其他区域?我应该在这个问题上研究或应用哪种算法或方法?

    EDIT1:使用Spektre的答案,看起来更好

    以下是我使用JAVA + Boofcv

    后的结果
    • 第1步:阈值处理或颜色分割 Thresholding or Color Segmentation

    • 第2步:模糊(使用高斯和中值滤波器) Blured

    • 第3步:绘制线性回归 Plot Linear Regression

      

    更多信息

         

    Full Code Here

         

    LinearRegression Class

         

    10 example images with the same process

2 个答案:

答案 0 :(得分:3)

对于您的源图片

source

我会:

  1. 创建棕色东西的面具

    仅限阈值H,S并忽略V,您已经拥有此功能。我使用整数255代替颜色(蓝色)进行后期计算。

    mask

  2. 模糊面具

    这将删除错误选择的部分的小群集。在此之后,您应该再次阈值掩码,因为掩码值将比255小一点,除非您有完全选定的区域。区域越大,值越大(越接近255)。我使用>=150

  3. 进行了限制
  4. 按水平线扫描遮罩

  5. 每条线的
  6. 找到所有选定像素的重心

    再次模糊和阈值后,使用过的蒙版处于 Aqua 。因此,计算每行中所有蒙板像素的平均点x坐标。此点标有白色

  7. 回归所有重心

    我使用我的approximation search,但你可以使用你想要的任何回归。回归线标有红色

    我使用了行x=x0+y*dxy=<0,pic1.ys>。并按时间间隔搜索解决方案:

    x0=<-pic1.xs,+2*pic1.xs>
    dx=<-10,+10>
    
  8. pic1.xs,pic1.ys是图像分辨率。所以你可以看到我没有涵盖全范围的角度,但我认为这无论如何都不适用于那些边缘情况(接近水平方向)。对于这种情况,您应该在垂直线上执行此操作,而是使用x=y0+x*dy

    final result

    这里 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)位置的像素,为 2D 字段
    • 字段的2x16位整数类型
    • p[y][x].db[4](x,y)位置的像素,为4x8位整数类型,便于频道访问
    • clear(color) - 清除整个图片
    • resize(xs,ys) - 将图片大小调整为新分辨率
    • bmp - VCL 封装 GDI 带有Canvas访问权限的位图
    • smooth(n) - 快速模糊图片n

    您可以根据区域和位置进行细分(删除小群集),从而进一步改善这一点。你也可以忽略邻居之间平均点的太大峰值。您也可以检测天空并忽略天空存在的整个区域。

    [edit1]顺畅

    这就是我顺利的样子:

    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位通道上的溢出问题,以及更好地格式化/简化代码(对于不同的像素格式支持)

    btw smooth base与

    的卷积相同
    0.00 0.25 0.00
    0.25 0.50 0.00
    0.00 0.00 0.00
    

    0.00 0.00 0.00
    0.00 0.50 0.25
    0.00 0.25 0.00
    

答案 1 :(得分:0)

如果我是对的,你会问到被误入歧途的棕色部分还是背景的其他部分?

你是怎么得到最后一张照片的?我假设你用原始图像乘以一个面具?即使你没有,你也可以通过选择图像所在的任何地方(图中任何简单,非常低的阈值)来简单地从图像中获取蒙版。 (应用自适应阈值处理,更准确的原始版本以获得更好的掩模)

拿出面具并使用形态学操作进行清理,在你的情况下关闭就足够了。形态学由过多的操作组成,可以为您提供极其干净的图像蒙版。阅读它们。