【问题标题】:OpenCV Python script from Gimp procedure - Grass/Hard surface edge detectionGimp 程序中的 OpenCV Python 脚本 - 草/硬表面边缘检测
【发布时间】:2018-12-14 01:52:24
【问题描述】:

我想开发一个 Python OpenCV 脚本来复制/改进我开发的 Gimp 程序。该过程的目标是提供一个 x,y 点阵列,该阵列遵循草地和硬表面之间的分界线。这个阵列可以让我完成我的 500 磅 54 英寸宽的压力清洗机器人,它有一个 Raspberry Pi Zero(和摄像头),这样它就可以以每秒几英寸的速度跟随那个边缘。我将监控并/或当我在沙发上看电视时,通过它的 wifi 视频流和 iPhone 应用程序控制机器人。

这是一个示例原始图像(60x80 像素):

Gimp 程序是:

  1. 将图像转换为索引的 2 种颜色。基本上一侧是草,另一侧是砖块或人行道。糟糕的阴影 哎呀,这就是我 :)

  1. 在这两种颜色中,使用较低的色相值和魔棒在该值的像素上使用以下魔杖设置。色调设置为 23 是我去除阴影的方法,羽毛设置是 15 是我去除岛屿/锯齿(裂缝中的草:)的方法。

  1. 使用以下高级设置值对路径进行高级选择(默认值的更改为黄色)。基本上我只想要线段,我的 (x,y) 点数组将是黄色路径点。

  1. 接下来我将路径导出到一个 .xml 文件,我可以从中解析和隔离上图中的黄点。这是 .xml 文件:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
              "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">

<svg xmlns="http://www.w3.org/2000/svg"
     width="0.833333in" height="1.11111in"
     viewBox="0 0 60 80">
  <path id="Selection"
        fill="none" stroke="black" stroke-width="1"
        d="M 60.00,0.00
           C 60.00,0.00 60.00,80.00 60.00,80.00
             60.00,80.00 29.04,80.00 29.04,80.00
             29.04,80.00 29.04,73.00 29.04,73.00
             29.04,73.00 30.00,61.00 30.00,61.00
             30.00,61.00 30.00,41.00 30.00,41.00
             30.00,41.00 29.00,30.85 29.00,30.85
             29.00,30.85 24.00,30.85 24.00,30.85
             24.00,30.85 0.00,39.00 0.00,39.00
             0.00,39.00 0.00,0.00 0.00,0.00
             0.00,0.00 60.00,0.00 60.00,0.00 Z" />
</svg>

我的 Pi Zero 上这个 OpenCV 程序的执行时间目标是大约 1-2 秒或更短(目前大约需要 0.18 秒)。

我拼凑了一些导致 Gimp xml 文件中相同点的东西。我完全不确定它是否在做 Gimp 关于遮罩的色调范围所做的事情。我还没有弄清楚如何在面罩上应用最小半径,我很确定当面罩在硬表面边缘出现“草”块作为面罩的一部分时,我将需要它。以下是目前为止的所有轮廓点(ptscanvas.bmp):

截至 2018 年 7 月 6 日美国东部标准时间下午 5:08,这是一个“仍然混乱”的脚本,它可以工作并找到这些点;

import numpy as np
import time, sys, cv2

img = cv2.imread('2-60.JPG')
cv2.imshow('Original',img)
# get a blank pntscanvas for drawing points on 
pntscanvas = np.zeros(img.shape, np.uint8)

print (sys.version)  
if sys.version_info[0] < 3:
    raise Exception("Python 3 or a more recent version is required.")

