Gimp 程序中的 OpenCV Python 脚本 - 草地/硬表面边缘检测

2023-12-06

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

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

enter image description here

Gimp程序是:

  1. 将图像转换为索引 2 种颜色。基本上一侧是草地,另一侧是砖块或人行道。该死的影子哦,那是我:)

enter image description here

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

enter image description here

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

enter image description here

  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):

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

剩下要做的事:

  1. 在非边缘像素上添加遮罩半径,以处理原始遮罩,例如 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

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

enter image description here enter image description here

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

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. 看起来你自己能够很好地完成这件事。

    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 代码进行的任何更改都会导致我的更改无效。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Gimp 程序中的 OpenCV Python 脚本 - 草地/硬表面边缘检测 的相关文章

随机推荐