【问题标题】:How to detect robot direction from Image?如何从图像中检测机器人方向?
【发布时间】:2016-09-06 10:31:06
【问题描述】:

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

这是图片示例。

处理后的图像 原始图像 分割图像

我使用以下步骤

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

  2. 第 2 步:所以它会检测选择的颜色是棕色或脏颜色,然后我收集两个数组中每个图像行的最左边和最右边的棕色或所选颜色(一个红点)。

  3. 第 3 步:我将 2 线性回归线绘制为蓝色点,并将交点计算为粉色点
  4. 第 4 步:绘制绿线以将粉红色点与其他图像进行比较。我还不确定如何处理这条绿线
  5. 问题是玉米叶之间也存在污垢或棕色,然后我让我的代码错过了计算

问题是如何过滤掉玉米叶之间或不在玉米路径中的其他区域的棕色像素?在这个问题中我应该学习或应用哪种算法或方法?

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

这是我用 JAVA+Boofcv 应用后的结果

  • 第 1 步:阈值化或颜色分割

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

  • 第 3 步:绘制线性回归

更多信息

Full Code Here

LinearRegression Class

10 example images with the same process

【问题讨论】:

  • 我会认为没有精确表述的问题(许多有用的“视觉/学习”问题,例如)过于宽泛。我希望有另一个专门为他们服务的网站,但我找不到。
  • 一个像 9x9 这样大小合适的中值滤波器应该可以去除大多数东西。您也可以尝试组合最大和最小过滤器。
  • @Piglet 谢谢。我应用中值滤波器作为您的建议来摆脱小颗粒。查看编辑后的帖子。

标签: java image-processing computer-vision boofcv


【解决方案1】:

为您的源图像

我愿意:

  1. 用棕色的东西制作面具

    只关注H,S 并忽略V,你已经拥有了这个。我使用整数 255 而不是颜色(蓝色)进行后面的计算。

  2. 模糊蒙版

    这将删除一小部分错误选择的零件。在此之后,您应该再次设置遮罩的阈值,因为遮罩值会比255 小一点,除非您完全选择了区域。面积越大,值越大(更接近255)。我以>=150为门槛

  3. 按水平线扫描蒙版

  4. 为每一行找到所有选定像素的重心

    再次进行模糊和阈值处理后,使用的蒙版处于 Aqua 中。所以计算每行中所有被掩码像素的平均点x 坐标。该点用White标记。

  5. 通过所有重心的回归线

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

    我使用了直线方程x=x0+y*dx,其中y=<0,pic1.ys>。并按时间间隔搜索解决方案:

    x0=<-pic1.xs,+2*pic1.xs>
    dx=<-10,+10>
    

其中pic1.xs,pic1.ys 是图像分辨率。如您所见,我没有涵盖所有角度,但我认为这无论如何都不适用于那些边缘情况(接近水平方向)。对于这种情况,您应该在垂直线上执行此操作,并改用 x=y0+x*dy

这里是 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 平滑基数与卷积相同

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

【讨论】:

  • 你(或库)使用哪种类型的算法来平滑()?
  • @SarinSuriyakoon 添加了带有平滑信息和代码的edit1
  • 我采纳了你的大部分建议,它和编辑后的帖子一样好用。
【解决方案2】:

如果我是对的,您是在询问被视为误入歧途或在背景的其他部分中的棕色部分?

您是如何获得最后一张图片的?我假设您将原始图像乘以蒙版?即使您没有,您也可以通过选择图像存在的任何位置来简单地从图像中获取蒙版(任何简单的、非常低的阈值都可以)。 (应用自适应阈值,更准确的原始版本以获得更好的掩码)

使用形态学操作将那个掩码清理干净,在你的情况下,关闭就足够了。形态学由大量操作组成,可以为您提供非常干净的图像蒙版。阅读它们。

【讨论】:

    猜你喜欢
    • 2016-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-03
    • 2011-02-16
    • 2018-06-05
    • 2021-04-17
    • 2021-01-15
    相关资源
    最近更新 更多