def doredo():
    start_time = time.time()

    # Use kmeans to convert to 2 color image
    hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    Z = hsv_img.reshape((-1,3))
    Z = np.float32(Z)
    # define criteria, number of clusters(K) 
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
    K = 2
    ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)

    # Create a mask by selecting a hue range around the lowest hue of the 2 colors
    if center[0,0] < center[1,0]:
        hueofinterest = center[0,0]
    else:
        hueofinterest = center[1,0]
    hsvdelta = 8
    lowv = np.array([hueofinterest - hsvdelta, 0, 0])
    higv = np.array([hueofinterest + hsvdelta, 255, 255])
    mask = cv2.inRange(hsv_img, lowv, higv)

    # Extract contours from the mask
    ret,thresh = cv2.threshold(mask,250,255,cv2.THRESH_BINARY_INV)
    im2,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    
    # Find the biggest area contour
    cnt = contours[0]
    max_area = cv2.contourArea(cnt)

    for cont in contours:
        if cv2.contourArea(cont) > max_area:
            cnt = cont
            max_area = cv2.contourArea(cont)

    # Make array of all edge points of the largets contour, named allpnts  
    perimeter = cv2.arcLength(cnt,True)
    epsilon = 0.01*cv2.arcLength(cnt,True) # 0.0125*cv2.arcLength(cnt,True) seems to work better
    allpnts = cv2.approxPolyDP(cnt,epsilon,True)
    
    end_time = time.time()
    print("Elapsed cv2 time was %g seconds" % (end_time - start_time))

    # Convert back into uint8, and make 2 color image for saving and showing
    center = np.uint8(center)
    res = center[label.flatten()]
    res2 = res.reshape((hsv_img.shape))

    # Save, show and print stuff
    cv2.drawContours(pntscanvas, allpnts, -1, (0, 0, 255), 2)
    cv2.imwrite("pntscanvas.bmp", pntscanvas)
    cv2.imshow("pntscanvas.bmp", pntscanvas)
    print('allpnts')
    print(allpnts)
    print("center")
    print(center)
    print('lowv',lowv)
    print('higv',higv)
    cv2.imwrite('mask.bmp',mask)
    cv2.imshow('mask.bmp',mask)
    cv2.imwrite('CvKmeans2Color.bmp',res2)
    cv2.imshow('CvKmeans2Color.bmp',res2)

print ("Waiting for 'Spacebar' to Do/Redo OR 'Esc' to Exit")
while(1):
    ch = cv2.waitKey(50)
    if ch == 27:
        break
    if ch == ord(' '):
        doredo()
        
cv2.destroyAllWindows()

剩下的事情:

  1. 在非边缘像素上添加蒙版半径以处理原始蒙版,例如 Gimp 在蒙版上运行最小半径之前创建的蒙版:

1a。编辑:截至 2018 年 7 月 9 日,我一直专注于这个问题,因为这似乎是我最大的问题。我无法让 cv2.findcontours 像 Gimp 那样用它的魔杖半径功能平滑“边缘草”。左侧是 2 色“问题”蒙版和叠加的“红色”点,它们直接使用 cv2.findcontours 找到,右侧是在 cv2 之前应用于左侧图像“问题”蒙版的 Gimp 半径蒙版。 findcontours 应用于它,从而产生正确的图像和点:

我曾尝试查看 Gimps 源代码,但它超出了我的理解范围,我找不到任何可以执行此操作的 OpenCV 例程。有没有办法对 OpenCV 中边缘蒙版的“非边缘”像素应用最小半径平滑??? “非边缘”是指如您所见,Gimp 不会对这些“角”(黄色高亮内部)进行半径处理,但似乎仅将半径平滑应用于图像“内部”的边缘(注意:Gimps 半径算法消除了所有掩码中的小岛,这意味着您不必在应用 cv2.findcontours 后找到最大区域轮廓来获取兴趣点):

  1. 从位于图像边缘的所有 pnt 中删除不相关的数组点。
  2. 弄清楚为什么它发现的数组点似乎围绕着绿草而不是硬表面,我以为我正在处理硬表面色调。
  3. 找出为什么 CvKmeans2Color.bmp 中的硬表面颜色显示为橙色而不是 Gimps 转换中的米色,以及为什么这与 Gimps 转换中的像素不匹配?这是 CvKmeans2Color.bmp 和 Gimps:

