NAO机器人高尔夫中的视觉系统设计
去年(2017)年分別參加了江蘇省和全國的NAO機器人高爾夫比賽,負責的是視覺部分編程。在這里把之前的工作總結一下。內容主要包括紅球和黃桿的識別和定位(包括在比賽中遇到的一些問題和解決辦法)。完整的代碼(C++和Python兩個版本)見https://github.com/ZhouJiaHuan/nao-golf-visual-task,本篇只以Python代碼為例進行介紹。
基本配置(基類)
在代碼實現上,為了方便擴展,我先定義了一個基類用來定義一些最基本的配置信息,然后再派生出視覺類(寫運動代碼也可以派生出一個運動的類)、用于紅球黃和黃桿的檢測。基類的定義如下:
# date: 1/15/2017 # description: basic class for all Nao tasks. ## ---------------------------------------------------------------------import sys # sys.path.append("/home/meringue/Softwares/pynaoqi-sdk/") # naoqi directory from naoqi import ALProxyclass ConfigureNao(object):"""a basic class for all nao tasks, including motion, bisualization etc."""def __init__(self, IP):self._IP = IPself._PORT = 9559self._cameraProxy = ALProxy("ALVideoDevice", self._IP, self._PORT)self._motionProxy = ALProxy("ALMotion", self._IP, self._PORT)self._postureProxy = ALProxy("ALRobotPosture", self._IP, self._PORT)self._tts = ALProxy("ALTextToSpeech",self._IP, self._PORT)self._memoryProxy = ALProxy("ALMemory", self._IP, self._PORT)從代碼中可以看出,ConfigureNao這個基類中主要包含了一些基本配置信息,如IP,端口號,并且創建了一些需要用到的NAO庫中自帶的類的對象(視覺、語音、運動等)。在等會我們定義視覺類的時候,可以直接從ConfigureNao這個類繼承。
視覺模塊
視覺基類——視覺任務的基類
由于視覺任務中(紅球檢測、黃桿檢測)都需要共用一些基本功能(如從攝像頭獲取數據),因此再定義一個視覺基類VisualBasis供使用,這個類是從ConfigureNao繼承出來的。定義如下:
class VisualBasis(ConfigureNao):"""a basic class for visual task."""def __init__(self, IP, cameraId, resolution=vd.kVGA):"""initilization. Args:IP: NAO's IPcameraId: bottom camera (1,default) or top camera (0).resolution: kVGA, default: 640*480)Return: none""" super(VisualBasis, self).__init__(IP)self._cameraId = cameraIdself._resolution = resolutionself._colorSpace = vd.kBGRColorSpaceself._fps = 20self._frameHeight = 0self._frameWidth = 0self._frameChannels = 0self._frameArray = Noneself._cameraPitchRange = 47.64/180*np.piself._cameraYawRange = 60.97/180*np.piself._cameraProxy.setActiveCamera(self._cameraId)def updateFrame(self, client="python_client"):"""get a new image from the specified camera and save it in self._frame.Args:client: client name.Return: none.""""""if self._cameraProxy.getActiveCamera() == self._cameraId:print("current camera has been actived.")else:self._cameraProxy.setActiveCamera(self._cameraId)"""self._videoClient = self._cameraProxy.subscribe(client, self._resolution, self._colorSpace, self._fps)frame = self._cameraProxy.getImageRemote(self._videoClient)self._cameraProxy.unsubscribe(self._videoClient)try:self._frameWidth = frame[0]self._frameHeight = frame[1]self._frameChannels = frame[2]self._frameArray = np.frombuffer(frame[6], dtype=np.uint8).reshape([frame[1],frame[0],frame[2]])except IndexError:raisedef getFrameArray(self):"""get current frame.Return: current frame array (numpy array)."""if self._frameArray is None:return np.array([])return self._frameArraydef showFrame(self):"""show current frame image."""if self._frameArray is None:print("please get an image from Nao with the method updateFrame()")else:cv2.imshow("current frame", self._frameArray)def printFrameData(self):"""print current frame data."""print("frame height = ", self._frameHeight)print("frame width = ", self._frameWidth)print("frame channels = ", self._frameChannels)print("frame shape = ", self._frameArray.shape)def saveFrame(self, framePath):"""save current frame to specified direction. Arguments:framePath: image path."""cv2.imwrite(framePath, self._frameArray)print("current frame image has been saved in", framePath)def setParam(self, paramName=None, paramValue = None):raise NotImplementedErrordef setAllParamsToDefault(self):raise NotImplementedError視覺基類中除了定義了一些默認的攝像頭參數,還定義了一些基本的成員函數、包括從指定攝像頭獲取一幀圖像、返回當前存儲的圖像數據、顯示當前圖像、保存當前圖像到本地,還有一些以后用到再定義的函數,先預留借口在這里。因為這個類中都是一些簡單的功能,此處不多介紹。
有了上面定義的視覺類,就可以繼續派生出紅球檢測類和黃桿檢測類,下面分別介紹。
紅球檢測類
其實NAO的官方庫里面提供了紅球識別的API,但我們測試過,發現效果很不好,非常容易受到一些干擾物的影響。因此我們打算基于OpenCV自己寫紅球識別的代碼。最簡單的思路就是顏色閾值分割+霍夫圓檢測,然而在測試的時候我們發現僅僅通過這兩個步驟檢測的結果并不穩定,于是我們在這基礎上針對比賽環境做了改進。
紅球檢測類的類名叫BallDetect,是從VisualBasis類繼承出來的,因此已經包含了VisualBasis類中的基本屬性和成員函數,我們只要在此基礎上繼續編寫紅球檢測需要的成員函數即可。
紅球基本屬性
我們需要保存的紅球相關的信息有兩塊:(1)紅球在圖像中的位置信息;(2)紅球相對于機器人坐標系的位置信息。因此我們先定義這兩個屬性,如下:
def __init__(self, IP, cameraId=vd.kBottomCamera, resolution=vd.kVGA):"""initialization."""super(BallDetect, self).__init__(IP, cameraId, resolution)self._ballData = {"centerX":0, "centerY":0, "radius":0}self._ballPosition= {"disX":0, "disY":0, "angle":0}self._ballRadius = 0.05紅球在圖像中的位置信息和實際位置信息分別用”_ballData”和”_ballPosition”來存放,初始值都設為0
圖像預處理
該函數功能是對圖像進行特定通道的分離和濾波,這里我分別針對RGB空間和HSV空間都寫了預處理函數供調用.
RGB空間預處理函數
def _getChannelAndBlur(self, color):"""get the specified channel and blur the result.Arguments:color: the color channel to split, only supports the color of red, geen and blue. Return: the specified color channel or None (when the color is not supported)."""try:channelB = self._frameArray[:,:,0]channelG = self._frameArray[:,:,1]channelR = self._frameArray[:,:,2]except:raise Exception("no image detected!")Hm = 6if color == "red":channelB = channelB*0.1*HmchannelG = channelG*0.1*HmchannelR = channelR - channelB - channelGchannelR = 3*channelRchannelR = cv2.GaussianBlur(channelR, (9,9), 1.5)channelR[channelR<0] = 0channelR[channelR>255] = 255return np.uint8(np.round(channelR))elif color == "blue":channelR = channelR*0.1*HmchannelG = channelG*0.1*HmchannelB = channelB - channelG - channelRchannelB = 3*channelB channelB = cv2.GaussianBlur(channelB, (9,9), 1.5)channelB[channelB<0] = 0channelB[channelB>255] = 255return np.uint8(np.round(channelB))elif color == "green":channelB = channelB*0.1*HmchannelR= channelR*0.1*HmchannelG = channelG - channelB - channelRchannelG = 3*channelGchannelG = cv2.GaussianBlur(channelG, (9,9), 1.5)channelG[channelG<0] = 0channelG[channelG>255] = 255return np.uint8(np.round(channelG))else:print("can not recognize the color!")print("supported color:red, green and blue.")return None雖然說是紅球檢測,但為了考慮代碼的一般性,我也增加了綠色和藍色。在分離的時候增強了紅色通道的值并削減了其他空間的結果,這樣可以使分離的結果更好。另外增加高斯濾波讓局部信息模糊有利于霍夫圓的檢測。
HSV空間預處理函數
def _binImageHSV(self, color):"""get binary image from the HSV image (transformed from BGR image)Args:color: the color for binarization.Return:binImage: binary image."""try:frameArray = self._frameArray.copy()imgHSV = cv2.cvtColor(frameArray, cv2.COLOR_BGR2HSV)except:raise Exception("no image detected!")if color == "red":minHSV1=np.array([0,43,46])maxHSV1=np.array([10,255,255])minHSV2=np.array([156,43,46])maxHSV2=np.array([180,255,255])frameBin1 = cv2.inRange(imgHSV, minHSV1, maxHSV1)frameBin2 = cv2.inRange(imgHSV, minHSV2, maxHSV2)frameBin = np.maximum(frameBin1, frameBin2)return frameBinelse:raise Exception("not recognize the color!")這個函數的輸入依然是一個RGB空間圖像,內部轉換為HSV空間進行處理。這里用到了OpenCV庫中的inRange()函數進行二值化,由于紅色對應的HSV的區間范圍有兩個,所以在代碼實現上用了“并”操作。函數實現上只提供了紅色的提取,但也可以拓展到其他顏色的預處理,這里給出一個各個顏色HSV空間的實驗取值范圍:
在實驗測試的時候,發現在一般的情況下,兩個空間都能準確地把紅球分割出來,但在光線條件較差的時候(較強或較弱),發現HSV空間更加穩定。
紅球識別
圖像預處理后,圖像上會分割出紅球所在區域和其他的一些噪聲。理想情況下,紅球所在的區域分割結果應該是一個圓(橢圓),這里我是直接通過OpenCV庫中的霍夫圓檢測函數實現的:
def _findCircles(self, img, minDist, minRadius, maxRadius):"""detect circles from an image.Arguments:img: image to be detected.minDist: minimum distance between the centers of the detected circles.minRadius: minimum circle radius.maxRadius: maximum circle radius.Return: an uint16 numpy array shaped circleNum*3 if circleNum>0, ([[circleX, circleY,radius]])else return None."""circles = cv2.HoughCircles(np.uint8(img), cv2.HOUGH_GRADIENT, 1, minDist, param1=150, param2=15, minRadius=minRadius, maxRadius=maxRadius)if circles is None:return np.uint16([])else:return np.uint16(np.around(circles[0, ]))根據比賽用球的大小要求可以大概限制一下紅球在圖像中的半徑范圍(和分辨率有關),代碼中的參數是基于640×480的分辨率設置的。需要注意的是,經過上述霍夫圓檢測到的球可能有多個(可能包含了一些噪聲),因此還應該對結果進一步的判斷。
紅球篩選
經過紅球識別的結果可能有如下2種情況:
- 圖像中沒有檢測到球。
- 圖像中檢測到一個或者多個球。
第一種情況不需要討論,只需要返回沒有球的信息即可。對于第二種情況,我們需要對每一個檢測出的紅球進行二次判斷。因為在比賽現場,NAO機器人最多只應該檢測到一個球。因此,針對第二種情況,我給出的篩選方法如下:
對于每一個檢測出的紅球,以紅球圓心為中心,以紅球的4倍半徑為邊長畫一個外圍正方形,計算外接正方形區域內紅色和綠色像素點所占的比值。一個簡單的示意圖如下:
最理想的情況下,紅色像素的比例為πr2/16r2=0.196πr2/16r2=0.196,綠色像素所占的比例為0.8040.804,但在實際檢測的時候,存在各種不確定因素(圓檢測誤差、光線不均勻導致顏色信息發生變化、其他干擾物的影響等),幾乎不可能達到理想情況。因此,在具體的實現上,我們需要把條件設定得寬松點。實現代碼如下:
def _selectCircle(self, circles):"""select one circle in list type from all circles detected. Args:circles: numpy array shaped (N, 3), N is the number of circles.Return:selected circle or None (no circle is selected)."""if len(circles) == 0 :return circlesif circles.shape[0] == 1:centerX = circles[0][0]centerY = circles[0][1]radius = circles[0][2]initX = centerX - 2*radiusinitY = centerY - 2*radiusif initX<0 or initY<0 or (initX+4*radius)>self._frameWidth or (initY+4*radius)>self._frameHeight or radius<1:return circleschannelB = self._frameArray[:,:,0]channelG = self._frameArray[:,:,1]channelR = self._frameArray[:,:,2]rRatioMin = 1.0; circleSelected = np.uint16([])for circle in circles:centerX = circle[0]centerY = circle[1]radius = circle[2]initX = centerX - 2*radiusinitY = centerY - 2*radiusif initX<0 or initY<0 or (initX+4*radius)>self._frameWidth or (initY+4*radius)>self._frameHeight or radius<1:continuerectBallArea = self._frameArray[initY:initY+4*radius+1, initX:initX+4*radius+1,:]bFlat = np.float16(rectBallArea[:,:,0].flatten())gFlat = np.float16(rectBallArea[:,:,1].flatten())rFlat = np.float16(rectBallArea[:,:,2].flatten())rScore1 = np.uint8(rFlat>1.0*gFlat)rScore2 = np.uint8(rFlat>1.0*bFlat)rScore = float(np.sum(rScore1*rScore2))gScore = float(np.sum(np.uint8(gFlat>1.0*rFlat)))rRatio = rScore/len(rFlat)gRatio = gScore/len(gFlat) print("red ratio = ", rRatio)print("green ratio = ", gRatio)if rRatio>=0.12 and gRatio>=0.1 and abs(rRatio-0.19)<abs(rRatioMin-0.19):circleSelected = circlereturn circleSelected該函數的輸入是一個2維數組,每一個代表一個檢測出來的紅球信息(x,y,r)(x,y,r),為了保證篩選后最多只剩一個紅球,代碼最后再所有滿足比例條件中的球中選擇了紅色比例最接近理想值0.19的紅球。
還有一點需要說明的是,上面的代碼中使用的是RGB空間進行顏色統計的,在實現上其實也可以使用HSV空間進行統計,方法還是一樣。一個完整的紅球檢測過程如下:
紅球定位
上面只是把紅球的位置在圖像中定位出來了,而我們在比賽中需要紅球相對于機器人(機器人坐標系)的位置信息。比較簡單的一種定位方法就是三角函數定位,也就是利用已知的一些參數(紅球半徑、機器人攝像頭離地面高度、攝像頭位置、廣角等)構造幾個直角三角形,最后即可得出紅球相當于機器人的位置信息。一個簡單的計算示意圖如下(具體計算公式見代碼):
對應的計算位置的代碼如下:
def _updateBallPosition(self, standState):"""compute and update the ball position with the ball data in frame.standState: "standInit" or "standUp"."""bottomCameraDirection = {"standInit":49.2/180*np.pi, "standUp":39.7/180*np.pi} try:cameraDirection = bottomCameraDirection[standState]except KeyError:print("Error! unknown standState, please check the value of stand state!")raiseelse:if self._ballData["radius"] == 0:self._ballPosition= {"disX":0, "disY":0, "angle":0}else:centerX = self._ballData["centerX"]centerY = self._ballData["centerY"]radius = self._ballData["radius"]cameraPos = self._motionProxy.getPosition(self._cameraName, motion.FRAME_WORLD, True)cameraX, cameraY, cameraHeight = cameraPos[:3]head_yaw, head_pitch = self._motionProxy.getAngles("Head", True)camera_pitch = head_pitch + cameraDirectionimg_center_x = self._frameWidth/2img_center_y = self._frameHeight/2center_x = self._ballData["centerX"]center_y = self._ballData["centerY"]img_pitch = (center_y-img_center_y)/(self._frameHeight)*self._cameraPitchRangeimg_yaw = (img_center_x-center_x)/(self._frameWidth)*self._cameraYawRangeball_pitch = camera_pitch + img_pitchball_yaw = img_yaw + head_yawprint("ball yaw = ", ball_yaw/np.pi*180)dis_x = (cameraHeight-self._ballRadius)/np.tan(ball_pitch) + np.sqrt(cameraX**2+cameraY**2)dis_y = dis_x*np.sin(ball_yaw)dis_x = dis_x*np.cos(ball_yaw)self._ballPosition["disX"] = dis_xself._ballPosition["disY"] = dis_yself._ballPosition["angle"] = ball_yaw代碼中前一部分主要是獲取計算所需要的信息(傳感器的值和常數項),后一部分是紅球位置計算公式。最后直接將計算的結果保存在類中。在實驗的時候,統計各個位置的平均誤差在1-2里面,視野中心的位置誤差較小,視野邊界附近的誤差較大。
這里還需要補充一點的是,之前由于我們代碼中的計算公式有誤(可對比上下代碼的不同),導致計算的位置信息有很大的偏差,具體表現是X方向的距離基本準確,Y方向的距離信息偏小,而且X越小的時候相對偏差越明顯。為此,我們通過采集視野中的不同位置信息(采集多次數據取平均),統計各個離散位置的誤差信息,最后用多項式對誤差進行補償。在測試的時候發現補償的結果可以把誤差縮小到1厘米左右。代碼如下:
def _updateBallPositionFitting(self, standState):"""compute and update the ball position with compensation.Args:standState: "standInit" or "standUp"."""bottomCameraDirection = {"standInit":49.2, "standUp":39.7} ballRadius = self._ballRadiustry:cameraDirection = bottomCameraDirection[standState]except KeyError:print("Error! unknown standState, please check the value of stand state!")raiseelse:if self._ballData["radius"] == 0:self._ballPosition= {"disX":0, "disY":0, "angle":0}else:centerX = self._ballData["centerX"]centerY = self._ballData["centerY"]radius = self._ballData["radius"]cameraPosition = self._motionProxy.getPosition("CameraBottom", 2, True)cameraX = cameraPosition[0]cameraY = cameraPosition[1]cameraHeight = cameraPosition[2]headPitches = self._motionProxy.getAngles("HeadPitch", True)headPitch = headPitches[0]headYaws = self._motionProxy.getAngles("HeadYaw", True)headYaw = headYaws[0]ballPitch = (centerY-240.0)*self._cameraPitchRange/480.0 # y (pitch angle)ballYaw = (320.0-centerX)*self._cameraYawRange/640.0 # x (yaw angle)dPitch = (cameraHeight-ballRadius)/np.tan(cameraDirection/180*np.pi+headPitch+ballPitch)dYaw = dPitch/np.cos(ballYaw)ballX = dYaw*np.cos(ballYaw+headYaw)+cameraXballY = dYaw*np.sin(ballYaw+headYaw)+cameraYballYaw = np.arctan2(ballY, ballX)self._ballPosition["disX"] = ballX# 誤差補償(多項式) if (standState == "standInit"):ky = 42.513*ballX**4 - 109.66*ballX**3 + 104.2*ballX**2 - 44.218*ballX + 8.5526 #ky = 12.604*ballX**4 - 37.962*ballX**3 + 43.163*ballX**2 - 22.688*ballX + 6.0526ballY = ky*ballYballYaw = np.arctan2(ballY,ballX) self._ballPosition["disY"] = ballYself._ballPosition["angle"] = ballYaw最后要實現上面的所有功能,在類中再定義一個函數,把之前實現的各個模塊封裝在一起,如下:
def updateBallData(self, standState="standInit", color="red", color_space="BGR", fitting=False):"""update the ball data with the frame get from the bottom camera.Arguments:standState: ("standInit", default), "standInit" or "standUp".color: ("red", default) the color of ball to be detected.color_space: "BGR", "HSV".fittting: the method of localization.Return: a dict with ball data. for example: {"centerX":0, "centerY":0, "radius":0}."""self.updateFrame()#cv2.imwrite("src_image.jpg", self._frameArray)minDist = int(self._frameHeight/30.0)minRadius = 1maxRadius = int(self._frameHeight/10.0)if color_space == "BGR":grayFrame = self._getChannelAndBlur(color)else:grayFrame = self._binImageHSV(color)#cv2.imshow("bin frame", grayFrame)#cv2.imwrite("bin_frame.jpg", grayFrame)#cv2.waitKey(20)circles = self._findCircles(grayFrame, minDist, minRadius, maxRadius)circle = self._selectCircle(circles)if len(circle) == 0:self._ballData = {"centerX":0, "centerY":0, "radius":0}self._ballPosition= {"disX":0, "disY":0, "angle":0}else: self._ballData = {"centerX":circle[0], "centerY":circle[1], "radius":circle[2]}if fitting == True:self._updateBallPositionFitting(standState=standState)else:self._updateBallPosition(standState=standState)黃桿檢測類
在NAO機器人高爾夫中,球洞上會立著一根黃桿用于NAO機器人遠程定位球洞的方向。因為NAO機器人的頭部攝像頭看到距離更遠,所以在比賽中會使用NAO機器人的頭部攝像頭檢測黃桿。但這也帶來一些問題,比如會把遠處一些黃色的東西錯認為黃桿。
同樣,我們需要定義一個黃桿檢測類StickDetect,它也是從視覺基類VisualBasis繼承出來的。類中我定義了三個屬性:一個列表用來存放檢測出來的黃桿的位置信息、一個值用來存放黃桿相對于機器人的角度、一個常數用來確定是否要對圖像進行裁剪。前兩個很好理解,定義一個裁剪的常用的原因是在比賽現場,當NAO機器人以正常走路的姿勢去尋找黃桿時,黃桿一般位于圖像的下方,因此將圖像上半圖像剪掉不但可以排除一些干擾物,還可以減少計算量。具體的定義如下:
def __init__(self, IP, cameraId=vd.kTopCamera, resolution=vd.kVGA):super(StickDetect, self).__init__(IP, cameraId, resolution)self._boundRect = []self._cropKeep = 1self._stickAngle = None # rad在我們的檢測方法中,采用HSV顏色分割+形狀判別的方式。其中HSV顏色分割和上面用HSV空間進行紅球分割是一樣的,只要改一下閾值范圍就行,函數如下:
def _preprocess(self, minHSV, maxHSV, cropKeep, morphology):"""preprocess the current frame for stick detection.(binalization, crop etc.)Arguments:minHSV: the lower limit for binalization.maxHSV: the upper limit for binalization.cropKeep: crop ratio (>=0.5).morphology: erosion and dilation.Return:preprocessed image for stick detection."""self._cropKeep = cropKeepframeArray = self._frameArrayheight = self._frameHeightwidth = self._frameWidthtry:frameArray = frameArray[int((1-cropKeep)*height):,:]except IndexError:raise frameHSV = cv2.cvtColor(frameArray, cv2.COLOR_BGR2HSV)frameBin = cv2.inRange(frameHSV, minHSV, maxHSV)kernelErosion = np.ones((5,5), np.uint8)kernelDilation = np.ones((5,5), np.uint8) frameBin = cv2.erode(frameBin, kernelErosion, iterations=1)frameBin = cv2.dilate(frameBin, kernelDilation, iterations=1)frameBin = cv2.GaussianBlur(frameBin, (9,9), 0)return frameBin另外,在代碼中我還加入了腐蝕膨脹和高斯濾波。這主要是在實驗中發現,當黃桿離機器人比較遠的時候,一些干擾物對檢測的影響很大,加入腐蝕膨脹可以很好地濾除大量不必要的干擾。當然濾波器的大小也需要把握好,否則可能會把黃桿也濾除。
圖片經上面的預處理后,會得到一張二值化圖。理想情況下,應主要包含黃桿信息(可能包含一些顏色相似的噪聲)。為此我們需要在二值化圖像中找到黃桿所在的位置。由于我們比賽用的黃桿是長條形,可以進一步借助形狀信息來判斷。我在代碼中用的判別方法如下:
先在二值化圖像上進行凸包檢測,移除面積或周長較小的凸包,找到剩下凸包的最小包圍矩形框,對每個外接矩形框進行形狀判別,保留長寬比符合要求的矩形框,最后在剩下的矩形框中選擇長寬比最大的矩形框。代碼如下:
def _findStick(self, frameBin, minPerimeter, minArea):"""find the yellow stick in the preprocessed frame.Args:frameBin: preprocessed frame.minPerimeter: minimum perimeter of detected stick.minArea: minimum area of detected stick.Return: detected stick marked with rectangle or []."""rects = []_, contours, _ = cv2.findContours(frameBin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)if len(contours) == 0:return rectsfor contour in contours:perimeter = cv2.arcLength(contour, True)area = cv2.contourArea(contour)if perimeter>minPerimeter and area>minArea:x,y,w,h = cv2.boundingRect(contour)rects.append([x,y,w,h])if len(rects) == 0:return rectsrects = [rect for rect in rects if (1.0*rect[3]/rect[2])>0.8]if len(rects) == 0:return rectsrects = np.array(rects)print(rects)rect = rects[np.argmax(1.0*(rects[:,-1])/rects[:,-2]),]rect[1] += int(self._frameHeight *(1-self._cropKeep))return rect上面的代碼中還加入和很多判斷。目的是為了當圖像中沒有我們需要的黃桿時,直接返回一個空的列表。還有需要注意的一點是,最后我們是在經過裁剪的圖像上進行檢測的,因此還需要把最終的坐標信息轉換的到原圖中。下面給出一個完整的黃桿檢測結果圖:
至此,我們已經實現了NAO機器人高爾夫比賽中紅球和黃桿的識別和定位。其實比賽場地上還有其他的目標需要檢測,如白線、障礙物甚至球洞,這里就不多介紹。
寫在最后的話:
感謝你一直讀到這里,希望本篇博客對你有點幫助。關于本篇博客中的任何問題歡迎指出,虛心接受各位大佬的教導!
總結
以上是生活随笔為你收集整理的NAO机器人高尔夫中的视觉系统设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原来,我连一个URL都写不对…
- 下一篇: 传圣火~~~递友情~~~~NAONAO是