|
@@ -0,0 +1,206 @@
|
|
|
+
|
|
|
+
|
|
|
+from __future__ import division
|
|
|
+import rospy
|
|
|
+import message_filters
|
|
|
+import numpy as np
|
|
|
+import cv2
|
|
|
+from matplotlib import pyplot as plt
|
|
|
+from cv_bridge import CvBridge
|
|
|
+
|
|
|
+from sensor_msgs.msg import Image
|
|
|
+from robot_follower.msg import position as PositionMsg
|
|
|
+from std_msgs.msg import String as StringMsg
|
|
|
+np.seterr(all='raise')
|
|
|
+displayImage=False
|
|
|
+
|
|
|
+plt.close('all')
|
|
|
+class visualTracker:
|
|
|
+ def __init__(self):
|
|
|
+ self.bridge = CvBridge()
|
|
|
+ self.targetUpper = np.array(rospy.get_param('~target/upper'))
|
|
|
+ self.targetLower = np.array(rospy.get_param('~target/lower'))
|
|
|
+ self.pictureHeight= rospy.get_param('~pictureDimensions/pictureHeight')
|
|
|
+ self.pictureWidth = rospy.get_param('~pictureDimensions/pictureWidth')
|
|
|
+ vertAngle =rospy.get_param('~pictureDimensions/verticalAngle')
|
|
|
+ horizontalAngle = rospy.get_param('~pictureDimensions/horizontalAngle')
|
|
|
+
|
|
|
+ self.tanVertical = np.tan(vertAngle)
|
|
|
+ self.tanHorizontal = np.tan(horizontalAngle)
|
|
|
+
|
|
|
+ self.lastPosition =None
|
|
|
+
|
|
|
+
|
|
|
+ im_sub = message_filters.Subscriber('/orbbec_astra/rgb/image_rect_color', Image)
|
|
|
+ dep_sub = message_filters.Subscriber('/orbbec_astra/depth/image', Image)
|
|
|
+ self.timeSynchronizer = message_filters.ApproximateTimeSynchronizer([im_sub, dep_sub], 10, 0.5)
|
|
|
+ self.timeSynchronizer.registerCallback(self.trackObject)
|
|
|
+
|
|
|
+
|
|
|
+ self.positionPublisher = rospy.Publisher('/object_tracker/current_position', PositionMsg, queue_size=3)
|
|
|
+ self.infoPublisher = rospy.Publisher('/object_tracker/info', StringMsg, queue_size=3)
|
|
|
+ rospy.logwarn(self.targetUpper)
|
|
|
+
|
|
|
+
|
|
|
+ def trackObject(self, image_data, depth_data):
|
|
|
+ if(image_data.encoding != 'rgb8'):
|
|
|
+ raise ValueError('image is not rgb8 as expected')
|
|
|
+
|
|
|
+
|
|
|
+ frame = self.bridge.imgmsg_to_cv2(image_data, desired_encoding='rgb8')
|
|
|
+ depthFrame = self.bridge.imgmsg_to_cv2(depth_data, desired_encoding='passthrough')
|
|
|
+
|
|
|
+ if(np.shape(frame)[0:2] != (self.pictureHeight, self.pictureWidth)):
|
|
|
+ raise ValueError('image does not have the right shape. shape(frame): {}, shape parameters:{}'.format(np.shape(frame)[0:2], (self.pictureHeight, self.pictureWidth)))
|
|
|
+
|
|
|
+
|
|
|
+ blurred = cv2.GaussianBlur(frame, (11,11), 0)
|
|
|
+ hsv = cv2.cvtColor(blurred, cv2.COLOR_RGB2HSV)
|
|
|
+
|
|
|
+
|
|
|
+ org_mask = cv2.inRange(hsv, self.targetUpper, self.targetLower)
|
|
|
+
|
|
|
+
|
|
|
+ mask = cv2.erode(org_mask, None, iterations=4)
|
|
|
+ mask = cv2.dilate(mask,None, iterations=3)
|
|
|
+
|
|
|
+
|
|
|
+ contours = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
|
|
|
+ newPos = None
|
|
|
+
|
|
|
+
|
|
|
+ if displayImage:
|
|
|
+ backConverted = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ plt.figure()
|
|
|
+ plt.subplot(2,2,1)
|
|
|
+ plt.imshow(frame)
|
|
|
+ plt.xticks([]),plt.yticks([])
|
|
|
+ plt.subplot(2,2,2)
|
|
|
+ plt.imshow(org_mask, cmap='gray', interpolation = 'bicubic')
|
|
|
+ plt.xticks([]),plt.yticks([])
|
|
|
+ plt.subplot(2,2,3)
|
|
|
+ plt.imshow(mask, cmap='gray', interpolation = 'bicubic')
|
|
|
+ plt.xticks([]),plt.yticks([])
|
|
|
+ plt.show()
|
|
|
+ rospy.sleep(0.2)
|
|
|
+
|
|
|
+
|
|
|
+ for contour in sorted(contours, key=cv2.contourArea, reverse=True):
|
|
|
+
|
|
|
+ pos = self.analyseContour(contour, depthFrame)
|
|
|
+
|
|
|
+
|
|
|
+ if newPos is None:
|
|
|
+ newPos = pos
|
|
|
+
|
|
|
+
|
|
|
+ if self.checkPosPlausible(pos):
|
|
|
+ self.lastPosition = pos
|
|
|
+ self.publishPosition(pos)
|
|
|
+ return
|
|
|
+
|
|
|
+ self.lastPosition = newPos
|
|
|
+
|
|
|
+ rospy.loginfo('no position found')
|
|
|
+ self.infoPublisher.publish(StringMsg('visual:nothing found'))
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def publishPosition(self, pos):
|
|
|
+
|
|
|
+ angleX = self.calculateAngleX(pos)
|
|
|
+ angleY = self.calculateAngleY(pos)
|
|
|
+
|
|
|
+ posMsg = PositionMsg(angleX, angleY, pos[1])
|
|
|
+ self.positionPublisher.publish(posMsg)
|
|
|
+
|
|
|
+ def checkPosPlausible(self, pos):
|
|
|
+ '''Checks if a position is plausible. i.e. close enough to the last one.'''
|
|
|
+
|
|
|
+
|
|
|
+ if self.lastPosition is None:
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+ ((centerX, centerY), dist)=pos
|
|
|
+ ((PcenterX, PcenterY), Pdist)=self.lastPosition
|
|
|
+
|
|
|
+ if np.isnan(dist):
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+ if abs(dist-Pdist)>0.5:
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+ if abs(centerX-PcenterX)>(self.pictureWidth /5):
|
|
|
+ return False
|
|
|
+
|
|
|
+ if abs(centerY-PcenterY)>(self.pictureHeight/5):
|
|
|
+ return False
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+
|
|
|
+ def calculateAngleX(self, pos):
|
|
|
+ '''calculates the X angle of displacement from straight ahead'''
|
|
|
+
|
|
|
+ centerX = pos[0][0]
|
|
|
+ displacement = 2*centerX/self.pictureWidth-1
|
|
|
+ angle = -1*np.arctan(displacement*self.tanHorizontal)
|
|
|
+ return angle
|
|
|
+
|
|
|
+ def calculateAngleY(self, pos):
|
|
|
+ centerY = pos[0][1]
|
|
|
+ displacement = 2*centerY/self.pictureHeight-1
|
|
|
+ angle = -1*np.arctan(displacement*self.tanVertical)
|
|
|
+ return angle
|
|
|
+
|
|
|
+ def analyseContour(self, contour, depthFrame):
|
|
|
+ '''Calculates the centers coordinates and distance for a given contour
|
|
|
+
|
|
|
+ Args:
|
|
|
+ contour (opencv contour): contour of the object
|
|
|
+ depthFrame (numpy array): the depth image
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ centerX, centerY (doubles): center coordinates
|
|
|
+ averageDistance : distance of the object
|
|
|
+ '''
|
|
|
+
|
|
|
+ centerRaw, size, rotation = cv2.minAreaRect(contour)
|
|
|
+
|
|
|
+
|
|
|
+ center = np.round(centerRaw).astype(int)
|
|
|
+
|
|
|
+
|
|
|
+ minSize = int(min(size)/3)
|
|
|
+
|
|
|
+
|
|
|
+ depthObject = depthFrame[(center[1]-minSize):(center[1]+minSize), (center[0]-minSize):(center[0]+minSize)]
|
|
|
+
|
|
|
+
|
|
|
+ depthArray = depthObject[~np.isnan(depthObject)]
|
|
|
+ averageDistance = np.mean(depthArray)
|
|
|
+
|
|
|
+ if len(depthArray) == 0:
|
|
|
+ rospy.logwarn('empty depth array. all depth values are nan')
|
|
|
+
|
|
|
+ return (centerRaw, averageDistance)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == '__main__':
|
|
|
+ print('starting')
|
|
|
+ rospy.init_node('visual_tracker')
|
|
|
+ tracker=visualTracker()
|
|
|
+ print('seems to do something')
|
|
|
+ try:
|
|
|
+ rospy.spin()
|
|
|
+ except rospy.ROSInterruptException:
|
|
|
+ print('exception')
|
|
|
+
|