编辑:截至 2018 年 7 月 12 日美国东部标准时间下午 5 点:我已经使用了我最容易使用 VB6 创建代码的语言,嗯,我知道。无论如何,我已经能够制作一个在像素级别上工作的线/边缘平滑例程来完成我想要的最小半径蒙版。它的工作方式就像一个 PacMan 沿着边缘的右侧漫游,尽可能靠近它,并在 Pac 的左侧留下一个面包屑轨迹。不确定我是否可以从该代码制作 python 脚本,但至少我有一个起点,因为没有人确认有 OpenCV 替代方法可以做到这一点。如果有人有兴趣here 是一个编译的 .exe 文件,它应该在大多数 Windows 系统上运行而无需安装(我认为)。这是它的屏幕截图(Blue/GreenyBlue 像素是未平滑的边缘,Green/GreenyBlue 像素是圆角边缘):

您可以通过这个 VB6 例程了解我的流程逻辑的要点:

Sub BeginFollowingEdgePixel()
   Dim lastwasend As Integer
   wasinside = False
   While (1)
      If HitFrontBumper Then
         GoTo Hit
      Else
         Call MoveForward
      End If
      If circr = orgpos(0) And circc = orgpos(1) Then
         orgpixr = -1 'resets Start/Next button to begin at first first found blue edge pixel
         GoTo outnow 'this condition indicates that you have followed all blue edge pixels
      End If
      Call PaintUnderFrontBumperWhite
      Call PaintGreenOutsideLeftBumper
nomove:
      If NoLeftBumperContact Then
         Call MoveLeft
         Call PaintUnderLeftBumperWhite
         Call PaintGreenOutsideLeftBumper
         If NoLeftBumperContact Then
            If BackBumperContact Then
               Call MakeLeftTheNewForward
            End If
         End If
      ElseIf HitFrontBumper Then
Hit:
         Call PaintAheadOfForwardBumperGreen
         Call PaintGreenOutsideLeftSide
         Call MakeRightTheNewForward
         GoTo nomove
      Else
         Call PaintAheadOfForwardBumperGreen
         Call PaintGreenOutsideLeftSide
         Call PaintUnderFrontBumperWhite
      End If
      If (circr = 19 + circrad Or circr = -circrad Or circc = 19 + circrad Or circc = -circrad) Then
         If lastwasend = 0 And wasinside = True Then
            'finished following one edge pixel
            lastwasend = 1
            GoTo outnow
            Call redrawit
         End If
      Else
         If IsCircleInsideImage Then
            wasinside = True
         End If
         lastwasend = 0
      End If
      Pause (pausev) 'seconds between moves - Pressing Esc advances early
   Wend
outnow:
End Sub

【问题讨论】:

  • 我也可以看看这个,但如果时间允许,它可能要到明天晚些时候或周六早上才会发生。澄清一下,您只是想找到最大绿色区域的边界多边形?
  • 实际上,试图找到另一种描述它的方式,我发现我的图像可以更可靠地分析,选择原始图像中最大米色区域(较低色调值)周围的一系列色调。阴影似乎会影响使用哪种颜色的决定。然后在该蒙版上应用半径过滤器,然后将蒙版制作成多边形,然后删除适当的多边形点,以便剩余的点创建从边缘点到位图然后返回到边缘点的连续路径。谢谢
  • 在某些时候,我将不得不暂停机器人并让它在到达栅栏的大门时等待指令。不确定算法在这种情况下会如何反应,我会得到一张这种情况的图片并添加它。我可能会在它弄糊涂之前钉一个红色反射器,在大门或我的船坡道进入水中等处。将红色反射器的扫描添加到脚本中非常容易。
  • 硬表面 可能会在硬表面的每个部分的末端添加红色反射器以阻止机器人。还将有一个“任意方向”的碰撞传感器来暂停它,并且可能会“向下看”传感器来阻止它越过台阶或进入游泳池:)

标签: python opencv raspberry-pi computer-vision


【解决方案1】:

