class PIDYawDirective(AbstractDroneDirective): # sets up this directive # plrratformColor: color to hover over. Altitude is maintained def __init__(self, poseTracker, target, yaw, platformNumber, waitDist=0.1): #self.Kp,self.Ki,self.Kd = 0.1,20.0,0.0005 #best self.Kp, self.Ki, self.Kd = 0.2, 0.0, 0.0005 self.moveTime = 0.2 self.waitTime = 0.0 self.tracker = poseTracker self.target = target self.waitDist = waitDist self.worldTarget = self.tracker.body2World(target)[:, 0] self.processVideo = ProcessVideo() self.platformNumber = platformNumber self.centery = 360 / 2.0 self.centerx = 640 / 2.0 self.pub = rospy.Publisher('ardrone/tracker', tracker) self.track = tracker() self.platform = [0, 0, 0] self.filterSize = 50 self.buff = np.repeat(np.asarray([self.worldTarget]).T, self.filterSize, axis=1) self.KpYaw, self.KiYaw, self.KdYaw = (1 / 180.0) * 0.8, 0, 0 self.targetYaw = (yaw + 360) % 360 self.worldPoint = np.asarray([[0, 0, 0]]).T #the amount of weight we would like to put towards correcting the drones drift by recognizing landmarks self.correctionRatio = 0.9999 def distance(self, x, y): dist = (x[0] - y[0])**2 + (x[1] - y[1])**2 dist = dist**(0.5) return dist def weightedUpdate(self, prediction, updateTerm): return (self.correctionRatio * updateTerm[0, 0] + (1 - self.correctionRatio) * prediction[0, 0], self.correctionRatio * updateTerm[1, 0] + (1 - self.correctionRatio) * prediction[1, 0], updateTerm[2, 0], 1.0) # given the image and navdata of the drone, returns the following in order: # # A directive status int: # 0 if algorithm is still running and drone isn't on orange yet # 1 if algorithm is finished and drone is now on orange # # A tuple of (xspeed, yspeed, yawspeed, zspeed): # indicating the next instructions to fly the drone # # An image reflecting what is being done as part of the algorithm def RetrieveNextInstruction(self, image, navdata): segImage, radius, center = self.processVideo.RecognizeShape( image, 'orange', (None, None)) blue = self.processVideo.detectColor(image, 'blue', 'segmented') lines, blueImg = self.processVideo.MultiShowLine(blue) bestTheta = None minDist = -1 for line in lines: theta = line[0] theta = -theta tapeCenter = line[1] dist = self.distance(center, tapeCenter) if minDist == -1 or dist < minDist: minDist = dist bestTheta = theta if bestTheta != None: self.currentYaw = bestTheta else: self.currentYaw = 0 #Calculate closest rotation to get to target angle theta = ((0 - self.currentYaw) % 360 + 360) % 360 theta = (theta - 360) if (theta > 180) else theta loc = (0, 0, 0, 0) #circle detection #rospy.logwarn("x: "+str(self.tracker.translation[0])+" y: "+str(self.tracker.translation[1])) if radius != None: predictedZ = self.processVideo.CalcDistanceNew(88.0, radius * 2) / 1000.0 scale = (88.0 / (radius * 2)) / 1000.0 #meters/pixel x = (center[0] - self.centerx) * scale y = (self.centery - center[1]) * scale tape = self.tracker.camera2Body([x, y, -predictedZ]) worldPoint = self.tracker.camera2World([x, y, -predictedZ]) self.worldPoint = worldPoint if (self.distance(worldPoint, self.worldTarget) < 0.35): for i in range(self.filterSize - 1): self.buff[:, i] = self.buff[:, i + 1] self.buff[:, self.filterSize - 1] = np.asarray( [worldPoint[0, 0], worldPoint[1, 0], worldPoint[2, 0]]) self.worldTarget = np.mean(self.buff, 1) ''' if self.tapeLocation != None: dist = self.distance(worldPoint,self.tapeLocation) if dist < 0.35 and dist > 0.15: loc = self.tracker.tape2World([x,y,-predictedZ],self.yaw,[self.tapeLocation[0],self.tapeLocation[1],0]) loc = self.weightedUpdate(worldPoint,loc) rospy.logwarn("Fixing location to ..."+str(loc)) ''' self.track.landMark = (self.worldTarget[0], self.worldTarget[1], 0.0, 1.0) else: self.track.landMark = (0, 0, 0, 0.0) #rospy.logwarn("world target: " + str(self.worldTarget)) self.track.landMark = (self.worldTarget[0], self.worldTarget[1], 0.0, 1.0) self.track.loc = loc self.pub.publish(self.track) self.currentTarget = self.tracker.world2Body(self.worldTarget) self.currentTime = time.time() if self.lastTime == 0: self.rollError = 0 self.pitchError = 0 self.yawError = 0 else: self.rollError = self.currentTarget[0] self.pitchError = self.currentTarget[1] self.yawError = theta self.dt = (self.currentTime - self.lastTime) / 1000. self.totalError = [ self.totalError[0] + self.rollError * self.dt, self.totalError[1] + self.pitchError * self.dt, self.totalError[2] + self.yawError * self.dt, 0 ] pRoll = -self.Kp * (self.rollError) iRoll = -self.Ki * (self.totalError[0]) dRoll = -self.Kd * ((self.rollError - self.lastError[0]) / self.dt) pPitch = self.Kp * (self.pitchError) iPitch = self.Ki * (self.totalError[1]) dPitch = self.Kd * ((self.pitchError - self.lastError[1]) / self.dt) pYaw = self.KpYaw * (self.yawError) iYaw = self.KiYaw * (self.totalError[2]) dYaw = self.KdYaw * ((self.yawError - self.lastYawError) / self.dt) yaw = pYaw + iYaw + dYaw self.lastError = self.currentTarget self.lastYawError = self.yawError self.lastTime = self.currentTime roll = pRoll + iRoll + dRoll pitch = pPitch + iPitch + dPitch if (abs(self.rollError) <= self.waitDist and abs(self.pitchError) <= self.waitDist and abs(self.yawError) < 2): directiveStatus = 1 rospy.logwarn(self.yawError) else: directiveStatus = 0 #Trim commands over the drones command limit roll = 1 if roll > 1 else roll roll = -1 if roll < -1 else roll pitch = 1 if pitch > 1 else pitch pitch = -1 if pitch < -1 else pitch #rospy.logwarn("roll: "+str(self.tracker.roll)) #rospy.logwarn("pitch: "+str(self.tracker.pitch)) rospy.logwarn(directiveStatus) return directiveStatus, ( roll, pitch, 0, 0), segImage, None, self.moveTime, self.waitTime, None # This method is called by the state machine when it considers this directive finished def Finished(self): self.Reset() #tapeLocation = self.tracker.body2World(self.target)[:,0] #loc = self.tracker.tape2World([x,y,-predictedZ],self.yaw,[tapeLocation[0],tapeLocation[1],0]) if (self.platformNumber % 3 == 0): loc = np.asarray([self.target]).T loc[2] = 1.0 rospy.logwarn("Reseting location to" + str(loc)) loc = self.weightedUpdate(self.worldPoint, loc) self.track.loc = loc self.pub.publish(self.track) def Reset(self): self.dt = 0 self.currentTime = time.time() self.lastTime = 0 self.rollError = 0 self.pitchError = 0 self.lastError = [0, 0, 0] self.lastYawError = 0 self.totalError = [0, 0, 0, 0]
class FlightstatsReceiver(object): def __init__(self): super(FlightstatsReceiver, self).__init__() # Subscribe to the navdata topic, and call self.ProcessNavdata whenever # update navdata messages are recieved self.navdataSub = rospy.Subscriber('/ardrone/navdata', Navdata, self.UpdateNavdata) self.altitudeSub = rospy.Subscriber('/ardrone/navdata_altitude', navdata_altitude, self.UpdateAltitude) self.video = rospy.Subscriber('/ardrone/image_raw', Image, self.VideoUpdate) # dictionary will hold a useful subset of available flight info # Key is the variable name; value is (description, value, units, (optional) direction string following units) self.defaultValue = "?" self.flightInfo = collections.OrderedDict() self.flightInfo["batteryPercent"] = [ "Battery Left: ", self.defaultValue, "%", "" ] self.flightInfo["state"] = ["Status: ", self.defaultValue, "", ""] self.flightInfo["altitude"] = [ "Drone Altitude: ", self.defaultValue, "mm", "" ] self.flightInfo["altitude_raw"] = [ "Drone Raw Altitude: ", self.defaultValue, "mm", "" ] self.flightInfo["SVCLAltitude"] = ["SVCL Altitude: ", -1, "mm", ""] self.flightInfo["center"] = [ "Inferred Center: ", self.defaultValue, "", "" ] self.flightInfo["lastCenter"] = [ "Previous Center: ", (None, None), "", "" ] self.flightInfo["lastRecordedCenter"] = [ "Last Recorded Algorithm Center: ", (None, None), "", "" ] self.flightInfo["allCenters"] = [ "All Centers: ", self.defaultValue, "", "" ] self.flightInfo["rotX"] = [ "Left/Right Tilt: ", self.defaultValue, u'\N{DEGREE SIGN}', "" ] self.flightInfo["rotY"] = [ "Front/Back Tilt: ", self.defaultValue, u'\N{DEGREE SIGN}', "" ] self.flightInfo["rotZ"] = [ "Rotation Amount: ", self.defaultValue, u'\N{DEGREE SIGN}', "" ] self.flightInfo["velY"] = [ "Left/Right Velocity: ", self.defaultValue, "mm/s", "" ] self.flightInfo["velX"] = [ "Forwards/Backwards Velocity: ", self.defaultValue, "mm/s", "" ] self.flightInfo["velZ"] = [ "Up/Down Velocity: ", self.defaultValue, "mm/s", "" ] self.flightInfo["accelZ"] = [ "Up/Down Acceleration: ", self.defaultValue, "mm/s", "" ] self.flightInfo["dispLR"] = [ "Left/Right Displacement: ", self.defaultValue, "mm", "" ] self.flightInfo["dispFB"] = [ "Forwards/Backwards Displacement: ", self.defaultValue, "mm", "" ] self.flightInfo["dispUD"] = [ "Up/Down Displacement: ", self.defaultValue, "mm", "" ] self.flightInfo["segImage"] = [None] self.LRDisplacement = 0.0 self.FBDisplacement = 0.0 self.UDDisplacement = 0.0 self.oldTime = rospy.Time.now() self.bridge = CvBridge() self.processVideo = ProcessVideo() # sometimes the altitude doesn't start at 0; "zero balances" the drone such that where it started is considered 0 altitude self.zeroBalanced = False self.zeroAltitude = 0 # counter is designed to throttle the lag that comes with executing videoupdate too often # a higher videoUpdateMax will make everything run faster, but getting height/center updates will be slower # describes a ratio: compute/rest self.computeMax = 1 self.restMax = 0 self.counter = 0 self.lastLocation = (None, None) self.lastLoc = (None, None) self.lastCenter = (None, None) self.lastCenterCount = 0 self.lastCenterMax = 8 def VideoUpdate(self, image): if (self.counter < self.computeMax) or self.restMax == 0: self.counter += 1 # converting to hsv image = self.bridge.imgmsg_to_cv2(image, "bgr8") segImage, radius, center = self.processVideo.RecognizeShape( image, 'orange', self.lastLocation) (self.flightInfo["center"][1]) = self.InferCenter( self.processVideo.RemoveNoise(segImage)) if radius == None: if center == (None, None): distance = -1 (self.flightInfo["SVCLAltitude"])[1] = distance else: distance = self.processVideo.CalcDistanceNew(88, radius * 2) (self.flightInfo["SVCLAltitude"])[1] = distance #(self.flightInfo["center"])[1] = center self.lastLocation = center (self.flightInfo["segImage"]) = segImage else: if self.counter < self.computeMax + self.restMax: self.counter += 1 if self.counter >= self.computeMax + self.restMax: self.counter = 0 # Uses a more complex way to infer what platform to use. If there is only one platform, this is trivial, # but becomes more useful if there are >1 visible. def InferCenter(self, image): centers, _ = self.processVideo.MultiCenterOfMass(image) (self.flightInfo["allCenters"][1]) = centers if len(centers) == 0: #rospy.logwarn("No Platform") center = (None, None) elif len(centers) == 1: center = centers[0] #rospy.logwarn("Found 1") elif len(centers) == 2: if self.lastLoc != (None, None): #rospy.logwarn("Found 2 -- picking closer to last") # just pick whichever is closer to the last center c1Dist = math.sqrt( math.pow((self.lastLoc[1] - centers[0][1]), 2) + math.pow((self.lastLoc[0] - centers[0][0]), 2)) c2Dist = math.sqrt( math.pow((self.lastLoc[1] - centers[1][1]), 2) + math.pow((self.lastLoc[0] - centers[1][0]), 2)) if c1Dist < c2Dist: center = centers[0] else: center = centers[1] else: #rospy.logwarn("Found 2 -- picking closer to center") # just pick whichever's x-coord is nearer to center xCenter = 320 c1XDist = abs(xCenter - centers[0][0]) c2XDist = abs(xCenter - centers[1][0]) if c1XDist < c2XDist: center = centers[0] else: center = centers[1] # if there are 3 or more platforms else: centers = sorted(centers, key=self.getX) if len(centers) % 2 == 0: midX = int((centers[len(centers) / 2][0] + centers[(len(centers) / 2) + 1][0]) / 2) midY = int((centers[len(centers) / 2][1] + centers[(len(centers) / 2) + 1][1]) / 2) else: midX = centers[int((len(centers) / 2.0) + 0.5) - 1][0] midY = centers[int((len(centers) / 2.0) + 0.5) - 1][1] center = (midX, midY) #rospy.logwarn("Found " + str(len(centers)) + ": " + str(centers)) #rospy.logwarn("Using " + str(center)) if len(centers) > 1 and self.lastCenter != (None, None): closest = None smallestLen = None # loop to pick whichever is closer to the last center for i in range(len(centers)): cXDist = abs(self.lastCenter[0] - centers[i][0]) cYDist = abs(self.lastCenter[1] - centers[i][1]) length = math.sqrt(math.pow(cXDist, 2) + math.pow(cYDist, 2)) if smallestLen == None or length < smallestLen: closest = centers[i] smallestLen = length self.lastCenter = closest (self.flightInfo["lastCenter"][1]) = self.lastCenter # algorithm waits for a brief moment if last location changes drastically to make # sure that it really changed if self.lastLoc != (None, None) and center != (None, None): dist = math.sqrt( math.pow((self.lastLoc[1] - center[1]), 2) + math.pow((self.lastLoc[0] - center[0]), 2)) if dist > 140: self.lastCenterCount += 1 rospy.logwarn("visible: " + str(len(centers)) + " change Center Count: " + str(self.lastCenterCount) + " / " + str(self.lastCenterMax)) if self.lastCenterCount >= self.lastCenterMax: rospy.logwarn("Changing center") self.lastCenterCount = 0 # saving last center in case self.lastCenter = self.lastLoc self.lastLoc = center else: self.lastCenterCount = 0 self.lastLoc = center else: self.lastLoc = center #return center return self.lastLoc def getX(self, coordinates): return coordinates[0] # force set what the center is def SetCenter(self, center): self.lastLoc = center def UpdateRecordedCenter(self, center): (self.flightInfo["lastRecordedCenter"][1]) = center def UpdateAltitude(self, altitude): if self.zeroBalanced: (self.flightInfo["altitude_raw"] )[1] = altitude.altitude_raw - self.zeroAltitude else: self.zeroBalanced = True self.zeroAltitude = altitude.altitude_raw # update dictionary as new info from drone comes def UpdateNavdata(self, navdata): # first, update instance variables (self.flightInfo["batteryPercent"])[1] = navdata.batteryPercent (self.flightInfo["state"])[1] = navdata.state #(self.flightInfo["altitude"])[1] = navdata.altd (self.flightInfo["rotX"])[1] = navdata.rotX (self.flightInfo["rotY"])[1] = navdata.rotY (self.flightInfo["rotZ"])[1] = navdata.rotZ (self.flightInfo["velX"])[1] = navdata.vx (self.flightInfo["velY"])[1] = navdata.vy (self.flightInfo["velZ"])[1] = navdata.vz (self.flightInfo["altitude"])[1] = navdata.altd (self.flightInfo["accelZ"])[1] = navdata.az dt = rospy.Time.now() - self.oldTime # Calculating horizontal displacement currLRVelocity = self.flightInfo["velY"][1] self.LRDisplacement += float(currLRVelocity) * dt.to_sec() (self.flightInfo["dispLR"])[1] = self.LRDisplacement # Calculating vertical displacement currFBVelocity = self.flightInfo["velX"][1] self.FBDisplacement += float(currFBVelocity) * dt.to_sec() (self.flightInfo["dispFB"])[1] = self.FBDisplacement # Calculating Z displacement currUDAcceleration = self.flightInfo["accelZ"][1] self.UDDisplacement += float(currUDAcceleration * dt.to_sec() * dt.to_sec()) (self.flightInfo["dispUD"])[1] = self.UDDisplacement self.oldTime = rospy.Time.now()