【发布时间】:2018-12-14 01:52:24
【问题描述】:
我想开发一个 Python OpenCV 脚本来复制/改进我开发的 Gimp 程序。该过程的目标是提供一个 x,y 点阵列,该阵列遵循草地和硬表面之间的分界线。这个阵列可以让我完成我的 500 磅 54 英寸宽的压力清洗机器人,它有一个 Raspberry Pi Zero(和摄像头),这样它就可以以每秒几英寸的速度跟随那个边缘。我将监控并/或当我在沙发上看电视时,通过它的 wifi 视频流和 iPhone 应用程序控制机器人。
这是一个示例原始图像(60x80 像素):
Gimp 程序是:
- 将图像转换为索引的 2 种颜色。基本上一侧是草,另一侧是砖块或人行道。糟糕的阴影 哎呀,这就是我 :)
- 在这两种颜色中,使用较低的色相值和魔棒在该值的像素上使用以下魔杖设置。色调设置为 23 是我去除阴影的方法,羽毛设置是 15 是我去除岛屿/锯齿(裂缝中的草:)的方法。
- 使用以下高级设置值对路径进行高级选择(默认值的更改为黄色)。基本上我只想要线段,我的 (x,y) 点数组将是黄色路径点。
- 接下来我将路径导出到一个 .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()
剩下的事情:
- 在非边缘像素上添加蒙版半径以处理原始蒙版,例如 Gimp 在蒙版上运行最小半径之前创建的蒙版:
1a。编辑:截至 2018 年 7 月 9 日,我一直专注于这个问题,因为这似乎是我最大的问题。我无法让 cv2.findcontours 像 Gimp 那样用它的魔杖半径功能平滑“边缘草”。左侧是 2 色“问题”蒙版和叠加的“红色”点,它们直接使用 cv2.findcontours 找到,右侧是在 cv2 之前应用于左侧图像“问题”蒙版的 Gimp 半径蒙版。 findcontours 应用于它,从而产生正确的图像和点:
我曾尝试查看 Gimps 源代码,但它超出了我的理解范围,我找不到任何可以执行此操作的 OpenCV 例程。有没有办法对 OpenCV 中边缘蒙版的“非边缘”像素应用最小半径平滑??? “非边缘”是指如您所见,Gimp 不会对这些“角”(黄色高亮内部)进行半径处理,但似乎仅将半径平滑应用于图像“内部”的边缘(注意:Gimps 半径算法消除了所有掩码中的小岛,这意味着您不必在应用 cv2.findcontours 后找到最大区域轮廓来获取兴趣点):
- 从位于图像边缘的所有 pnt 中删除不相关的数组点。
- 弄清楚为什么它发现的数组点似乎围绕着绿草而不是硬表面,我以为我正在处理硬表面色调。
- 找出为什么 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