好的,我终于有时间看这个了。我将解决您的每一点,然后显示代码中的更改。如果您有任何问题或建议,请告诉我。

  1. 看起来您自己能够很好地做到这一点。

    1.a.这可以通过在对图像进行任何处理之前模糊图像来解决。为了实现这一点,对代码进行了以下更改;

    ...
    start_time = time.time()                                              
    
    blur_img = cv2.GaussianBlur(img,(5,5),0) #here                        
    
    # Use kmeans to convert to 2 color image                              
    hsv_img = cv2.cvtColor(blur_img, cv2.COLOR_BGR2HSV)
    ...
    
  2. 我已更改代码以删除完全符合图像侧面的直线上的点。草边应该基本不可能也与此重合。

    ...
    allpnts = cv2.approxPolyDP(cnt,epsilon,True)                          
    
    new_allpnts = []                                                      
    
    
    for i in range(len(allpnts)):                                         
        a = (i-1) % len(allpnts)                                          
        b = (i+1) % len(allpnts)                                          
    
        if ((allpnts[i,0,0] == 0 or allpnts[i,0,0] == (img.shape[1]-1)) and (allpnts[i,0,1] == 0 or allpnts[i,0,1] == (img.shape[0]-1))):          
            tmp1 = allpnts[a,0] - allpnts[i,0]                            
            tmp2 = allpnts[b,0] - allpnts[i,0]                                                                                                                     
            if not (0 in tmp1 and 0 in tmp2):                             
                new_allpnts.append(allpnts[i])
        else:
            new_allpnts.append(allpnts[i])
    ...
    cv2.drawContours(pntscanvas, new_allpnts, -1, (0, 0, 255), 2)
    ...
    
  3. 由于如何在图像中找到轮廓,我们可以简单地翻转阈值函数并找到图像其他部分周围的轮廓。变化如下;

    ...
    #Extract contours from the mask                                      
    ret,thresh = cv2.threshold(mask,250,255,cv2.THRESH_BINARY) #here      
    im2,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    ...
    
  4. 至于颜色差异,您已将图像转换为 HSV 格式,并且在保存之前不会将其切换回 BGR。对 HSV 的这种更改确实会给您带来更好的结果,所以我会保留它,但它是一个不同的调色板。变化如下;

    ...
    cv2.imshow('mask.bmp',mask)                                           
    res2 = cv2.cvtColor(res2, cv2.COLOR_HSV2BGR)                          
    cv2.imwrite('CvKmeans2Color.bmp',res2)                                
    cv2.imshow('CvKmeans2Color.bmp',res2)
    ...
    

免责声明:这些更改基于上面的 python 代码。对不在提供代码中的 python 代码的任何更改都会使我的更改无效。

【讨论】:

  • 感谢您为我解决了很多问题,但我认为我不能接受高斯模糊作为 Gimp 程序的复制/改进。我在 Gimp 中使用了各种模糊和过滤器,并且在草 / 硬表面边缘偏差误差的准确性方面结果有限。模糊确实提供了一些平滑,但代价是减少了我的眼睛可以在原始图像上插入的实际边缘像素上的色调变化梯度。因此,创建蒙版的 inRange 像素选择的色调范围在找到我想要的真实路径时效果会降低。我的任务还在继续……
  • 换句话说,模糊不会“复制” Gimp 过程,因为 Gimp 过程包括对蒙版的半径平滑,并且由于上述原因,模糊不是 Gimp 过程的“改进”。
  • 另外,2. 没有解决,因为当我使用你所有的代码更改时,我仍然会得到位于图像边缘的额外“红色”数组点。 2. 的实际解决方案不会像删除图像角上的所有数组点那样简单,因为如果草/硬表面线实际上从图像角开始,这将不起作用。在我得到一些代码/解决方案后,我将解决这个问题
  • 我已经在 #2 上编辑了我的答案。应该删除边缘上的点,只要两条线都是跟随图像边缘的一部分。致力于#1。
  • #2 上的好主意和代码!!尝试使用 5 的 hsvdelta,您会看到我们开始在草边缘获得更多的“红色”点,这些点将通过 Gimps 的“边缘半径过滤器”在 5 甚至更低的 hsvdelta 中移除。我们可能不得不让你和我编辑你的答案,用脚注编辑我的问题以继续说话,让其他关注的人看到这个进展。如果我们将其移至聊天,其他人如果想看到我们未来就此进行对话,可以关注我们吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多