python - 来自 Gimp 程序的 OpenCV Python 脚本 - 草/硬表面边缘检测

标签 python opencv raspberry-pi computer-vision

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

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

enter image description here

Gimp程序是:

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

  • enter image description here
  • 在两种颜色中,使用以下魔杖设置在该值的像素上取较低的色调值和魔杖。色调设置 23 是我去除阴影的方式,羽化设置 15 是我去除岛屿/锯齿(裂缝中的草:)的方式。

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

  • enter image description here
  • 接下来,我将路径导出到 .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 对 mask 的色调范围所做的事情。我还没有弄清楚如何在面具上应用最小半径,我很确定当面具在硬表面的边缘上有一个“草”丛作为面具的一部分时,我会需要它。这是到目前为止的所有轮廓点(ptscanvas.bmp):

    enter image description here

    截至 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()


    剩下要做的:
  • 在非边缘像素上添加蒙版半径以处理像 Gimp 在蒙版上运行最小半径之前创建的原始蒙版:

  • enter image description here

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

    enter image description here enter image description here

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

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

  • enter image description here
    enter image description here

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

    enter image description here

    您可以通过这个 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

    最佳答案

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

  • 看起来你自己已经能够很好地做到这一点了。

    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)
    ...
    
  • 我已经更改了代码以删除完全跟随图像侧面的线上的点。草边也应该与此重合应该是基本不可能的。
    ...
    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)
    ...
    
  • 由于如何在图像中找到轮廓,我们可以简单地翻转阈值函数并找到图像其他部分周围的轮廓。变化如下;
    ...
    #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)
    ...
    
  • 至于颜色差异,您已将图像转换为 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 代码的任何更改都会使我的更改无效。

    关于python - 来自 Gimp 程序的 OpenCV Python 脚本 - 草/硬表面边缘检测,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51193713/

    相关文章:

    python - 在使用多处理模块在 python 中创建新子进程时,哪些内容将从父进程复制到子进程

    python - 如何从数组数组中创建一个 numpy 数组?

    opencv - 代码块 OpenCV 构建失败

    matlab - 阅读未压缩的yuv视频文件的帧?

    python - 如何在 python 排序(列表)中指定 2 个键?

    python - Pycharm 终端在使用 VirtualEnv 的 Windows 10 上无法正常工作

    php - 访问远程服务器上的 shell 脚本

    c - 在 Raspberry 中使用 C 语言的时钟

    python - 去除圆形蒙版周围的空白

    c++ - 如何将 MATLAB Lab 色标转换为 OpenCV Lab 色标?