我想开发一个 Python OpenCV 脚本来复制/改进我开发的 Gimp 程序。该过程的目标是提供一个 x,y 点阵列,它遵循草地和硬表面之间的分界线。这个阵列可以让我完成我的 500 磅 54"宽的压力清洗机器人,它有一个 Raspberry Pi Zero(和摄像头),这样它就可以以每秒几英寸的速度跟随那个边缘。我将监视和/或在我在沙发上看电视时通过其 wifi 视频流和 iPhone 应用程序控制机器人。
这是一个示例原始图像(60x80 像素):
Gimp程序是:
<?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):
截至 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()
剩下要做的:
1a.编辑:截至 2018 年 7 月 9 日,我一直专注于这个问题,因为它似乎是我最大的问题。我无法让 cv2.findcontours 平滑“边缘草”,就像 Gimp 使用其魔杖半径功能一样。左边是一个 2 色的“问题”蒙版和叠加的结果“红色”点,它们直接使用 cv2.findcontours 找到,右边是在 cv2 之前应用于左侧图像“问题”蒙版的 Gimp 圆角蒙版。 findcontours 应用于它,产生正确的图像和点:
我曾尝试查看 Gimps 源代码,但它超出了我的理解范围,而且我找不到任何可以执行此操作的 OpenCV 例程。有没有办法将最小半径平滑应用于 OpenCV 中边缘蒙版的“非边缘”像素????通过“非边缘”,我的意思是,正如您所看到的,Gimp 不会对这些“角”(黄色高光内)进行半径处理,但似乎只是将半径平滑应用于图像“内部”边缘(注意:Gimps 半径算法消除了所有掩码中的小岛,这意味着您不必在应用 cv2.findcontours 后找到最大区域轮廓来获取兴趣点):
编辑:截至美国东部时间 2018 年 7 月 12 日下午 5 点:我已经使用了我最容易使用的语言,VB6,呃,我知道。无论如何,我已经能够制作一个在像素级别工作的线条/边缘平滑例程来做我想要的最小半径掩码。它的工作原理就像吃 bean 人沿着边缘的右侧漫游,尽可能靠近,并在吃 bean 人的左侧留下面包屑痕迹。不确定我可以从该代码制作一个 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
最佳答案
好吧,我终于有时间看看这个了。我将解决您的每一点,然后显示代码中的更改。如果您有任何问题或建议,请告诉我。
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)
...
...
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/